Files
SuperBizAgent-java/docs/learning/03-核心疑问解答.md
T
2026-05-31 21:45:14 +08:00

311 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3个核心疑问解答
> 创建日期:2026-05-30
> 主题:Prompt 占位符、outputKey 冲突、多 key 读取
> 相关文件:`AiOpsService.java`
---
## ❓ 疑问 1:Prompt 中的 `{}` 占位符如何替换?
### 机制
Spring AI Agent Framework 的**模板引擎自动替换**
### 示例
**原始 Prompt**:
```java
// AiOpsService.java:147
"读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
```
**执行时的替换过程**:
```
1. Agent Framework 扫描 Prompt 中的 {} 占位符
2. 从 OverAllState 中查找对应的 key
3. 替换为实际值
```
**实际发送给 LLM 的 Prompt**:
```
读取当前输入任务 你是企业级 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 |
---
### 等价代码(简化版)
```java
// 如果你想看替换后的实际 Prompt,可以在 Agent 执行前打印:
ReactAgent plannerAgent = buildPlannerAgent(chatModel, toolCallbacks);
// 内部会做类似这样的事情(简化版):
String prompt = buildPlannerPrompt(); // 含 {executor_feedback}
String actualPrompt = prompt.replace(
"{executor_feedback}",
state.get("executor_feedback").toString()
);
// 然后发送给 LLM
```
---
### 代码证据
**Planner Prompt 中引用 2 个 key**:
```java
// AiOpsService.java:147
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^ ^^^^^^^^^^^^^^^^^^^
// 第1个key 第2个key
```
**Executor Prompt 中引用 1 个 key**:
```java
// AiOpsService.java:243
"你是 Executor Agent,负责读取 Planner 最新输出 {planner_plan},只执行其中的第一步。"
// ^^^^^^^^^^^^^^^
// 从 state 读取
```
---
## ❓ 疑问 2:如果两个 Agent 用同一个 outputKey 会怎样?
### 后果
**后执行的 Agent 会覆盖先执行的 Agent 的输出** ⚠️
---
### 错误示例
```java
// ❌ 错误示例
ReactAgent agent1 = ReactAgent.builder()
.name("agent1")
.outputKey("shared_key") // ← 相同的 key
.build();
ReactAgent agent2 = ReactAgent.builder()
.name("agent2")
.outputKey("shared_key") // ← 相同的 key
.build();
// 执行顺序:
// 1. agent1.invoke() → state["shared_key"] = "agent1的输出"
// 2. agent2.invoke() → state["shared_key"] = "agent2的输出" (覆盖!)
//
// 最终结果:agent1 的输出丢失了!
```
---
### 正确做法
```java
// ✅ 正确示例
ReactAgent agent1 = ReactAgent.builder()
.name("agent1")
.outputKey("agent1_output") // ← 不同的 key
.build();
ReactAgent agent2 = ReactAgent.builder()
.name("agent2")
.outputKey("agent2_output") // ← 不同的 key
.build();
// 执行后:
// state["agent1_output"] = "agent1的输出"
// state["agent2_output"] = "agent2的输出"
// 两者都保留!
```
---
### 实际案例
在 `AiOpsService.java` 中:
- Planner 用 `"planner_plan"`(第 108 行)
- Executor 用 `"executor_feedback"`(第 123 行)
- **绝对不能重复**,否则 Supervisor 无法正确调度
**代码证据**:
```java
// AiOpsService.java:100-109
ReactAgent plannerAgent = ReactAgent.builder()
.name("planner_agent")
.outputKey("planner_plan") // ← Planner 的 key
.build();
// AiOpsService.java:115-124
ReactAgent executorAgent = ReactAgent.builder()
.name("executor_agent")
.outputKey("executor_feedback") // ← Executor 的 key(不同)
.build();
```
---
### 调试技巧
如果怀疑 outputKey 冲突,可以在 Supervisor 调用后打印 state:
```java
Optional<OverAllState> stateOptional = supervisorAgent.invoke(taskPrompt);
if (stateOptional.isPresent()) {
OverAllState state = stateOptional.get();
logger.debug("State keys: {}", state.keys()); // 查看有哪些 key
// 检查是否有意外覆盖
state.keys().forEach(key -> {
logger.debug("{} = {}", key, state.value(key));
});
}
```
---
## ❓ 疑问 3:如何在 Prompt 中读取多个 key?
### 答案
直接在 Prompt 中使用**多个 `{}` 占位符**即可
---
### 示例:读取 3 个 key
```java
// 示例:Planner 需要读取 3 个 key
private String buildPlannerPrompt() {
return """
你是 Planner Agent,负责:
1. 读取用户任务:{input}
2. 读取 Executor 的反馈:{executor_feedback}
3. 读取历史分析记录:{history}
根据以上信息,制定下一步计划...
""";
}
// 执行时自动替换为:
// 1. 读取用户任务:你是企业级 SRE,接到了...
// 2. 读取 Executor 的反馈:{"status":"SUCCESS"...}
// 3. 读取历史分析记录:[上一次分析的内容]
```
---
### 实际应用
在 `AiOpsService.java:147` 中,Planner 的 Prompt 就读取了 **2 个 key**:
```java
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^ ^^^^^^^^^^^^^^^^^^^
// 第1个key 第2个key
```
**替换后**:
```
1. 读取当前输入任务 [taskPrompt的内容] 以及 Executor 的最近反馈 [executor的JSON反馈]。
```
---
### 高级技巧:条件读取(模板引擎语法)
如果某个 key 可能不存在,可以在 Prompt 中加判断逻辑:
```java
private String buildPlannerPrompt() {
return """
你是 Planner Agent,负责:
1. 读取用户任务:{input}
{% if executor_feedback %}
2. 参考 Executor 的反馈:{executor_feedback}
{% else %}
2. 这是第一次规划,没有反馈
{% endif %}
""";
}
```
**注意**:Spring AI Agent Framework 使用的模板引擎(可能是 Freemarker 或 Velocity),具体语法细节需要查阅官方文档。
---
### 代码证据
**Executor Prompt 读取 1 个 key**:
```java
// AiOpsService.java:243
"你是 Executor Agent,负责读取 Planner 最新输出 {planner_plan},只执行其中的第一步。"
// ^^^^^^^^^^^^^^^
// 读取 Planner 的输出
```
**Planner Prompt 读取 2 个 key**:
```java
// AiOpsService.java:147
"1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。"
// ^^^^^^^ ^^^^^^^^^^^^^^^^^^^
// key1 key2
```
---
## 🎯 总结
| 疑问 | 核心答案 | 关键点 |
|------|---------|--------|
| **1. Prompt 占位符如何替换?** | Spring AI 自动从 `state` 中读取 | `{key}` → `state.get("key")` |
| **2. 两个 Agent 用同一个 outputKey?** | 后者覆盖前者,数据丢失 | 必须保证 outputKey 唯一 |
| **3. 如何读取多个 key?** | 直接用多个 `{}` 占位符 | 无数量限制,按需引用 |
---
## 📊 快速参考表
### Prompt 占位符替换规则
| 占位符 | 替换为 | 代码位置 |
|--------|--------|---------|
| `{input}` | `state.value("input")` | Supervisor.invoke(taskPrompt) |
| `{planner_plan}` | `state.value("planner_plan")` | Planner outputKey |
| `{executor_feedback}` | `state.value("executor_feedback")` | Executor outputKey |
### outputKey 命名规范
| 风格 | 示例 | 推荐度 |
|------|------|--------|
| `<角色>_<数据类型>` | `planner_plan`, `executor_feedback` | ⭐️⭐️⭐️ 推荐 |
| `<角色>_output` | `agent1_output`, `agent2_output` | ⭐️⭐️ 可用 |
| `<数据类型>` | `plan`, `feedback`, `result` | ⭐️ 不推荐(易冲突) |
| 泛化命名 | `output`, `data`, `result1` | ❌ 避免 |
---
## 🔗 相关文档
- [AI Ops 核心设计 - Essence 报告](./01-AI-Ops-核心设计-Essence报告.md)
- [outputKey 深度解析](./02-outputKey-深度解析.md)
---
> 💡 **下一步**:尝试在自己的项目中实现一个简单的 2-Agent 协同(Planner + Executor),验证这些机制。