在 LlamaIndex 中构建「人在回路」(Human in the Loop)交互时,核心是通过事件驱动模型实现智能体与人类的安全协作。本文将先拆解核心事件类型,再深入解析事件发送与等待的关键方法,最后通过实战案例演示完整交互流程,确保逻辑连贯、易于理解。
一、两类核心事件:人机交互的「通信协议」
LlamaIndex 定义了两组互补的事件,作为智能体与人类交互的「语言」:
1. InputRequiredEvent
:智能体的「求助信号」
- 作用:智能体主动发送的请求事件,用于告知人类需要输入信息或确认操作。
- 核心字段:
prefix
:必填,人类可见的提示文本(如「是否确认删除数据?」)。user_name
:可选,指定需要响应的用户(多用户场景精准匹配)。metadata
:可选,附加业务数据(如操作 ID、风险等级)。
- 示例:
python
from llama_index.core.workflow import InputRequiredEvent event = InputRequiredEvent( prefix="检测到大额转账,是否继续?", user_name="admin", metadata={"transaction_id": "TX-20240609-001"} )
2. HumanResponseEvent
:人类的「决策反馈」
- 作用:人类或外部系统返回给智能体的响应事件,携带决策结果。
- 核心字段:
response
:必填,人类输入的内容(如「yes」「拒绝」「需补充信息」)。user_name
:必填,需与请求事件中的user_name
一致(身份验证)。metadata
:可选,回传业务数据(如审核理由、处理时间)。
- 示例:
python
from llama_index.core.workflow import HumanResponseEvent
response = HumanResponseEvent(
response="no",
user_name="admin",
metadata={"reason": "风险等级过高"}
)
二、write_event_to_stream
:单向事件发射器
1. 功能与用法
- 作用:将事件写入事件流,通知外部交互需求,不阻塞智能体后续逻辑。
- 核心场景:
- 异步交互(如 GUI 弹窗、人工审核流程)。
- 多步骤流程(需先发送请求,再处理其他任务)。
- 代码示例:
python
async def send_confirmation_request(ctx: Context): # 发送确认请求,不阻塞后续代码 ctx.write_event_to_stream( InputRequiredEvent( prefix="是否确认执行危险任务?", user_name=ctx.user_id ) ) # 发送后可立即执行其他操作(如检查系统资源) await check_system_resources() # 手动等待响应(需外部处理后发送HumanResponseEvent) response = await ctx.get_event(HumanResponseEvent, requirements={"user_name": ctx.user_id}) return "已确认" if response.response == "yes" else "已取消"
2. 关键参数说明
参数 | 必要性 | 说明 |
---|---|---|
event | 必选 | 需发送的事件对象(如InputRequiredEvent ) |
stream | 可选 | 目标事件流(默认使用智能体上下文的事件流) |
immediate | 可选 | 是否立即发送(默认True ,适用于延迟发送场景) |
三、wait_for_event
:双向交互的「阻塞式等待」
1. 功能与用法
- 作用:发送请求事件并阻塞当前协程,直至收到目标响应事件,自动完成闭环。
- 核心场景:
- 即时确认(如命令行交互、单步流程)。
- 需要同步获取结果的简单场景。
- 代码示例:
python
async def dangerous_operation(ctx: Context): # 发送请求并阻塞等待响应 response = await ctx.wait_for_event( event_cls=HumanResponseEvent, waiter_event=InputRequiredEvent( prefix="警告:此操作将格式化磁盘,是否继续?(yes/no)", user_name=ctx.user_id ), requirements={ "user_name": ctx.user_id, "response": lambda r: r.strip().lower() in ["yes", "no"] } ) return "操作已执行" if response.response == "yes" else "操作已终止"
2. 阻塞机制解析
- 非阻塞线程:基于
async/await
的协程阻塞,不影响事件循环处理其他任务(如同时处理多个用户请求)。 - 超时控制:可通过自定义事件循环参数实现超时处理(需结合
asyncio.wait_for
)。
四、方法对比:何时选择哪一个?
维度 | write_event_to_stream | wait_for_event |
---|---|---|
交互模式 | 单工通信(仅发送请求) | 全双工通信(请求 - 响应闭环) |
代码复杂度 | 需手动实现响应监听逻辑 | 内置闭环逻辑,代码量减少 50%+ |
适用场景 | ✅ GUI 弹窗 ✅ 异步审核 ✅ 多步流程 | ✅ 命令行确认 ✅ 简单单次交互 |
典型代码量 | 20-30 行(含监听逻辑) | 8-15 行(自动处理事件流) |
五、实战案例:完整的人机确认流程
场景:金融转账前的双重审核
- 智能体发送初审请求(通过
write_event_to_stream
)。 - 人工审核系统捕获事件并提示审核员。
- 审核员回复后,智能体通过
wait_for_event
获取结果。
python
# 1. 智能体侧:发送初审请求
async def transfer_money(ctx: Context, amount: float, recipient: str):
ctx.write_event_to_stream(
InputRequiredEvent(
prefix=f"申请转账{amount}元至{recipient},请审核",
user_name="finance_auditor",
metadata={"transfer_id": "TR-20240610-001"}
)
)
# 等待终审结果(阻塞等待)
final_approval = await ctx.wait_for_event(
HumanResponseEvent,
requirements={
"user_name": "finance_auditor",
"metadata.transfer_id": "TR-20240610-001"
}
)
return "转账成功" if final_approval.response == "approve" else "转账失败"
# 2. 审核系统侧:监听事件并回复
async def audit_system(handler):
async for event in handler.stream_events():
if isinstance(event, InputRequiredEvent) and event.user_name == "finance_auditor":
# 模拟人工审核(如弹出GUI窗口)
approval = await show_audit_dialog(event.prefix)
handler.ctx.send_event(
HumanResponseEvent(
response=approval,
user_name=event.user_name,
metadata=event.metadata
)
)
六、生产级扩展:安全与性能优化
1. 权限与审计
- 权限控制:通过
user_name
限制响应者角色(如仅admin
可确认高危操作)。 - 操作审计:将事件记录至数据库,包含时间戳、用户、事件类型及内容:
python
async def log_event_to_db(event: Event): async with db_connection() as conn: await conn.execute( "INSERT INTO audit_log (user_name, event_type, content) VALUES (?, ?, ?)", (event.user_name, event.type, str(event)) )
2. 状态持久化
当交互需跨进程或长时间处理时,序列化上下文状态:
python
from llama_index.core.workflow import JsonPickleSerializer
# 保存状态至磁盘
ctx_state = ctx.to_dict(serializer=JsonPickleSerializer())
with open("transfer_ctx.pkl", "wb") as f:
pickle.dump(ctx_state, f)
# 恢复状态
with open("transfer_ctx.pkl", "rb") as f:
restored_ctx = Context.from_dict(workflow, pickle.load(f))
结尾:构建可信赖的智能体交互
通过清晰的事件定义与方法分工,LlamaIndex 让「人在回路」交互既安全又灵活。write_event_to_stream
适合异步复杂场景,wait_for_event
适合简单即时确认,两者结合可覆盖 90% 以上的人机协作需求。
如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~