DEV Community

vectronodeAPI
vectronodeAPI

Posted on

One Environment Contract for Python, JavaScript, and PowerShell AI Agents

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The BASE_URL value above is the only provider URL needed by the examples. Keep the file local and add .env to .gitignore:

.env
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

Run it with the interpreter that owns the dependencies:

.\.venv\Scripts\python.exe .\agent_probe.py
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Run it from the directory containing .env:

node .\agent_probe.mjs
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Clear the process variable when the test is finished:

Remove-Item Env:API_KEY
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

The useful assertions are:

  1. ok is true, not merely an HTTP 2xx status.
  2. choices[0].message.content exists and matches the startup contract after whitespace trimming.
  3. finish_reason is recorded for later diagnosis.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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)