Docs/LangChain Guide
LangChain Integration

LangChain + Tork Governance

Add on-device PII governance to your LangChain chains and agents. Govern prompts, generations, chain inputs and outputs, redact or deny by policy, and keep a local receipt for every decision.

Policy Enforcement

Block unsafe content automatically

Chain Governance

Wrap any LangChain chain

Tool Safety

Callbacks govern tool inputs and outputs

LCEL Support

Works with modern LangChain

Installation

Install Tork with LangChain dependencies.

bash
pip install tork-governance langchain langchain-openai

The adapter ships inside the on-device package: import it from tork_governance.adapters.langchain. It exports exactly three names: TorkCallbackHandler, TorkGovernedChain and create_governed_chain. PII detection and the allow/redact/deny decision run on your machine; no API key is required.

TorkCallbackHandler

Monitor and govern all LangChain operations via callbacks.

The callback handler implements the LangChain callback method names and runsTork.govern() on every LLM prompt, LLM generation, tool input and tool output. Redacted text is written back in place; withblock_on_pii=True and a Tork configured withdefault_action=GovernanceAction.DENY, a denied prompt raisesValueError. Chain start/end hooks exist but are no-ops.

pythoncallback_example.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from tork_governance.adapters.langchain import TorkCallbackHandler

# The handler builds its own on-device Tork() instance. No API key is
# needed: PII detection and the decision run on this machine.
handler = TorkCallbackHandler()

# Attach it to any LangChain component that accepts callbacks
llm = ChatOpenAI(model="gpt-4o", callbacks=[handler])

prompt = ChatPromptTemplate.from_template("Tell me about {topic}")
chain = prompt | llm

result = chain.invoke({"topic": "machine learning"})

# on_llm_start governs every prompt, on_llm_end every generation.
# Each call appends a dict: {"type", "receipt", "action"}.
for entry in handler.receipts:
    print(entry["type"], entry["action"], entry["receipt"].receipt_id)

Callback Events

on_llm_startEvaluate prompts before sending to LLM
on_llm_endEvaluate LLM responses
on_chain_startNo-op in 0.26.1 (use TorkGovernedChain for chain inputs)
on_chain_endNo-op in 0.26.1 (use TorkGovernedChain for chain outputs)
on_tool_startEvaluate tool inputs
on_tool_endEvaluate tool outputs

TorkGovernedChain

Wrap any LangChain chain with governance controls.

TorkGovernedChain (or thecreate_governed_chain() factory) wraps any LangChain runnable. Every string value in the input is governed before chain.invoke(), and a string output or a message's .content is governed after. PII is redacted in place by default; a DENY decision raises ValueError.

pythongoverned_chain.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from tork_governance.adapters.langchain import create_governed_chain

llm = ChatOpenAI(model="gpt-4o")

# A plain LCEL chain
prompt = ChatPromptTemplate.from_template(
    "You are a helpful assistant. Answer: {question}"
)
chain = prompt | llm

# Wrap it: every string input is governed before the chain runs,
# and the output (str, or a message with .content) after it.
governed_chain = create_governed_chain(chain)

# Use it like the original chain
result = governed_chain.invoke({"question": "What is Python?"})
print(result.content)

# The last governance decision is kept on the wrapper
last = governed_chain.last_result
print(last.action.value, last.receipt.receipt_id)

Governing individual tools

What the adapter does and does not provide for tools.

The published adapter has no per-tool wrapper (there is no TorkGovernedTool). Tool inputs and outputs are governed as strings through the callback handler'son_tool_start / on_tool_end hooks, which record a receipt but do not stop the tool from running. To gate a side effect, callTork.govern() on the arguments inside the tool itself, as the AgentExecutor example below does for send_email.

LCEL Integration

Use with LangChain Expression Language (LCEL) chains.

Both patterns work with LCEL runnables. Use the callback handler to govern what the LLM sees and says, or TorkGovernedChain to govern the chain's own inputs and outputs.

pythonlcel_example.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from tork_governance import Tork
from tork_governance.adapters.langchain import TorkCallbackHandler, TorkGovernedChain

# One Tork instance shared by both patterns
tork = Tork(policy_version="1.0.0")

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

llm = ChatOpenAI(model="gpt-4o")
output_parser = StrOutputParser()

chain = prompt | llm | output_parser

# Option 1: callback handler — governs LLM prompts and generations
handler = TorkCallbackHandler(tork=tork)
result = chain.invoke(
    {"input": "Write a hello world in Python"},
    config={"callbacks": [handler]},
)

# Option 2: wrap the whole chain — governs the chain's inputs and outputs
governed = TorkGovernedChain(chain, tork=tork)
result = governed.invoke({"input": "Explain recursion"})

AgentExecutor with Governance

Full governance for multi-step agent workflows.

