EvalLayerTry the demo

Adding commercial guardrails to LangChain agents

Published 2026-07-27 · EvalLayer

You add commercial guardrails to LangChain agents by wrapping the tools that act on the outside world in a tool-call middleware: before the handler executes send_email or issue_refund, the wrapper posts the intended content to EvalLayer's /authorize endpoint and switches on the decision, executing, substituting the compliant rewrite, queueing for a human, or refusing with a reason the model can read. One wrapper, declared once on create_agent, covers every consequential tool.

Why LangChain agents create commercial risk

LangChain's 1.0 reset consolidated agent-building into create_agent with LangGraph as the runtime underneath, and the ecosystem's enormous tool surface is the draw: agents wired to email, CRMs, payment APIs, and support desks in an afternoon. Every one of those tools converts model text into a business action. The model does not need to be unsafe to be expensive; it needs to be helpful to a customer asking for 25 percent off, or confident about a ship date, in a channel where its word binds the company that deployed it.

The 1.0 middleware system is the right substrate for a fix. It gives you named hooks at each stage of the agent loop, including a wrapper around every tool invocation, so interception is now a supported pattern rather than a monkey-patch. What middleware does not include is the judgment: nothing in the framework knows your discount cap, your signing authority, or which claims legal approved. That is the piece you attach.

The interception point: a tool-call wrapper

LangChain middleware exposes a hook that wraps tool execution: it receives the pending tool call and a handler that will run it, and it decides what actually happens. That is the exact seam for pre-action authorization. The shape below follows the documented wrapper pattern; treat it as a pattern to adapt to your middleware setup rather than copy-paste, since request object fields vary by version.

import httpx
from langchain.agents.middleware import wrap_tool_call

GUARDED = {"send_email", "send_quote", "issue_refund"}

POLICY = {
    "max_discount_pct": 10,
    "commitment_authority_usd": 1000,
    "forbidden_commitments": ["delivery_date", "refund_promise"],
    "approved_claims": ["soc2_certified"],
    "required_disclaimers": ["pricing_subject_to_change"],
    "blocked_topics": [],
}

@wrap_tool_call
def commercial_guardrail(request, handler):
    name = request.tool_call["name"]
    if name not in GUARDED:
        return handler(request)

    draft = request.tool_call["args"].get("body", "")
    r = httpx.post(
        "https://api.evallayer.ai/authorize",
        headers={"Authorization": "Bearer " + EVALLAYER_KEY},
        json={"action_type": name, "content": draft,
              "context": {"tool": name}, "policy": POLICY},
        timeout=10,
    )
    data = r.json()

    if data["decision"] == "allow":
        return handler(request)
    if data["decision"] == "rewrite":
        request.tool_call["args"]["body"] = data["approved_rewrite"]
        return handler(request)
    if data["decision"] == "require_approval":
        queue_for_human(draft, data["authorization_id"])
        return tool_message(request,
            "Held for human approval (ref " + data["authorization_id"] + ").")
    return tool_message(request,
        "Blocked by commercial policy: " + data["violations"][0]["detail"])

agent = create_agent(model, tools=tools, middleware=[commercial_guardrail])

Returning a message instead of calling the handler is the important move on the two failing branches: the send never happens, but the agent learns why in-band and can continue the conversation gracefully, telling the customer the offer is being confirmed rather than going silent.

Decision handling

  • allow: call the handler; the tool runs with the original arguments.
  • rewrite: swap the outbound text for approved_rewrite, then call the handler. The customer receives the compliant version; the loop never stalls.
  • require_approval: skip the handler, queue the draft with its authorization_id, and return a tool message saying so. A human approves or rejects out-of-band.
  • block: skip the handler and return the violation reason. Log the authorization_id; the model gets enough context to take a different path.

Latency: wrap sends, not thinking

The GUARDED set is the control. Retrieval, search, and calculator tools should not pass through authorization; only tools with external side effects should. On those, deterministic-only policies cost milliseconds and semantic checks one to two seconds, which is a rounding error next to the model calls already in the loop. Set a client timeout and decide failure modes per action type: fail closed on refunds, fail open with logging on low-stakes replies if availability matters more.

What to log

Record the authorization_id in the tool message and in your tracing (it slots naturally into LangSmith metadata). Every consequential send in a trace then carries its policy verdict, which turns incident review from transcript forensics into a lookup: draft, decision, violations, and what actually went out.

Try it on one tool

Wrap your single riskiest tool first and point it at /demo/authorize, which allows three free checks per day without a key, or preview the decision card in the interactive demo. The full decision semantics live in the authorization guide, and policy design covers picking caps and forbidden commitments that mirror real authority.

Frequently asked questions

Which middleware hook should the check use?

The tool-call wrapper. LangChain 1.0 middleware exposes hooks around the model call and around tool execution; the commercial risk lives at tool execution, where send_email or issue_refund actually fires. Wrapping the model call instead would check text the agent may never act on and miss arguments assembled at call time.

Is this different from LangChain's content moderation integrations?

Yes. Moderation layers ask whether text is harmful or off-brand in general. Commercial authorization asks whether this specific action complies with this business's policy: the discount inside the cap, the commitment one the company can honor, the disclaimer present. A polite, on-brand email can still promise a delivery date nobody approved.

What does the check cost per tool call?

Deterministic policy checks return in milliseconds. Semantic checks add one to two seconds, about one extra model call on the sends that matter. Authorization is priced from $0.01 per request with a Bearer key or x402, and /demo/authorize allows three free checks per day for development.

See the authorization layer live

Paste a message, set a policy, get allow / rewrite / require approval / block in seconds.

Try the demo