项目地址 :gitee.com/guoxuezha/e...
1. 问题的本质:速度与深度的博弈
在微服务日志诊断中,存在一个核心矛盾:
- 快速响应:大多数异常(如 NullPointerException、SQL 语法错误)只需看一眼 error.log 的堆栈信息就能定位,此时 SSH + grep 几秒内就能搞定。
- 深度分析:少数复杂异常(如偶发的线程池耗尽、跨服务调用超时)需要查看完整日志上下文,甚至需要在海量日志中精准定位异常锚点。
如果每次都走"深度分析",下载完整日志 + 构建 RAG 索引,可能要花费 30 秒到数分钟;但如果只走"快速抓取",又可能因为日志截断、关键字不匹配等原因导致漏判。
我们的解决方案是:两条路同时准备,哪条先通走哪条。
2. 双路径策略全景图
ini
用户输入
│
▼
┌─────────────────────┐
│ fetchServiceLog │ ◄──── Fast Path: SSH + grep
│ (SSH 远程抓取) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ assessLogQuality │ ◄──── LLM 质量评分 (0-10)
│ (日志质量评估) │
└──────┬───────┬──────┘
│ │
[>= 6分] [< 6分]
Pass Fail
│ │
│ ├─────────────────────────┐
│ │ │
│ ▼ ▼
│ ┌───────────┐ ┌──────────────────┐
│ │ fetchRetry│ │ triggerRagBuild │ ◄── 异步启动
│ │ (标准重试) │ │ (后台下载+构建RAG)│
│ └─────┬─────┘ └──────────────────┘
│ │ │
│ [回到fetch] [主循环继续]
│ │
│ ┌────────────────────────┘
│ │ (RAG就绪后)
│ ▼
│ ┌───────────────────┐
│ │ragDrivenPrecise │ ◄── Slow Path: RAG 精准抓取
│ │Fetch │
│ └────────┬──────────┘
│ │
│ [回到fetch]
│
▼
┌─────────────────────┐
│ extractKeyInfo │ ◄──── 提取关键信息
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│performRootCause │ ◄──── 根因分析
│Analysis │
└─────────────────────┘
3. Fast Path:SSH + grep 快速命中
Fast Path 是默认的首选路径,目标是"用最少的代价解决问题"。
3.1 首次抓取
fetchServiceLog Action 根据用户输入的服务编码和发生时间,通过 SSH 连接到目标服务器,执行 grep 命令抓取日志片段:
java
@Action(
description = "通过 SSH 远程抓取服务日志",
pre = {"hasValidFetchParams"},
post = {"logNotEvaluatedYet"},
canRerun = true // 关键:允许重复执行
)
public SshLogResult fetchServiceLog(FetchServiceLogInput input, OperationContext ctx) {
List<ServiceResource> resources = knowledgeBaseService.findServiceResources(serviceCode);
SshLogResult sshLogResult;
if (StrUtil.isEmpty(keyWord)) {
// 按时间匹配(默认模式)
sshLogResult = sshService.firstLogCatch(resources, occurTime, ...);
} else {
// 按关键字精确匹配
sshLogResult = sshService.logSectionCatch(resources, occurTime, keyWord, ...);
}
// ...
}
底层执行的是类似这样的 Linux 命令:
bash
cd /log/dir && grep -F '10:30' error.log -A 50 -B 50 | tail -n 500
3.2 质量评估
抓取到日志后,assessLogQuality Action 调用 LLM 对日志质量进行评分(0-10分)。评分标准非常细致:
| 分数段 | 含义 | 系统行为 |
|---|---|---|
| 0-3 | 严重不足(空日志、完全无关) | 进入重试或 RAG |
| 4-5 | 基本可用但信息不足 | 标准重试(调整参数) |
| 6-10 | 信息充分,可直接诊断 | 进入根因分析 |
评估的 Prompt 设计是整个系统的精髓之一。我们让 LLM 不仅输出分数,还要输出详细的评估理由和下一步建议:
json
{
"qualityScore": 4,
"needsMoreContext": true,
"needsKeywordFilter": false,
"extractedKeywords": ["TooManyResultsException", "orderId=12345"],
"switchFileRecommended": true,
"missingInfoTypes": ["stacktrace"],
"isTruncated": true,
"isRelevantToProblem": true,
"deviationRisk": "low",
"recommendedAction": "extendByKeyword",
"reasoning": "日志包含部分异常信息但堆栈被截断,建议增大下边界行数..."
}
这些结构化输出,为后续的重试策略提供了精确的指导。
4. 标准重试:LLM 驱动的自适应策略
当质量评估不通过(score < 6)且 RAG 尚未就绪时,系统进入 fetchRetry Action------这是 Fast Path 的"自我修复"机制。
核心思路是:将历次评估结果全部交给 LLM,让它生成下一轮的抓取参数。
java
@Action(
description = "根据质量评分决定下一轮延伸策略",
pre = {"qualityScoreFail", "notRagReady"},
post = {"hasValidFetchParams"},
canRerun = true
)
public FetchServiceLogInput fetchRetry(FetchServiceLogInput lastFetch,
AssessLogQualityOutput qualityOutput,
OperationContext ctx) {
// 查询所有历史评估结果
List<AssessLogQualityOutput> assessments = ctx.objectsOfType(AssessLogQualityOutput.class);
// 防无限循环
if (assessments.size() > 5) {
// 终止诊断
return FetchServiceLogInput.builder().needBreak(true).build();
}
// LLM 生成下一轮参数
String prompt = PromptBuilder.buildNextFetchRequestPrompt(assessments);
return ctx.ai().withDefaultLlm().createObject(prompt, FetchServiceLogInput.class);
}
LLM 可以调整的参数维度非常丰富:
| 参数 | 含义 | 调整策略示例 |
|---|---|---|
occurTime |
grep 匹配的时间关键字 | 前后偏移 5-30 秒(如 10:30:15 → 10:30:10) |
keyWord |
替代时间戳的精确关键字 | 使用异常类名、orderId 等 |
upperBoundary |
grep -B 行数(上文) | 从 50 增到 200(堆栈被截断时) |
lowerBoundary |
grep -A 行数(下文) | 从 50 增到 500(需要更多后文时) |
maxLine |
tail -n 行数(最大输出) | 从 500 增到 2000(日志密度大时) |
这种"LLM 自适应调参"的设计,让系统在面对各种日志场景时都能逐步逼近最优的抓取策略,而不需要硬编码任何重试规则。
5. Slow Path:RAG 驱动的深度挖掘
当标准重试多轮仍未达标时,后台的 RAG 构建可能已经完成。此时系统切换到 Slow Path------利用 RAG 索引的全文检索能力,实现"大海捞针"般的精准定位。
5.1 异步 RAG 构建
当第一次质量评估不通过时,系统在后台悄悄启动 RAG 构建:
java
private void triggerRagBuildIfNeeded(FetchServiceLogInput fetchInput,
SshLogResult logResult,
OperationContext ctx) {
// 检查是否已启动过
RagBuildProgress existing = ctx.last(RagBuildProgress.class);
if (existing != null && (existing.isBuilding() || existing.isReady())) {
return; // 幂等性保护
}
RagBuildProgress progress = new RagBuildProgress();
progress.setBuilding(true);
ctx.set("ragBuildProgress", progress); // 写入黑板,供 Condition 检测
// 异步执行:下载 + 解析 + 索引
CompletableFuture.runAsync(() -> {
// Step 1: SSH下载日志文件(gzip压缩传输)
String localFileName = sshService.downloadLogFile(resources.getFirst(), ...);
if (progress.isCancelled()) return; // Fast Path 成功了,取消
// Step 2: 解析日志并构建 RAG 索引
IngestionResult result = logIngestionService.ingestFromFile(serviceCode, localFileName);
// Step 3: 标记就绪
progress.markReady(result.totalTraces(), result.totalChunks());
});
}
关键设计点:
- 不阻塞主循环 :RAG 构建在后台线程执行,主循环的
fetchRetry → fetchServiceLog → assessLogQuality继续运转。 - 可取消 :如果 Fast Path 在 RAG 就绪前就成功了,
extractKeyInfoAction 会取消后台任务,避免浪费资源。 - 幂等性 :通过检查
RagBuildProgress的状态,确保不会重复构建。
5.2 RAG 精准抓取
当 RAG 就绪(ragReady = true)且仍有 qualityScoreFail 时,规划器会路由到 ragDrivenPreciseFetch Action:
java
@Action(
description = "RAG驱动的深度精准抓取",
pre = {"qualityScoreFail", "ragReady"},
post = {"hasValidFetchParams"},
canRerun = true
)
public FetchServiceLogInput ragDrivenPreciseFetch(AssessLogQualityOutput qualityOutput,
RagBuildProgress ragProgress,
OperationContext ctx) {
// 收集历次评估中的关键字和推理信息
String searchQuery = buildSearchQueryFromAssessments(ctx);
// 利用 RAG 索引 + LLM 生成精准抓取参数
FetchServiceLogInput result = ctx.ai()
.withDefaultLlm()
.withReference(insuranceRag) // 注入 RAG 工具
.createObject(ragSearchPrompt, FetchServiceLogInput.class);
return result;
}
此时 LLM 拥有了 RAG 的全文检索能力(通过 ToolishRag 暴露的 textSearch / vectorSearch 工具),可以:
- 在完整日志中搜索异常锚点(如
NullPointerException at com.guo.doctor.service)。 - 定位锚点的精确时间戳和上下文。
- 输出高度精准的
FetchServiceLogInput(仅针对锚点前后小范围)。
然后系统用这个精准参数再次执行 SSH grep,得到的日志片段质量通常会大幅提升。
6. 竞速与协调:两条路如何协作
整个双路径策略可以用一个"竞速模型"来理解:
ini
时间线 ─────────────────────────────────────────────────────────────────────────────────────────→
Fast Path: [fetch₁] → [assess₁] → [retry] → [fetch₂] → [assess₂] → [PASS!] → [extract] → [report]
│
cancel ─────────┘
│
Slow Path: ──── [download] ──── [parse] ──── [index] ──── [cancelled]
(异步启动) (后台执行)
如果 Fast Path 在第 2 轮就成功了,Slow Path 的 RAG 构建会被取消,不浪费任何额外资源。
如果 Fast Path 5 轮重试仍未成功,而 Slow Path 的 RAG 已就绪,系统会自动切换到 RAG 精准抓取模式。
如果两条路都失败(5 轮重试 + RAG 也未找到有效信息),系统会触发 generateFailureReport,输出一份包含完整诊断过程的降级报告。
7. 防无限循环与终止机制
为了防止 Agent 陷入无限循环,系统设置了多层保护:
- 最大重试次数 :
assessLogQualityOutputs.size() > 5时强制终止。 - DiagnosisTermination :终止标志写入黑板,触发
shouldTerminate条件,路由到generateFailureReport。 - Agent 级超时 :
AgentService的runWithTimeout方法设置 300 秒总超时。 - StuckHandler:超时后触发诊断钩子,输出黑板状态帮助排查。
8. 实际效果对比
| 场景 | Fast Path 命中 | Slow Path 命中 | 均未命中 |
|---|---|---|---|
| 典型异常(NPE、SQL 错误) | 1-2 轮,5-15秒 | --- | --- |
| 复杂异常(线程池耗尽) | 3-4 轮,20-40秒 | 1 轮 RAG,40-60秒 | 失败报告,60秒 |
| 偶发异常(间歇性超时) | 通常不命中 | 1-2 轮 RAG,30-60秒 | 失败报告 |
在实际测试中,约 70% 的异常 在 Fast Path 的 1-2 轮内就被定位,20% 需要借助 Slow Path 的 RAG 能力,剩余 10% 因日志本身信息不足而输出降级报告。
9. 小结
双路径策略的核心思想是"乐观快速,悲观兜底"------大部分问题走 Fast Path 快速解决,少部分问题通过 Slow Path 深度挖掘。两条路径并行竞速,由 GOAP 规划器根据实时状态自动路由。这种设计既保证了响应速度,又不牺牲诊断深度,是 Agent 架构带来的最大收益之一。