n4nAI

LangChain environment variables for n4n.ai base URL and key

Configure LangChain to route requests through n4n.ai using environment variables for base URL and API key, with verification steps and troubleshooting.

n4n Team4 min read957 words

Audio narration

Coming soon — every post will get a voice note here.

LangChain’s OpenAI-compatible client accepts a base URL and API key at initialization, but hardcoding credentials in source control is a liability. The standard pattern — used across the LangChain ecosystem — is to configure the client through environment variables so the same code runs locally, in CI, and in production without changes. This guide walks through wiring LangChain to n4n.ai using only environment variables, verifying the connection, and handling common pitfalls.

Step 1: Install the required packages

Start with a clean virtual environment. You need langchain-openai for the ChatOpenAI wrapper and python-dotenv if you want to load a .env file locally.

python -m venv .venv
source .venv/bin/activate
pip install langchain-openai python-dotenv

If you’re already in a project with a pyproject.toml or requirements.txt, add those two dependencies there instead.

Step 2: Create a .env file (local development only)

Never commit secrets. Create a .env file at your repository root and add it to .gitignore.

echo ".env" >> .gitignore

Now populate .env with the two values LangChain’s OpenAI client reads by default:

OPENAI_API_KEY=sk-n4n-XXXXXXXXXXXXXXXXXXXXXXXX
OPENAI_BASE_URL=https://api.n4n.ai/v1

The key format above is illustrative — use the actual key issued from your n4n.ai dashboard. The base URL must include the /v1 path suffix; the client appends /chat/completions and other endpoints to it.

If you prefer not to use a .env file, export the variables directly in your shell profile or CI environment:

export OPENAI_API_KEY=sk-n4n-XXXXXXXXXXXXXXXXXXXXXXXX
export OPENAI_BASE_URL=https://api.n4n.ai/v1

Step 3: Load environment variables in your application

LangChain’s ChatOpenAI class reads OPENAI_API_KEY and OPENAI_BASE_URL automatically at instantiation time. You don’t need to pass them explicitly — but you do need to ensure they’re loaded before the first import of langchain_openai.

For scripts and notebooks, load .env at the very top of your entry point:

# app.py
from dotenv import load_dotenv
load_dotenv()  # reads .env into os.environ

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("Reply with the word 'ok' and nothing else.")
print(response.content)

Run it:

python app.py
# ok

If you see ok printed, the client successfully routed through n4n.ai and returned a completion.

Step 4: Verify the request actually hit n4n.ai

A successful response doesn’t prove the request went where you think it went. Confirm the network path in two ways.

Option A: Enable HTTP logging

Add this block before importing ChatOpenAI to see the full request line and headers on stdout:

import logging
import http.client

http.client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

Run the script again. You should see a POST https://api.n4n.ai/v1/chat/completions line in the debug output, confirming the base URL was respected.

Option B: Inspect response headers for routing hints

n4n.ai returns a x-n4n-model header indicating which upstream provider served the request, and forwards provider cache-control hints. Capture it programmatically:

from langchain_openai import ChatOpenAI
import httpx

# Monkey-patch to capture the last response
original_init = ChatOpenAI.__init__

def capturing_init(self, *args, **kwargs):
    original_init(self, *args, **kwargs)
    self._last_response_headers = {}

    original_request = self.client.request
    def capturing_request(*a, **kw):
        resp = original_request(*a, **kw)
        self._last_response_headers = dict(resp.headers)
        return resp
    self.client.request = capturing_request

ChatOpenAI.__init__ = capturing_init

llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("Say 'verified'")
print("Response:", response.content)
print("x-n4n-model:", llm._last_response_headers.get("x-n4n-model"))
print("cache-control:", llm._last_response_headers.get("cache-control"))

Output example:

Response: verified
x-n4n-model: anthropic/claude-3.5-sonnet
cache-control: public, max-age=0, must-revalidate

The presence of x-n4n-model confirms the request transited n4n.ai’s gateway.

Step 5: Override the model at runtime (optional)

The model parameter you pass to ChatOpenAI is forwarded as-is to the gateway. n4n.ai accepts any model identifier it recognizes — including provider-prefixed names like anthropic/claude-3.5-sonnet or google/gemini-1.5-pro.

llm = ChatOpenAI(model="anthropic/claude-3.5-sonnet")
response = llm.invoke("What is 2+2?")
print(response.content)

You can also set a default model via environment variable if you want zero code changes across environments:

# .env
OPENAI_API_KEY=sk-n4n-XXXXXXXXXXXXXXXXXXXXXXXX
OPENAI_BASE_URL=https://api.n4n.ai/v1
DEFAULT_MODEL=anthropic/claude-3.5-sonnet
import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=os.getenv("DEFAULT_MODEL", "gpt-4o-mini"))

Step 6: Configure timeouts and retries for production

The default httpx client has no timeout. In production, always set explicit timeouts and a retry policy. ChatOpenAI exposes timeout and max_retries parameters that map to the underlying httpx.Client configuration.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    timeout=httpx.Timeout(connect=5.0, read=60.0, write=10.0, pool=5.0),
    max_retries=3,
)

For more granular control — per-endpoint timeouts, custom retry predicates, or a shared connection pool across multiple ChatOpenAI instances — construct your own httpx.Client and pass it via the http_client parameter:

import httpx
from langchain_openai import ChatOpenAI

