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:

ParameterTypeDefaultDescription
base_urlstrrequiredPrism instance URL
tenant_idstrrequiredTenant identifier, sent as X-Tenant-Id
modelBaseChatModel | Callable | NoneNoneModel for intent inference. Required unless both mappers are provided
api_keystr | NoneNoneOptional API key
agent_idstr"unknown"Agent identifier included in intent events
principal_idstr"unknown"Human user or service account on whose behalf the agent acts (audit only)
enforcement_modestr"block""block" raises PermissionError on DENY; "warn" logs and continues
timeoutfloat2.0Request timeout in seconds
fail_openboolTrueIf True, allows execution when Prism is unreachable; if False, raises
action_mapperCallable[[str, dict], str] | NoneNoneOverrides model inference for the action field
resource_mapperCallable[[str], str] | NoneNoneOverrides model inference for the resource field

Mapper precedence

action_mapperresource_mappermodel required?
providedprovidedNo
providedabsentYes (used for resource)
absentprovidedYes (used for action)
absentabsentYes (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