
最近一直在折腾一个设备故障诊断方向的Agent项目从最开始只想着“接个大模型API就完事”到后面被迫把LLM、Tools、Prompt这三块硬骨头一个个啃下来中间踩了不少坑。今天不聊虚的直接把我沉淀下来的这套从零搭建AI诊断Agent的方法论拆开揉碎讲清楚尤其是LLM选型、工具调用、Prompt编排这三者之间到底怎么配合设计时每一步为什么要这么做。这篇文章适合刚接触Agent开发、想快速落地一个诊断类应用的开发者也适合那些已经跑通了Demo但感觉效果不稳、想系统优化一轮的同学。我会尽量把背后的逻辑和实操细节都说透。1. 先搞清楚诊断Agent到底在解决什么问题1.1 传统诊断流程的三大痛点做诊断类系统不管是设备故障诊断、业务系统异常排查还是供应链风险识别传统做法大同小异人肉盯监控指标出问题时翻日志、查数据库、查配置再凭借经验做交叉比对。这种做法有几个绕不开的痛点。首先是慢。系统告警之后技术人员要花几分钟甚至几十分钟去找对应服务的日志文件、筛选关键字、翻看时间线等真正定位到根因业务影响面已经扩大了。其次是经验高度依赖个人。同一种故障现象资深工程师看一眼日志就能锁定问题新人可能要摸索半天而这个“看一眼就知道”的判断过程很难量化沉淀。第三是跨系统排查成本高。一个故障往往涉及应用日志、中间件状态、数据库性能、网络链路等多个数据源每个系统都有独立的查询入口和查询语法诊断一次要把这些工具挨个用一遍操作繁琐。1.2 Agent化之后的诊断流程长什么样AI诊断Agent的出发点就是把这套“感知—采集—推理—行动—结论”的过程自动化。Agent借助大语言模型的理解和规划能力可以把原本需要人工完成的诊断步骤串联起来收到异常事件后自动判断需要看哪些数据调用对应的工具去拉取日志或指标分析数据之间的关系推断可能的根因最后输出带有置信度的结论和建议动作。这和传统的“自动化脚本”有本质区别。脚本是固化的if-else逻辑只能在预设好的路径里走一旦出现设计时没考虑到的异常组合脚本就哑火了。而基于LLM的Agent具备推理能力遇到新情况时可以动态调整排查策略生成下一步的查询计划再根据返回结果修正推断。说白了脚本是“执行者”Agent是“可思考的执行者”。明白了这一点我们再来看Agent的三个核心组成LLM负责理解与推理是Agent的“大脑”Tools提供感知与行动能力是Agent的“手和眼”Prompt定义行为边界与输出规范相当于“剧本”。三者的关系不是我最初理解的那种“拼积木”而是一套互相制约的协作机制。接下来逐个展开。2. LLM诊断Agent的“大脑”选型与接入2.1 模型选型先算清楚任务复杂度这笔账很多第一次做Agent的人会陷入一个误区模型越强越好直接上最贵的。实际用过一轮之后我的体会是选模型要从任务复杂度、实时性要求和成本三个维度一起算账不能只看效果。诊断类任务有一个特殊性它往往不是一次性输出而是多轮推理加工具调用的循环。每多一轮token消耗就翻一倍。如果选用超大规模模型单次诊断可能消耗几千甚至上万token单次成本虽然看起来不多但放到每天几万次诊断请求的场景里就是一笔不小的开销。我在实际项目中做了一张任务复杂度对照表用来帮助选型任务类型典型场景推荐模型规模关键指标单数据源规则判断根据一条日志判断错误类型7B~14B级开源模型分类准确率多数据源交叉分析结合日志指标推断根因商用中型/大型API工具调用准确率复杂链路诊断多层依赖、多系统联动排查商用强推理模型规划合理性对话式诊断解释面向用户输出诊断过程与建议商用中大型API表达清晰度这里有一个判断原则如果任务的决策路径基本能在两个工具调用以内完成中小规模模型性价比最高如果涉及完整的因果链分析才需要动用强推理模型。另外很多场景下“开源模型本地部署商用模型兜底”的混合方案也值得考虑——常规问题走本地模型省钱保隐私疑难杂症再升级到商用接口。2.2 上下文窗口与数据投喂策略诊断Agent和普通ChatBot最大的不同在于它要被塞入大量真实的系统数据。日志、指标、配置这些内容往往会迅速撑爆上下文窗口。这里需要提前想清楚两个问题诊断一个典型问题需要多少时间窗口的数据这些数据用什么形式送入模型最合适时间窗口的选择我一般按故障类型区分。CPU飙升、内存溢出这类资源型问题看故障前5到10分钟的指标就够而连接泄漏、慢SQL这类渐进型问题可能需要回溯30分钟甚至更久的趋势数据。在数据进模型之前先做一层时间对齐和裁剪把无关字段去掉只保留时间戳、级别、关键字和关键数值。送入模型的数据格式也需要设计不能直接把原始JSON往Prompt里砸。我常用的做法是转成紧凑的文本格式例如按“时间—来源—事件”排列的日志摘要行。这样既保留了核心信息又控制了token消耗。经验数值供参考一个10分钟窗口的日志经过裁剪和摘要之后体积能压缩到原始规模的十分之一到二十分之一但诊断所需的信息完整度不会明显下降。3. Tools给Agent装上“手和眼”3.1 工具的本质把不可见的系统变成可调用的能力LLM能思考但它的训练数据有截止日期也不知道你的系统里实时发生了什么。诊断Agent要真正落地必须解决“让模型看见真实世界”的问题。这就要靠Tools。工具层做的事情本质上就是把你系统的数据访问能力封装成一个个可供模型按需调用的接口。比如查询指定服务最近N分钟的日志、拉取某个指标的时间序列、检查配置文件中的某个参数、执行一条只读的SQL查询。每个工具都有自己的名字、功能描述和参数定义LLM在推理过程中会自主决定调哪个工具、填什么参数然后读取返回结果继续分析。3.2 工具函数的定义规范与注册机制工具定义是影响Agent效果的关键细节但经常被忽视。LLM并不知道你的系统里有什么工具全靠函数名称和描述来理解。描述写得含糊模型就可能选错工具或者反复调用同一个工具。我在项目中总结出一套工具描述模板{ name: query_service_logs, description: 查询指定服务在指定时间范围内的错误日志返回日志条目的时间戳、日志级别和原始日志文本。当需要定位某服务出现异常的原因时使用此工具。, parameters: { type: object, properties: { service_name: { type: string, description: 目标服务名称须与部署清单中的服务名完全一致 }, start_time: { type: string, description: 查询起始时间格式为YYYY-MM-DD HH:MM:SS }, end_time: { type: string, description: 查询结束时间格式为YYYY-MM-DD HH:MM:SS }, keyword: { type: string, description: 可选按关键字过滤日志内容 } }, required: [service_name, start_time, end_time] } }这里有几个实操中总结出来的要点描述里一定要写清楚“什么时候应该用这个工具”以及工具的限制条件。参数描述必须明确取值边界避免模型生成非法参数。时间格式要在参数描述里显式指定否则模型经常猜错格式。3.3 工具调用的执行与反馈回路模型决定调用工具之后执行层需要处理两件看起来简单、实际坑不少的事调用策略和结果反馈。调用策略上Agent框架一般支持串行和并行两种。串行适合有依赖关系的工具链比如先查服务状态、再决定拉取哪个时间段的日志并行适合相互独立的查询比如同时拉取CPU指标和错误日志。并行能大幅提升诊断速度代价是token消耗更大因为每个工具的返回结果都会占用上下文。我的经验是绝大多数诊断场景控制在两到三个并行工具调用以内超过这个数量容易造成上下文混乱反而不利于模型集中推理。结果反馈同样需要设计。工具返回的原始日志可能几百行模型看着看着就把前面的推理步骤忘了。我在执行层做了一道“结果预处理”工序对工具返回结果进行长度截断、关键信息抽取、按时间线合并。这样返回给模型的数据永远是经过整理的、可读性强的信息块。关键提示工具结果返回前必须做超时控制和异常包装。工具调用超时或报错时不要直接中断Agent而是把异常信息作为结果返回给模型。模型知道上一次查询失败了会主动尝试换一个工具或者调整参数这种容错设计在实际诊断中非常实用。4. Prompt诊断行为与输出规范的“剧本”4.1 System Prompt的设计角色、边界、流程Prompt是很多Agent项目效果不稳定的重灾区。我的体会是Agent的System Prompt不能简单复制ChatBot的套路它更像是给模型写一份“岗位说明书”需要把三件事交代清楚角色定位、行为边界、工作流程。角色定位让模型知道自己在做什么场景下的诊断。行为边界则明确哪些事情能做、哪些不能做——比如只读操作、不修改线上配置、不访问无关系统。工作流程部分最关键要让模型遵循“提出假设—收集数据—验证假设—得出结论”的路径而不是拿到一条错误日志就急着给结论。我随手写一个简化版的System Prompt示例不同任务往里面替换场景细节即可你是一名故障诊断Agent。你的任务是根据用户提供的异常描述结合可用的诊断工具逐步分析问题根因。 工作准则 1. 每一步推理都必须基于工具返回的数据禁止凭空猜测。 2. 优先收集证据再下结论。至少调用一个工具验证假设后才能给出最终结论。 3. 如果第一次调用结果不足以判断可以调整参数再次查询。 4. 所有操作及工具调用都必须是只读性质禁止任何修改、删除、写入行为。 5. 输出最终结论时必须提供置信度评分并列出支撑证据的时间点和数据来源。这里有一个从实践中总结出的细节准则的第二条“至少调用一个工具验证假设”能显著减少模型的胡猜行为。很多模型在自由度高的对话场景里会直接依赖内部知识作答加了这条约束之后它会自觉回到数据上来。4.2 思维链引导与诊断步骤的固化LLM在单步推理时很强但连续多步推理时容易迷失。诊断Agent恰恰需要多步推理所以要用Prompt把推理结构“固化”下来。我的做法是把诊断流程拆成可复用的阶段模板让模型按阶段输出。典型的分段是这几块异常现象重现、可能原因枚举、验证计划制定、工具调用与证据收集、根因确认与排除、结论输出。每一阶段都要求模型输出自己的中间思考但这里要特别注意只能让模型“在推理过程中”内部思考而不必把所有思考过程完整暴露给最终用户。更具体地在Prompt中加入一个“推理引导”段落告诉模型如果返回的日志中出现特定模式该怎么应对。比如内存溢出相关关键字与CPU飙升同时出现优先怀疑GC频繁导致的资源竞争连接数持续增长且超时比例上升优先怀疑连接泄漏。这相当于把专家经验预埋在Prompt里模型遇到类似信号时会顺着这条路径排查比完全自由发挥稳定得多。4.3 输出结构化从自由文本到可解析数据诊断Agent的输出不能只是几句自然语言结论下游系统还要据此联动——自动创建工单、通知运维群、触发其他检查流程。因此最终输出必须是结构化、可程序解析的格式。在Prompt末尾我通常会要求模型按固定的JSON结构返回诊断结果{ root_cause: 问题根因的简要描述, confidence: 0.0, evidence: [证据1, 证据2], analyzed_services: [服务A, 服务B], suggestions: [建议动作1, 建议动作2] }对LLM输出做JSON解析有一条很重要的经验不要指望模型能严格百分之百输出合法JSON必须做一层容错解析。现实中有不少情况是模型在JSON前后加了额外说明文字或者某个字段值为空被我写的校验逻辑拦截。我在项目中采用了解析后校验加自动修复的流程解析失败时把错误信息返回给模型让它自我纠正实测下来成功率能达到九成以上。5. 实操从零搭建一个设备故障诊断Agent5.1 环境准备与最小框架选型理论部分讲完进入实际搭建环节。就以一个简化的设备故障诊断场景为例某监控系统每5分钟采集一次CPU、内存、磁盘等指标并记录应用错误日志现在要构建一个诊断Agent让它收到异常事件后自动定位是资源瓶颈、代码异常还是配置问题。框架层面我用了常见的Python 某开源Agent编排库的组合。选它的原因是社区活跃、工具调用和对话管理开箱即用省去自己实现函数调用协议的工作量。如果不想依赖框架直接用LangChain式的手写循环也能实现核心是处理好“模型返回—解析工具调用—执行工具—回填结果—再次调用模型”这个循环。5.2 核心代码实现工具注册与Agent循环先定义两个基础工具一个是查询指标一个是查询日志。为了演示简洁工具内部用模拟数据代替真实数据源。import json from typing import Dict, Any # 模拟数据存储 MOCK_METRICS { host-01: [ {time: 2024-01-01 10:00:00, cpu_percent: 58.2, mem_percent: 61.5}, {time: 2024-01-01 10:05:00, cpu_percent: 87.6, mem_percent: 78.3}, {time: 2024-01-01 10:10:00, cpu_percent: 95.1, mem_percent: 82.9} ] } MOCK_LOGS { host-01: [ {time: 2024-01-01 10:06:12, level: ERROR, message: java.lang.OutOfMemoryError: Java heap space}, {time: 2024-01-01 10:08:30, level: ERROR, message: connection pool exhausted} ] } def query_metrics(host_name: str, start_time: str, end_time: str) - str: 查询指定主机的CPU与内存指标 records MOCK_METRICS.get(host_name, []) result [ {time: r[time], cpu: r[cpu_percent], mem: r[mem_percent]} for r in records if start_time r[time] end_time ] return json.dumps(result) def query_logs(host_name: str, start_time: str, end_time: str, keyword: str ) - str: 查询指定主机的错误日志 records MOCK_LOGS.get(host_name, []) result [ {time: r[time], level: r[level], message: r[message]} for r in records if start_time r[time] end_time and (not keyword or keyword in r[message]) ] return json.dumps(result)然后定义工具的注册元信息也就是给LLM看的“说明书”TOOL_REGISTRY { query_metrics: { description: 查询指定主机在指定时间范围内的CPU使用率和内存使用率指标。当需要判断是否存在资源瓶颈时使用。, parameters: { type: object, properties: { host_name: {type: string, description: 主机名}, start_time: {type: string, description: 起始时间格式YYYY-MM-DD HH:MM:SS}, end_time: {type: string, description: 结束时间格式YYYY-MM-DD HH:MM:SS} }, required: [host_name, start_time, end_time] }, function: query_metrics }, query_logs: { description: 查询指定主机在指定时间范围内的错误日志。当需要定位具体报错原因时使用。, parameters: { type: object, properties: { host_name: {type: string, description: 主机名}, start_time: {type: string, description: 起始时间格式YYYY-MM-DD HH:MM:SS}, end_time: {type: string, description: 结束时间格式YYYY-MM-DD HH:MM:SS}, keyword: {type: string, description: 可选按关键字过滤日志} }, required: [host_name, start_time, end_time] }, function: query_logs } }Agent主循环用伪代码展示会更清晰实际工程中直接对接框架的AgentExecutor即可def run_agent(problem_description: str): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: problem_description} ] max_steps 5 # 限制最大工具调用轮数防止Agent陷入死循环 for step in range(max_steps): response llm_with_tools(messages, TOOL_REGISTRY) if response.finish_reason tool_call: tool_name response.tool_call.name args json.loads(response.tool_call.arguments) tool_result TOOL_REGISTRY[tool_name][function](**args) messages.append({ role: tool, tool_call_id: response.tool_call.id, content: tool_result }) else: return response.content return 达到最大诊断步数已终止本轮诊断请人工介入排查。这里给初次上手的朋友一个提示最大工具调用轮数一定要显式设置。Agent陷入“反复调用工具但不产出结论”的循环是常见故障设置上限可以保证系统整体的响应时效。5.3 运行效果实录与调优记录拿上面模拟数据跑一轮。输入异常描述“主机host-01在10:10出现CPU使用率超过95%请诊断可能原因。”Agent第一轮调用query_metrics查询10:00到10:10的指标发现CPU从58%逐步升高到95%同时内存也从61%升到82%。模型据此推断可能是内存压力引发GC频繁进而CPU飙升为了验证再调query_logs看10:00到10:10的日志。日志里出现OutOfMemoryError和connection pool exhausted基本锁定根因是内存溢出导致的GC频繁和资源耗尽。最终输出的诊断结论{ root_cause: 主机host-01发生内存溢出导致频繁GC和连接池耗尽进而CPU使用率大幅飙升, confidence: 0.85, evidence: [ 10:05-10:10内存使用率从78.3%升至82.9%, 10:06出现OutOfMemoryError日志, 10:08出现连接池耗尽日志 ], analyzed_services: [host-01], suggestions: [检查Java堆内存配置, 排查是否存在内存泄漏, 必要时扩容或重启实例] }这个结果比较理想。但如果模型在第一轮就只看到内存指标飙升、还没查日志就直接下结论说明Prompt里“必须调用至少一个工具验证假设”的约束没有被严格执行这时候要把该约束从过往对话里剥离出来、重新强调并且适当调低模型的温度参数。6. 常见问题与排查技巧实录6.1 工具调用“幻觉”问题工具调用幻觉表现得很直接模型调用了不存在的工具或者给工具传入了错误参数。比如把服务名写成了“host-01”而注册表里是“web-server-1”或者时间格式填成了“10:10”而不是“2024-01-01 10:10:00”。这类问题的排查顺序是先看工具描述是不是足够清晰。如果描述含糊比如“时间格式为日期格式”这种说法模型大概率会猜错。再检查参数命名是否与业务直觉一致有些通用的参数名比如“input”会让模型困惑改成具体明确的名称会好很多。如果描述没问题还出现幻觉可以考虑在System Prompt中加一条“只能使用提供的工具禁止臆造工具名称”。6.2 诊断结论“想当然”模型没有真正分析数据而是从提示词里的某些关键词直接联想出结论。比如用户说“系统很卡”模型不先查指标就推断“可能存在死锁”。这类问题在诊断场景中危害最大因为结论听上去很有道理实际却是编的。对策我在前面已经提到过在Prompt里设置强制证据链约束。此外可以配合一个后置检查逻辑最终结构化输出里的每个证据字段都必须在前面工具返回中真实出现过。在工程实现上解析模型输出后逐个核对证据来源对不上的证据直接标记为“未验证”这样既能拦截一部分幻觉结论也能让不靠谱的输出暴露在测试阶段。6.3 上下文爆炸与成本失控诊断Agent每个循环都会塞入工具结果几轮下来上下文很容易膨胀到几万token。这不仅拖慢响应速度还会影响模型在长上下文中的注意力分布导致后续推理质量下降。处理上下文膨胀我常用的手段有三个对历史工具结果做滚动摘要把已经分析过的旧结论压缩成几句话对工具返回结果做截断只保留时间线关键事件分阶段重置上下文把完整诊断切成多个独立子任务分开跑。这里需要明确这属于工程调优手段不同场景要按数据量灵活切换不是所有诊断任务都适合做滚动摘要如果前后阶段有强依赖强行摘要会丢失细节。6.4 安全边界与权限最小化诊断Agent本身具备调用各种数据源的能力这决定了它必须做严格的权限管控。我强烈建议所有工具函数只提供只读能力不允许Agent执行任何修改动作涉及敏感数据的查询接口要在工具层做字段脱敏后再进入模型上下文。还有一条容易被忽略的边界Agent的输出内容会进入日志系统日志中可能包含内部IP、账号等敏感信息输出层也要做一次脱敏过滤。注意将真实业务数据送入外部商用大模型API前务必确认数据合规要求。对数据敏感的场景优先选择私有化部署的开源模型或使用支持数据隔离的云服务不能为了效果牺牲合规底线。7. 写在最后一点个人经验聊到这儿这套从零到一搭建AI诊断Agent的核心方法论就基本讲完了。最后再分享几条我在实际项目中体会最深的经验。第一条Agent的效果是“调”出来的。不要指望一次把Prompt写完美把第一版跑起来收集失败样本再针对失败原因逐步修正Prompt和工具定义这个循环比反复推敲理论更有用。第二条最大工具轮数、超时控制、输出容错解析这三项属于工程底线必须在第一版就落地。很多Agent项目Demo阶段跑得挺好一上线就出各种偶发问题大多是因为这些边界条件没兜住。第三条也是我觉得最值得分享的一点诊断Agent的价值不只在“自动给出答案”更在“把诊断过程完整记录下来”。一套好的Agent系统天然沉淀了排查链路的日志——哪一步看了什么数据、得出什么判断、排除了什么可能。这些数据反过来能用来持续优化Prompt和工具设计形成数据飞轮。如果后续还想往深了走可以从两个方向扩展一个是引入多Agent协作让感知Agent、分析Agent、行动Agent各司其职另一个是给Agent接入更强的时序数据分析能力用专门的异常检测算法辅助LLM做趋势判断。改天有空再单独写一篇今天这篇就先到这。