This commit is contained in:
aruo
2026-05-31 21:45:14 +08:00
parent d4b5015beb
commit ac08345369
67 changed files with 11120 additions and 387 deletions
+297
View File
@@ -0,0 +1,297 @@
# 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 写入
**代码**:
```java
// AiOpsService.java:108
ReactAgent plannerAgent = ReactAgent.builder()
.outputKey("planner_plan") // ← 声明:我要写入 "planner_plan" 这个 key
.build();
// 执行后,Planner 的输出会自动写入到:
// state.put("planner_plan", plannerAgent的输出)
```
---
### Step 2: Executor 读取 & 写入
**Prompt 中引用**:
```java
// AiOpsService.java:147 - Planner 的 Prompt 中
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^^^^^^^^^^^^^^^
// 这是从 state 中读取的!
```
**关键点**:Prompt 中的 `{executor_feedback}` 会被自动替换为:
```java
state.get("executor_feedback")
```
**Executor 写入**:
```java
// AiOpsService.java:123
ReactAgent executorAgent = ReactAgent.builder()
.outputKey("executor_feedback") // ← Executor 写入这个 key
.build();
```
---
### Step 3: 从 state 中提取最终结果
**代码**:
```java
// 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,可以这样理解:
```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
```java
// 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. 命名规范
```java
// ✅ 好的命名(表达角色 + 数据类型)
.outputKey("planner_plan") // Planner 的计划
.outputKey("executor_feedback") // Executor 的反馈
.outputKey("reviewer_verdict") // Reviewer 的判决
// ❌ 差的命名
.outputKey("output") // 太泛,不知道谁的输出
.outputKey("data") // 太泛
.outputKey("result1") // 没有语义
```
### 2. 在 Prompt 中引用
```java
// Executor 的 Prompt
"读取 Planner 最新输出 {planner_plan},只执行其中的第一步。"
// ^^^^^^^^^^^^^^^
// 会被自动替换为 state.get("planner_plan")
// Planner 的 Prompt (Replanner 角色)
"读取 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^^^^^^^^^^^^^
// 会被自动替换为 state.get("executor_feedback")
```
### 3. 提取最终结果
```java
// 从 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:
```java
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",...}"}]
```
---
## 📚 相关阅读
- [AI Ops 核心设计 - Essence 报告](./01-AI-Ops-核心设计-Essence报告.md)
- [3个核心疑问解答](./03-核心疑问解答.md)
---
> 💡 **总结**:outputKey 是 Agent 间通信的桥梁,没有它,3 个 Agent 就无法协同工作。