# Agent SDK 中的人机协同工具

- 来源：OpenRouter：Announcements（RSS）
- 作者：Kenny Rogers
- 发布时间：2026-05-08 20:00
- AIHOT 分数：56
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmp1f9fw80xoxsllhoycngo1e
- 原文链接：https://openrouter.ai/announcements/human-in-the-loop-tools

## 精选理由

OpenRouter给Agent SDK加了人类介入挂钩，做复杂流程的团队不用自己写循环管理代码了，关键决策能拉人进来确认，是个实用的小升级。

## AI 摘要

OpenRouter Agent SDK 引入了一种新工具类型，使智能体能够自动处理常规决策，并在高风险决策时暂停以请求人工输入。该功能通过两个钩子实现，无需编写任何循环管理代码，从而在自动化流程中灵活嵌入关键的人工判断环节。

## 正文

Human-in-the-Loop Tools for the Agent SDK — OpenRouter Blog

Human-in-the-Loop Tools for the Agent SDK

Kenny Rogers · 5/8/2026

On this page

Auto-resolve or escalate, per call

Post-process human responses before the model sees them

How the pause and resume cycle works

When to use HITL vs requireApproval

Start building

The Agent SDK now supports a fourth tool type: human-in-the-loop (HITL) tools. They let your agent handle routine calls automatically and pause for a human when stakes are high, all controlled by a single hook.

Install the SDK, define your HITL tool, and follow the cookbook recipe for a working implementation.

npm install @openrouter/agent

Auto-resolve or escalate, per call

Regular tools always execute. Manual tools always pause. HITL tools do both: your onToolCalled hook inspects the input and decides.

import { tool } from '@openrouter/agent/tool';
import { z } from 'zod';

const approvePayment = tool({
name: 'approve_payment',
description: 'Approve a payment, escalating large amounts to a human',
inputSchema: z.object({
amount: z.number(),
recipient: z.string(),
}),
outputSchema: z.object({
approved: z.boolean(),
reviewedAt: z.number().optional(),
}),
onToolCalled: async (input) => {
if (input.amount < 100) {
return { approved: true };
}
// Pause for human review
return null;
},
});

Return a value and the agent keeps going (like a regular tool). Return null and the loop pauses with status: 'awaiting_hitl', surfacing the pending call to your application. You resume by calling callModel again with a function_call_output item containing the human’s decision.

This pattern fits anywhere the decision depends on data: dollar thresholds, risk scores, content policy flags, compliance checks. The branching logic lives in one function, not scattered across your application code.

Post-process human responses before the model sees them

An optional second hook, onResponseReceived, fires when a human supplies a result for a paused call. It transforms the raw input before passing it to the model.

onResponseReceived: async (raw) => {
return { ...(raw as Record<string, unknown>), reviewedAt: Date.now() };
},

Use it to stamp metadata, normalize formats, validate against business rules, or enrich the response with context the human didn’t need to provide manually. If it throws, the error surfaces to the model as { error: ..., originalOutput: ... } so nothing gets silently swallowed.

How the pause and resume cycle works

Here’s the full lifecycle:

The model calls your HITL tool during an agent loop.

onToolCalled runs. If it returns a value, the agent continues. If it returns null, the loop pauses.

Your application reads the pending calls via getToolCalls() and presents them to the user.

The user makes a decision.

You call callModel again with the decision as a function_call_output item.

onResponseReceived (if defined) transforms the response.

The model receives the result and the agent loop resumes.

const result = openrouter.callModel({
model: 'openai/gpt-4o',
input: 'Pay $500 to Acme Corp for the May invoice',
tools: [approvePayment] as const,
state,
});

const response = await result.getResponse();

if (response.state?.status === 'awaiting_hitl') {
const pending = response.state.pendingToolCalls ?? [];
// Present pending[0] to your user, collect their decision, then resume:
const resumed = openrouter.callModel({
model: 'openai/gpt-4o',
input: [{
type: 'function_call_output' as const,
callId: pending[0].id,
output: JSON.stringify({ approved: true }),
}],
tools: [approvePayment] as const,
state,
});
}

The SDK handles all the state tracking, hook dispatch, and schema validation. You wrote zero loop code.

When to use HITL vs requireApproval

Both pause for human input. The difference is in the decision logic.

HITL (onToolCalled) requireApproval

When it pauses Only when your hook returns null Always, before any execution

Decision type Data-driven (thresholds, scoring, policy) Binary yes/no consent

Auto-resolve Return a value to skip human review Not available

Post-processing onResponseReceived transforms the response Not available

Use requireApproval when every invocation needs explicit human consent regardless of input (think: “delete this database,” “send this email”). Use HITL when some calls can proceed automatically and others need a human (think: “approve this payment if it’s under $100”).

Start building

The HITL tools cookbook recipe walks through a complete implementation: defining the tool, detecting pauses, collecting human input, and resuming the loop.

For the full type signatures and API surface, see the tools documentation and the API reference.

Get your API key and tell us what you’re building on Discord.
