很多做 Agent 开发的团队都会遇到一种尴尬:skill 写完了,跑通了一个手工测试用例,然后就上线了。可一到真实对话里,问题一个接一个冒出来——Agent 该调用的时候不调用,不该调用的时候乱调用,传参传得牛头不对马嘴,用户只能一遍遍纠正。最可怕的是,这些问题不会出现在你精心准备的那几个测试用例里,它们藏在成百上千条真实对话中间。
这件事的本质,是大多数 skill 缺少一套“体检机制”。我们会对代码做 review,会对接口做回归测试,却很少像检查身体一样,定期检查一套 skill 在真实对话里的调用率、失败率和用户纠正情况。而 skill 恰恰是最需要这种反馈的:它的效果好不好,不取决于你把它写得有多长、多全面,而取决于它放在真实的 Agent 工作流里,能不能被准确调用、稳定执行、有效产出。
skill-doctor 的思路,就是解决这个问题的:不要靠人肉翻日志,让 Agent 自己把最近一段时间(比如 45 天)的对话记录重新过一遍,扮演一个“体检医生”,专门检查这套 skill 写得怎么样,然后产出一份可以执行的诊断报告。读完这篇文章,你会知道怎么整理对话日志,怎么设计 skill 的诊断维度,怎么搭建一个最小可用的 skill-doctor 原型,也会知道怎么防止诊断 Agent 自己胡说八道。
1. 先搞清楚:Agent、Skill 和“写得烂”分别指什么
先说概念边界。Agent 和 Skill 是两个容易混淆的词。
Agent 是一个具备感知、决策和行动能力的 AI 程序,它拿着大模型当“大脑”,在用户的目标驱动下,规划步骤、调用工具、使用记忆,最终完成任务。Skill 则是 Agent 可以调用的一种能力封装:它通常包含一段提示词、可能携带工具参数模板、输入输出约束,甚至是一段可执行逻辑。可以这样理解:Agent 是那个“人”,Skill 是它掌握的“技能”。遇到一个任务时,Agent 需要先判断“这时候该用哪个技能”,然后才能执行。
很多开发者在这一点上就开始犯错了。他们把 Skill 写成了一个大而全的“百科全书”:把所有可能用到的情况、所有边界条件、所有注意事项全部塞进提示词里。结果就是,Skill 描述太泛,Agent 根本判断不出来“现在该不该用它”;Skill 内部逻辑太重,一次调用占掉大量上下文,反而挤压了对话信息的空间;Skill 的触发条件写得含糊,用户随口说一句擦边的话,Agent 就错误地调用了它。
这就是“技能写得很烂”的典型表现。总结起来,可以从三个层面观察:
- 调用面不对:Agent 在该调用某个 skill 时没有调用,或者在不该调用时乱调用。
- 执行不稳定:skill 内部参数抽取失败、工具调用报错、输出格式解析不了。
- 产出无效:用户调用完 skill 之后,还在继续追加纠正指令,甚至直接放弃、反复重试。
这三类问题,在开发阶段极难被发现。原因很简单:测试话术是你自己写的,你天然知道该让 Agent 调用哪个 skill,也知道答案大概长什么样。这相当于给 skill 出了一套开卷考试。而真实对话是闭卷考试,用户不会按照你的假设提问。
传统的 skill review 方式,无非是打开 skill 文件,把提示词重新读一遍,凭感觉判断哪里可能有问题。这种静态审查只能发现语法错误和表述别扭,发现不了“这个 skill 在真实对话里根本没被调用”这种致命问题。你可能需要换个思路:让它到真实的战场里去检测,而不是在会议室里模拟演练。
2. skill-doctor 的核心逻辑:让对话日志变成 skill 的体检样本
skill-doctor 的核心思想,是把“真实对话记录”当作 skill 的体检样本。
假设你的 Agent 已经上线运行了 45 天,这期间产生了大量用户和 Agent 的对话。这些对话是极端宝贵的资源,因为它们记录了 skill 在未经修饰的真实场景下到底表现如何。用户后续的纠正,是天然的反馈标注;Agent 的误调用和漏调用,是天然的 bug 样本;工具调用失败的记录,是天然的稳定性报告。
skill-doctor 要做的事,就是把这段时间的对话记录重新交给一个诊断 Agent,让它逐段检查,对照 skill 的定义来判断刚才那段对话里发生了什么:Agent 该不该调用这个 skill?调用后有没有出错?用户有没有纠正?然后把这些观察汇总成一份结构化的诊断报告。
这个“让 agent 翻 45 天对话给自己体检”的设计,妙在两个地方。
第一,它把测试集从“人工编写的用例”变成了“真实历史对话”。人工用例的作用是验证预期行为,但真实对话的作用是发现意外行为。只有让 skill 面对真实用户花样百出的表达,你才会知道它的触发条件是不是足够鲁棒。
第二,它把评估者从“开发者本人”换成了“另一个角色的 Agent”。开发者对自己的 skill 有天然的感情,也很容易陷入“我当时是这么设计的,所以逻辑没错”的思维惯性。诊断 Agent 没有这种包袱,它只负责看对话里实际发生了什么,然后机械地给出结构化结论。这就相当于你不再自己给自己看病,而是让一个医生拿着你的体检数据做判断。
为什么选择 45 天作为回看窗口?这不是什么玄学,而是覆盖面与时效性的折中。窗口太短,比如 7 天,可能会漏掉一部分低频 skill 的调用记录;窗口太长,比如 90 天,对话场景可能已经漂移,诊断的时效性变差。45 天能够覆盖绝大多数日常型 skill 的多次完整调用,又不至于让数据规模膨胀到难以处理。如果你的 Agent 上线时间还不满 45 天,那就以实际上线天数为准,关键是样本要足够多。
还需要强调一点:这个“诊断 Agent”和“业务 Agent”最好是两个上下文相互隔离的流程。不要在用户正在参与的对话里让它做体检,而是离线后把历史记录抽出来,批量喂给诊断流程。这样既不会拖慢线上响应,也避免诊断逻辑污染用户的真实会话。
3. 体检要量化哪些指标:诊断维度设计
既然要给 skill 体检,就不能只拿一段提示词让大模型“点评一下”。没有量化指标,诊断就变成了玄学。skill-doctor 的产出应该是结构化的指标项,最好能落到数字上。
在看一组具体指标前,先建立一个判断:一套合格的 skill,至少要满足“入口准、执行稳、产出有效”三个条件。所有指标都应该围绕这三个条件展开。下面是我建议的指标项。
| 指标 | 含义 | 健康标准 | 出现问题的信号 |
|---|---|---|---|
| 调用次数 | 统计周期内该 skill 被触发执行的总次数 | 与业务预期基本一致 | 很多次数/极少次数都值得关注 |
| 漏调用次数 | 根据语义判断本应调用却未调用 | 趋近于 0 | Agent 把 skill 内容当成普通聊天复述 |
| 误调用次数 | 不该调用时却调用了 | 趋近于 0 | 用户问天气,Agent 调用订单查询 skill |
| 参数抽取失败率 | skill 需要从对话中抽取参数,抽取失败的占比 | 越低越好,最好低于 5% | 用户说了明确的需求,Agent 却没提取到 |
| 工具调用失败率 | skill 内部调用了工具,工具报错或返回异常的比例 | 越低越好 | 频繁出现超时、鉴权失败、字段缺失 |
| 用户纠正次数 | 调用后用户在后续消息中纠正 Agent | 绝大多数调用后没有纠正 | 单次调用后跟着 2 条以上纠偏消息 |
| 输出格式达标率 | 输出是否符合 skill 定义的 JSON/文本格式 | 接近 100% | 下游处理 JSON 解析失败 |
| 平均上下文占用 | 该 skill 被调用时平均消耗多少 token | 能用更少的 token 解决问题时不应超大 | skill 提示词长到挤占对话窗口 |
这 8 个指标并不需要全部一次性实现。你可以根据自己的业务情况,先从 3 个最关键的入手:调用次数、参数抽取失败率、用户纠正次数。这三个指标最容易从对话日志中统计,而且能直接暴露最严重的 skill 问题。
只看指标还不够。单看“客户端找不到路由,频繁调用订单接口”并不能说明是 skill 写坏了,还是产品只暴露了这一个入口。所以,每一个指标异常背后,都要让诊断 Agent 给出对应的对话片段 ID,方便开发者回看原始上下文。指标是从数据里来的,但最终判断仍然需要结合真实语境。
在设计诊断维度的时候,还有一个容易忽略的角度:skill 的命名和描述质量。诊断 Agent 可以特别关注那些“描述与内容不一致”的情况。比如,一个 skill 描述里写着“用于回答用户关于退货政策的疑问”,但实际内容是用来处理售后审批的,这就容易造成误调用。这一类问题通常不会在编译或语法层面暴露,只有在大量真实对话中才会显现。
4. 准备体检数据:对话日志怎么组织
skill-doctor 能不能跑起来,很大程度上取决于你手头有没有结构化的对话日志。如果你的 Agent 是在没有日志的情况下跑了一个多月,那第一步不是写诊断脚本,而是先补日志。
一份能用于体检的对话日志,至少需要包含下面几个字段。
{ "conversation_id": "conv_20250115_0001", "turn_id": 23, "timestamp": "2025-01-15T14:23:11Z", "role": "assistant", "content": "已为您查询到订单状态,目前正在配送中。", "called_skill": "order_query", "skill_version": "v3.2.0", "skill_input": { "order_id": "SO-TS-882" }, "skill_output": { "status": "shipping", "eta": "2025-01-16T18:00:00Z" }, "status": "ok", "user_correction": false }实际上,你并不需要强制让日志完整记录所有这些字段才能开始。如果你只有一个对话记录,也可以通过规则大致补全。比如,已知 Agent 输出“已为您查询到订单状态”,就可以反推它大概率调用了订单查询 skill。但这种反推属于事后猜测,准确率有限。更推荐的做法是,在 Agent 的执行框架中显式埋点:每次调用 skill 之前写一条日志,记录被调用的 skill 名和传入参数;调用结束后再写一条日志,记录返回结果和执行状态。
如果用的是主流 Agent 框架,通常都有 trace 或中间产物机制。Skill 的调用记录、LLM 的 token 消耗、工具链的执行轨迹,一般都能从 trace 里提取。如果没有现成的链路追踪能力,至少要保证把“用户输入、Agent 输出、被调用的 skill、skill 的输入输出、执行状态”这五样东西写入日志。
另外,因为这里的原始数据是用户隐私的高风险区,所以在把日志送入诊断 Agent 之前,必须做脱敏处理。手机号、姓名、订单号、地址等字段要用占位符替换。这个步骤不是建议,而是底线。一个能看到的做法是:在写入日志时直接只记录脱敏后的简写,不要等到体检时才临时清洗。
45 天的对话日志可能非常大。如果全部塞给大模型做体检,上下文放不下,成本也扛不住。因此通常要先做一轮数据预处理:按 conversation_id 分组、过滤掉无 skill 调用的纯聊天会话、再按 skill 名聚合统计基础指标。最后,对于每份需要细看的对话片段,截断到 skill 调用前后各 2 到 3 轮,形成“诊断窗口”。这样既保留了上下文,又不会把整个长对话全部吞进模型。
5. skill-doctor 最小实现:Python 原型
这一节我们动手写一个最小可用的 skill-doctor。为了方便理解,我把整个流程分成三步:预处理日志、构造诊断提示词、调用大模型生成报告。
假设你已经把对话日志整理成一个 JSONL 文件,每行是一条消息记录,字段结构沿用上一节的 schema。下面的 Python 脚本会读取这个文件,按 skill 聚合统计基础指标。
# 文件路径:skill_doctor/prepare_replay.py import json from collections import defaultdict from datetime import datetime def load_conversations(path: str): """读取 JSONL 格式的对话日志,按 conversation_id 分组。""" conversations = defaultdict(list) with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue record = json.loads(line) conversations[record["conversation_id"]].append(record) return conversations def build_skill_replay(conversations, skill_name: str, window: int = 3): """提取某个 skill 的所有调用片段,每条包含调用前后 window 轮上下文。""" replay_items = [] for conv_id, turns in conversations.items(): turns = sorted(turns, key=lambda x: x["turn_id"]) for idx, turn in enumerate(turns): if turn.get("called_skill") != skill_name: continue start = max(0, idx - window) end = min(len(turns), idx + window + 1) replay_items.append({ "conversation_id": conv_id, "turn_id": turn["turn_id"], "context": turns[start:end], }) return replay_items def compute_metrics(replay_items): """统计最基础的三个指标:调用次数、成功次数、用户纠正相关信号。""" total = len(replay_items) success = sum(1 for item in replay_items if any("status" in t and t.get("status") == "ok" for t in item["context"])) user_followup_correction = 0 for item in replay_items: context = item["context"] assistant_idx = None for i, t in enumerate(context): if t.get("role") == "assistant" and t.get("called_skill"): assistant_idx = i break if assistant_idx is not None: for t in context[assistant_idx + 1:]: if t.get("role") == "user": user_followup_correction += 1 return { "total_calls": total, "success_calls": success, "user_followup_correction": user_followup_correction, } if __name__ == "__main__": convs = load_conversations("replay_logs.jsonl") items = build_skill_replay(convs, skill_name="order_query") metrics = compute_metrics(items) print(json.dumps(metrics, ensure_ascii=False, indent=2))这段代码做了三件事:按 conversation_id 分组对话;把某个 skill 的每次调用连同前后几轮上下文切出来;统计调用次数、成功次数以及用户后续纠正的条数。这里的“用户后续纠正”用了一个比较粗的信号:只要在 skill 调用后还有用户消息,就计数一次。真实场景里你可能需要更精细的语义判断,但作为最小原型,这个信号足以及时暴露出很多问题。
接下来是核心环节:设计诊断 Agent 的提示词。这里不建议直接用一句话“请评价这个 skill 写得好不好”,因为这种指令会让大模型给出泛泛而谈的表扬或批评。更好的做法是要求它忠实于给定的对话片段,一个点一个点地检查,只输出结构化 JSON。
# 文件路径:skill_doctor/diagnose.py diagnose_prompt_template = """你是一位 skill-doctor,负责审查 AI Agent 的技能定义。 你会收到: 1. 一个 skill 的定义,包括名称、描述、触发条件和提示词内容。 2. 若干段真实对话片段,每段都来自用户与该 Agent 的历史交互。 你的任务: - 逐段检查对话中 Agent 是否在合适的时机调用了该 skill。 - 如果调用了,检查输入参数是否从用户消息中正确抽取。 - 检查 skill 调用后的回复是否满足用户意图,用户是否继续纠正。 - 不要编写虚构案例,只能基于给定的对话片段作出判断。 输出格式必须是 JSON,不要输出任何解释性文字。 { "skill_name": "order_query", "findings": [ { "type": "missed_call|wrong_call|param_error|user_correction|ok", "conversation_id": "对话ID", "turn_id": "回合ID", "evidence": "从对话摘录的原文证据", "suggestion": "针对该问题的改进建议" } ], "summary": { "total_checked": 0, "issue_count": 0, "health_level": "good|warning|bad" } } """ def build_messages(skill_definition: dict, replay_items: list): user_content = "skill 定义如下:\n" + json.dumps(skill_definition, ensure_ascii=False, indent=2) user_content += "\n\n对话片段如下:\n" + json.dumps(replay_items, ensure_ascii=False, indent=2) return [ {"role": "system", "content": diagnose_prompt_template}, {"role": "user", "content": user_content}, ] def call_llm(messages: list) -> str: # 这里替换成你实际使用的 LLM SDK # 例如 OpenAI / Anthropic / 国内模型,只要支持 messages 格式即可 # response = openai.ChatCompletion.create(model="...", messages=messages, response_format={"type": "json_object"}) # return response.choices[0].message.content raise NotImplementedError("请在真实环境中接入你自己的 LLM 调用") def run_diagnosis(skill_definition, replay_items, max_items: int = 50): sampled = replay_items[:max_items] messages = build_messages(skill_definition, sampled) raw = call_llm(messages) return json.loads(raw)这段代码把诊断 Agent 的定义封装成一个模板。需要注意,我刻意在系统提示词里加了“不要编写虚构案例,只能基于给定的对话片段作出判断”。这是因为大模型在没有约束的情况下,很容易为了凑结构而补充一些并不存在的“用户投诉”,这是诊断场景里最要命的幻觉。
最后,你需要把它串起来,写成一个入口脚本。由于真实的大模型调用和本地框架强相关,这里只给出一个伪代码级别的入口函数,读者可以把它替换成自己的模型调用。
# 文件路径:skill_doctor/main.py import json from prepare_replay import load_conversations, build_skill_replay from diagnose import run_diagnosis if __name__ == "__main__": skill_definition = { "name": "order_query", "description": "根据用户提供的订单号查询订单状态", "trigger_condition": "用户明确询问订单状态、物流进度或配送时间", "input_schema": { "order_id": "string, 用户提供的订单号" }, "prompt": "你是订单查询助手,先确认用户订单号,再调用查询接口..." } conversations = load_conversations("replay_logs.jsonl") replay_items = build_skill_replay(conversations, skill_name="order_query") report = run_diagnosis(skill_definition, replay_items, max_items=30) with open("diagnosis_report.json", "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=2) print("诊断完成,报告已写入 diagnosis_report.json")到这里,一个最小可用的 skill-doctor 原型就跑通了。它的核心结构并不复杂:读日志,切样本,拼提示词,请求模型,存报告。真正决定它有没有用的,是你是否愿意在上线前和上线后都坚持跑这个流程。
6. 跑一次体检:从一份 45 天样本到诊断报告
假设你已经准备好了 45 天的对话日志,现在按照上一节的入口脚本运行一次。预期你会得到一份类似下面的报告。
{ "skill_name": "order_query", "findings": [ { "type": "missed_call", "conversation_id": "conv_20250201_0088", "turn_id": 12, "evidence": "用户:我要退掉昨天买的那个手机。Agent:请提供订单号。用户:订单号是 SO-7788。Agent:已记录您的退货申请,请等待审核。", "suggestion": "用户已经提供订单号,且请求实际是退货,但 Agent 没有调用 order_query,导致没有核验订单信息直接进入退货流程。建议把触发条件从'查询订单状态'扩大到'涉及订单信息的任何操作',或在退货流程前强制调用订单校验。" }, { "type": "param_error", "conversation_id": "conv_20250120_0032", "turn_id": 44, "evidence": "用户:我上周买的那双42码的鞋到了没?Agent:请提供订单号。", "suggestion": "用户提供了商品特征和时间描述,但订单号并不在原文中。skill 的参数抽取逻辑过于依赖显式订单号,缺少从用户描述中关联历史订单的能力。可考虑在调用前增加一轮订单候选确认。" }, { "type": "wrong_call", "conversation_id": "conv_20250210_0121", "turn_id": 7, "evidence": "用户:怎么客服电话打不通?Agent:已为您查询订单状态,您的包裹正在派送中。", "suggestion": "用户咨询客服联系方式,并未询问订单状态,Agent 却调用了 order_query。触发条件中的'配送时间'相关描述被过度泛化,需要收紧。" } ], "summary": { "total_checked": 30, "issue_count": 10, "health_level": "warning" } }拿到报告后,不要被“warning”这个标签牵着走。先把 findings 里每一项都点开,回到对应 conversation_id 去读原始上下文。看一次真实的对话,胜过读十句模型给出的“改进建议”。
这里有一个解读报告的技巧:把 findings 按照 type 分组统计。如果结构里大量出现 missed_call,说明 skill 的触发条件过窄;如果大量出现 wrong_call,说明触发条件过宽;如果大量出现 param_error,说明输入 schema 的设计和用户真实表达脱节;如果大量出现 user_correction,说明 skill 的执行结果本身没有满足用户预期。
这三种情况对应的治疗手段完全不一样。触发条件过窄,你要扩充描述;触发条件过宽,你要加约束、加负面示例;参数抽取脱节,你要重新设计输入 schema,甚至给 skill 增加一个“追问环节”。如果你只是笼统地看到“有很多问题”就开始改提示词,很可能改完以后问题数量不减反增。
这份报告本身不一定要做到百分之百准确。它的价值在于:帮你把 45 天里肉眼看不到的问题压缩成一个可审查的清单。毕竟,让你自己去翻几百上千条对话,你根本坚持不下来,而一个诊断 Agent 可以。
7. 常见问题与排查方法
skill-doctor 在落地过程中会遇到一些高频问题。我把它们整理成一张排查表,方便你在实践中直接对照。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 日志里没有 called_skill 字段 | 埋点缺失,未记录 skill 调用 | 检查 Agent 执行框架的日志输出 | 在 skill 调用入口和出口各加一行结构化日志 |
| 诊断报告里出现明显不存在的“用户投诉” | 大模型幻觉,自己补充了对话内容 | 对照报告的 conversation_id 和原文 | 在提示词中强调“只允许使用给定的对话片段”,并开启 JSON 输出约束 |
| 45 天数据量太大,模型上下文放不下 | 未做裁剪,直接把全量对话喂给模型 | 查看 request 的 token 消耗 | 按 skill 聚合抽样,按调用前后各 3 轮切分窗口 |
| 某个 skill 调用次数为 0,无法诊断 | skill 本身就很少被触发 | 检查这个 skill 是否已经过时 | 考虑是否下线,或升级触发条件观察下个周期 |
| 诊断结果不稳定,同一样本每次结论不同 | 采样随机性,或模型 temperature 过高 | 固定随机种子,降低 temperature | 对诊断流程设置 temperature=0,或跑 3 次取多数投票 |
| 用户隐私字段被送入模型 | 没做脱敏,直接把原始对话送入诊断 | 检查日志清洗流程 | 在写入日志时只保留脱敏后的简写,诊断前再做一次脱敏校验 |
| 诊断报告泛泛而谈,没有针对具体问题 | 提示词缺少结构化输出约束 | 检查返回结果是否都是 JSON 格式 | 把输出格式写死在 prompt 中,并增加“必须引用原文证据”要求 |
里面最隐蔽的问题是“调用次数为 0”。当你辛辛苦苦搞了一套诊断系统,却发现某个 skill 一次都没被调用过,你可能会觉得这个 skill 很健康。但真相往往相反:这个 skill 可能已经名存实亡,Agent 在真实场景里完全没有识别到该用它的时机。这种情况下,应该去翻一翻用户对话,看看是否存在“用户本来需要这个能力,但 Agent 却做了别的处理”的漏调用案例。
还有一个需要警惕的坑:不要试图用同一个 prompt 检查所有 skill。订单查询类 skill 和内容生成类 skill,它们的诊断侧重点完全不同。前者更看重参数抽取和调用时机,后者更看重输出风格和事实准确性。如果你把所有 skill 都丢进同一个诊断模板,报告的质量会明显下降。实际做法是,根据 skill 的类型预设几套诊断提示词模板,或者让诊断 Agent 先读取 skill 定义,再自动决定检查重点。
8. 把 skill 体检变成工程规范:最佳实践
skill-doctor 做出来之后,不应该只是心血来潮跑一次。把它嵌入团队的学习与发布流程,价值会放大很多。
第一个实践建议:把体检和发布绑定。每次修改 skill 版本时,都强制回放最近一段时间的对话日志,把“体检报告”和 skill 的新版本一起提交。如果报告中新增了严重问题,就说明这次修改有问题,要么回滚,要么继续修。这其实就相当于给 skill 增加了一道“回放测试门禁”。因为你永远不可能靠几个手工用例覆盖真实对话的多样性,而历史对话就是最接近真实分布的数据集。
第二个建议:给每个 skill 建一份“病历档案”。每一个版本的体检报告都保留下来,形成历史的健康曲线。这样你才能回答一个关键问题:上一次我改完提示词以后,漏调用率到底是降了还是升了?很多情况下,开发者凭记忆判断“好像改完之后更好了”,但翻出病历就会发现,某个问题反而恶化了。
第三个实践建议:不要让自动报告直接代替人修改 skill。skill-doctor 的定位是体检医生,它负责发现问题,但治疗方案的最终决定权必须掌握在开发者和业务负责人手里。原因很简单,大模型给出的“优化建议”有时候看起来有理有据,但它不理解你的业务约束、不掌握历史需求,很容易把 skill 改得更合适于历史对话,却丢失了对未来新需求的处理能力。因此,报告里的 suggestion 只能作为参考,真正改代码的还得是人。
第四个建议:体检频率不一定要严格等到 45 天。如果对话量很大,可以拆成每周跑一次“周体检”,每月跑一次“月体检”。对于刚刚上线的 skill,前两周最好每天跑一次增量体检,因为新 skill 的触发条件最容易在这一时期暴露出问题,越早发现,修起来越便宜。
第五个建议:把诊断 Agent 的每一次判断也当作数据收集。这个诊断 Agent 本身也是一个 Agent,它的判断质量需要被评估。你可以定期抽查报告,看它给出的 missed_call 判断到底准不准,把误判反馈到诊断 prompt 里。这样,skill-doctor 会随着使用次数的增加而越来越像这个团队自己的“资深审查员”。
9. 总结:skill 不是写完的,而是“养”出来的
这篇文章真正想说明白的一件事,就是 skill 的质量不能靠写的时候自我感觉良好,也不能靠几组手工测试用例来保证。它需要在真实对话中被反复检验,逐步调整,这个过程就是“养” skill。skill-doctor 的存在意义,是把这个养的过程从人肉翻日志变成有数据、有指标、有报告的结构化流程。
你现在已经知道了核心思路:对话日志是 skill 最好的测试集,诊断 Agent 是人类开发者的体检医生,量化指标是连接原始日志与改进动作的桥梁。接下来你可以先从一个小范围开始,比如只挑一个最核心的 skill,把它最近 30 天的对话日志抽出来,按本文的 Python 原型跑一次,看看它会暴露多少你完全没想到的问题。
等你跑完第一轮就会发现,真正有价值的不是那份报告本身,而是报告把你引向的那几段原始对话。那些对话里藏着的,才是 skill 优化最重要的线索。别指望一份报告能直接帮你改好技能,但它能帮你把目光从“我该怎么写提示词”挪到“用户实际上在怎么和 Agent 互动”上。这个挪动,比任何技巧都管用。