Skip to main content
Veto integrates with LangChain through middleware, callback handlers, and LangGraph ToolNode wrappers. This provides validation for every tool call made by LangChain agents.

Installation

npm install veto-sdk langchain @langchain/core

Quick Start

Using Middleware with create_agent

The simplest way to add Veto to a LangChain agent is with middleware:
import { createAgent } from 'langchain';
import { Veto } from 'veto-sdk';
import { createVetoLangChainMiddleware } from 'veto-sdk/integrations/langchain';

const veto = await Veto.init();

const agent = createAgent({
  model: 'openai:gpt-4o',
  tools: [searchTool, emailTool],
  middleware: [createVetoLangChainMiddleware(veto)],
});

const result = await agent.invoke({
  messages: [{ role: 'user', content: 'Search for latest news' }],
});

Middleware Options

Both TypeScript and Python middleware accept optional callbacks:
import { createVetoLangChainMiddleware } from 'veto-sdk/integrations/langchain';

const middleware = createVetoLangChainMiddleware(veto, {
  // Called when a tool call is allowed
  onAllow: (toolName, args) => {
    console.log(`βœ“ Allowed ${toolName}`);
  },
  
  // Called when a tool call is denied
  onDeny: (toolName, args, reason) => {
    console.error(`βœ— Denied ${toolName}: ${reason}`);
  },
  
  // Throw ToolCallDeniedError instead of returning ToolMessage
  throwOnDeny: false, // default: false
});

Error Handling

By default, denied tool calls return a ToolMessage to the model indicating the denial. Set throwOnDeny: true to raise an exception instead:
const middleware = createVetoLangChainMiddleware(veto, {
  throwOnDeny: true,
});

try {
  await agent.invoke({ messages });
} catch (error) {
  if (error instanceof ToolCallDeniedError) {
    console.error(`Tool ${error.toolName} denied: ${error.validationResult.reason}`);
  }
}

LangGraph ToolNode Integration

For LangGraph workflows, wrap your ToolNode to add validation:
import { createVetoToolNode } from 'veto-sdk/integrations/langchain';
import { ToolNode } from '@langchain/langgraph/prebuilt';

const toolNode = new ToolNode([searchTool, emailTool]);
const vetoToolNode = createVetoToolNode(veto, toolNode, {
  onDeny: (toolName, args, reason) => {
    console.error(`Blocked in graph: ${toolName} - ${reason}`);
  },
});

// Use in your StateGraph
const workflow = new StateGraph({
  channels: agentState,
})
  .addNode('agent', agentNode)
  .addNode('tools', vetoToolNode)  // Use wrapped tool node
  .addEdge(START, 'agent')
  .addConditionalEdges('agent', shouldContinue)
  .addEdge('tools', 'agent');

Callback Handler (Observability)

For observability without blocking execution, use the callback handler:
import { createVetoCallbackHandler } from 'veto-sdk/integrations/langchain';

const handler = createVetoCallbackHandler(veto, {
  logAllowed: true,
  logDenied: true,
});

const result = await agent.invoke(
  { messages },
  { callbacks: [handler] }
);
The callback handler does not block tool execution β€” it only logs validation results. Use middleware to enforce policies.

Complete Example

Here’s a complete example with LangChain and Veto:
import { ChatOpenAI } from '@langchain/openai';
import { createAgent } from 'langchain';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { Veto } from 'veto-sdk';
import { createVetoLangChainMiddleware } from 'veto-sdk/integrations/langchain';

// Define tools
const sendEmail = tool(
  async ({ to, subject, body }) => {
    console.log(`Sending email to ${to}...`);
    return { sent: true, messageId: '123' };
  },
  {
    name: 'send_email',
    description: 'Send an email to a recipient',
    schema: z.object({
      to: z.string().email(),
      subject: z.string(),
      body: z.string(),
    }),
  }
);

// Initialize Veto with middleware
const veto = await Veto.init();
const middleware = createVetoLangChainMiddleware(veto, {
  onDeny: (name, args, reason) => {
    console.error(`πŸ›‘ Blocked ${name}: ${reason}`);
  },
});

