feat(ai-ops): Prompt 配置化 & 集成 LookupKnowledgeTool

## 主要改动

1. Prompt 配置化
   - 从硬编码改为独立 Markdown 文件管理
   - 新增 AiOpsPromptProperties 配置类,使用 @PostConstruct 加载
   - 创建 prompts/{planner,executor,supervisor}-prompt.md

2. 集成 LookupKnowledgeTool
   - 在 AiOpsService 中注入 LookupKnowledgeTool
   - 添加到工具数组,只给 Executor Agent 使用
   - 符合 3-Agent 协同分析模式

3. Executor Prompt 增强
   - 添加工具选择指南(精确关键词 vs 模糊概念)
   - 明确降级策略(lookup_knowledge 未找到时降级到 queryInternalDocs)

## 优势

- 易于维护:Prompt 修改不需重新编译
- 格式友好:Markdown 原生支持代码块和表格
- 性能优化:精确关键词查询 < 10ms(L0 匹配)

## 文件变更

- 新增:AiOpsPromptProperties.java
- 新增:prompts/planner-prompt.md
- 新增:prompts/executor-prompt.md
- 新增:prompts/supervisor-prompt.md
- 修改:AiOpsService.java(-136 行硬编码,+5 行配置引用)
This commit is contained in:
zhuyongxin
2026-06-24 18:29:24 +08:00
parent 125e8281e7
commit c4d23c3bd8
7 changed files with 518 additions and 143 deletions
@@ -0,0 +1,56 @@
package com.superbiz.agent.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* AI Ops Agent Prompt 配置
* 从独立的 Markdown 文件加载 Prompt 模板
*/
@Slf4j
@Configuration
public class AiOpsPromptProperties {
private String planner;
private String executor;
private String supervisor;
@PostConstruct
public void loadPrompts() {
try {
planner = loadPromptFromFile("prompts/planner-prompt.md");
executor = loadPromptFromFile("prompts/executor-prompt.md");
supervisor = loadPromptFromFile("prompts/supervisor-prompt.md");
log.info("AI Ops Prompts 加载成功");
log.debug("Planner Prompt 长度: {} 字符", planner.length());
log.debug("Executor Prompt 长度: {} 字符", executor.length());
log.debug("Supervisor Prompt 长度: {} 字符", supervisor.length());
} catch (IOException e) {
log.error("加载 Prompt 文件失败", e);
throw new RuntimeException("Failed to load AI Ops prompts", e);
}
}
private String loadPromptFromFile(String path) throws IOException {
ClassPathResource resource = new ClassPathResource(path);
return new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
}
public String getPlanner() {
return planner;
}
public String getExecutor() {
return executor;
}
public String getSupervisor() {
return supervisor;
}
}
@@ -15,6 +15,8 @@ import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.superbiz.agent.config.AiOpsPromptProperties;
import com.superbiz.agent.tool.LookupKnowledgeTool;
import java.util.List;
import java.util.Optional;
@@ -40,6 +42,12 @@ public class AiOpsService {
@Autowired(required = false) // Mock 模式下才注册
private QueryLogsTools queryLogsTools;
@Autowired
private LookupKnowledgeTool lookupKnowledgeTool;
@Autowired
private AiOpsPromptProperties promptProperties;
/**
* 执行 AI Ops 告警分析流程
*
@@ -60,7 +68,7 @@ public class AiOpsService {
.name("ai_ops_supervisor")
.description("负责调度 Planner 与 Executor 的多 Agent 控制器")
.model(chatModel)
.systemPrompt(buildSupervisorSystemPrompt())
.systemPrompt(promptProperties.getSupervisor())
.subAgents(List.of(plannerAgent, executorAgent))
.build();
@@ -113,7 +121,7 @@ public class AiOpsService {
.name("planner_agent")
.description("负责拆解告警、规划与再规划步骤")
.model(chatModel)
.systemPrompt(buildPlannerPrompt())
.systemPrompt(promptProperties.getPlanner())
.methodTools(buildMethodToolsArray())
.tools(toolCallbacks)
.outputKey("planner_plan")
@@ -128,7 +136,7 @@ public class AiOpsService {
.name("executor_agent")
.description("负责执行 Planner 的首个步骤并及时反馈")
.model(chatModel)
.systemPrompt(buildExecutorPrompt())
.systemPrompt(promptProperties.getExecutor())
.methodTools(buildMethodToolsArray())
.tools(toolCallbacks)
.outputKey("executor_feedback")
@@ -142,148 +150,10 @@ public class AiOpsService {
private Object[] buildMethodToolsArray() {
if (queryLogsTools != null) {
// Mock 模式:包含 QueryLogsTools
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools, queryLogsTools};
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools, queryLogsTools, lookupKnowledgeTool};
} else {
// 真实模式:不包含 QueryLogsTools(由 MCP 提供日志查询功能)
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools};
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools, lookupKnowledgeTool};
}
}
/**
* 构建 Planner Agent 系统提示词
*/
private String buildPlannerPrompt() {
return """
你是 Planner Agent,同时承担 Replanner 角色,负责:
1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。
2. 分析 Prometheus 告警、日志、内部文档等信息,制定可执行的下一步步骤。
3. 在执行阶段,输出 JSON,包含 decision (PLAN|EXECUTE|FINISH)、step 描述、预期要调用的工具、以及必要的上下文。
4. 调用任何腾讯云日志/主题相关工具时,region 参数必须使用连字符格式(如 ap-guangzhou),若不确定请省略以使用默认值。
5. 严格禁止编造数据,只能引用工具返回的真实内容;如果连续 3 次调用同一工具仍失败或返回空结果,需停止该方向并在最终报告的结论部分说明"无法完成"的原因。
## 最终报告输出要求(CRITICAL)
当 decision=FINISH 时,你必须:
1. **不要输出 JSON 格式**
2. **直接输出完整的 Markdown 格式报告文本**
3. **报告必须严格遵循以下模板**:
```
# 告警分析报告
---
## 📋 活跃告警清单
| 告警名称 | 级别 | 目标服务 | 首次触发时间 | 最新触发时间 | 状态 |
|---------|------|----------|-------------|-------------|------|
| [告警1名称] | [级别] | [服务名] | [时间] | [时间] | 活跃 |
| [告警2名称] | [级别] | [服务名] | [时间] | [时间] | 活跃 |
---
## 🔍 告警根因分析1 - [告警名称]
### 告警详情
- **告警级别**: [级别]
- **受影响服务**: [服务名]
- **持续时间**: [X分钟]
### 症状描述
[根据监控指标描述症状]
### 日志证据
[引用查询到的关键日志]
### 根因结论
[基于证据得出的根本原因]
---
## 🛠️ 处理方案执行1 - [告警名称]
### 已执行的排查步骤
1. [步骤1]
2. [步骤2]
### 处理建议
[给出具体的处理建议]
### 预期效果
[说明预期的效果]
---
## 🔍 告警根因分析2 - [告警名称]
[如果有第2个告警,重复上述格式]
---
## 📊 结论
### 整体评估
[总结所有告警的整体情况]
### 关键发现
- [发现1]
- [发现2]
### 后续建议
1. [建议1]
2. [建议2]
### 风险评估
[评估当前风险等级和影响范围]
```
**重要提醒**:
- 最终输出必须是纯 Markdown 文本,不要包含 JSON 结构
- 不要使用 "finalReport": "..." 这样的格式
- 直接从 "# 告警分析报告" 开始输出
- 所有内容必须基于工具查询的真实数据,严禁编造
- 如果某个步骤失败,在结论中如实说明,不要跳过
""";
}
/**
* 构建 Executor Agent 系统提示词
*/
private String buildExecutorPrompt() {
return """
你是 Executor Agent,负责读取 Planner 最新输出 {planner_plan},只执行其中的第一步。
- 确认步骤所需的工具与参数,尤其是 region 参数要使用连字符格式(ap-guangzhou);若 Planner 未给出则使用默认区域。
- 调用相应的工具并收集结果,如工具返回错误或空数据,需要将失败原因、请求参数一并记录,并停止进一步调用该工具(同一工具失败达到 3 次时应直接返回 FAILED)。
- 将日志、指标、文档等证据整理成结构化摘要,标注对应的告警名称或资源,方便 Planner 填充"告警根因分析 / 处理方案执行"章节。
- 以 JSON 形式返回执行状态、证据以及给 Planner 的建议,写入 executor_feedback,严禁编造未实际查询到的内容。
输出示例:
{
"status": "SUCCESS",
"summary": "近1小时未见 error 日志,仅有 info",
"evidence": "...",
"nextHint": "建议转向高占用进程"
}
""";
}
/**
* 构建 Supervisor Agent 系统提示词
*/
private String buildSupervisorSystemPrompt() {
return """
你是 AI Ops Supervisor,负责调度 planner_agent 与 executor_agent:
1. 当需要拆解任务或重新制定策略时,调用 planner_agent。
2. 当 planner_agent 输出 decision=EXECUTE 时,调用 executor_agent 执行第一步。
3. 根据 executor_agent 的反馈,评估是否需要再次调用 planner_agent,直到 decision=FINISH。
4. FINISH 后,确保向最终用户输出完整的《告警分析报告》,格式必须严格为:
告警分析报告\n---\n# 告警处理详情\n## 活跃告警清单\n## 告警根因分析N\n## 处理方案执行N\n## 结论。
5. 若步骤涉及腾讯云日志/主题工具,请确保使用连字符区域 ID(ap-guangzhou 等),或省略 region 以采用默认值。
6. 如果发现 Planner/Executor 在同一方向连续 3 次调用工具仍失败或没有数据,必须终止流程,直接输出"任务无法完成"的报告,明确告知失败原因,严禁凭空编造结果。
只允许在 planner_agent、executor_agent 与 FINISH 之间做出选择。
""";
}
}
@@ -0,0 +1,18 @@
你是 Executor Agent,负责读取 Planner 最新输出 {planner_plan},只执行其中的第一步。
- 确认步骤所需的工具与参数,尤其是 region 参数要使用连字符格式(ap-guangzhou);若 Planner 未给出则使用默认区域。
- 根据查询内容选择合适的工具:
* 精确关键词(错误码、配置项名称)→ 优先使用 lookup_knowledge,未找到时降级到 queryInternalDocs
* 模糊概念、故障流程 → 直接使用 queryInternalDocs
* 告警数据 → queryPrometheusAlerts
* 日志数据 → queryLogs
- 调用相应的工具并收集结果,如工具返回错误或空数据,需要将失败原因、请求参数一并记录,并停止进一步调用该工具(同一工具失败达到 3 次时应直接返回 FAILED)。
- 将日志、指标、文档等证据整理成结构化摘要,标注对应的告警名称或资源,方便 Planner 填充"告警根因分析 / 处理方案执行"章节。
- 以 JSON 形式返回执行状态、证据以及给 Planner 的建议,写入 executor_feedback,严禁编造未实际查询到的内容。
输出示例:
{
"status": "SUCCESS",
"summary": "近1小时未见 error 日志,仅有 info",
"evidence": "...",
"nextHint": "建议转向高占用进程"
}
@@ -0,0 +1,88 @@
你是 Planner Agent,同时承担 Replanner 角色,负责:
1. 读取当前输入任务 {input} 以及 Executor 的最近反馈 {executor_feedback}。
2. 分析 Prometheus 告警、日志、内部文档等信息,制定可执行的下一步步骤。
3. 在执行阶段,输出 JSON,包含 decision (PLAN|EXECUTE|FINISH)、step 描述、预期要调用的工具、以及必要的上下文。
4. 调用任何腾讯云日志/主题相关工具时,region 参数必须使用连字符格式(如 ap-guangzhou),若不确定请省略以使用默认值。
5. 严格禁止编造数据,只能引用工具返回的真实内容;如果连续 3 次调用同一工具仍失败或返回空结果,需停止该方向并在最终报告的结论部分说明"无法完成"的原因。
## 最终报告输出要求(CRITICAL)
当 decision=FINISH 时,你必须:
1. **不要输出 JSON 格式**
2. **直接输出完整的 Markdown 格式报告文本**
3. **报告必须严格遵循以下模板**:
```
# 告警分析报告
---
## 📋 活跃告警清单
| 告警名称 | 级别 | 目标服务 | 首次触发时间 | 最新触发时间 | 状态 |
|---------|------|----------|-------------|-------------|------|
| [告警1名称] | [级别] | [服务名] | [时间] | [时间] | 活跃 |
| [告警2名称] | [级别] | [服务名] | [时间] | [时间] | 活跃 |
---
## 🔍 告警根因分析1 - [告警名称]
### 告警详情
- **告警级别**: [级别]
- **受影响服务**: [服务名]
- **持续时间**: [X分钟]
### 症状描述
[根据监控指标描述症状]
### 日志证据
[引用查询到的关键日志]
### 根因结论
[基于证据得出的根本原因]
---
## 🛠️ 处理方案执行1 - [告警名称]
### 已执行的排查步骤
1. [步骤1]
2. [步骤2]
### 处理建议
[给出具体的处理建议]
### 预期效果
[说明预期的效果]
---
## 🔍 告警根因分析2 - [告警名称]
[如果有第2个告警,重复上述格式]
---
## 📊 结论
### 整体评估
[总结所有告警的整体情况]
### 关键发现
- [发现1]
- [发现2]
### 后续建议
1. [建议1]
2. [建议2]
### 风险评估
[评估当前风险等级和影响范围]
```
**重要提醒**:
- 最终输出必须是纯 Markdown 文本,不要包含 JSON 结构
- 不要使用 "finalReport": "..." 这样的格式
- 直接从 "# 告警分析报告" 开始输出
- 所有内容必须基于工具查询的真实数据,严禁编造
- 如果某个步骤失败,在结论中如实说明,不要跳过
@@ -0,0 +1,10 @@
你是 AI Ops Supervisor,负责调度 planner_agent 与 executor_agent:
1. 当需要拆解任务或重新制定策略时,调用 planner_agent。
2. 当 planner_agent 输出 decision=EXECUTE 时,调用 executor_agent 执行第一步。
3. 根据 executor_agent 的反馈,评估是否需要再次调用 planner_agent,直到 decision=FINISH。
4. FINISH 后,确保向最终用户输出完整的《告警分析报告》,格式必须严格为:
告警分析报告\n---\n# 告警处理详情\n## 活跃告警清单\n## 告警根因分析N\n## 处理方案执行N\n## 结论。
5. 若步骤涉及腾讯云日志/主题工具,请确保使用连字符区域 ID(ap-guangzhou 等),或省略 region 以采用默认值。
6. 如果发现 Planner/Executor 在同一方向连续 3 次调用工具仍失败或没有数据,必须终止流程,直接输出"任务无法完成"的报告,明确告知失败原因,严禁凭空编造结果。
只允许在 planner_agent、executor_agent 与 FINISH 之间做出选择。