LangChain
Overview
**langchain-prism** is the official Prism integration for LangChain and LangGraph. It provides a drop-in callback handler that intercepts tool calls before execution and enforces your Prism policies without requiring any changes to your agent code.
Enforcement operates at two levels: tool calls via PrismCallback, and LLM calls via PrismModel, the latter required for LangGraph, which swallows callback exceptions raised from LLM hooks.
Installation
pip install langchain-prism
How It Works
PrismCallback intercepts tool calls before execution via LangChain's BaseCallbackHandler interface. For each tool call, it forwards an intent event to the Prism data plane, which evaluates it against installed policies and returns **ALLOW** or **DENY**. A DENY raises a PermissionError that halts execution before the tool runs.
Enforcement operates at two levels:
- **Tool calls**: via
PrismCallback(works for both LangChain and LangGraph) - **LLM calls**: via
PrismModel(wraps your chat model; required because LangGraph swallows callback exceptions from LLM hooks)
Usage
With model (standard)
Provide a lightweight chat model and Prism will infer the action and resource fields from each tool call automatically.
from prism.langchain import PrismCallback
from langchain_openai import ChatOpenAI
callback = PrismCallback(
base_url="https://your-prism-instance",
tenant_id="your-tenant-id",
model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
)
# Attach at invocation. No changes to agent code required
agent.invoke(input, config={"callbacks": [callback]})
graph.invoke(input, config={"callbacks": [callback]})
With custom mappers (deterministic)
Supply action_mapper and/or resource_mapper callables to bypass model inference entirely. When both are provided, no model is required.
callback = PrismCallback(
base_url="https://your-prism-instance",
tenant_id="your-tenant-id",
action_mapper=lambda tool_name, tool_input: "deleting record",
resource_mapper=lambda tool_name: "database",
)
Mixed
Provide one mapper and let the model handle the remaining field.
callback = PrismCallback(
base_url="https://your-prism-instance",
tenant_id="your-tenant-id",
model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
action_mapper=lambda tool_name, tool_input: "deleting record",
# resource inferred by model
)
LLM-Level Enforcement
LangGraph swallows exceptions raised inside LLM callback hooks. To enforce policies on LLM calls within a graph, wrap your model with PrismModel. It delegates to the underlying LLM while routing enforcement through the same PrismCallback instance.
from prism.langchain import PrismCallback, PrismModel
from langchain_openai import ChatOpenAI
inner_llm = ChatOpenAI(model="gpt-4o")
callback = PrismCallback(base_url=..., tenant_id=..., model=ChatOpenAI(model="gpt-4o-mini", temperature=0))
llm = PrismModel(llm=inner_llm, callback=callback)
Parameters
All constructor parameters for PrismCallback:
| Parameter | Type | Default | Description |
|---|---|---|---|
| base_url | str | required | Prism instance URL |
| tenant_id | str | required | Tenant identifier, sent as X-Tenant-Id |
| model | BaseChatModel | Callable | None | None | Model for intent inference. Required unless both mappers are provided |
| api_key | str | None | None | Optional API key |
| agent_id | str | "unknown" | Agent identifier included in intent events |
| principal_id | str | "unknown" | Human user or service account on whose behalf the agent acts (audit only) |
| enforcement_mode | str | "block" | "block" raises PermissionError on DENY; "warn" logs and continues |
| timeout | float | 2.0 | Request timeout in seconds |
| fail_open | bool | True | If True, allows execution when Prism is unreachable; if False, raises |
| action_mapper | Callable[[str, dict], str] | None | None | Overrides model inference for the action field |
| resource_mapper | Callable[[str], str] | None | None | Overrides model inference for the resource field |
Mapper precedence
| action_mapper | resource_mapper | model required? |
|---|---|---|
| provided | provided | No |
| provided | absent | Yes (used for resource) |
| absent | provided | Yes (used for action) |
| absent | absent | Yes (used for both) |
Lightweight Model Options
Any BaseChatModel works. For low-latency intent inference, prefer a small model. You can also supply a plain callable that accepts a system message and a user message and returns raw JSON.
# OpenAI
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Anthropic
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-haiku-4-5-20251001", temperature=0)
# Google
from langchain_google_genai import ChatGoogleGenerativeAI
model = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
# Custom callable
def my_model(system_msg: str, user_msg: str) -> str:
# Return raw JSON: {"action": "...", "resource": "..."}
...
model = my_model