CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain

TypeScript framework for building LLM-powered applications with agents, tools, middleware, and model interoperability

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

hitl.mddocs/middleware/

Human-in-the-Loop Middleware

Pause agent execution for human review and approval of tool calls or model responses.

API Reference

/**
 * Create human-in-the-loop middleware
 * @param config - HITL configuration
 * @returns HITL middleware instance
 */
function humanInTheLoopMiddleware(
  config: HumanInTheLoopMiddlewareConfig
): AgentMiddleware;

interface HumanInTheLoopMiddlewareConfig {
  /**
   * When to interrupt for human review
   */
  interruptOn?: InterruptOnConfig;

  /**
   * Configuration for review interface
   */
  reviewConfig?: ReviewConfig;
}

Interrupt Configuration

/**
 * Configuration for when to interrupt
 */
type InterruptOnConfig =
  | "tools" // Interrupt before tool execution
  | "tool_results" // Interrupt after tool execution
  | ((state: State, toolCalls?: ToolCall[]) => boolean); // Custom logic

interface ReviewConfig {
  /**
   * Factory for generating action descriptions
   */
  descriptionFactory?: DescriptionFactory;
}

type DescriptionFactory = (
  action: Action,
  state: State
) => string | Promise<string>;

HITL Types

/**
 * Request for human review
 */
interface HITLRequest {
  /**
   * Action requiring review
   */
  action: Action;

  /**
   * Current agent state
   */
  state: State;

  /**
   * Description of the action
   */
  description?: string;
}

/**
 * Human decision response
 */
interface HITLResponse {
  /**
   * Decision made by human
   */
  decision: Decision;

  /**
   * Optional feedback or modifications
   */
  feedback?: string;
}

/**
 * Union of possible decisions
 */
type Decision = ApproveDecision | EditDecision | RejectDecision;

/**
 * Approve the action
 */
interface ApproveDecision {
  type: "approve";
}

/**
 * Edit and resubmit the action
 */
interface EditDecision {
  type: "edit";
  modifications: Record<string, any>;
}

/**
 * Reject the action
 */
interface RejectDecision {
  type: "reject";
  reason?: string;
}

/**
 * Action interface
 */
interface Action {
  type: "tool_call" | "model_response";
  payload: any;
}

/**
 * Action request interface
 */
interface ActionRequest extends Action {
  requestId: string;
  timestamp: number;
}

/**
 * Decision type enum
 */
type DecisionType = "approve" | "edit" | "reject";

Usage Examples

Basic HITL

import { createAgent, humanInTheLoopMiddleware, tool } from "langchain";
import { z } from "zod";

// Create tool that requires approval
const sendEmail = tool(
  async ({ to, subject, body }) => {
    await emailService.send({ to, subject, body });
    return `Email sent to ${to}`;
  },
  {
    name: "send_email",
    description: "Send an email",
    schema: z.object({
      to: z.string().email(),
      subject: z.string(),
      body: z.string(),
    }),
  }
);

// Create HITL middleware
const hitl = humanInTheLoopMiddleware({
  interruptOn: "tools", // Interrupt before tool execution
});

// Create agent with HITL
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [sendEmail],
  middleware: [hitl],
  checkpointer: checkpointer, // Required for interrupts
});

// Start conversation
const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "Send email to john@example.com" }],
  },
  {
    configurable: { thread_id: "thread-123" },
  }
);

// Agent will interrupt when tool is about to be called
// Resume after human approval
const resumed = await agent.invoke(
  {
    messages: [], // Empty to resume
  },
  {
    configurable: {
      thread_id: "thread-123",
      // Human decision goes here
    },
  }
);

Custom Interrupt Logic

import { humanInTheLoopMiddleware } from "langchain";

// Interrupt only for specific tools
const hitl = humanInTheLoopMiddleware({
  interruptOn: (state, toolCalls) => {
    // Review only dangerous operations
    const dangerousTools = ["delete_file", "send_email", "make_payment"];

    return (
      toolCalls?.some((call) => dangerousTools.includes(call.name)) ?? false
    );
  },
});

With Description Factory

import { humanInTheLoopMiddleware } from "langchain";

const hitl = humanInTheLoopMiddleware({
  interruptOn: "tools",
  reviewConfig: {
    descriptionFactory: async (action, state) => {
      if (action.type === "tool_call") {
        const { name, args } = action.payload;
        return `Agent wants to call ${name} with arguments: ${JSON.stringify(
          args,
          null,
          2
        )}`;
      }
      return "Action requires review";
    },
  },
});

Interrupt After Tool Results

import { humanInTheLoopMiddleware } from "langchain";

// Review tool results before continuing
const hitl = humanInTheLoopMiddleware({
  interruptOn: "tool_results",
});

// Use case: Verify API responses before agent acts on them
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [apiTool],
  middleware: [hitl],
  checkpointer: checkpointer,
});

Multiple Decision Types

import { humanInTheLoopMiddleware, createAgent } from "langchain";

const hitl = humanInTheLoopMiddleware({
  interruptOn: "tools",
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [tool1, tool2],
  middleware: [hitl],
  checkpointer: checkpointer,
});

// Initial invocation - will interrupt
await agent.invoke(
  { messages: [{ role: "user", content: "Do something" }] },
  { configurable: { thread_id: "t1" } }
);

// Resume with approval
await agent.invoke(
  { messages: [] },
  {
    configurable: {
      thread_id: "t1",
      decision: { type: "approve" },
    },
  }
);

// Or resume with edit
await agent.invoke(
  { messages: [] },
  {
    configurable: {
      thread_id: "t1",
      decision: {
        type: "edit",
        modifications: { to: "different@email.com" },
      },
    },
  }
);

// Or resume with rejection
await agent.invoke(
  { messages: [] },
  {
    configurable: {
      thread_id: "t1",
      decision: {
        type: "reject",
        reason: "Insufficient information",
      },
    },
  }
);

Combining with Other Middleware

import {
  createAgent,
  humanInTheLoopMiddleware,
  toolCallLimitMiddleware,
  piiMiddleware,
} from "langchain";

// HITL with other safety middleware
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [tools],
  middleware: [
    piiMiddleware({ builtInTypes: ["email", "credit_card"] }), // Check PII first
    toolCallLimitMiddleware({ runLimit: 5 }), // Limit tool calls
    humanInTheLoopMiddleware({ interruptOn: "tools" }), // Then human review
  ],
  checkpointer: checkpointer,
});

Best Practices

When to Use HITL

  • Financial transactions
  • Data deletion or modification
  • External communications (email, messages)
  • Access control changes
  • Any irreversible actions

Configuration

  • Use interruptOn: "tools" for preventive review
  • Use interruptOn: "tool_results" for verification
  • Use custom function for selective interrupts
  • Always provide a checkpointer

User Experience

  • Provide clear descriptions via descriptionFactory
  • Include relevant context in interrupts
  • Allow editing for minor corrections
  • Provide rejection feedback to agent

Error Handling

  • Handle rejected actions gracefully
  • Provide alternative paths for rejections
  • Log all human decisions
  • Implement timeouts for pending reviews

State Management

  • Use persistent checkpointers
  • Store thread IDs properly
  • Clean up old threads
  • Handle concurrent reviews carefully

docs

glossary.md

index.md

quick-reference.md

task-index.md

tile.json