// Create agent
const agent = createAgent({
  model: new ChatOpenAI({ model: 'gpt-4o' }),
  tools: [sendEmail],
  middleware: [middleware],
});

// Run agent
const result = await agent.invoke({
  messages: [
    {
      role: 'user',
      content: 'Send an email to [email protected] about the meeting',
    },
  ],
});

console.log(result);

How It Works

The LangChain middleware intercepts tool calls through the wrapToolCall / wrap_tool_call middleware hook:
  1. Agent decides to call a tool (e.g., send_email)
  2. Middleware intercepts the tool call before execution
  3. Veto validates arguments against your rules:
    • Deterministic checks (e.g., to must end with @company.com)
    • Optional LLM checks (e.g., β€œis this email professional?”)
  4. Decision:
    • Allow: Modified arguments (if any) forwarded to original handler
    • Deny: Returns ToolMessage with reason (or throws if throwOnDeny: true)

Advanced: Argument Modification

Veto can modify tool arguments based on rules (e.g., sanitize input, redact data). Modified arguments are automatically forwarded:
rules:
  - id: sanitize-email-body
    name: Remove sensitive data from emails
    action: allow
    tools:
      - send_email
    modify:
      arguments.body:
        pattern: "SSN: \\d{3}-\\d{2}-\\d{4}"
        replacement: "[REDACTED]"
The middleware detects modified arguments and passes them through:
// Original call: send_email({ body: "SSN: 123-45-6789" })
// Veto modifies to: send_email({ body: "[REDACTED]" })
// Tool receives the sanitized version

TypeScript API Reference

createVetoLangChainMiddleware(veto, options?)

Create LangChain middleware that validates tool calls. Parameters:
  • veto: Veto - Initialized Veto instance
  • options?: VetoLangChainMiddlewareOptions
    • onAllow?: (toolName, args) => void | Promise<void>
    • onDeny?: (toolName, args, reason) => void | Promise<void>
    • throwOnDeny?: boolean - Throw instead of returning ToolMessage (default: false)
Returns: { name: string, wrapToolCall: Function }

createVetoToolNode(veto, toolNode, options?)

Wrap a LangGraph ToolNode with Veto validation. Parameters:
  • veto: Veto - Initialized Veto instance
  • toolNode: ToolNode - LangGraph ToolNode instance
  • options?: VetoToolNodeOptions - Same as middleware options
Returns: Wrapped ToolNode compatible with StateGraph

createVetoCallbackHandler(veto, options?)

Create a callback handler for observability (non-blocking). Parameters:
  • veto: Veto - Initialized Veto instance
  • options?: VetoCallbackOptions
    • logAllowed?: boolean - Log allowed calls (default: false)
    • logDenied?: boolean - Log denied calls (default: true)
Returns: BaseCallbackHandler

Python API Reference

VetoMiddleware(veto, *, on_allow, on_deny, throw_on_deny)

Class-based LangChain middleware. Parameters:
  • veto: Veto - Initialized Veto instance
  • on_allow: Callable | None - Callback for allowed calls
  • on_deny: Callable | None - Callback for denied calls
  • throw_on_deny: bool - Raise exception instead of returning ToolMessage (default: False)

create_veto_tool_node(veto, tool_node, *, on_allow, on_deny)

Wrap a LangGraph ToolNode with Veto validation. Parameters:
  • veto: Veto - Initialized Veto instance
  • tool_node: ToolNode - LangGraph ToolNode instance
  • on_allow: Callable | None - Callback for allowed calls
  • on_deny: Callable | None - Callback for denied calls
Returns: Wrapped ToolNode

VetoCallbackHandler(veto, *, log_allowed, log_denied)

Callback handler for observability. Parameters:
  • veto: Veto - Initialized Veto instance
  • log_allowed: bool - Log allowed calls (default: False)
  • log_denied: bool - Log denied calls (default: True)

Next Steps

Configure Rules

Define validation rules for your tools

LangGraph Guide

Build a LangGraph workflow with Veto

Error Handling

Handle denied tool calls gracefully

API Reference

Full Veto API documentation

Build docs developers (and LLMs) love