Files
SuperBizAgent-java/docs/learning/02-outputKey-深度解析.md
T
2026-05-31 21:45:14 +08:00

9.4 KiB
Raw Blame History

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 的本质:

  1. 写入地址:Agent 把输出写入 state[outputKey]
  2. 读取地址:Prompt 中的 {outputKey} 会被替换为 state[outputKey]
  3. 共享内存:所有 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 就无法协同工作。