ag2-hitl

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Human-in-the-loop

人机交互循环(Human-in-the-loop)

When to use

使用场景

  • The agent should ask for confirmation before doing something risky.
  • The agent needs information from the user mid-conversation (a password, an API key, missing context).
  • A specific tool call should require human approval before it runs (irreversible / expensive / sensitive).
  • Quality assurance — show a draft, get human edits/approval before finalising.
Two distinct mechanisms — pick by intent:
NeedUse
Tool asks an open question and waits for a typed answer
context.input()
from inside the tool +
hitl_hook
on the agent
Approve / deny a specific tool call before its body runs
approval_required()
tool middleware
  • Agent在执行高风险操作前应请求确认
  • Agent在对话过程中需要向用户索要信息(如密码、API密钥、缺失的上下文)。
  • 特定工具调用需经人工批准后方可执行(不可逆/高成本/敏感操作)。
  • 质量保障环节——展示草稿,在最终定稿前获取人工修改或批准。
两种不同机制,根据需求选择:
需求对应方案
工具提出开放式问题并等待输入答案在工具内部调用
context.input()
+ 为Agent配置
hitl_hook
在工具主体执行前批准/拒绝特定工具调用使用
approval_required()
工具中间件

Pattern 1 —
context.input()
for open questions

模式1 ——
context.input()
实现开放式提问

A tool requests input via
Context.input(message, timeout=...)
. The agent must have a
hitl_hook
that knows how to collect that input.
python
from ag2 import Agent, Context, tool
from ag2.events import HumanInputRequest, HumanMessage

@tool
async def execute_query(context: Context) -> str:
    answer = await context.input(
        "Are you sure you want to run this query? (yes/no)",
        timeout=60.0,
    )
    if answer.strip().lower() != "yes":
        return "Query cancelled."
    return "Query executed successfully."

def hitl_hook(event: HumanInputRequest) -> HumanMessage:
    print(f"Agent asks: {event.content}")
    return HumanMessage(content=input("Your answer: "))

agent = Agent("dba", tools=[execute_query], hitl_hook=hitl_hook)
The hook receives a
HumanInputRequest
(the prompt is in
event.content
) and returns either a
HumanMessage
or a plain
str
(the framework wraps a
str
via
HumanMessage.ensure_message
). Both
def
and
async def
hooks are supported.
You can also register the hook after construction:
python
agent = Agent("dba", tools=[execute_query])

@agent.hitl_hook
async def async_hitl_hook(event: HumanInputRequest) -> HumanMessage:
    answer = await collect_from_ui(event.content)
    return HumanMessage(content=answer)
The decorator overrides any hook set in the constructor — but if one was already set (e.g. via the constructor), applying
@agent.hitl_hook
emits a
RuntimeWarning
(
"You already set HITL hook, provided value overrides it"
). Set the hook in exactly one place to avoid the warning.
The hook participates in dependency injection —
Context
,
Inject
,
Variable
,
Depends
work the same as in tools.
If
context.input()
is called and no hook is registered, the framework raises
HumanInputNotProvidedError
.
工具通过
Context.input(message, timeout=...)
请求输入。Agent必须配置一个
hitl_hook
来处理输入收集。
python
from ag2 import Agent, Context, tool
from ag2.events import HumanInputRequest, HumanMessage

@tool
async def execute_query(context: Context) -> str:
    answer = await context.input(
        "Are you sure you want to run this query? (yes/no)",
        timeout=60.0,
    )
    if answer.strip().lower() != "yes":
        return "Query cancelled."
    return "Query executed successfully."

def hitl_hook(event: HumanInputRequest) -> HumanMessage:
    print(f"Agent asks: {event.content}")
    return HumanMessage(content=input("Your answer: "))

agent = Agent("dba", tools=[execute_query], hitl_hook=hitl_hook)
钩子会接收
HumanInputRequest
(提示内容在
event.content
中),并返回
HumanMessage
或普通字符串(框架会通过
HumanMessage.ensure_message
将字符串包装为
HumanMessage
)。同步
def
和异步
async def
钩子均支持。
你也可以在Agent构建完成后注册钩子:
python
agent = Agent("dba", tools=[execute_query])

