ag2-hitl
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHuman-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:
| Need | Use |
|---|---|
| Tool asks an open question and waits for a typed answer | |
| Approve / deny a specific tool call before its body runs | |
- Agent在执行高风险操作前应请求确认。
- Agent在对话过程中需要向用户索要信息(如密码、API密钥、缺失的上下文)。
- 特定工具调用需经人工批准后方可执行(不可逆/高成本/敏感操作)。
- 质量保障环节——展示草稿,在最终定稿前获取人工修改或批准。
两种不同机制,根据需求选择:
| 需求 | 对应方案 |
|---|---|
| 工具提出开放式问题并等待输入答案 | 在工具内部调用 |
| 在工具主体执行前批准/拒绝特定工具调用 | 使用 |
Pattern 1 — context.input()
for open questions
context.input()模式1 —— context.input()
实现开放式提问
context.input()A tool requests input via . The agent must have a that knows how to collect that input.
Context.input(message, timeout=...)hitl_hookpython
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 (the prompt is in ) and returns either a or a plain (the framework wraps a via ). Both and hooks are supported.
HumanInputRequestevent.contentHumanMessagestrstrHumanMessage.ensure_messagedefasync defYou 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 emits a (). Set the hook in exactly one place to avoid the warning.
@agent.hitl_hookRuntimeWarning"You already set HITL hook, provided value overrides it"The hook participates in dependency injection — , , , work the same as in tools.
ContextInjectVariableDependsIf is called and no hook is registered, the framework raises .
context.input()HumanInputNotProvidedError工具通过请求输入。Agent必须配置一个来处理输入收集。
Context.input(message, timeout=...)hitl_hookpython
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)钩子会接收(提示内容在中),并返回或普通字符串(框架会通过将字符串包装为)。同步和异步钩子均支持。
HumanInputRequestevent.contentHumanMessageHumanMessage.ensure_messageHumanMessagedefasync 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_hookRuntimeWarning"You already set HITL hook, provided value overrides it"钩子支持依赖注入——、、、的使用方式与工具中完全一致。
ContextInjectVariableDepends如果调用但未注册钩子,框架会抛出。
context.input()HumanInputNotProvidedErrorPattern 2 — approval_required()
for specific tool calls
approval_required()模式2 —— approval_required()
管控特定工具调用
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 , the user sees (with the default ):
delete_accountallow_always=TrueAgent 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. , , or approve this one call; 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 (default ) and can adjust. The default is 30 seconds. Set to drop the "Always" option (the prompt then shows just ).
yyes1alwaysdenied_message"User denied the tool call request"timeoutallow_always=FalseY/N?approval_required()context.input()hitl_hook使用内置的批准中间件为单个工具添加管控。在工具主体执行前会提示用户,用户可批准或拒绝。
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_accountallow_always=TrueAgent wants to call the tool:
`delete_account`, {"user_id": "abc-123"}
Please approve or deny this request.
Y/N/Always?输入的答案会先转为小写再进行匹配。、或表示批准本次调用;表示批准本次调用以及同一上下文后续所有相同工具的调用(会设置一个上下文专属的绕过标记)。其他任何输入均表示拒绝;Agent会收到(默认值为)并可做出调整。默认超时时间为30秒。设置可移除“Always”选项(提示会变为仅显示)。
yyes1alwaysdenied_message"User denied the tool call request"allow_always=FalseY/N?approval_required()context.input()hitl_hookCustom 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}{tool_arguments}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 triggers a second human interaction.
context.input()对于既需要在运行过程中收集输入又需要批准的工具:
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.mdxmiddleware).approval_required - Tool middleware in general — . See also
website/docs/user-guide/tools/tool_middleware.mdxfor agent-wide HITL interception viaag2-middleware.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实现Agent级别的人机交互拦截。BaseMiddleware.on_human_input() - 人机交互钩子的依赖注入与工具完全一致——详见。
../ag2-add-custom-tool/references/dependency_injection.md
Common pitfalls
常见陷阱
- without a
approval_required()— the middleware callshitl_hook, so the agent needs a hook. You'll seecontext.input()otherwise.HumanInputNotProvidedError - Forgetting to handle the denial path — returns whatever the hook returns. If you only branch on "yes", any other answer (including silence/default) lets the operation continue. Always validate.
context.input() - Sync in an async UI —
input()blocks the event loop. Use an async hook (input()) and an async input collector (web socket, message queue) for any non-CLI app.async def - No timeout — can wait forever. Pass
context.input(prompt)(seconds) for any production path.timeout=60.0 - Decorator hook overrides constructor hook — if you set both, the decorator wins and emits a . Pick one place.
RuntimeWarning - Expecting to flow into the conversation history automatically — it does for the requesting tool's return value, but mid-run inputs collected via
HumanMessageare not separate user turns. They live in the tool's scope.ctx.input()
- 使用但未配置
approval_required()——中间件会调用hitl_hook,因此Agent需要配置钩子。否则会触发context.input()。HumanInputNotProvidedError - 未处理拒绝路径——会返回钩子的输出结果。如果仅针对“yes”分支处理,其他任何答案(包括无输入/默认值)都会让操作继续执行。务必进行验证。
context.input() - 在异步UI中使用同步——
input()会阻塞事件循环。对于非CLI应用,请使用异步钩子(input())和异步输入收集器(如WebSocket、消息队列)。async def - 未设置超时——可能会无限等待。对于生产环境路径,请设置
context.input(prompt)(单位:秒)。timeout=60.0 - 装饰器钩子覆盖构造函数钩子——如果同时设置了两种钩子,装饰器钩子会生效并触发。请仅选择一处设置。
RuntimeWarning - 认为会自动流入对话历史——工具返回值对应的
HumanMessage会流入历史,但通过HumanMessage收集的运行中输入不属于独立的用户对话轮次,它们仅存在于工具的作用域内。ctx.input()