shared_client = httpx.Client(
    timeout=httpx.Timeout(connect=5.0, read=60.0),
    limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
    transport=httpx.HTTPTransport(retries=3),
)

llm = ChatOpenAI(model="gpt-4o-mini", http_client=shared_client)

Reuse shared_client across your application to benefit from connection pooling.

Step 7: Handle streaming responses

Streaming works identically to the official OpenAI client. Use stream() for token-by-token iteration or astream() in async contexts.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)

for chunk in llm.stream("Count to 5 slowly."):
    print(chunk.content, end="", flush=True)
print()

The gateway forwards server-sent events from the upstream provider without buffering, so latency to first token matches a direct provider call.

Step 8: Structured output with Pydantic models

LangChain’s with_structured_output() works through n4n.ai because the gateway passes through the response_format parameter to providers that support it (OpenAI, Anthropic, Google). Define a schema and bind it:

from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class Extraction(BaseModel):
    name: str = Field(description="Person's full name")
    email: str = Field(description="Email address")
    confidence: float = Field(ge=0.0, le=1.0, description="Confidence score")

llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(Extraction)

result = structured_llm.invoke(
    "Extract contact info: John Doe, john.doe@example.com, very confident."
)
print(result)
# name='John Doe' email='john.doe@example.com' confidence=0.95

If the selected upstream model doesn’t support structured output, the gateway returns a 400 with a clear error — handle it with a fallback model or parsing logic.

Step 9: CI/CD and production deployment

In CI and production, do not use .env files. Inject OPENAI_API_KEY and OPENAI_BASE_URL as protected secrets in your platform:

  • GitHub Actions: Repository Settings → Secrets → Actions → OPENAI_API_KEY, OPENAI_BASE_URL
  • GitLab CI/CD: Settings → CI/CD → Variables → protected/masked
  • Kubernetes: Secret resource mounted as environment variables
  • Vercel/Netlify/Render: Project settings → Environment Variables

Your application code remains unchanged — it reads os.environ at startup.

# .github/workflows/test.yml
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python -m pytest

Step 10: Troubleshooting common issues

“AuthenticationError: Invalid API key”

The key is malformed, revoked, or not scoped for the requested model. Verify in the n4n.ai dashboard that the key is active and has access to the model you’re requesting. Rotate and re-paste if in doubt — invisible whitespace is a frequent culprit.

“APIConnectionError: Cannot connect to host api.n4n.ai:443”

DNS or egress firewall. From the same network, run:

curl -v https://api.n4n.ai/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY"

If curl fails, the issue is network-level, not LangChain.

“RateLimitError” or 429 responses

n4n.ai automatically falls back to healthy providers when one is rate-limited or degraded. If you still see 429s, you’ve exceeded your account-level quota. Implement client-side backoff:

from langchain_openai import ChatOpenAI
from tenacity import retry, stop_after_attempt, wait_exponential_jitter

llm = ChatOpenAI(model="gpt-4o-mini")

@retry(
    wait=wait_exponential_jitter(initial=1, max=30),
    stop=stop_after_attempt(5),
)
def safe_invoke(prompt: str):
    return llm.invoke(prompt)

“Model not found” for a model you know exists

The model identifier must match exactly what n4n.ai’s catalog exposes. List available models programmatically:

import httpx
import os

resp = httpx.get(
    f"{os.getenv('OPENAI_BASE_URL')}/models",
    headers={"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}"}
)
resp.raise_for_status()
for m in resp.json()["data"]:
    print(m["id"])

Use the exact id string from that list.

Streaming hangs or times out

Some corporate proxies buffer SSE streams. Test with curl first:

curl -N -X POST https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hi"}],"stream":true}'

If curl -N (no buffer) works but your Python stream hangs, configure httpx with http2=True or adjust proxy settings.

Step 11: Verify end-to-end with a smoke test

Add a lightweight integration test to your test suite that runs against the real gateway (optionally gated by an environment flag so it doesn’t run on every PR).

# tests/test_n4n_integration.py
import os
import pytest
from langchain_openai import ChatOpenAI

@pytest.mark.skipif(
    not os.getenv("RUN_INTEGRATION_TESTS"),
    reason="Set RUN_INTEGRATION_TESTS=1 to run"
)
def test_n4n_gateway_reachable():
    llm = ChatOpenAI(model="gpt-4o-mini", timeout=30.0)
    response = llm.invoke("Reply with the exact string: SMOKE_TEST_OK")
    assert "SMOKE_TEST_OK" in response.content

Run it explicitly:

RUN_INTEGRATION_TESTS=1 pytest tests/test_n4n_integration.py -v

This catches credential drift, network policy changes, and catalog updates before they hit production.

Summary checklist

  • OPENAI_API_KEY and OPENAI_BASE_URL set in environment (.env locally, secrets in CI/prod)
  • OPENAI_BASE_URL includes /v1 suffix
  • load_dotenv() called before first LangChain import (if using .env)
  • Explicit timeout and max_retries configured on ChatOpenAI
  • Verified x-n4n-model header present on responses
  • Smoke test added to CI gate

That’s it. Your LangChain application now routes through n4n.ai with zero vendor-specific code — just standard OpenAI-compatible environment variables.

Tagslangchainn4n-aienvironment-variablesconfiguration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langchain getting started with n4n.ai posts →