# 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 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),验证这些机制。