@agent.hitl_hook
async def async_hitl_hook(event: HumanInputRequest) -> HumanMessage:
    answer = await collect_from_ui(event.content)
    return HumanMessage(content=answer)
该装饰器会覆盖构造函数中设置的钩子——如果已设置钩子(例如通过构造函数),使用
@agent.hitl_hook
会触发
RuntimeWarning
(提示信息为
"You already set HITL hook, provided value overrides it"
)。请仅在一处设置钩子以避免警告。
钩子支持依赖注入——
Context
Inject
Variable
Depends
的使用方式与工具中完全一致。
如果调用
context.input()
但未注册钩子,框架会抛出
HumanInputNotProvidedError

Pattern 2 —
approval_required()
for specific tool calls

模式2 ——
approval_required()
管控特定工具调用

Gate a single tool with the built-in approval middleware. The user is prompted before the tool body runs and can approve or deny.
python
from ag2 import Agent, tool
from ag2.config import OpenAIConfig
from ag2.middleware import approval_required

@tool(middleware=[approval_required()])
def delete_account(user_id: str) -> str:
    """Deletes a user account by ID permanently."""
    return f"Account {user_id} deleted."

agent = Agent(
    "support",
    config=OpenAIConfig(model="gpt-4o-mini"),
    tools=[delete_account],
    hitl_hook=lambda event: input(event.content),
)
When the agent calls
delete_account
, the user sees (with the default
allow_always=True
):
Agent wants to call the tool:
`delete_account`, {"user_id": "abc-123"}
Please approve or deny this request.
Y/N/Always?
The answer is lowercased before matching.
y
,
yes
, or
1
approve this one call;
always
approves this call and all subsequent calls of the same tool in the same context (it sets a per-context bypass flag). Anything else denies it; the agent receives
denied_message
(default
"User denied the tool call request"
) and can adjust. The default
timeout
is 30 seconds. Set
allow_always=False
to drop the "Always" option (the prompt then shows just
Y/N?
).
approval_required()
calls
context.input()
under the hood, so it also requires a
hitl_hook
— without one you'll get a runtime error.
使用内置的批准中间件为单个工具添加管控。在工具主体执行前会提示用户,用户可批准或拒绝。
python
from ag2 import Agent, tool
from ag2.config import OpenAIConfig
from ag2.middleware import approval_required

@tool(middleware=[approval_required()])
def delete_account(user_id: str) -> str:
    """Deletes a user account by ID permanently."""
    return f"Account {user_id} deleted."

agent = Agent(
    "support",
    config=OpenAIConfig(model="gpt-4o-mini"),
    tools=[delete_account],
    hitl_hook=lambda event: input(event.content),
)
当Agent调用
delete_account
时,用户会看到(默认
allow_always=True
):
Agent wants to call the tool:
`delete_account`, {"user_id": "abc-123"}
Please approve or deny this request.
Y/N/Always?
输入的答案会先转为小写再进行匹配。
y
yes
1
表示批准本次调用;
always
表示批准本次调用以及同一上下文后续所有相同工具的调用(会设置一个上下文专属的绕过标记)。其他任何输入均表示拒绝;Agent会收到
denied_message
(默认值为
"User denied the tool call request"
)并可做出调整。默认超时时间为30秒。设置
allow_always=False
可移除“Always”选项(提示会变为仅显示
Y/N?
)。
approval_required()
内部会调用
context.input()
,因此它同样需要配置
hitl_hook
——未配置的话会触发运行时错误。

Custom prompt

自定义提示

python
@tool(middleware=[approval_required(
    message="⚠️ Run `{tool_name}` with {tool_arguments}? (y/n)",
    denied_message="Operation blocked by user.",
)])
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to the given address."""
    ...
{tool_name}
and
{tool_arguments}
are interpolated.
python
@tool(middleware=[approval_required(
    message="⚠️ Run `{tool_name}` with {tool_arguments}? (y/n)",
    denied_message="Operation blocked by user.",
)])
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to the given address."""
    ...
{tool_name}
{tool_arguments}
会被自动替换为对应内容。

Pairing both patterns

两种模式结合使用

For a tool that both gathers input mid-run and requires approval:
python
@tool(middleware=[approval_required()])
async def schedule_report(name: str, context: Context) -> str:
    """Schedule a report — asks the user for the cadence, then runs after approval."""
    cadence = await context.input("How often? (daily / weekly / monthly)")
    return f"Scheduled '{name}' on {cadence} cadence."
