项目地址 :gitee.com/guoxuezha/e...
1. LLM 在系统中的角色定位
在手动版(validate-prototypes)中,LLM 只在一个环节被调用:接收异常日志,输出诊断报告。这是一个简单的"单次调用"模式。
在 Agent 版(embabel-example)中,LLM 被深度嵌入了诊断流程的每一个关键决策点:
| 调用场景 | LLM 的角色 | 输入 | 输出类型 |
|---|---|---|---|
| 用户输入解析 | 信息提取专家 | 自然语言描述 | FetchServiceLogInput (JSON) |
| 日志质量评估 | 资深诊断专家 | 日志 + 元数据 + 问题描述 | LogQualityAssessment (JSON) |
| 重试策略生成 | 策略优化专家 | 历次评估结果 | FetchServiceLogInput (JSON) |
| 关键信息提取 | 日志分析专家 | 日志内容 | ExtractedInfo (JSON) |
| 根因分析 | 故障诊断专家 | 日志 + 提取信息 | 诊断报告文本 |
| 全链路分析 | 全链路分析专家 | 多服务日志 | 综合分析文本 |
| RAG 检索指导 | RAG 检索专家 | 搜索关键字 + RAG 工具 | FetchServiceLogInput (JSON) |
每个场景的 Prompt 都经过精心设计,遵循"角色定义 → 上下文注入 → 任务说明 → 输出约束"的统一范式。
2. Prompt 设计的核心原则
原则一:结构化输出,杜绝自由文本
LLM 的输出必须能被程序可靠解析。我们统一使用 createObject 模式,让 LLM 直接输出 JSON,由框架自动反序列化为 Java 对象:
java
LogQualityAssessment assessment = ctx.ai()
.withDefaultLlm()
.createObject(prompt, LogQualityAssessment.class);
这比"让 LLM 输出 Markdown,再用正则解析"要可靠得多。Prompt 中必须明确指定 JSON 结构:
json
【输出格式要求】
请严格按照以下 JSON 格式输出:
```json
{
"qualityScore": 整数(0-10),
"needsMoreContext": true/false,
"extractedKeywords": ["关键字1", "关键字2"],
"reasoning": "用中文说明评估理由"
}
markdown
#### 原则二:注入足够上下文,让 LLM "知情决策"
日志质量评估的 Prompt 中,我们不仅传递了日志内容,还包括:
- 服务编码、服务器 IP、发生时间
- 当前抓取的日志文件名
- 执行的命令和是否成功
- 服务器上所有可用日志文件的列表(含类型、大小、更新时间)
- 用户的初步分析
这让 LLM 能做出更准确的判断。例如,当 LLM 看到"服务器上只有一个日志文件"时,就不会建议"切换到其他日志文件"。
#### 原则三:明确判断规则,减少"猜测"
LLM 容易在模糊场景下"瞎猜"。我们通过详细的判断规则来约束它的行为:
【关键判断规则】(必须严格遵守)
-
switchFileRecommended 判断:
- 如果 extractLogInfos 中只有一个日志文件 → 必须返回 false
- 如果 extractLogInfos 中有多个日志文件,且当前日志不足以诊断 → 可以返回 true
-
qualityScore 评分标准:
- 0-3: 严重不足(缺少关键信息、日志截断、完全不相关)
- 4-6: 基本可用(有部分信息但需要补充)
- 7-10: 信息充分(有完整堆栈、上下文清晰、可直接解释问题发生原因)
perl
#### 原则四:传递底层机制说明,让 LLM "理解工具"
重试策略 Prompt 中,我们详细说明了底层 grep 命令的工作原理:
日志抓取的底层 Linux 命令为: cat {logFile} | grep '{occurTime}' -B {upperBoundary} -A {lowerBoundary} | tail -n {maxLine}
其中:
- occurTime: grep 的匹配关键字(通常是异常发生的时间戳)
- upperBoundary: grep 的 -B 参数,表示匹配行之前显示的行数
- lowerBoundary: grep 的 -A 参数,表示匹配行之后显示的行数
- maxLine: tail 的参数,限制最终输出的最大行数
markdown
LLM 理解了工具的工作原理后,能更精准地调整参数。例如:
- "堆栈被截断了" → 增大 `lowerBoundary`(显示更多后续行)
- "缺少发生前的上下文" → 增大 `upperBoundary`(显示更多前序行)
- "时间戳不匹配" → 偏移 `occurTime`(前后调整 5-30 秒)
### 3. 关键 Prompt 详解
#### 3.1 用户输入解析
这是 Agent 执行的第一个 Prompt,目标是从自然语言中提取结构化参数:
你是一名微服务异常诊断助手的信息提取专家。
【任务说明】 从用户输入的文本中提取关键信息,构造一个结构化的日志抓取请求对象。
【字段提取规则】
- serviceCode(必填): 服务编码,如 'his-doctor-service'
- occurTime(必填): 异常发生时间,格式 'yyyy-MM-dd HH:mm:ss'
- 如果是相对时间(如'10分钟前'),需要转换为绝对时间
- keyWord(可选): 异常类名、业务ID等
【用户输入】 帮我看看his-doctor-service在2026-06-09 10:30:15左右报了个TooManyResultsException
【期望输出】 { "serviceCode": "his-doctor-service", "occurTime": "2026-06-09 10:30:15", "keyWord": "TooManyResultsException" }
markdown
设计要点:
- 给出了具体的字段示例,降低 LLM 的理解成本。
- 对相对时间的处理做了特别说明(LLM 需要知道当前时间才能转换)。
- 明确了可选字段的处理方式(设为 null)。
#### 3.2 日志质量评估
这是整个 Prompt 工程中最复杂的部分,因为它需要 LLM 同时考虑多个维度:
**角色定义**:"你是一名资深的微服务异常诊断专家,擅长分析多服务日志关联和质量评估。"
**上下文注入**:
- 用户报告的问题
- 当前抓取到的日志元数据(服务编码、服务器IP、文件名等)
- 服务器上所有可用日志文件列表
- 实际日志内容
**判断规则**(9 条详细规则,如上文所述)
**输出格式**:包含 14 个字段的 JSON 结构
这个 Prompt 的长度约 1500 字,但正是这种详尽的描述,确保了 LLM 在各种边界场景下都能给出合理的评估。
#### 3.3 重试策略生成
这个 Prompt 的独特之处在于:它接收的是**历次评估结果的汇总**,需要在"历史信息"的基础上做出"前瞻性决策"。
【历史评估结果汇总】
--- 第 1 轮抓取 --- 抓取参数:
- 服务编码: his-doctor-service
- 发生时间: 2026-06-09 10:30:15
- 关键字: 无 评估结果:
- 质量评分: 3/10
- 日志被截断: true
- 需要更多上下文: true
- 推荐行动: extendByKeyword
- 评估理由: 日志只包含了部分错误信息,堆栈被截断...
--- 第 2 轮抓取 --- 抓取参数:
- 关键字: TooManyResultsException
- 上边界: 100
- 下边界: 200 评估结果:
- 质量评分: 6/10
- ...
markdown
LLM 基于这些历史数据,可以做出非常智能的决策:
- "第 1 轮时间戳匹配为空,第 2 轮用关键字匹配成功了" → 继续使用关键字。
- "堆栈仍然被截断" → 进一步增大 `lowerBoundary`。
- "评分从 3 提升到 6 了" → 方向正确,继续微调。
#### 3.4 根因分析
根因分析 Prompt 采用了 `generateText` 模式(而非 `createObject`),因为它需要输出较为自由的长篇分析报告:
【分析要求】
- 问题摘要:用1-2句话精准概括问题的核心
- 根因分析:详细说明问题的根本原因,包括:
- 异常发生的直接原因
- 潜在的深层原因(配置问题、代码缺陷、资源不足等)
- 证据链支撑(引用日志中的关键行)
- 修复建议:按优先级(HIGH/MEDIUM/LOW)给出具体可行的修复方案
- 置信度评估:HIGH/MEDIUM/LOW
【置信度判断标准】
- HIGH: 日志完整、异常堆栈清晰、根因明确
- MEDIUM: 日志基本可用、能推断可能原因但证据不够充分
- LOW: 日志缺失关键信息、只能猜测可能原因
scss
对于根因分析的输出,我们使用 `LlmResponseParseUtil` 进行后处理------提取 Markdown 中的各个章节(问题摘要、根因分析、修复建议、置信度),解析为 `DiagnosisReport` 对象。
### 4. 多模型配置与动态切换
系统支持同时注册多个 LLM 提供商的多个模型,通过 `LlmInitConfig` 在启动时自动注册:
```java
public LlmInitConfig(LlmProperties llmProps, GenericApplicationContext context) {
for (LlmProperties.LlmProvider llmProp : llmProps.getLlmOpenAi()) {
String provider = llmProp.getProvider();
for (String modelName : llmProp.getModels()) {
// 创建 OpenAiApi(支持不同 provider 的 API 路径差异)
OpenAiApi api = OpenAiApi.builder()
.apiKey(llmProp.getApiKey())
.baseUrl(llmProp.getBaseUrl())
.completionsPath("zhipu".equals(provider)
? "/v4/chat/completions" : "/v1/chat/completions")
.build();
// 创建 ChatModel
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(api)
.defaultOptions(OpenAiChatOptions.builder()
.model(modelName)
.temperature(0.2)
.build())
.build();
// 包装为 SpringAiLlmService 并注册为 Bean
SpringAiLlmService service = new SpringAiLlmService(modelName, provider, chatModel);
context.registerBean("llm-" + modelName, SpringAiLlmService.class, () -> service);
}
}
}
配置文件示例:
yaml
agent:
llm-open-ai:
- provider: deepseek
api-key: ${DEEPSEEK_API_KEY}
base-url: https://api.deepseek.com
models:
- deepseek-chat # 通用对话
- deepseek-reasoner # 推理增强
- deepseek-v4-flash # 快速版(默认)
- deepseek-v4-pro # 专业版
- provider: zhipu
api-key: ${ZHIPU_API_KEY}
base-url: https://open.bigmodel.cn/api/paas
models:
- glm-4.5-air
- glm-4.7
在 Action 中,通过 withDefaultLlm() 使用默认模型(配置在 embabel.models.default-llm 中),也可以通过 withLlm("deepseek-v4-pro") 切换到指定模型。这种灵活性让我们可以为不同场景选择最合适的模型:
- 日志质量评估 (快速判断):使用
deepseek-v4-flash,响应快、成本低。 - 根因分析 (深度推理):使用
deepseek-v4-pro,推理能力更强。
5. createObject vs generateText
系统中两种 LLM 调用模式的选择策略:
| 模式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
createObject |
结构化输出(评估、参数生成) | 自动反序列化,类型安全 | 受限于 JSON 格式 |
generateText |
自由文本(分析报告) | 输出灵活,可包含 Markdown | 需要手动解析 |
在根因分析中,我们选择了 generateText + 手动解析,因为诊断报告需要包含代码片段、层级结构等复杂内容,不适合严格的 JSON 约束。而对于质量评估、参数生成等场景,createObject 的可靠性是不可替代的。
6. Prompt 演进与调优经验
在开发过程中,Prompt 经历了多轮迭代。以下是几个关键的调优经验:
问题一:LLM 总是给高分
- 原因:评估 Prompt 中没有强调"与用户问题的相关性"。
- 修复:添加规则------"即使异常堆栈信息完整,但不能解释用户遇到的问题,也不能打出高分"。
问题二:重试策略总是建议切换文件
- 原因:LLM 不知道服务器上只有一个日志文件。
- 修复:在 Prompt 中注入"可用日志文件列表",并添加规则------"只有一个文件时必须返回 false"。
问题三:JSON 中的代码片段导致解析失败
- 原因:代码中的双引号破坏了 JSON 结构。
- 修复:在 Prompt 中添加转义规则------"codeSnippet 中的双引号必须转义为
""。
问题四:occurTime 格式不一致
- 原因:LLM 有时输出 ISO 格式,有时输出自定义格式。
- 修复:在 Prompt 中反复强调"必须为 yyyy-MM-dd HH:mm:ss 格式",并给出示例。
7. 温度与稳定性的平衡
所有 LLM 调用都设置了 temperature=0.2(通过 OpenAiChatOptions),这是一个偏低的温度值。在日志诊断场景中,我们需要的是稳定的、可预测的输出,而非"创造性"的回答。低温度确保了:
- 相同输入产生相似的输出(可复现性)。
- JSON 格式更稳定(减少格式错误)。
- 评分标准更一致(不会忽高忽低)。
8. 小结
Prompt 工程是将 LLM 从"通用助手"塑造为"领域专家"的关键手段。在本项目中,我们通过 7 个精心设计的 Prompt,覆盖了诊断流程的每个决策点:信息提取、质量评估、策略优化、关键信息提取、根因分析、全链路分析和 RAG 检索指导。每个 Prompt 都遵循"角色-上下文-任务-约束"的统一范式,并通过多轮迭代不断优化。结合多模型配置和 createObject 的结构化输出机制,LLM 真正成为了系统的"大脑",驱动整个诊断流程的智能决策。
系列总结:从博客五到博客九,我们完整介绍了 Agent 版微服务日志诊断系统的核心架构:GOAP Agent 编排(博客五)、双路径策略(博客六)、RAG 引擎(博客七)、全链路追踪(博客八)和 Prompt 工程(博客九)。这套系统将 SSH 自动化、知识库关联、RAG 检索和 LLM 推理有机地融合在一起,实现了从"人肉排查"到"AI 自主诊断"的跨越。如果您对项目感兴趣,欢迎到 Gitee 上 Star 和提 Issue!