导语:沉默的杀手

你是否遇到过这样的“智能”客服?

你:“帮我退款。”
AI:“好的,正在为您办理。”
(五分钟过去了,屏幕一片死寂…)
你:“喂?还在吗?”
AI:“系统处理中,请稍候。”

或者更糟的场景:

AI:“请提供订单号。”
你:“是昨天下午买的那个红色的杯子。”
AI:“抱歉,我不理解。请提供订单号。”
你:“…”

这两个场景暴露了 Agent 系统的两大交互顽疾:

  1. 黑盒焦虑 (Black Box Anxiety): 用户不知道 AI 在干嘛,是在思考?在查库?还是死机了?
  2. 沟通僵局 (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
2
3
4
5
6
7
8
9
// 事件流示例
[
{"type": "THOUGHT", "content": "正在分析用户意图...", "timestamp": 1001},
{"type": "TOOL_START", "tool": "search_order", "args": {"keyword": "红色杯子"}, "timestamp": 1002},
{"type": "TOOL_END", "result": "找到订单: 12345", "timestamp": 1005},
{"type": "MESSAGE_DELTA", "content": "我找", "timestamp": 1006},
{"type": "MESSAGE_DELTA", "content": "到了", "timestamp": 1007},
{"type": "MESSAGE_DELTA", "content": "您的订单...", "timestamp": 1008}
]

前端渲染策略:

  • 收到 THOUGHT:在界面顶部显示一个旋转的 loading 状态:“AI 正在思考…”。
  • 收到 TOOL_START:显示折叠的卡片:“正在查询订单系统…”。
  • 收到 MESSAGE_DELTA:像打字机一样逐字渲染最终回复。

Java 后端实现 (Spring WebFlux):

1
2
3
4
5
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<AgentEvent>> streamEvents(@RequestParam String threadId) {
return workflowEngine.subscribe(threadId) // 订阅 L4 层的事件总线
.map(event -> ServerSentEvent.builder(event).build());
}

第三部分:主动反问 (Input Request) —— Agent 是怎么知道它缺信息的?

这是 L5 层最高级的特性,也是很多开发者最困惑的地方。
Agent 毕竟只是代码,它怎么会产生“我不知道,我要问问”这种人类的自我意识?

技术解密:
Agent 不会凭空产生“我缺数据”的念头。这种感知必须被架构师显式地设计出来。我们通常采用 “Schema 驱动的空槽位检测 (Slot Filling)” 模式。

3.1 机制揭秘:基于 Extraction Node 的显式空值检测

这是最常用、最稳健的方法。逻辑分为三步:定义空槽 -> 诚实抽取 -> 代码拦截

Step 1: 定义目标 Schema
我们告诉 LLM:“你的任务是填满这张表格。”

1
2
3
4
5
6
7
8
9
10
public class TicketRequest {
@Description("出发城市")
public String fromCity; // 必填

@Description("目的城市")
public String toCity; // 必填

@Description("出发日期,格式 YYYY-MM-DD")
public String date; // 必填,但在第一轮对话中往往缺失
}

Step 2: LLM 的部分填充 (Partial Extraction)
用户说:“帮我订去上海的票。”
LLM 执行提取后,返回的 JSON 是这样的:

1
2
3
4
5
{
"toCity": "上海",
"fromCity": null,
"date": null
}

关键点: L2 层的 LLM 诚实地将未提及的字段设为 null。它没有幻觉出一个日期。

Step 3: L5 层的拦截逻辑 (The Interceptor)
在代码层面,我们编写一个通用的拦截器。“反问”本质上是一个异常处理流程。

1
2
3
4
5
6
7
8
9
10
11
// L5 层的通用检测逻辑
public void checkMissingFields(TicketRequest request, WorkflowContext ctx) {
if (request.toCity == null) {
throw new InputRequiredException("toCity", "请问您要去哪个城市?");
}
if (request.date == null) {
// 触发反问机制!
throw new InputRequiredException("date", "请问您计划哪天出发?");
}
// 全部字段非空,流程继续
}

3.2 架构实现:中断与唤醒

当引擎捕获到 InputRequiredException 时,它会执行以下动作:

  1. 挂起 (Suspend): 引擎立即停止当前 Workflow 的执行,并保存当前 Context(此时 Context 里包含已填好的 toCity=上海)。
  2. 推送事件: 向前端发送 {"type": "INPUT_REQUIRED", "field": "date", "question": "请问您计划哪天出发?"}
  3. 前端渲染 (Generative UI): 聊天界面不再是普通的输入框,而是变成了一个日历选择器组件。
  4. 用户操作: 用户点击日历选择“明天”。
  5. 恢复 (Resume): 前端调用 POST /resume 把日期传回。引擎把日期填入 Context,再次运行 checkMissingFields,发现通过了,于是继续执行后续的订票逻辑。

架构师启示:
所谓的“智能反问”,其实是 L2 层的诚实提取L5 层的严格校验 配合的结果。我们用 Java 代码的确定性逻辑,弥补了 AI 的不确定性。


第四部分:审批流 (Approval) —— 人类的最后防线

对于高风险操作(转账、发邮件、删库),AI 绝不能全自动。

4.1 场景:风险阻断

Agent:“我准备给客户发这封邮件,内容如下… 您批准吗?”

4.2 架构实现:带 Token 的回调

审批不仅可以在 App 内完成,还应该支持邮件审批IM 审批

流程设计:

  1. Agent 运行到 ApprovalNode,挂起。
  2. 系统生成一个带有签名 Token 的链接:https://api.company.com/approve?token=xyz123
  3. 系统给经理发送一封邮件/钉钉消息,包含这个链接。
  4. 经理点击“同意”。
  5. Web Server 验证 Token 有效性,找到对应的 thread_id,恢复 Workflow 执行。

Java 安全实现:

1
2
3
4
5
public void generateApprovalLink(String threadId, String action) {
// 使用 HMAC 算法生成防篡改的 Token
String token = jwtService.sign(Map.of("tid", threadId, "act", action));
sendNotification(managerEmail, "请点击审批: " + baseUrl + "/callback?t=" + token);
}

架构师的权衡 (The Architect’s Trade-off)

  1. 用户体验 vs 复杂性:
    实现流式输出 (SSE) 和动态 UI 组件 (Generative UI) 会极大地增加前端和后端的耦合度。
  • 权衡: 对于内部工具,可以简化为“Loading 转圈 + 纯文本反问”。对于面向 C 端的商业产品,必须投入资源做极致的流式体验,因为这是用户感知“智能”的唯一窗口。
  1. 超时控制:
    如果发起了 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 成本。