LangChain adapter
Returns standard LangChain BaseTool (specifically StructuredTool) instances — drop-in compatible with any agent, chain, or AgentExecutor. Two integration patterns:
as_governed_tool— wrap a single Python callable as a governedBaseToolgovern_toolkit— wrap an existing list ofBaseToolinstances (e.g. from a LangChain community toolkit) without mutating them
Both produce tools whose _run / _arun paths run through the policy gate before the underlying function executes.
Install
pip install kitelogik langchain-core
# or for the full LangChain umbrella:
pip install kitelogik langchainlangchain-core is not a hard dependency — only imported on first adapter call. If missing, you get a clear ImportError with the install command.
Setup
from kitelogik import OPAClient, PolicyGate, SessionContext
gate = PolicyGate(opa_client=OPAClient())
context = SessionContext(
session_id="sess_001",
user_role="support_agent",
session_scopes=["read_customer", "approve_refund_under_100"],
)Pattern 1 — as_governed_tool
Wrap a single callable. Sync or async — both produce a StructuredTool that supports both invoke() and ainvoke().
from kitelogik.adapters.langchain import as_governed_tool
def approve_refund(customer_id: str, amount: float) -> str:
return f"Refunded ${amount:.2f} to {customer_id}"
refund_tool = as_governed_tool(
name="approve_refund",
fn=approve_refund,
gate=gate,
context=context,
description="Approve a refund. Args: customer_id (str), amount (float).",
)Pass it to any LangChain agent:
from langchain.agents import create_react_agent
agent = create_react_agent(llm, tools=[refund_tool])The adapter inspects fn's signature and builds a Pydantic args_schema automatically, so per-argument type validation works the same way it would on a hand-written StructuredTool.
Pattern 2 — govern_toolkit
Wrap an existing list of BaseTools — useful when you're consuming a LangChain community toolkit:
from kitelogik.adapters.langchain import govern_toolkit
from langchain_community.agent_toolkits import SQLDatabaseToolkit
raw_tools = SQLDatabaseToolkit(db=db, llm=llm).get_tools()
governed_tools = govern_toolkit(raw_tools, gate=gate, context=context)
agent = create_react_agent(llm, tools=governed_tools)Original tool objects are not mutated — new wrapper objects are returned. Each wrapper preserves the original tool's args_schema, name, and description, falling back to inferring from _run / _arun signatures when no schema is exposed.
The adapter prefers the public ainvoke / invoke API on the wrapped tool (forward-compatible with langchain-core ≥ 0.3 which requires a config kwarg in _arun), and falls back to the private accessors only on older versions.
What happens on a deny
A denied call returns the string "[BLOCKED] <error message>" from the tool — the agent loop continues, the model sees the refusal and can react.
Pattern 1 — as_governed_tool
ALLOW: Refunded $42.00 to cust_001
BLOCK: [BLOCKED] Governance denied tool call: Refunds over $200 require manager approval
Pattern 2 — govern_toolkit
ALLOW: Refunded $42.00 to cust_001
BLOCK: [BLOCKED] Governance denied tool call: Refunds over $200 require manager approvalFunction signatures
as_governed_tool(
name: str,
fn: Callable,
gate: PolicyGate,
context: SessionContext,
description: str = "",
action: str | None = None,
sanitize: bool = True,
) -> BaseToolgovern_toolkit(
tools: list[BaseTool],
gate: PolicyGate,
context: SessionContext,
sanitize: bool = True,
) -> list[BaseTool]| Param | Use |
|---|---|
action | Override the OPA action name (defaults to name) |
sanitize | Run prompt-injection scan on string returns (default True) |
Runnable example
examples/04_langchain_agent.py in the OSS repo demonstrates both patterns end-to-end.
Source
kitelogik/adapters/langchain.py on GitHub.