
最近在折腾 AI coding agent发现自己像个拿着手电筒走夜路的人。Agent 跑起来确实像模像样但一旦出问题它就是一团你看不透的黑盒。我试过给 Agent 加日志、加 console、加回调最后还是决定认真搞一遍可观测与自愈。这篇文章就把我摸索出的几个开源思路整理出来适合正在用 LangGraph、CrewAI、AutoGPT或者自研 coding agent 的开发者参考。核心只有一句话先让 Agent 的每一步可观测再谈自动修复。1. 为什么要给 AI coding agent 加“眼睛”1.1 Agent 的黑盒困境你看到的是“忙”不是“对”AI coding agent 和你平时调一个 API 完全不同。普通 API 调用是“输入一次、输出一次”最多加个超时重试就能应付。可 Agent 是一个循环理解任务、拆解步骤、调用工具、读返回结果、再生成下一轮操作。这里面任何一步出错后续所有动作都可能跟着跑偏。我见过最典型的场景Agent 要改一个 bug它先运行测试然后开始翻代码翻到一半上下文窗口被日志塞满最后直接开始“即兴发挥”——凭空生成一个根本不存在的函数。从外面看它每一步都在输出终端滚动得飞快好像很忙但你不知道它为什么突然放弃原来的方案也不知道它读到的工具输出到底有没有被正确解析。这就是黑盒困境。没有可观测性你只能靠最终结果猜问题。而 coding agent 的最终结果又往往是“代码 diff 测试报告”如果测试没覆盖到错误就被悄悄吞掉了。更麻烦的是Agent 的失败通常不是“崩溃”而是“低质量地完成了任务”。这类失败不会自动报警只有把它的思考过程、工具调用、上下文片段全部记录下来才能定位到具体节点。1.2 可观测与自愈的关系观察、决策、行动、再观察很多人把可观测性和自愈当成两件事先上监控再写重试逻辑。但实际做下来它们必须合在一起。自愈的本质是一套反馈闭环先通过观测发现异常再根据异常类型做决策然后执行修复动作最后再次观测验证修复是否有效。这个闭环特别像开车。你开车去一个陌生地方不能蒙着眼睛踩油门。你需要导航告诉你当前在哪、离目标还有多远如果走错路导航会重新规划路线。AI coding agent 也一样“导航”就是可观测性“重新规划路线”就是自愈。没有前者后者就是瞎猜没有后者前者只让你眼睁睁看着 Agent 越跑越远一点忙都帮不上。所以我在实践里从来不把“加日志”和“加重试”分开做。想给 Agent 加自愈必须先回答三个问题我知道它现在在做什么吗我知道它为什么失败吗我知道修复动作有没有带来真正改善这三个问题都依赖同一套数据——trace、span、事件、评估结果。想清楚这个闭环再去选开源工具才不会装了一堆监控面板却不知道怎么用。2. 开源可观测方案把 Agent 的每一步变成数据2.1 传统监控为什么不够用传统监控体系长这样Metrics 看 QPS、延迟和错误率Logging 看服务日志Tracing 看请求链路。这套东西对于常规后端服务是够用的但放到 AI coding agent 身上就有点水土不服。Agent 一次任务会产生大量异构事件用户提示词、Agent 内部计划、工具调用参数、工具返回结果、中间代码片段、token 消耗、模型名称、温度参数、评估得分。这些事件不像普通 HTTP 请求那样能用 URL 和 status code 概括。比如“工具返回了一个空列表”这在监控系统里可能完全不被视为错误但对 Agent 来说这可能意味着它拿不到关键上下文接下来会开始胡编乱造。另一个问题是成本观测。Coding agent 烧的是 token不是单纯的计算资源。同一个任务用不同模型、不同提示词策略token 消耗可能差好几倍。传统监控里没有“token 消耗”这个指标但这对 Agent 系统来说就是最重要的成本项。不开源方案解决这些问题靠手写日志最后一定是灾难格式不统一、上下文对不上、排查问题还得把好几处日志拼起来。2.2 OpenTelemetry 在 Agent 场景的落地姿势OpenTelemetry 你可能听过它现在几乎是可观测性的标准底座而且它已经出了针对生成式 AI 的语义约定。简单说这套约定规定了 LLM 调用应该记录哪些字段比如 gen_ai.operation.name、gen_ai.request.model、gen_ai.usage.input_tokens 等。这样不同团队上报的数据格式统一后面接任何后端都能用。在 Agent 场景里我建议不要只埋“LLM 调用”这一层还要把 Agent 的决策过程也建模成 span。一个任务的完整链路可以这样拆根 spanagent.run代表整个任务子 spanagent.plan记录 Agent 拆解的任务步骤子 spantool.call记录每一步工具名、输入、输出摘要子 spanllm.generate记录每次模型调用的 prompt、completion、token 数子 spanvalidation记录测试或评估器的结果这样每个环节的时间、成本、结果都能对齐。OpenTelemetry 社区已经有 opentelemetry-instrumentation-openai、opentelemetry-instrumentation-langchain 之类的库可以直接把 LLM 调用信息自动上报。Agent 自身的状态流转比如 ReAct 循环里的“Thought / Action / Observation”则需要自己写一点埋点代码但这个成本很低给关键步骤包一个 span 就够了。2.3 开源观测平台选型对比把数据收集起来之后需要一个地方展示和查询。开源生态里可选的项目不少我实际测过几个挑核心差异说一下。项目核心定位部署方式特色适合场景LangfuseLLM 可观测 评估可自托管trace 清晰支持 prompt 管理有 eval 回放想快速上手的团队Arize PhoenixLLM 追踪 在线评估可自托管和 OpenTelemetry 结合紧密支持 embedding 分析需要深度调试 embedding 检索OpenLITOpenTelemetry 原生可自托管轻量专注于 OpenTelemetry 采集和导出已经有 OTel 基础设施的团队AgentOpsAgent 行为追踪云服务 开源版本围绕 Agent 状态机设计能看步骤回放用主流 Agent 框架的场景LangtraceLLM 可观测可自托管接入简单SDK 覆盖多种模型中小项目快速埋点我个人的建议是别一上来就铺全套先用一个能自托管的 trace 后端把数据接到 OpenTelemetry后面再叠加评估和告警。Langfuse 的社区版够用Phoenix 适合做在线评估OpenLIT 则适合本来就重度用 OpenTelemetry 的人。选型标准不是谁功能多而是谁能让你最快把 Agent 的关键路径变成可查询的 span。2.4 埋点代码从一个 Trace 到一整套链路埋点其实不复杂重点是找到“包住整个 Agent 运行”的边界。下面是一段用 OpenTelemetry 给 Agent 主循环加 span 的示例from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter provider TracerProvider(resourceResource.create({service.name: coding-agent})) provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpointhttp://localhost:4318/v1/traces))) trace.set_tracer_provider(provider) tracer trace.get_tracer(coding-agent) def run_agent(task: str): with tracer.start_as_current_span(agent.run) as root_span: root_span.set_attribute(task, task) plan plan_task(task) with tracer.start_as_current_span(agent.plan) as span: span.set_attribute(steps, len(plan)) for step in plan: with tracer.start_as_current_span(tool.call) as span: span.set_attribute(tool.name, step.tool) span.set_attribute(tool.input, step.input[:200]) result execute_tool(step) span.set_attribute(tool.output, result[:500]) return result这里有几个细节容易踩坑。第一工具输出不要全量塞进 attribute尽量截断到几百字符否则可能把 tracing 后端打爆。第二要给 span 加 status 和 error 事件这样后面自愈逻辑能直接在 trace 数据里筛失败原因。第三如果用了 LangChain 或 LlamaIndex优先用官方 instrumentation别重复埋。3. 自愈能力的分层实现思路3.1 第一层确定性兜底重试与降级策略自愈的第一步不是让 Agent 自己思考而是先做确定性的兜底。很多失败其实是瞬时性的模型 API 超时、网络抖动、工具进程被信号打断。这类问题跟 Agent 的推理能力没关系加一个带指数退避的重试就够。另一个是降级。比如 Agent 要调用某个在线代码解释器如果服务不稳定可以降级成本地沙箱执行如果某次工具调用返回了明显异常的数据可以丢弃这轮结果强制 Agent 重新生成。降级动作要记录到 trace 里否则你只看到任务成功了不知道它其实绕过了一个关键环节这种“成功”很可能埋雷。我在重试逻辑里一定会加三个约束最大重试次数、单次任务成本上限、全局并发上限。原因很简单LLM 调用不像普通数据库查询重试一下成本不高每次失败重试都在烧 token而且如果 Agent 已经在错误方向上走了好几步光重试当前工具调用解决不了问题。所以确定性兜底只适合处理“已知的、瞬时的、可安全重试”的失败其余情况要交给更上层。3.2 第二层让 Agent 学会反思和修正当重试也救不回来的时候就要让 Agent 自己当“主治医生”。现在开源 Agent 框架里常说的 ReAct、Reflexion 本质上都是反思机制。做法是让 Agent 执行一遍任务拿到失败信息或测试结果后再让它生成一段反思——分析哪里错了、可能的修复方案是什么——然后带着反思重新执行。这里最容易犯的错是把“反思”变成“嘴上道歉”。如果只是把错误文本原样塞回 prompt然后让 Agent 再来一次它大概率会重复同样的错误。我通常会把反思做得更结构化要求 Agent 输出“本次失败的具体环节”“该环节的输入是什么”“期望输出与实际输出的差异”“最小改动方案”。这四段信息会作为下一轮执行的额外上下文。在 LangGraph 里实现这个循环很直观一个节点负责执行一个节点负责反思反思结果决定是回到执行节点还是进入终止节点。给个简化思路from langgraph.graph import StateGraph, END def execute(state): result run_agent(state[task]) return {result: result, attempts: state[attempts] 1} def reflect(state): critique generate_critique(state[result]) if critique[is_ok] or state[attempts] state[max_attempts]: return {final_result: state[result]} return {critique: critique[advice]}实际上反思这一层如果没有外部信号很容易变成自我安慰。所以做反思之前一定要先解决“怎么判断失败”的问题也就是下一层要讲的评估。3.3 第三层用测试和评估器判断“是否修好”自愈不能只靠“Agent 觉得修好了”必须有一个独立的裁判。在 coding agent 场景里最可靠的裁判就是测试。任务如果是改代码那就必须有对应的回归测试任务如果是生成新代码那就先用静态检查和单元测试把最低质量门槛卡住。除了传统测试LLM 自身的评估能力也能当裁判。开源项目 DeepEval 提供了多种用例比如回答是否忠实于上下文、是否符合预期格式、逻辑是否连贯。你可以定义一组评估指标让另一个 LLM 当 judge对 Agent 的输出打分。注意 judge 模型最好和 Agent 模型不是同一个否则容易出现“互相觉得没问题”的集体幻觉。完整的自愈校验环节通常长这样Agent 生成代码或补丁静态检查工具跑一遍eslint、ruff、mypy 等单元测试和集成测试跑一遍若失败收集失败输出和 diff将失败信息作为反馈写入下一轮 prompt限制最多修复 N 次最终以“测试通过”作为唯一验收标准这个思路放在 Agent 框架里的价值是它给了系统一个“成功”的明确定义让自愈不再是碰运气。3.4 第四层把失败经验沉淀成长期记忆修复成功一次之后如果下次还踩同一个坑就太亏了。最好让 Agent 把失败的场景、修复方法、验证结果记录下来存到外部记忆里下次遇到类似问题直接从记忆里检索方案。这就是一个增强版 RAG 的思路。可以用 Chroma、Qdrant 或 PostgreSQL 的向量检索能力把失败记录切成小块存进去。存储内容不是普通日志而是结构化的问题单错误类型、出错工具、错误信息摘要、修复补丁、验证结果。当新任务产生类似错误时Agent 先检索历史问题单把最相似的几条记录作为 few-shot 示例加入 prompt。这样做还有个额外好处你等于在给 Agent 积累团队知识库。每修好一个问题系统就多一条经验而且这些经验都经过测试验证不会像网上搜到的代码片段那样可信度参差不齐。当然记忆也不是越多越好要定期清理过时内容否则检索结果太多会把上下文窗口挤爆反而干扰判断。4. 一个可落地的开源组合实践4.1 组合架构Trace、Eval、Sandbox 怎么串起来理论讲完我分享一个实际跑通的组合。整个结构围绕三条线可观测线、执行线、自愈线。执行线负责干活的 Agent 本体我用的 LangGraph工具集包括代码检索、终端命令、文件读写。可观测线走 OpenTelemetry把所有 span 导出到 Langfuse。自愈线由三部分组成测试执行器pytest、评估器DeepEval、重试控制器自研脚本。整个流程是用户提交任务 - Agent 规划并逐步执行 - 每一步的 trace 写入 Langfuse - 完成后自动跑测试和评估 - 如果失败把失败信息交回给 Agent 反思 - Agent 生成修复补丁 - 再次执行测试循环直到通过或达到最大次数 - 最终 trace 里记录所有尝试轮次和成本。是不是看起来像一个带监督的闭环对的这就是把“观察-决策-行动-再观察”落到了代码上。关键是工具选型可以随意替换但这个闭环结构最好不要变没有观测你不知道失败在哪没有测试你不知道成功是什么。4.2 快速接入 Langfuse 和 OpenTelemetry先用 pip 装依赖pip install langfuse opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-openaiLangfuse 本身已经暴露了 OpenTelemetry 端点所以不需要再单独起一个 collector。初始化 Langfuse 之后用它的装饰器就能自动跟踪 LLM 调用from langfuse.decorators import observe from langfuse import Langfuse langfuse Langfuse( secret_keyyour-secret, public_keyyour-public, hosthttps://your-langfuse-host, ) observe() def generate_code(prompt: str) - str: return call_llm(prompt) observe() def run_tests(code: str): ...但 decorator 只覆盖函数级调用Agent 内部的工具循环还是得手动埋点。我一般会在 LangGraph 的节点函数里用observe()装饰每个节点这样 graph 的每一步都会变成 Langfuse 里的一条 observation父子关系自动串起来。成本数据和 token 数也会被 Langfuse 的 SDK 自动解析不用自己数 token。还有一个必须注意的点不要把完整的 prompt 和代码 diff 默认全量上报。先做脱敏和截断策略尤其是如果 Agent 在处理私有仓库代码。Langfuse 支持在初始化时配置 masking 函数可以提前把敏感字段替换掉这个成本很低但合规上很重要。4.3 自愈循环的参考实现下面是一个很简化但能跑通思路的 Python 自愈循环import time def run_coding_agent_with_healing(task: str, max_attempts: int 3): history [] for attempt in range(max_attempts): with tracer.start_as_current_span(agent.attempt) as span: span.set_attribute(attempt, attempt) output run_agent(task, historyhistory) span.set_attribute(output_preview, str(output)[:300]) validation validate_output(output) # 内部会跑 pytest / static check if validation[passed]: return output, attempt 1, True if attempt max_attempts - 1: return output, attempt 1, False feedback build_feedback(validation[errors], output) history.append({ attempt: attempt 1, output: output, errors: validation[errors], }) task f{task}\n\n以下是上一次运行结果和失败原因请修复问题\n{feedback} time.sleep(pow(2, attempt))这个实现的核心是history。每一轮失败后我不是简单地把错误文本追加给 Agent而是连带上一次的完整输出和失败上下文并且要求 Agent 在修复时只输出最小 diff不要重新生成整个项目。这样能避免它把原本正确的代码也改坏。另一个设计细节是validate_output。它不只是一个单元测试脚本还会做“回归覆盖率检查”如果修复后的代码让测试通过了但修改代码的时候把某个原本被测试覆盖的分支删掉了就要再拉一轮信号。这个功能用 pytest-cov 就能做但需要你把覆盖率阈值也作为自愈的验收标准之一。4.4 实测效果与关键观察我在一个内部项目上跑了这套组合任务类型是“根据 issue 描述修复 bug”和“给现有模块补单元测试”。接上可观测性之后最明显的变化不是成功率暴涨而是排查时间大幅下降。以前一个问题要看十几屏日志现在直接在 Langfuse 里按 trace 看Agent 每个步骤的工具输入输出都摆在那里哪个环节开始跑偏一目了然。自愈循环的效果要看任务复杂度。简单 bug 修复通过测试自动解决的概率在加入测试反馈后有明显提升但复杂的跨文件重构任务自愈很容易变成“原地打转”。所以我加了最大尝试次数一般设 3 到 5 次再多就是纯烧钱。还有一个观察自愈能不能生效很大程度取决于验证器的质量。验证器太松Agent 会给出“看起来没问题但实际很脆”的补丁验证器太严又会让 Agent 不断尝试改已经正确的地方。需要花时间打磨那几条关键测试用例。5. 常见问题与排查技巧实录5.1 观测数据太多太杂怎么采样给 Agent 加全量 trace 之后第一个遇到的问题是数据量爆炸。一个 5 步的 Agent 任务可能产生几十个 span每个 span 里都塞了输入输出日志后端很快就撑不住了。我试过几种方案。最有效的是分层采样全量记录“运行级”span也就是 agent.run、agent.plan 这种对于 tool.call 和 llm.generate只记录核心属性和截断后的摘要。真正出问题时再针对失败 trace 开启详细模式把完整输入输出补采回来。这个逻辑可以做成一个开关由告警触发。还有一点不要把所有模型输出都离线存一份。Langfuse 这类工具默认会保存全文但你可以配置保留期长短期数据分开。最后尽量把 trace 的采样率当成一个可调参数放进配置中心不要写死在代码里。不然每次调整都要重新发布服务太痛苦。5.2 Agent 自愈变成死循环怎么办这是大家问得最多的。Agent 发现自己修不好又不敢停下就反复重试最后 token 烧完问题还是没解决。我的经验是死循环的根因不是“Agent 太执着”而是自愈逻辑里缺少停止标准和失败分类。第一必须设置最大尝试次数而且这个次数应该和任务复杂度挂钩不要全局写死。简单语法错误修 2 次就该停复杂重构可以给 5 次。第二单次任务的成本上限也要有一旦累计 token 消耗超过阈值强制终止并把“任务失败”写成最终状态。第三要识别“无进展循环”如果连续两轮产生的 diff 完全一样或者测试失败输出没有变化就说明 Agent 在重复自己这时候应该中止而不是继续。我的做法是给每轮自愈打一个 hash比较相邻尝试的 diff 相似度。相似度超过 90% 就直接判断为没有进展触发 break。这个逻辑用 difflib 几十行就能实现但能帮你省下大笔模型调用费用。5.3 开源方案生产级使用的几个顾虑开源可观测和自愈方案并不是拿回来就能躺平用的。首先是性能开销。OpenTelemetry 的 SDK 和 exporter 会占用一小部分 CPU 和内存如果 Agent 任务并发很高trace 上报还可能导致网络 IO 成为瓶颈。建议使用 BatchSpanProcessor 和独立的上报线程不要和主流程抢时间。其次是存储成本。Langfuse 和 Phoenix 这类工具默认存储所有 trace 全文跑上一阵子磁盘占用会涨得很快。需要提前做存储规划比如对接对象存储或定期归档。数据量大的时候可以把 trace 的 prompt 字段单独拎出来存冷存储查询的时候再动态加载。最后是数据隐私。很多 coding agent 都在处理私有代码用开源自托管方案时要确保 trace 后端部署在可管控的网络环境里并做好 token、密钥、代码片段的脱敏。说实话在这个环节上偷懒比 Agent 写 bug 严重多了。我个人现在的态度是不要迷信“自动修复”可观测性才是地基。开源工具能让你低成本拿到一套完整的数据闭环但最终的修复策略一定要结合自己的业务场景去调。先把 trace 和评估跑顺再慢慢加自愈规则这条路才走得稳。