Attach one callback handler to the LLM and the executor. Prompts, generations, tool inputs and tool outputs are each governed and receipted; gate side effects inside the tool body.

pythonagent_executor.py
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from tork_governance import Tork
from tork_governance.adapters.langchain import TorkCallbackHandler

tork = Tork()
handler = TorkCallbackHandler(tork=tork)

@tool
def search_database(query: str) -> str:
    """Search the internal database for information."""
    return f"Results for: {query}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to the specified recipient."""
    # Govern the arguments yourself before the side effect: the adapter
    # has no per-tool wrapper, but Tork.govern() works on any string.
    governed_body = tork.govern(body).output
    return f"Email sent to {to} with body: {governed_body}"

llm = ChatOpenAI(model="gpt-4o", callbacks=[handler])

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful research assistant."),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_functions_agent(
    llm=llm,
    tools=[search_database, send_email],
    prompt=prompt,
)

executor = AgentExecutor(
    agent=agent,
    tools=[search_database, send_email],
    callbacks=[handler],
    verbose=True,
)

result = executor.invoke({
    "input": "Search for Q4 results and email a summary to manager@company.com"
})

# on_tool_start / on_tool_end govern each tool's input and output string
for entry in handler.receipts:
    print(f"{entry['type']}: {entry['action']} ({entry['receipt'].receipt_id})")

Policy Enforcement Patterns

Redact by default, deny when you choose to.

The on-device SDK has one decision rule: when PII is detected it applies the Tork instance'sdefault_action (REDACT unless you set DENY). A DENY insideTorkGovernedChain.invoke() raises a plainValueError; there is no custom exception class.

pythonpolicy_handling.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from tork_governance import Tork, GovernanceAction
from tork_governance.adapters.langchain import TorkGovernedChain

# The default action on PII is REDACT (the text goes through with the PII
# replaced). To block instead, construct Tork with default_action=DENY.
strict = Tork(default_action=GovernanceAction.DENY)

llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_template("{input}")
chain = prompt | llm

governed = TorkGovernedChain(chain, tork=strict)

def safe_invoke(user_input: str) -> str:
    """Invoke the chain; a DENY decision raises ValueError."""
    try:
        result = governed.invoke({"input": user_input})
        return result.content if hasattr(result, "content") else str(result)
    except ValueError as e:
        # Message is "Input blocked: <receipt_id>" — the receipt stays
        # available on the Tork instance's decision, nothing else is exposed.
        return "I cannot process requests containing personal information."

response = safe_invoke("What's the weather like?")     # allowed
response = safe_invoke("My SSN is 123-45-6789")        # blocked: PII detected

# Note: the on-device adapter detects PII. It does not detect jailbreak
# attempts — that check is a separate server-side endpoint, POST /api/v1/jailbreak.

Advanced Patterns

Streaming, async, and compliance receipts

python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.callbacks import StreamingStdOutCallbackHandler
from tork_governance.adapters.langchain import TorkCallbackHandler

tork_handler = TorkCallbackHandler()
stream_handler = StreamingStdOutCallbackHandler()

llm = ChatOpenAI(
    model="gpt-4o",
    streaming=True,
    callbacks=[tork_handler, stream_handler],
)

prompt = ChatPromptTemplate.from_template("Write a poem about {topic}")
chain = prompt | llm

# The prompt is governed in on_llm_start before any token streams.
# The generation is governed in on_llm_end — AFTER the stream has already
# been printed by stream_handler, so redaction there does not retract
# tokens a user has seen. Govern the prompt strictly if that matters.
result = chain.invoke({"topic": "technology"})

Best Practices

Use callbacks for the LLM boundary, TorkGovernedChain for the chain boundary

The handler governs prompts and generations; the wrapper governs the chain's inputs and outputs. Both redact in place.

Gate side effects inside the tool

Callbacks receipt tool I/O but do not stop a tool. Call Tork.govern() on the arguments before a database write, email or file change.

Handle ValueError from a DENY decision

With default_action=DENY the wrapper raises ValueError. Catch it and return a user-friendly message.

Persist handler.receipts yourself

Receipts are local objects. They reach the dashboard only with Tork(api_key=...) and then as client attestations.

Share one Tork instance

Pass the same Tork(policy_version=...) to every handler and wrapper so receipts carry one policy version.

Imports Reference

python
from tork_governance import Tork, GovernanceAction, GovernanceResult, Receipt
from tork_governance.adapters.langchain import (
    TorkCallbackHandler,    # Callback handler (prompts, generations, tool I/O)
    TorkGovernedChain,      # Chain wrapper (inputs, outputs)
    create_governed_chain,  # Factory for TorkGovernedChain
)

Next Steps

Configure policies in the dashboard and explore other integration options.

Documentation

Learn to integrate TORK

Upgrade Plan

Current: free

Support

Get help from our team