Skip to main content

LangChain Integration

Add governance to your LangChain chains and agents with a callback handler. Drop it into any LangChain component and all LLM calls and tool invocations are automatically enforced by your dashboard policies.

Setup

pip install controlzero langchain langchain-openai
from controlzero import Client
from controlzero.integrations.langchain import ControlZeroCallbackHandler
from langchain_openai import ChatOpenAI

cz = Client(api_key="cz_live_your_api_key_here")
handler = ControlZeroCallbackHandler(cz)

# Add the handler to any LangChain LLM. All calls are now governed
llm = ChatOpenAI(model="gpt-5.4", callbacks=[handler])

One extra line: create the handler and pass it to your LLM or chain. Every LLM call and tool invocation is now checked against your dashboard policies automatically.

What Gets Enforced Automatically

The callback handler intercepts two types of events:

LangChain EventPolicy ActionPolicy Resource
LLM call (on_llm_start)llm.generatemodel/{model_name}
Tool call (on_tool_start)tool.calltool/{tool_name}

When LangChain triggers an LLM call, the handler extracts the model name and checks it against your policies. When a tool is invoked, it extracts the tool name and checks that too.

Audit-only by default

ControlZeroCallbackHandler logs every LLM call and tool invocation to your audit trail but does not block actions on its own. To enforce policies and stop execution on a deny decision, wrap your tools with GovernedTool or add explicit cz.guard() calls inside each tool function.

Example: Research Agent with Policy Enforcement

from controlzero import Client
from controlzero.integrations.langchain import ControlZeroCallbackHandler
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool

# --- Setup Control Zero ---
cz = Client(api_key="cz_live_your_api_key_here")
handler = ControlZeroCallbackHandler(cz)

# --- Define tools ---
@tool
def search_web(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"

@tool
def read_database(query: str) -> str:
"""Query the internal database."""
return f"DB results for: {query}"

# --- Create agent with governance ---
llm = ChatOpenAI(model="gpt-5.4", callbacks=[handler])

prompt = ChatPromptTemplate.from_messages([
("system", "You are a research assistant."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, [search_web, read_database], prompt)
executor = AgentExecutor(
agent=agent,
tools=[search_web, read_database],
callbacks=[handler],
)

# --- Run it ---
try:
result = executor.invoke({"input": "Find recent sales data"})
print(result["output"])
except controlzero.PolicyDeniedError as e:
print(f"Blocked by policy: {e.decision.reason}")

Example Policy for the Research Agent

Define this in the Control Zero dashboard:

{
"name": "research-agent-policy",
"description": "Allow web search but block database access",
"rules": [
{ "effect": "allow", "action": "llm:generate", "resource": "model/gpt-5.4" },
{ "effect": "allow", "action": "tool:call", "resource": "tool/search_web" },
{ "effect": "deny", "action": "tool:call", "resource": "tool/read_database" }
]
}

What happens at runtime:

  • When the agent calls GPT-4 for reasoning: ALLOWED (matches rule 1).
  • When the agent tries to use search_web: ALLOWED (matches rule 2).
  • When the agent tries to use read_database: DENIED (matches rule 3). The decision is logged to the audit trail. To block execution, use GovernedTool or call cz.guard() inside the tool.

Using with Chains

The handler works with any LangChain component that accepts callbacks:

from langchain_core.output_parsers import StrOutputParser

chain = llm | StrOutputParser()
# Handler is already attached to the llm, so all calls in this chain are governed

result = chain.invoke("Summarize this report")

Next Steps