The approval middleware runs first (outermost). Once approved, the tool body executes and
context.input()
triggers a second human interaction.
对于既需要在运行过程中收集输入又需要批准的工具:
python
@tool(middleware=[approval_required()])
async def schedule_report(name: str, context: Context) -> str:
    """Schedule a report — asks the user for the cadence, then runs after approval."""
    cadence = await context.input("How often? (daily / weekly / monthly)")
    return f"Scheduled '{name}' on {cadence} cadence."
批准中间件会先执行(最外层)。获得批准后,工具主体开始执行,
context.input()
会触发第二次人机交互。

Going deeper

深入了解

  • Source docs:
    website/docs/user-guide/context/human_in_the_loop.mdx
    (
    context.input
    ,
    hitl_hook
    ),
    website/docs/user-guide/tools/approval_required.mdx
    (
    approval_required
    middleware).
  • Tool middleware in general —
    website/docs/user-guide/tools/tool_middleware.mdx
    . See also
    ag2-middleware
    for agent-wide HITL interception via
    BaseMiddleware.on_human_input()
    .
  • HITL hooks support dependency injection identically to tools — see
    ../ag2-add-custom-tool/references/dependency_injection.md
    .
  • 源码文档:
    website/docs/user-guide/context/human_in_the_loop.mdx
    (介绍
    context.input
    hitl_hook
    ),
    website/docs/user-guide/tools/approval_required.mdx
    (介绍
    approval_required
    中间件)。
  • 工具中间件概述——
    website/docs/user-guide/tools/tool_middleware.mdx
    。另可查看
    ag2-middleware
    ,了解如何通过
    BaseMiddleware.on_human_input()
    实现Agent级别的人机交互拦截。
  • 人机交互钩子的依赖注入与工具完全一致——详见
    ../ag2-add-custom-tool/references/dependency_injection.md

Common pitfalls

常见陷阱

  • approval_required()
    without a
    hitl_hook
    — the middleware calls
    context.input()
    , so the agent needs a hook. You'll see
    HumanInputNotProvidedError
    otherwise.
  • Forgetting to handle the denial path
    context.input()
    returns whatever the hook returns. If you only branch on "yes", any other answer (including silence/default) lets the operation continue. Always validate.
  • Sync
    input()
    in an async UI
    input()
    blocks the event loop. Use an async hook (
    async def
    ) and an async input collector (web socket, message queue) for any non-CLI app.
  • No timeout
    context.input(prompt)
    can wait forever. Pass
    timeout=60.0
    (seconds) for any production path.
  • Decorator hook overrides constructor hook — if you set both, the decorator wins and emits a
    RuntimeWarning
    . Pick one place.
  • Expecting
    HumanMessage
    to flow into the conversation history automatically
    — it does for the requesting tool's return value, but mid-run inputs collected via
    ctx.input()
    are not separate user turns. They live in the tool's scope.
  • 使用
    approval_required()
    但未配置
    hitl_hook
    ——中间件会调用
    context.input()
    ,因此Agent需要配置钩子。否则会触发
    HumanInputNotProvidedError
  • 未处理拒绝路径——
    context.input()
    会返回钩子的输出结果。如果仅针对“yes”分支处理,其他任何答案(包括无输入/默认值)都会让操作继续执行。务必进行验证。
  • 在异步UI中使用同步
    input()
    ——
    input()
    会阻塞事件循环。对于非CLI应用,请使用异步钩子(
    async def
    )和异步输入收集器(如WebSocket、消息队列)。
  • 未设置超时——
    context.input(prompt)
    可能会无限等待。对于生产环境路径,请设置
    timeout=60.0
    (单位:秒)。
  • 装饰器钩子覆盖构造函数钩子——如果同时设置了两种钩子,装饰器钩子会生效并触发
    RuntimeWarning
    。请仅选择一处设置。
  • 认为
    HumanMessage
    会自动流入对话历史
    ——工具返回值对应的
    HumanMessage
    会流入历史,但通过
    ctx.input()
    收集的运行中输入不属于独立的用户对话轮次,它们仅存在于工具的作用域内。