Agent篇(14):交互协同层 (Layer 5) —— 人机共舞:异步通信、主动反问与 HITL 最佳实践
导语:沉默的杀手
你是否遇到过这样的“智能”客服?
你:“帮我退款。”
AI:“好的,正在为您办理。”
(五分钟过去了,屏幕一片死寂…)
你:“喂?还在吗?”
AI:“系统处理中,请稍候。”
或者更糟的场景:
AI:“请提供订单号。”
你:“是昨天下午买的那个红色的杯子。”
AI:“抱歉,我不理解。请提供订单号。”
你:“…”
这两个场景暴露了 Agent 系统的两大交互顽疾:
- 黑盒焦虑 (Black Box Anxiety): 用户不知道 AI 在干嘛,是在思考?在查库?还是死机了?
- 沟通僵局 (Communication Deadlock): 当信息不足时,AI 像个死板的填表机器,不懂得像人一样通过“追问”来获取上下文。
在 L5 交互协同层,我们的目标是打破这种沉默与僵局。
我们要让 Agent 学会显式地展示思考过程,学会聪明地感知信息缺失并优雅地请求人类帮助。让它从一个冷冰冰的执行者,变成一个有温度的协作者。这就是 Human-in-the-Loop (HITL) 的真谛。
第一部分:第一性原理——同步 vs 异步的鸿沟
为什么传统的 Request-Response 模式(HTTP)不适合 Agent?
- HTTP: 适合毫秒级任务(如 CRUD)。
- Agent: 思考+查库+生成可能需要 30 秒甚至 3 天(等待审批)。
如果你让前端 await fetch('/api/run_agent') 等待 30 秒,浏览器会超时,用户会关掉页面。
因此,L5 层的核心架构必须是基于事件的异步通信 (Event-Driven Asynchronous Communication)。
第二部分:让思考可见——流式输出与中间状态
用户不怕慢,怕的是不知道为什么慢。
2.1 架构设计:Server-Sent Events (SSE)
与其等待整个任务完成再一次性返回结果,不如让 Agent 一边想一边说。
我们需要建立一条 SSE 或 WebSocket 管道,实时推送 L4 层产生的每一个事件。
2.2 协议定义:Agent Event Stream
我们定义一套标准的事件协议:
1 | // 事件流示例 |
前端渲染策略:
- 收到
THOUGHT:在界面顶部显示一个旋转的 loading 状态:“AI 正在思考…”。 - 收到
TOOL_START:显示折叠的卡片:“正在查询订单系统…”。 - 收到
MESSAGE_DELTA:像打字机一样逐字渲染最终回复。
Java 后端实现 (Spring WebFlux):
1 |
|
第三部分:主动反问 (Input Request) —— Agent 是怎么知道它缺信息的?
这是 L5 层最高级的特性,也是很多开发者最困惑的地方。
Agent 毕竟只是代码,它怎么会产生“我不知道,我要问问”这种人类的自我意识?
技术解密:
Agent 不会凭空产生“我缺数据”的念头。这种感知必须被架构师显式地设计出来。我们通常采用 “Schema 驱动的空槽位检测 (Slot Filling)” 模式。
3.1 机制揭秘:基于 Extraction Node 的显式空值检测
这是最常用、最稳健的方法。逻辑分为三步:定义空槽 -> 诚实抽取 -> 代码拦截。
Step 1: 定义目标 Schema
我们告诉 LLM:“你的任务是填满这张表格。”
1 | public class TicketRequest { |
Step 2: LLM 的部分填充 (Partial Extraction)
用户说:“帮我订去上海的票。”
LLM 执行提取后,返回的 JSON 是这样的:
1 | { |
关键点: L2 层的 LLM 诚实地将未提及的字段设为 null。它没有幻觉出一个日期。
Step 3: L5 层的拦截逻辑 (The Interceptor)
在代码层面,我们编写一个通用的拦截器。“反问”本质上是一个异常处理流程。
1 | // L5 层的通用检测逻辑 |
3.2 架构实现:中断与唤醒
当引擎捕获到 InputRequiredException 时,它会执行以下动作:
- 挂起 (Suspend): 引擎立即停止当前 Workflow 的执行,并保存当前 Context(此时 Context 里包含已填好的
toCity=上海)。 - 推送事件: 向前端发送
{"type": "INPUT_REQUIRED", "field": "date", "question": "请问您计划哪天出发?"}。 - 前端渲染 (Generative UI): 聊天界面不再是普通的输入框,而是变成了一个日历选择器组件。
- 用户操作: 用户点击日历选择“明天”。
- 恢复 (Resume): 前端调用
POST /resume把日期传回。引擎把日期填入 Context,再次运行checkMissingFields,发现通过了,于是继续执行后续的订票逻辑。
架构师启示:
所谓的“智能反问”,其实是 L2 层的诚实提取 与 L5 层的严格校验 配合的结果。我们用 Java 代码的确定性逻辑,弥补了 AI 的不确定性。
第四部分:审批流 (Approval) —— 人类的最后防线
对于高风险操作(转账、发邮件、删库),AI 绝不能全自动。
4.1 场景:风险阻断
Agent:“我准备给客户发这封邮件,内容如下… 您批准吗?”
4.2 架构实现:带 Token 的回调
审批不仅可以在 App 内完成,还应该支持邮件审批或IM 审批。
流程设计:
- Agent 运行到
ApprovalNode,挂起。 - 系统生成一个带有签名 Token 的链接:
https://api.company.com/approve?token=xyz123。 - 系统给经理发送一封邮件/钉钉消息,包含这个链接。
- 经理点击“同意”。
- Web Server 验证 Token 有效性,找到对应的
thread_id,恢复 Workflow 执行。
Java 安全实现:
1 | public void generateApprovalLink(String threadId, String action) { |
架构师的权衡 (The Architect’s Trade-off)
- 用户体验 vs 复杂性:
实现流式输出 (SSE) 和动态 UI 组件 (Generative UI) 会极大地增加前端和后端的耦合度。
- 权衡: 对于内部工具,可以简化为“Loading 转圈 + 纯文本反问”。对于面向 C 端的商业产品,必须投入资源做极致的流式体验,因为这是用户感知“智能”的唯一窗口。
- 超时控制:
如果发起了 Input Request,用户三天都没回怎么办?Workflow 一直挂在数据库里?
- 权衡: L5 层必须引入 TTL (Time-To-Live) 机制。如果
ApprovalNode挂起超过 24 小时,自动触发超时回调(如:自动取消任务,并通知用户),然后销毁 Context,释放存储资源。
结语:从工具到伙伴
通过本章的改造,我们的 Agent 终于学会了“做人”。
它不再是一个闷头干活的黑盒,而是一个:
- 透明的(通过 SSE 告诉你它在想什么);
- 谦虚的(不懂就通过 Input Request 问你);
- 谨慎的(关键时刻通过 Approval 请示你)。
这才是人类愿意与之共事的伙伴。
它不再是冷冰冰的代码,它开始有了温度。
但是,作为一个企业级系统的构建者,我们还有最后一层担忧。
现在我们有了几十个、上百个这样的 Agent 在系统里跑。
谁来监控它们的健康状况?
如果某个 Prompt 突然导致成本飙升怎么办?
如果某个 RAG 节点突然召回了错误的数据怎么办?
我们需要一个上帝视角。
The Next Step:
在下一章 《L7 编排治理层:上帝视角 —— 全链路追踪、成本监控与自动化评估体系》 中,我们将完成这套架构的最后一块拼图。
我们将探讨如何集成 OpenTelemetry 实现全链路 Trace,如何构建 Evaluation Pipeline 对 AI 的回答质量进行自动化打分,以及如何精打细算地控制每一分 Token 成本。

