LangChain / LangGraph
Add a safety layer to your LangChain and LangGraph agents. Every external action your agent takes is verified against your knowledge base and evaluated by your policies before it reaches the outside world.
How the integration works
- Install the
groundtruth-aipackage with the LangChain extra. - Call create_tools() to get configured
@toolfunctions, then pass them to your agent. - Your agents call the tools before any external action. GroundTruth returns approved, blocked, or escalated.
Installation
pip install groundtruth-ai[langchain] langchain-openai langgraphSet your API key as an environment variable:
export GROUNDTRUTH_API_KEY="hg_sk_your_api_key"
export OPENAI_API_KEY="sk-your_openai_key"Quick Start
from groundtruth.langchain import create_tools
# Create all GroundTruth tools, configured with your settings
tools = create_tools(
agent_name="support-agent",
session_id="sess_123",
user_id="user_jane",
)
# Pass tools to your LangChain agent
# tools is a list of @tool-decorated functions compatible with any LangChain agent
create_tools() returns four @tool functions:
groundtruth_execute
Submit an action for safety verification. Verifies content, evaluates policies, and executes the action. Returns approved, blocked, or escalated.
groundtruth_verify
Fact-check AI-generated text against your knowledge base. Use for display-only text (no external action).
groundtruth_await_approval
Poll for a human decision on an escalated action. Uses exponential backoff (2s → 30s) and connection reuse to handle long review times efficiently.
groundtruth_session_history
Retrieve past execution results for the current session.
When to Use Verify vs Execute
A common question is when to use groundtruth_verify vs groundtruth_execute. They serve different purposes:
Execute
Verify + policies + action, all in one.
Use when the agent is performing an external action — sending an email, replying to a ticket, calling a webhook, issuing a refund.
Execute already verifies the content internally, so there is no need to call verify separately before execute.
Verify
Fact-check only. No policies, no action execution.
Use when the agent is showing text to a user — a chatbot response on screen, a draft preview, or comparing multiple options for accuracy.
No action is taken. You just want to know if the text is factually correct.
| Scenario | Tool | Why |
|---|---|---|
| Agent replies to a support ticket | execute | External action — needs policies + verification |
| Agent sends an email | execute | External action |
| Chatbot shows answer in UI | verify | Display only — no external action |
| Agent compares 3 draft options | verify | Picking the most accurate draft |
| Agent issues a refund | execute | External action — needs approval flow |
Avoid calling verify before execute. Since execute already verifies content internally, calling verify first is redundant and can cause confusion — the agent may verify a short draft (risk 0) but then send a longer, different version to execute (risk 1). Use execute directly for external actions.
Configuration
| Param | Description |
|---|---|
| api_key | API key. Falls back to GROUNDTRUTH_API_KEY env var. |
| agent_name | Identifies this agent in the execution log. |
| session_id | Session tracking ID for grouping executions. |
| user_id | Who the agent acts on behalf of. |
| base_url | Defaults to https://app.groundtruth.dev. |
Polling & Timeouts
When an action is escalated, the groundtruth_await_approval tool polls for a human decision. Since reviews can take minutes or hours, the tool uses exponential backoff and connection reuse to avoid exhausting HTTP connections.
| Param | Default | Description |
|---|---|---|
| timeout | 3600 | Maximum seconds to wait (1 hour). |
| poll_interval | 2 | Initial seconds between polls. Responsive for quick approvals. |
| max_poll_interval | 30 | Upper bound for the backoff. Interval grows by 1.5x each poll until capped. |
Backoff curve (defaults)
2s → 3s → 4.5s → 6.7s → 10s → 15s → 22s → 30s (capped)
~7 polls in the first minute, ~125 over a full hour — vs 720 at a constant 5-second interval. The underlying HTTP client reuses the same TCP connection across all polls, so no new connections are opened during the wait.
# Override defaults for a specific call
result = agent.invoke({
"messages": [{
"role": "user",
"content": "Reply to ticket #4521 with a 2-hour approval window.",
}]
})
# Or configure globally via the tool:
# groundtruth_await_approval(execution_id, timeout=7200, max_poll_interval=60)Customer Support Agent with LangGraph
A LangGraph ReAct agent that handles customer support tickets. The agent drafts replies, routes them through GroundTruth for verification, retries with safe rewrites when blocked, and waits for human approval when escalated.
Code
from dotenv import load_dotenv
load_dotenv()
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from groundtruth.langchain import create_tools
# Create GroundTruth tools
gt_tools = create_tools(
agent_name="support-agent",
session_id="sess_demo_001",
user_id="demo-user",
)
# Create agent
llm = ChatOpenAI(model="gpt-4o")
agent = create_react_agent(
llm,
tools=gt_tools,
prompt=(
"You are a customer support agent. When replying to customers:\n"
"1. Draft your reply\n"
"2. Use groundtruth_execute with action='reply_ticket' to send it.\n"
" Pass params as a JSON string, e.g. "
"'{\"ticket_id\": \"4521\", \"body\": \"Your reply\"}\n"
" Always include the full reply text as the 'content' parameter.\n"
" Execute already verifies the content against the knowledge base.\n"
"3. If ESCALATED, use groundtruth_await_approval to wait\n"
"4. If BLOCKED, take the Suggested Rewrite from the response, then\n"
" call groundtruth_execute again with the rewritten text."
),
)
# Run
result = agent.invoke({
"messages": [
{
"role": "user",
"content": "Reply to ticket #4521: customer asks about "
"our cancellation policy.",
}
]
})Agent conversation flow
Agent drafts reply
Execute verifies & decides
If escalated, await approval
What happens at runtime
Real output from running the agent above against a GroundTruth instance with a product pricing knowledge base and safety policies.
Agent drafts a reply and calls execute
The agent drafts a reply about the cancellation policy and submits it via groundtruth_execute with action='reply_ticket'.
BLOCKED — 0/3 claims supported
GroundTruth verifies the reply and finds 3 claims, none supported by the knowledge base. Policies "Block high-risk responses" and "Block AI responses contradicting knowledge base facts" trigger. The action is blocked and a suggested rewrite is returned.
Decision: BLOCKED
Matched Policies: Block high-risk responses, Block AI responses contradicting knowledge base facts
Agent retries with rewrite — ESCALATED
The agent takes the suggested rewrite and calls groundtruth_execute again. This time 1/2 claims are supported, but 1 claim is flagged as "Needs Review." Policy "Escalate medium-risk responses" triggers.
Decision: ESCALATED
Verification: 1/2 claims supported — queued for human review
Agent calls await_approval — polls for decision
The agent calls groundtruth_await_approval with the execution ID. The tool polls with exponential backoff (starting at 2s, scaling to 30s) while a human reviews the action in the GroundTruth dashboard. Connections are reused across polls to avoid exhaustion.
APPROVED — human approves in dashboard
A reviewer approves the action. The await tool returns APPROVED. The agent confirms the reply was sent to the customer.
Approval APPROVED
The action has been approved. You may proceed.
Agent responds to user
"I've successfully replied to ticket #4521. Here's the response sent to the customer..."
The agent went through blocked → retried → escalated → approved in a single conversation. GroundTruth prevented an inaccurate reply from reaching the customer, and a human reviewer approved the corrected version.
Using the Core Functions Directly
If you need programmatic access without the LangChain @tool decorator, import the core functions directly:
from groundtruth import execute, verify
# Verify text (for display-only, no external action)
result = verify(answer="You can cancel anytime with a full refund.")
print(result)
# Execute an action (verify + policies + action in one call)
result = execute(
action="send_email",
params={"to": "customer@acme.com", "body": "..."},
content="Your email text here.",
channel="email",
agent="my-script",
)
print(result)