An OpenAI-compatible endpoint can make three clients look interchangeable. The request code may be nearly identical, but the surrounding configuration often is not. One script reads OPENAI_API_KEY, another reads API_KEY, one appends /v1, and another appends it a second time. The Python test passes while JavaScript returns 401 or PowerShell receives 404.
This article solves one concrete problem: make the first AI Agent call reproducible across Python, JavaScript, and PowerShell while switching models with one environment variable. The solution is deliberately small: define one environment contract, run a deterministic startup probe, and only then let the agent perform useful work.
Disclosure: VectorNode is the gateway used in the examples. The environment-contract and startup-probe pattern is provider-independent; it does not assume gateway-managed recovery or identical behavior from every model.
1. The developer problem
Imagine a command-line agent that summarizes a deployment ticket. You test it with Python and get a response. A teammate rewrites the same first call in JavaScript, and the request fails. A PowerShell smoke test then reports a different error.
The three commands appear equivalent:
Python -> 200 OK
JavaScript -> 401 Unauthorized
PowerShell -> 404 Not Found
This is hard to debug because the failure is outside the prompt. The clients may be using different variable names, a stale model ID, or a base URL that already contains /v1. The agent should not start a multi-step workflow until this basic contract is proven.
2. Why configuration drifts
An OpenAI-compatible client normally needs three values:
| Variable | Meaning | Typical mistake |
|---|---|---|
API_KEY |
Bearer credential | A client reads OPENAI_API_KEY instead |
BASE_URL |
API root |
/v1 is omitted or appended twice |
MODEL |
Model identifier | A copied ID is unavailable in the account |
The wire request can still be syntactically valid when any of these values is wrong. HTTP success is therefore not enough for an agent startup gate. The gate must confirm that the selected model answered the expected probe and that the response has the fields the application will read.
The official OpenAI Chat Completions reference describes the interface as POST /chat/completions, with messages and model in the request body. It also notes that parameter support can vary by model. Check the live gateway parameter contract at https://o1kqetbjmk.apifox.cn before adding optional fields such as tool definitions, JSON mode, or provider-specific controls.
3. Configuration and call steps
Use one vocabulary in every language. Do not translate API_KEY into a language-specific name in one script; that creates hidden defaults.
Create a local .env file for Python and JavaScript, or set the same variables in the process environment used by PowerShell:
API_KEY=replace-with-a-real-key-locally
BASE_URL=https://www.vectronode.com/v1
MODEL=replace-with-a-model-id-visible-in-your-account
The BASE_URL value above is the only provider URL needed by the examples. Keep the file local and add .env to .gitignore:
.env
Install the language clients in isolated environments:
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install openai python-dotenv
npm install openai dotenv
Before running an example, replace only the local values. For a model-switch test, keep API_KEY and BASE_URL unchanged and change MODEL to another ID that is currently available to the same account.
4. Minimal runnable examples
All three programs send the same first Agent probe. The exact text is intentionally boring: it makes configuration failures visible and avoids confusing a successful network request with a usable startup state.
Python
Save as agent_probe.py:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
api_key = os.getenv("API_KEY")
base_url = os.getenv("BASE_URL")
model = os.getenv("MODEL")
missing = [name for name, value in {
"API_KEY": api_key,
"BASE_URL": base_url,
"MODEL": model,
}.items() if not value]
if missing:
raise RuntimeError(f"Missing environment variables: {', '.join(missing)}")
client = OpenAI(api_key=api_key, base_url=base_url)
expected = "AGENT_READY:v1"
response = client.chat.completions.create(
model=model,
messages=[
{
"role": "system",
"content": "You are an agent startup probe. Return exactly AGENT_READY:v1.",
},
{"role": "user", "content": "Run the startup probe."},
],
temperature=0,
max_tokens=20,
)
choice = response.choices[0]
text = (choice.message.content or "").strip()
result = {
"ok": text == expected,
"requested_model": model,
"reported_model": response.model,
"finish_reason": choice.finish_reason,
"text": text,
}
print(result)
if not result["ok"]:
raise SystemExit("Startup contract failed; do not start the agent workflow.")
Run it with the interpreter that owns the dependencies:
.\.venv\Scripts\python.exe .\agent_probe.py
JavaScript
Save as agent_probe.mjs:
import "dotenv/config";
import OpenAI from "openai";
const required = ["API_KEY", "BASE_URL", "MODEL"];
const missing = required.filter((name) => !process.env[name]);
if (missing.length) {
throw new Error(`Missing environment variables: ${missing.join(", ")}`);
}
const client = new OpenAI({
apiKey: process.env.API_KEY,
baseURL: process.env.BASE_URL,
});
const expected = "AGENT_READY:v1";
const response = await client.chat.completions.create({
model: process.env.MODEL,
messages: [
{
role: "system",
content: "You are an agent startup probe. Return exactly AGENT_READY:v1.",
},
{ role: "user", content: "Run the startup probe." },
],
temperature: 0,
max_tokens: 20,
});
const choice = response.choices?.[0];
const text = (choice?.message?.content ?? "").trim();
const result = {
ok: text === expected,
requested_model: process.env.MODEL,
reported_model: response.model,
finish_reason: choice?.finish_reason ?? null,
text,
};
console.log(JSON.stringify(result, null, 2));
if (!result.ok) {
process.exitCode = 1;
}
Run it from the directory containing .env:
node .\agent_probe.mjs
PowerShell
This example keeps the key in a process-scoped variable. Read-Host -AsSecureString prevents it from being echoed while you prepare the test shell.
$env:BASE_URL = "copy-the-BASE_URL-value-from-your-local-env-file"
$env:MODEL = "copy-the-MODEL-value-from-your-local-env-file"
$secureKey = Read-Host "API key" -AsSecureString
$env:API_KEY = [Net.NetworkCredential]::new("", $secureKey).Password
$headers = @{
Authorization = "Bearer $env:API_KEY"
"Content-Type" = "application/json"
}
$payload = @{
model = $env:MODEL
messages = @(
@{
role = "system"
content = "You are an agent startup probe. Return exactly AGENT_READY:v1."
}
@{
role = "user"
content = "Run the startup probe."
}
)
temperature = 0
max_tokens = 20
} | ConvertTo-Json -Depth 6
try {
$response = Invoke-RestMethod `
-Uri "$env:BASE_URL/chat/completions" `
-Method Post `
-Headers $headers `
-Body $payload `
-TimeoutSec 60
} catch {
$status = $_.Exception.Response.StatusCode.value__
throw "Startup request failed with HTTP $status. Check BASE_URL, API_KEY, and MODEL."
}
$choice = $response.choices[0]
$text = ([string]$choice.message.content).Trim()
$result = [ordered]@{
ok = ($text -eq "AGENT_READY:v1")
requested_model = $env:MODEL
reported_model = $response.model
finish_reason = $choice.finish_reason
text = $text
}
$result | ConvertTo-Json
if (-not $result.ok) {
exit 1
}
Clear the process variable when the test is finished:
Remove-Item Env:API_KEY
5. Result verification and model switching
A passing probe should produce a record similar to this, although reported_model depends on the gateway response:
{
"ok": true,
"requested_model": "model-id-from-your-account",
"reported_model": "model-id-from-your-account",
"finish_reason": "stop",
"text": "AGENT_READY:v1"
}
The useful assertions are:
-
okistrue, not merely an HTTP 2xx status. -
choices[0].message.contentexists and matches the startup contract after whitespace trimming. -
finish_reasonis recorded for later diagnosis. - The requested and reported model IDs are logged without logging the API key.
To switch models, change only MODEL, then rerun the same probe in each client:
$env:MODEL = "first-model-id"
node .\agent_probe.mjs
$env:MODEL = "second-model-id"
node .\agent_probe.mjs
Repeat with agent_probe.py or the PowerShell request. If one language fails while the others pass, print the resolved variable names and URL path, never the secret value. The comparison tells you whether the problem is configuration drift or model-specific support.
6. Common errors and a short diagnosis
| Symptom | Likely cause | Check first |
|---|---|---|
401 Unauthorized |
Missing, truncated, revoked, or differently named key | Confirm the process has API_KEY and that the value is active |
404 Not Found |
Wrong API root or duplicated /v1
|
Print BASE_URL and the final path; it should end in /chat/completions
|
model_not_found |
The ID is not available to this account | Copy the current model ID into MODEL
|
400 or 422
|
Unsupported parameter or request shape | Remove optional fields and compare with the current parameter contract |
429 |
Rate, quota, or account limit | Wait, inspect the account status, and avoid a tight retry loop |
| Timeout | Network path or client timeout | Test the same URL from the same shell and use a bounded timeout |
HTTP 200 but ok: false
|
The model answered, but not the agent startup contract | Inspect the redacted response and decide whether the model fits this workflow |
Do not "fix" a cross-language discrepancy by adding a different fallback key or hard-coding a second base URL. That hides the source of drift and makes the next model switch harder to reproduce.
7. Security boundaries and when this pattern fits
Keep the API key on a trusted server, worker, or local development shell. A browser bundle, mobile package, public repository, screenshot, issue, or video can expose it. Use .env only for local development, keep it out of Git, redact response logs, and rotate the key immediately if it appears anywhere public.
The startup probe is useful when an agent has a clear minimum contract and you need to switch models without rewriting three clients. It is not a substitute for provider-specific evaluation. If your workflow depends on a particular tool schema, vision modality, streaming event sequence, latency guarantee, or compliance boundary, test those capabilities separately and pin the model deliberately.
The application still owns model selection, authorization, retries, cost limits, and business validation. An OpenAI-compatible request gives you a common wire shape; it does not transfer those decisions to the gateway.
Try the example
After you have an account and a model ID, use the tested Python, JavaScript, and PowerShell examples in the VectorNode API Quickstart:
https://github.com/Vector-Compute-Engine/api-quickstart
Never publish your complete API key in source code, screenshots, videos, or public issues.
Top comments (0)