最近一直在折腾一个设备故障诊断方向的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做趋势判断。改天有空再单独写一篇,今天这篇就先到这。
最近一直在折腾一个设备故障诊断方向的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做趋势判断。改天有空再单独写一篇,今天这篇就先到这。