LangChain: using the VIDAI Control Plane as the backend¶
LangChain's provider integrations (langchain-openai,
langchain-anthropic, langchain-google-genai) wrap the official
vendor SDKs and forward base_url through. Two of the three work
via pure environment variables.
TL;DR¶
export OPENAI_API_BASE=https://your-vidaiserver.example.com/v1
export OPENAI_API_KEY=sk-your-vidai-key
export ANTHROPIC_API_URL=https://your-vidaiserver.example.com
export ANTHROPIC_API_KEY=sk-your-vidai-key
export GOOGLE_API_KEY=sk-your-vidai-key
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI
# Env vars alone; no kwargs needed
chat_oai = ChatOpenAI(model="gpt-4o-mini")
chat_ant = ChatAnthropic(model="claude-haiku-4-5")
# Gemini needs base_url on the constructor (no env var hook today)
chat_gem = ChatGoogleGenerativeAI(
model="gemini-2.5-flash",
base_url="https://your-vidaiserver.example.com",
)
Prerequisites¶
langchain-openai >= 1.1,langchain-anthropic >= 1.0,langchain-google-genai >= 4.2(only install the ones you use)- The control plane URL and an API key
New project¶
- Install the integrations you need:
- Set env vars (see TL;DR above).
- Use LangChain normally:
Existing project: migrating to the VIDAI Control Plane¶
Change env vars. That's almost always the entire migration.
ChatOpenAI¶
# Before
export OPENAI_API_KEY=sk-openai-...
# After
export OPENAI_API_BASE=https://your-vidaiserver.example.com/v1
export OPENAI_API_KEY=sk-your-vidai-key
ChatAnthropic¶
# Before
export ANTHROPIC_API_KEY=sk-ant-...
# After
export ANTHROPIC_API_URL=https://your-vidaiserver.example.com
export ANTHROPIC_API_KEY=sk-your-vidai-key
ChatGoogleGenerativeAI¶
No env var for base_url is read by this wrapper. You need to pass
base_url= as a constructor kwarg. If you have many call sites,
wrap it:
# llm_factory.py
import os
from langchain_google_genai import ChatGoogleGenerativeAI
def gemini(model: str, **kw) -> ChatGoogleGenerativeAI:
return ChatGoogleGenerativeAI(
model=model,
base_url=os.environ["VIDAI_SERVER_URL"],
**kw,
)
Streaming¶
Unchanged: .stream() yields AIMessageChunks; the + reducer
produces the final merged message:
chunks = list(chat.stream("count to five"))
merged = chunks[0]
for c in chunks[1:]:
merged = merged + c
print(merged.content)
Tool binding¶
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"{city}: 20C"
bound = chat.bind_tools([get_weather])
resp = bound.invoke("Weather in Paris?")
# resp.tool_calls is a list of {name, args, id}
print(resp.tool_calls)
Structured output¶
from pydantic import BaseModel, Field
class WeatherReport(BaseModel):
city: str = Field(description="city name")
temperature_c: int = Field(description="temperature in celsius")
structured = chat.with_structured_output(WeatherReport)
result = structured.invoke("Report weather for Paris, 20C")
print(result.city, result.temperature_c)
Cross-provider routing¶
Ops can register a model that routes to any supported upstream. The LangChain side is unchanged; you just name the model. See the cross-provider matrix in Client integrations for which (LangChain wrapper × upstream) combinations are supported.
Reading the control plane's response headers¶
LangChain's AIMessage doesn't expose the underlying HTTP response
headers directly, so the x-vidai-* routing headers aren't readable
from a vanilla chat.invoke(). If you need the routing trail for
debug dashboards, the cleanest path is to call the underlying vendor
SDK for those specific debug requests (see e.g.
openai-sdk.md "Reading the control plane's response
headers").
Some versions of the LangChain wrappers populate
response_metadata on the returned AIMessage with provider
metadata that may include vidai headers; check what your version
does:
See routing-and-headers.md for what the headers mean.
Verify it works: probe¶
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(
model="gpt-4o-mini",
base_url="https://your-vidaiserver.example.com/v1",
api_key="sk-your-vidai-key",
)
print(chat.invoke("say: ok").content)
If this prints a response, your setup is correct. If it raises an
auth error, the key is wrong. If it raises NotFoundError, the
model isn't registered in the control plane for your key.