9.4 KiB
9.4 KiB
outputKey 深度解析
创建日期:2026-05-30
相关文件:AiOpsService.java
核心概念:Agent 状态共享机制
🎯 outputKey 是什么?
一句话总结:outputKey 是 Agent 状态共享的关键机制,就像是一个共享内存的地址。
📚 核心机制
┌─────────────────────────────────────────────────────────┐
│ OverAllState │
│ (类似一个全局的 Map<String, Object>) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Key Value │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ "planner_plan" → Planner 的输出 (AssistantMessage)│ │
│ │ "executor_feedback" → Executor 的输出 (JSON) │ │
│ │ "input" → 最初的任务输入 │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
🔄 工作流程(3 步)
Step 1: Planner 写入
代码:
// AiOpsService.java:108
ReactAgent plannerAgent = ReactAgent.builder()
.outputKey("planner_plan") // ← 声明:我要写入 "planner_plan" 这个 key
.build();
// 执行后,Planner 的输出会自动写入到:
// state.put("planner_plan", plannerAgent的输出)
Step 2: Executor 读取 & 写入
Prompt 中引用:
// AiOpsService.java:147 - Planner 的 Prompt 中
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^^^^^^^^^^^^^^^
// 这是从 state 中读取的!
关键点:Prompt 中的 {executor_feedback} 会被自动替换为:
state.get("executor_feedback")
Executor 写入:
// AiOpsService.java:123
ReactAgent executorAgent = ReactAgent.builder()
.outputKey("executor_feedback") // ← Executor 写入这个 key
.build();
Step 3: 从 state 中提取最终结果
代码:
// AiOpsService.java:83
Optional<AssistantMessage> plannerFinalOutput = state.value("planner_plan")
.filter(AssistantMessage.class::isInstance)
.map(AssistantMessage.class::cast);
String reportText = plannerFinalOutput.get().getText();
⏱️ 完整时间线示例
时间线 ───────────────────────────────────────────────────►
1️⃣ Supervisor 启动
state = {}
2️⃣ Supervisor 调用 Planner
Planner: "需要查询告警,decision=EXECUTE"
state = {
"planner_plan": "需要查询告警,decision=EXECUTE"
}
3️⃣ Supervisor 读取 decision=EXECUTE,调用 Executor
Executor 读取: {planner_plan} = "需要查询告警,decision=EXECUTE"
Executor 调用工具: queryPrometheusAlerts()
Executor: "查询成功,发现 3 个告警"
state = {
"planner_plan": "需要查询告警,decision=EXECUTE",
"executor_feedback": "查询成功,发现 3 个告警" ← 新增
}
4️⃣ Supervisor 再次调用 Planner(重新规划)
Planner 读取: {executor_feedback} = "查询成功,发现 3 个告警"
Planner: "需要查询日志,decision=EXECUTE"
state = {
"planner_plan": "需要查询日志,decision=EXECUTE", ← 更新
"executor_feedback": "查询成功,发现 3 个告警"
}
5️⃣ Supervisor 调用 Executor
Executor 读取: {planner_plan} = "需要查询日志,decision=EXECUTE"
Executor 调用工具: queryLogs()
Executor: "查询成功,找到 OOM 日志"
state = {
"planner_plan": "需要查询日志,decision=EXECUTE",
"executor_feedback": "查询成功,找到 OOM 日志" ← 更新
}
6️⃣ Supervisor 再次调用 Planner(最终生成报告)
Planner 读取: {executor_feedback} = "查询成功,找到 OOM 日志"
Planner: "decision=FINISH,输出完整 Markdown 报告"
state = {
"planner_plan": "# 告警分析报告\n...", ← 最终报告
"executor_feedback": "查询成功,找到 OOM 日志"
}
7️⃣ Supervisor 结束,返回 state
8️⃣ Controller 提取报告
finalReport = state.value("planner_plan")
🤔 为什么需要 outputKey?
| 场景 | 没有 outputKey | 有 outputKey |
|---|---|---|
| Agent 间通信 | 无法传递数据 | ✅ 通过 state 共享 |
| 重新规划 | Planner 读不到 Executor 的结果 | ✅ 读取 {executor_feedback} |
| 最终提取 | 不知道从哪里读取报告 | ✅ state.value("planner_plan") |
| 调试 | 无法追踪中间状态 | ✅ 可以打印整个 state |
💻 等价代码理解
如果你熟悉 JavaScript,可以这样理解:
// 没有 outputKey 的版本(行不通)
const plannerOutput = plannerAgent.invoke(input);
const executorOutput = executorAgent.invoke(???); // 😱 怎么传递 plannerOutput?
// 有 outputKey 的版本
const state = {};
plannerAgent.invoke(input, state); // 写入 state["planner_plan"]
executorAgent.invoke(state); // 读取 state["planner_plan"],写入 state["executor_feedback"]
plannerAgent.invoke(state); // 读取 state["executor_feedback"],更新 state["planner_plan"]
📖 Prompt 中的占位符替换
原始 Prompt
// AiOpsService.java:147
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
替换后的实际 Prompt(发送给 LLM)
1. 读取当前输入任务 你是企业级 SRE,接到了自动化告警排查任务... 以及 Executor 的最近反馈 {"status":"SUCCESS","summary":"查询成功,发现3个告警"}。
替换规则
| 占位符 | 查找位置 | 值来源 |
|---|---|---|
{input} |
state.value("input") |
Supervisor 初始调用时的 taskPrompt |
{planner_plan} |
state.value("planner_plan") |
Planner Agent 的 outputKey |
{executor_feedback} |
state.value("executor_feedback") |
Executor Agent 的 outputKey |
🎯 核心洞察
outputKey 的本质:
- 写入地址:Agent 把输出写入
state[outputKey] - 读取地址:Prompt 中的
{outputKey}会被替换为state[outputKey] - 共享内存:所有 Agent 共享同一个
OverAllState对象
类比:
outputKey就像文件系统的路径OverAllState就像文件系统本身- Planner 写入
/planner_plan - Executor 读取
/planner_plan,写入/executor_feedback - Supervisor 协调读写顺序
💡 实践建议
1. 命名规范
// ✅ 好的命名(表达角色 + 数据类型)
.outputKey("planner_plan") // Planner 的计划
.outputKey("executor_feedback") // Executor 的反馈
.outputKey("reviewer_verdict") // Reviewer 的判决
// ❌ 差的命名
.outputKey("output") // 太泛,不知道谁的输出
.outputKey("data") // 太泛
.outputKey("result1") // 没有语义
2. 在 Prompt 中引用
// Executor 的 Prompt
"读取 Planner 最新输出 {planner_plan},只执行其中的第一步。"
// ^^^^^^^^^^^^^^^
// 会被自动替换为 state.get("planner_plan")
// Planner 的 Prompt (Replanner 角色)
"读取 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^^^^^^^^^^^^^
// 会被自动替换为 state.get("executor_feedback")
3. 提取最终结果
// 从 state 中提取
Optional<AssistantMessage> finalOutput = state.value("planner_plan")
.filter(AssistantMessage.class::isInstance)
.map(AssistantMessage.class::cast);
String reportText = finalOutput.get().getText();
🔍 调试技巧
在 AiOpsService.java:70 的 invoke 调用后打印 state:
Optional<OverAllState> stateOptional = supervisorAgent.invoke(taskPrompt);
// 添加调试代码
if (stateOptional.isPresent()) {
OverAllState state = stateOptional.get();
logger.debug("Final State Keys: {}", state.keys()); // 打印所有 key
logger.debug("Planner Plan: {}", state.value("planner_plan"));
logger.debug("Executor Feedback: {}", state.value("executor_feedback"));
}
你会看到类似:
Final State Keys: [input, planner_plan, executor_feedback]
Planner Plan: Optional[AssistantMessage{text="# 告警分析报告..."}]
Executor Feedback: Optional[AssistantMessage{text="{"status":"SUCCESS",...}"}]
📚 相关阅读
💡 总结:outputKey 是 Agent 间通信的桥梁,没有它,3 个 Agent 就无法协同工作。