LangChain
LangChain’s OpenAI integration is a thin wrapper over the OpenAI SDK, so the firewall is two constructor arguments away. Your chains, agents, retrievers, and tools do not change — they are downstream of the model object, and the model object is the only thing that knows where the gateway is.
Install
pip install langchain-openai
npm install @langchain/openai
Configure
Have the CLI print the Python snippet with your organisation UUID already in it:
vulnetix ai-firewall snippet --lang python --sdk langchain --provider openai
import os
from langchain_openai import ChatOpenAI
org = os.environ["VULNETIX_ORG_UUID"]
llm = ChatOpenAI(
base_url=f"https://guardrails.vulnetix.com/openai/{org}/v1",
api_key=os.environ["VULNETIX_API_KEY"], # Vulnetix key, not an OpenAI key
model="gpt-4o-mini",
)
print(llm.invoke("Hello").content)
import { ChatOpenAI } from '@langchain/openai'
const org = process.env.VULNETIX_ORG_UUID
const llm = new ChatOpenAI({
apiKey: process.env.VULNETIX_API_KEY, // Vulnetix key, not an OpenAI key
model: 'gpt-4o-mini',
configuration: {
baseURL: `https://guardrails.vulnetix.com/openai/${org}/v1`,
},
})
const response = await llm.invoke('Hello')
console.log(response.content)
Note that in JavaScript the base URL goes inside configuration, which LangChain
passes straight through to the OpenAI SDK.
Any OpenAI-compatible provider works through the same class — point the slug at
groq, mistral, deepseek, openrouter, and use that provider’s model string.
You do not need ChatGroq or ChatMistralAI to reach them through the firewall,
and those classes will not route through it: they have their own base URLs.
Streaming
Unchanged.
for chunk in llm.stream("Hello"):
print(chunk.content, end="", flush=True)
Handling a guardrail block
LangChain does not wrap SDK exceptions, so a guardrail refusal surfaces as the
underlying OpenAI error: an HTTP 403 raised as openai.PermissionDeniedError.
It is raised from invoke, stream, ainvoke, and from any chain or agent step
that calls the model.
import openai
try:
result = chain.invoke({"question": question})
except openai.PermissionDeniedError as e: # HTTP 403
err = e.response.json().get("error", {})
code = err.get("code") # request_blocked, model_denied, ...
rule = err.get("blocked_by") # the rule that stopped it
if code == "request_blocked":
raise RuntimeError(f"AI firewall blocked this chain: rule {rule!r}") from e
raise
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule; violations lists every rule that matched. |
provider_denied | Policy does not allow this provider. |
model_denied / model_not_allowed | Policy does not allow this model. |
provider_key_missing | No provider key in the vault for this provider. |
In an agent loop this matters more than in a single call: the block can land on any turn, including one triggered by a tool result the model asked for. Catch it around the agent invocation, not just around the first prompt.
pii_redact rule rewrites the
matched spans to the literal [REDACTED] and lets the request through. A RAG chain
whose retrieved documents contain PII will therefore run to completion with those
spans redacted, and the model will answer as if it had never seen them. That is
usually what you want — but it is worth knowing before you debug a chain that
“ignored” its context.Tool / function calling
bind_tools, tool-calling agents, and LangGraph nodes all pass through untouched.
Guardrails inspect the request LangChain sends, and on a tool-calling turn that
request contains the ToolMessage results from the previous step — so a tool that
reads a secret and returns it into the conversation produces a request the firewall
can block or redact, before it ever reaches the provider.
Gotchas
api_keyis your Vulnetix key. A provider key here is the most common401.langchain-openaireadsOPENAI_API_BASEandOPENAI_API_KEYfrom the environment, sovulnetix ai-firewall install shellcan route an existing chain with no code change at all. Passingbase_urlexplicitly is the version that survives a shell that was never re-sourced, and the version you can review in a pull request.- Provider-specific chat classes (
ChatAnthropic,ChatGroq,ChatMistralAI) do not route through the gateway unless you also give them its base URL. For the Anthropic one, remember its base URL takes no/v1— see Anthropic (Python). - Embeddings are not proxied by the firewall —
OpenAIEmbeddingspointed at the gateway will not find an endpoint. Leave embeddings talking to their provider directly.