Veto/docs

LangChain Integration

Use Veto with LangChain agents — middleware, LangGraph ToolNode, and callback handler.

Veto integrates with LangChain at three levels. Pick the pattern that matches your setup.

PatternBest forBlocks tool calls?
Middleware (recommended)createAgent / LangGraphYes
ToolNode wrapperRaw StateGraph with ToolNodeYes (partial denial supported)
Callback handlerAudit logging onlyNo

Installation

npm install veto-sdk @langchain/core
pip install veto langchain-core

Middleware

The middleware intercepts every tool call before execution. Pass it to createAgent or any LangGraph workflow that supports middleware.

import { Veto } from 'veto-sdk';
import { createVetoLangChainMiddleware } from 'veto-sdk/integrations/langchain';
import { createAgent } from '@langchain/core/agents';

const veto = await Veto.init();
const middleware = createVetoLangChainMiddleware(veto);

const agent = createAgent({
  tools: myTools,
  middleware: [middleware],
});
from veto import Veto
from veto.integrations.langchain import VetoMiddleware

veto = await Veto.init()
middleware = VetoMiddleware(veto)

agent = create_agent(
    tools=my_tools,
    middleware=[middleware],
)

A functional alternative is also available:

from veto.integrations.langchain import veto_wrap_tool_call

wrap_fn = veto_wrap_tool_call(veto)
agent = create_agent(
    tools=my_tools,
    middleware=[{"name": "veto", "wrapToolCall": wrap_fn}],
)

Runnable SDK example

See:

  • packages/sdk/examples/langchain/langchain_agent.ts

The example includes both:

  • veto.guard(...) preflight checks
  • wrapped agent execution with veto.wrap(...)

Middleware options

OptionTypeDefaultDescription
onAllow(toolName, args) => voidCalled when a tool call passes validation
onDeny(toolName, args, reason) => voidCalled when a tool call is denied
throwOnDenybooleanfalseThrow ToolCallDeniedError instead of returning a ToolMessage
OptionTypeDefaultDescription
on_allowCallableNoneCalled when a tool call passes validation
on_denyCallableNoneCalled when a tool call is denied
throw_on_denyboolFalseRaise ToolCallDeniedError instead of returning a ToolMessage

How denial works

By default, denied tool calls return a ToolMessage with the denial reason:

Tool call denied by Veto: <reason>

The agent sees the denial as a normal tool response and can adapt. Set throwOnDeny / throw_on_deny to true to throw a ToolCallDeniedError instead — useful when you want to halt the agent on any denied call.

If @langchain/core / langchain_core is not installed, the middleware falls back to returning a plain dict with content and tool_call_id fields.

LangGraph ToolNode

For raw StateGraph workflows that use a ToolNode directly, wrap it with createVetoToolNode / create_veto_tool_node. This validates all tool calls in the batch before executing any of them.

import { createVetoToolNode } from 'veto-sdk/integrations/langchain';
import { ToolNode } from '@langchain/langgraph/prebuilt';

const toolNode = new ToolNode(myTools);
const safeToolNode = createVetoToolNode(veto, toolNode);

const graph = new StateGraph(...)
  .addNode("tools", safeToolNode)
  // ...
from veto.integrations.langchain import create_veto_tool_node
from langgraph.prebuilt import ToolNode

tool_node = ToolNode(my_tools)
safe_tool_node = create_veto_tool_node(veto, tool_node)

graph = StateGraph(...)
graph.add_node("tools", safe_tool_node)

ToolNode options

OptionTypeDescription
onAllow / on_allowcallbackCalled per-allowed tool call
onDeny / on_denycallbackCalled per-denied tool call

No throwOnDeny option — denied calls always return ToolMessage objects.

Partial denial

When a batch contains multiple tool calls, the ToolNode wrapper validates all of them first:

  • All denied: Returns denial ToolMessage objects without calling the underlying ToolNode
  • All allowed: Passes through to ToolNode normally
  • Mixed: Strips denied calls from the message, executes allowed calls via ToolNode, then returns both denial messages and execution results

Callback handler

The callback handler is observational — it cannot block tool execution. Use it for audit logging alongside other validation methods.

import { createVetoCallbackHandler } from 'veto-sdk/integrations/langchain';

const handler = createVetoCallbackHandler({
  onToolStart: (toolName, input) => {
    console.log(`Tool started: ${toolName}`);
  },
  onToolEnd: (toolName, output) => {
    console.log(`Tool finished: ${toolName}`);
  },
  onToolError: (toolName, error) => {
    console.error(`Tool error: ${toolName}`, error);
  },
});

const result = await agent.invoke(input, { callbacks: [handler] });
from veto.integrations.langchain import VetoCallbackHandler

handler = VetoCallbackHandler(
    on_tool_start=lambda name, input: print(f"Tool started: {name}"),
    on_tool_end=lambda name, output: print(f"Tool finished: {name}"),
    on_tool_error=lambda name, error: print(f"Tool error: {name}: {error}"),
)

result = await agent.ainvoke(input, config={"callbacks": [handler]})

The Python handler inherits from langchain_core.callbacks.BaseCallbackHandler when available, making it compatible with LangChain's CallbackManager. Callbacks must be synchronous (async is not supported for callback handlers).

Callback options

OptionTypeDescription
onToolStart / on_tool_startcallbackCalled when a tool starts executing
onToolEnd / on_tool_endcallbackCalled when a tool finishes
onToolError / on_tool_errorcallbackCalled when a tool throws an error

All callbacks are optional.

Exports

import {
  createVetoLangChainMiddleware,
  createVetoToolNode,
  createVetoCallbackHandler,
} from 'veto-sdk/integrations/langchain';

import type {
  VetoLangChainMiddlewareOptions,
  VetoToolNodeOptions,
  VetoCallbackOptions,
} from 'veto-sdk/integrations/langchain';
from veto.integrations.langchain import (
    VetoMiddleware,
    veto_wrap_tool_call,
    create_veto_tool_node,
    VetoCallbackHandler,
)