news 2026/8/27 5:51:38

Agent 技能过百后命中率下降?六个维度系统优化 Skill 调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 技能过百后命中率下降?六个维度系统优化 Skill 调用

Skill 数量过百之后,真正的问题不是“有没有 Skill”,而是“Agent 到底能不能在正确的时候选中正确的 Skill”。很多团队在初期只有十几个 Skill 时,靠提示词描述、名字前缀、少量示例就能跑通;但当 Skill 数量超过 100 个,模型的选择空间变大,描述冲突变多,上下文被无关内容挤占,调用命中率会肉眼可见地下降。这篇文章围绕 Agent 调用 Skill 的命中率问题,从选型、命名、描述、召回、评估、监控六个维度展开,适合正在做 Agent 应用开发、需要管理大量 Skill 的工程师阅读。

在实际项目里,Skill 数量过百以后,调用命中率下降通常不是某个大模型能力不够,而是 Skill 管理方式还停留在小规模阶段。小规模时,开发者可以在 System Prompt 里把所有 Skill 列一遍,模型基本能凭名字匹配;规模上到 100 以后,System Prompt 长度受限,模型注意力被稀释,Skill 之间的语义差异变小,重名和模糊描述开始互相干扰。这篇文章会先解释 Skill 调用链路里最容易出问题的环节,再给出环境搭建、Skill 规范设计、召回优化、评估方法和监控手段,最后提供一个可以落地的排查清单。

1. 先理解 Skill 调用命中率为什么会在过百后下降

1.1 Skill、Tool 和 Agent 之间的关系要先对齐

Skill 在不同 Agent 框架里的叫法不完全一致。有的框架叫 Tool,有的叫 Action,有的叫 Function Calling,还有的框架把一组相关 Tool 的集合称为 Skill。为了避免后续讨论出现歧义,这里统一约定:

  • Tool 是 Agent 可以调用的最小能力单元,通常对应一个函数、一个 API 或一段可执行脚本。
  • Skill 是由多个 Tool、提示词模板、参数规则和调用说明组成的能力包,描述的是“完成某类任务”的整体能力。
  • Agent 是负责理解用户请求、规划步骤、调用 Skill、处理返回结果的执行体。

在 Claude Code、Codex、自研 Agent 框架等场景里,Skill 通常以目录或配置文件形式存在。一个 Skill 目录下可能有SKILL.md、示例文件、脚本、参数定义和校验规则。模型在收到用户请求后,会先判断当前任务是否需要某个 Skill,再从 Skill 列表里选中它,最后执行 Skill 内部逻辑。

当 Skill 数量较少时,Agent 可以依赖“名字 + 单行描述”完成匹配。当数量超过 100 个,模型需要处理的候选变多,描述语义重叠变多,选择错误的概率随之上升。这个阶段开始,命中率问题本质上是信息检索和决策链路的工程问题,而不只是提示词问题。

1.2 命中率下降的四个直接原因

从实际表现看,Skill 过百后的命中率下降可以归纳为四个原因:

第一,候选集过大导致注意力稀释。Agent 的上下文窗口虽然越来越大,但把 100 个 Skill 的完整描述全部塞进 System Prompt 后,真正决定调用的关键描述会被淹没。模型在处理用户请求时,需要从大量文本里找到最相关的那一段,信息密度越低,选错概率越高。

第二,Skill 描述语义重叠。很多 Skill 在定义时使用了相近词汇,比如“生成周报”“生成日报”“生成项目报告”,三个 Skill 的名字和描述高度相似。模型无法从短文本里准确区分边界,出现“本想调用日报 Skill,结果选了周报 Skill”的情况。

第三,命名和描述与用户口语表达不一致。Skill 描述写的是开发视角的语言,用户请求则是自然语言。比如 Skill 名叫parse_resume,描述是“解析简历文件并提取字段”,但用户说“帮我看下这份 PDF 里有哪些候选人信息”,模型需要把口语意图映射到 Skill 语义,映射失败时命中率自然下降。

第四,没有召回阶段,直接让模型做全量选择。小规模时可以直接让模型在所有 Skill 里选择;过百后应当先通过关键词、向量或规则做一次召回,把候选集从 100 个缩小到 5 到 10 个,再让模型做最终决策。这一步缺失,命中率会明显受噪声影响。

这四个原因在真实项目中往往是叠加的。只优化其中一个,命中率提升有限。下文会从工程角度给出系统化处理方式。

1.3 命中率不能只看“是否选对了 Skill”

评估 Skill 调用命中率时,还需要区分几个层次:

  • 意图识别是否准确:用户请求是否真的需要调用 Skill。如果不需要调用却调了,属于误调用。
  • Skill 选择是否准确:需要调用时,是否选中了正确的 Skill。
  • Skill 内部执行是否成功:选中正确 Skill 后,参数、权限、脚本是否顺利执行。
  • 返回结果是否满足用户需求:执行成功不代表结果正确。

本文讨论的“调用命中率”主要指第二层,即从多个 Skill 中选出正确 Skill 的比例。但监控时不能只统计这一层,因为第一层误调用和第三层执行失败也会表现为“好像没命中”。建立完整评估链路后,才能定位问题到底出在召回、选择还是执行阶段。

2. 环境准备:先搭一个可统计命中率的 Skill 管理工程

2.1 建议的技术栈和目录结构

如果你的 Agent 项目已经存在,可以直接在现有项目上增加 Skill 管理和评估模块。如果从零开始验证,推荐使用一个相对独立的最小工程。下面是一个参考目录结构,适用于大多数支持 Skill 的 Agent 框架:

agent-skill-lab/ ├── skills/ │ ├── weekly-report/ │ │ ├── SKILL.md │ │ ├── scripts/ │ │ │ └── generate_report.py │ │ └── examples/ │ │ └── input_sample.json │ ├── daily-report/ │ │ ├── SKILL.md │ │ └── scripts/ │ └── resume-parser/ │ ├── SKILL.md │ └── scripts/ ├── config/ │ ├── skill_registry.yaml │ └── eval_config.yaml ├── evaluator/ │ ├── build_eval_set.py │ ├── run_eval.py │ └── analyze_result.py ├── retriever/ │ ├── keyword_retriever.py │ ├── vector_retriever.py │ └── hybrid_retriever.py ├── logs/ │ └── skill_call.log └── requirements.txt

这个结构把 Skill 定义、召回模块、评估脚本和日志分开。skills目录放各 Skill 的定义文件和资源,retriever目录放召回逻辑,evaluator目录放评估数据集和评估脚本,logs目录用于后续监控分析。

在实际项目中,Skill 目录可能由多个团队共同维护,因此配置管理比目录结构更重要。skill_registry.yaml建议作为统一注册入口,避免直接扫描目录导致命名混乱。

2.2 定义统一的 Skill 元数据格式

无论你使用什么 Agent 框架,都应该为每个 Skill 定义一组结构化元数据。元数据越统一,后续召回和评估越容易。下面是一份参考 YAML 配置:

- name: weekly-report-generator display_name: 周报生成器 version: 1.2.0 description: 根据本周工作记录生成结构化周报,支持按项目、按时间范围过滤,输出 Markdown 或 Word 格式。 category: report tags: - 周报 - 工作汇报 - 项目管理 trigger_examples: - "把本周的工作记录整理成周报" - "帮我生成这周的周报" - "整理一下项目 A 的周度进展" inputs: - name: start_date type: string required: false description: 开始日期,格式 YYYY-MM-DD - name: project type: string required: false description: 项目名称 owner: team-report visibility: public enabled: true eval_set: - input: "帮我生成这周的周报" expected_skill: weekly-report-generator

这份配置的含义是:每个 Skill 除了描述,还包含分类、标签、触发示例、输入参数、负责人、可见性和评估用例。其中trigger_examples对召唤召回和评估特别重要,它提供了用户口语表达与 Skill 语义之间的映射桥梁。

eval_set里的用例可以直接用于后续命中率评估。建议每个 Skill 至少配置 5 条真实用户表达,条件允许时配置 10 条以上。评估用例如果只依赖开发者自己编,容易和实际用户表达脱节;更好做法是从日志里抽取历史用户真实请求。

2.3 环境依赖与版本确认

在开始写召回和评估代码之前,先确认依赖环境。不同 Agent 框架对 Skill 的支持方式不同,但多数会用到 Python 生态中的以下组件:

组件用途示例
Agent 框架Agent 调度和 Skill 执行Claude Code、Codex、自研框架
向量数据库Skill 描述向量召回Chroma、Milvus、FAISS
Embedding 模型将 Skill 描述和用户请求编码为向量text-embedding-3-small、bge-m3
编排框架定义 Agent、Tool、Skill 生命周期LangGraph、CrewAI、自研
日志和监控记录每次调用和选择结果ClickHouse、Elasticsearch、文件

这里要特别注意:原始材料没有指定某个固定技术栈。上面表格列的是常见选型,实际项目落地前要先确认自己的 Agent 框架是否支持这些组件,以及版本是否兼容。如果只是验证 Skill 召回思路,可以先不引入向量数据库,用关键字召回加规则召回就能跑通第一版。

下面是一个最小requirements.txt示例:

openai>=1.30.0 chromadb>=0.5.0 pydantic>=2.0.0 pyyaml>=6.0 pandas>=2.0.0 scikit-learn>=1.3.0

注意:版本号只是示例,实际安装时要以官方当前稳定版本为准,避免因为 API 变更导致代码失效。

3. 让 Skill 定义本身先“可选对”:命名、描述和触发示例规范

3.1 命名规范:从“开发视角”切到“用户视角”

Skill 数量过百后,命名直接影响候选召回和模型判断。命名不能只看开发者是否容易理解,还要看用户请求是否容易映射到这个名字上。

推荐命名规则:

  • 使用动词开头,表达能力含义,例如generate_weekly_reportparse_resumesearch_employee_info
  • 避免只使用技术名词,例如ReportUtilParserJob这类命名对模型没有任何语义提示。
  • 长度控制在 3 到 6 个单词之间。过长会稀释注意力,过短则丢失语义。
  • 同一分类下的 Skill 前缀保持一致,例如report_weeklyreport_dailyreport_project,这样召回阶段可以按前缀快速过滤。

错误示例:

Skill 名问题推荐命名
handle_data语义太泛,无法判断是什么能力clean_csv_data
export_v2_final包含版本号和冗余后缀,表达不清晰export_order_excel
API_Utils技术视角命名,用户请求无法映射query_order_status

3.2 描述规范:写“做什么、什么时候用、不做什么”

Skill 描述是模型选择和召回的关键信号。描述写得太短,模型缺少判断依据;写得太长,上下文被无关内容占满。建议每个 Skill 的描述控制在 3 到 5 句话,覆盖三个维度:

  • 做什么:明确 Skill 的执行结果和输出格式。
  • 什么时候用:说明该 Skill 适用哪些用户意图。
  • 不做什么:说明边界,避免和相邻 Skill 混淆。

示例:

description: 根据本周工作记录生成结构化周报,支持按项目、按时间范围过滤,输出 Markdown 或 Word 格式。 使用场景:当用户要求整理周报、周度汇报、工作周总结时使用。 不适用场景:如果用户只需要查看工作日志,不需要生成汇总文档,请使用 search_work_log。

这样写的好处是让模型在模糊请求下能通过“不适用场景”排除错误候选。类似的 Skill 越多,越要写清边界。

3.3 触发示例:让用户口语表达与 Skill 语义对齐

描述解决的是“Skill 想表达什么”,触发示例解决的是“用户实际会怎么说”。建议为每个 Skill 配置 5 到 20 条触发示例,覆盖常见表达、同义表达和边界表达。

例如weekly-report-generator

trigger_examples: - "把本周的工作记录整理成周报" - "帮我生成这周的周报" - "整理一下项目 A 的周度进展" - "周报还没写,帮我根据日志生成一份" - "我要向上级汇报本周工作,出个周报"

这些示例不仅用于提示词或召回索引,还可以当作评估集的种子数据。后续做命中率评估时,可以把每条触发示例变成一条测试用例,统计模型或召回模块的命中情况。

这里有一个容易被忽略的点:触发示例不能只写“理想表达”,还要写用户常见的模糊表达和错误表达。例如“我要总结一下工作”可能既会被归类到周报 Skill,也会被归类到日志查询 Skill。把这种边界表达写进示例,模型才能学会判断。

3.4 分类和标签:给召回阶段提供第一层过滤条件

当 Skill 数量过百,让模型在全部 Skill 里做最终选择是不现实的。合理做法是先用分类和标签缩小候选集,再让模型在小集合里决策。

分类建议从业务维度划分,例如:

  • report:报告生成类
  • data_query:数据查询类
  • document:文档处理类
  • code_execution:代码执行类
  • communication:消息通知类
  • system_admin:系统管理类

标签可以更细,例如“周报”“日报”“简历解析”“SQL 查询”“文件转换”。标签的作用是让召回模块可以按标签过滤,同时让模型在候选集里更容易理解每个 Skill 的差异。

skill_registry.yaml中,分类和标签应该与描述保持一致。如果分类写report,描述却写“查询员工信息”,那么召回和模型判断会产生矛盾。

4. 召回层设计:从“全量选择”变成“先召回再选择”

4.1 为什么必须加召回层

当 Skill 数量只有 20 个时,把全部 Skill 列表交给模型选择不会有太大问题。当数量过百,上下文里的 Skill 描述会占用大量 token,同时无关 Skill 会对选择产生噪声干扰。召回层的目的是把候选集从 100 个缩小到 5 到 10 个,减少模型决策负担,同时保留真正相关的选项。

召回层需要平衡召回率和精确率。召回率低,正确 Skill 被过滤掉,模型无论如何都选不中;精确率低,候选集里全是噪声,模型决策容易出错。实际项目中通常采用混合召回,兼顾关键词、向量和规则。

4.2 关键字召回:快速、可解释、适合精确词匹配

关键字召回的核心思路是对用户请求和 Skill 元数据做词法匹配。优点是没有额外模型依赖,速度快,结果可解释;缺点是无法处理同义表达和上下文歧义。

最小实现可以基于 TF-IDF 或 BM25。下面是一个基于scikit-learn的简单示例:

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity skill_docs = [ "周报 生成 工作日志 汇报", "日报 生成 每日工作记录", "简历 解析 提取 候选人 字段", ] user_query = "帮我根据日志整理本周周报" vectorizer = TfidfVectorizer(token_pattern=r"\b\w+\b") vectors = vectorizer.fit_transform(skill_docs + [user_query]) query_vector = vectors[-1] skill_vectors = vectors[:-1] scores = cosine_similarity(query_vector, skill_vectors).flatten() top_indices = scores.argsort()[-3:][::-1] for idx in top_indices: print(f"Score: {scores[idx]:.4f}, Skill: {skill_docs[idx]}")

这个示例说明关键字召回的逻辑:先把每个 Skill 的索引文本拼成一段,再用 TF-IDF 计算用户请求与各 Skill 的相似度,最后取 Top N。生产环境建议直接使用 Elasticsearch 的 BM25 或专门的关键词检索引擎。

4.3 向量召回:处理同义表达和跨语言表达

向量召回使用 Embedding 模型将 Skill 描述和用户请求编码成向量,再计算余弦相似度。它能处理“周报”和“工作汇报”“weekly report”等不同表达之间的语义关联,解决关键字召回无法覆盖的同义问题。

下面是一个基于 Chroma 的最小示例:

import chromadb from chromadb.utils import embedding_functions client = chromadb.PersistentClient(path="./chroma_db") embedding_fn = embedding_functions.DefaultEmbeddingFunction() collection = client.get_or_create_collection( name="skills", embedding_function=embedding_fn, ) # 添加 Skill 索引 collection.upsert( ids=["weekly-report", "daily-report", "resume-parser"], documents=[ "根据工作日志生成结构化周报,适合周度汇报场景", "根据每日工作记录生成日报,适合日度汇报场景", "解析简历文件并提取候选人关键字段,适合招聘筛选场景", ], metadatas=[ {"category": "report", "name": "weekly-report-generator"}, {"category": "report", "name": "daily-report-generator"}, {"category": "document", "name": "resume-parser"}, ], ) user_query = "帮我写一份这周的汇报材料" results = collection.query( query_texts=[user_query], n_results=2, ) for doc, meta in zip(results["documents"][0], results["metadatas"][0]): print(meta["name"], doc)

这里使用collection.query返回最相似的 Skill。生产环境通常使用更专业的向量数据库,如 Milvus、Qdrant、Elasticsearch 的向量检索能力。向量召回的优势是语义泛化,但也需要控制索引内容质量,避免把相似描述都塞进索引导致结果区分度下降。

4.4 混合召回:兼顾精确匹配和语义相似

混合召回的基本思路是:先通过规则或关键字召回获取第一候选集,再通过向量召回获取第二候选集,合并后去重,保留 Top K 给模型选择。不同召回结果的分数需要归一化,避免某个召回源垄断候选集。

一个简单实现:

def hybrid_recall(user_query, top_k=8): keyword_candidates = keyword_retrieve(user_query, top_k=top_k) vector_candidates = vector_retrieve(user_query, top_k=top_k) merged = {} for skill_name, score in keyword_candidates: merged[skill_name] = max(merged.get(skill_name, 0), score) for skill_name, score in vector_candidates: merged[skill_name] = max(merged.get(skill_name, 0), score) sorted_skills = sorted(merged.items(), key=lambda x: x[1], reverse=True) return sorted_skills[:top_k]

生产实现时还需要考虑:

  • 规则召回:某些用户请求带有明确指令词,例如“导出”“解析”“生成”,可以直接映射到特定 Skill。
  • 同义词扩展:为常用概念维护同义词表,例如“周报”与“weekly report”互相关联。
  • 热门 Skill 兜底:如果召回结果为空,可以返回最近调用频率最高的几个 Skill 作为降级策略。

4.5 召回效果不好时先查这些地方

召回效果差大概率不是模型问题,而是索引数据或召回逻辑的问题。按以下顺序排查:

问题现象可能原因检查方式解决建议
正确 Skill 没有被召回索引文本与用户请求语义差异过大打印召回 Top 10,确认正确 Skill 是否出现增加触发示例,优化描述关键词
召回结果全是无关 Skill索引文本重复度高或向量模型不匹配检查向量相似度分数分布调整索引文本,换更合适的 Embedding 模型
同义表达召回失败关键字召回无法处理同义词构造同义表达测试引入同义词表或向量召回
候选集不够稳定混合召回分数归一化不一致打印每个召回源的分数统一分数范围,使用加权融合

召回层是整个命中率提升的基础。如果召回阶段已经把正确 Skill 过滤掉了,后面让模型选择也没有意义。因此,在优化模型提示词之前,先确保召回层在评测集上的召回率足够高。

5. 命中率评估:用数据说话,而不是靠感觉

5.1 构造评估集:来自日志、触发示例和人工标注

评估集是命中率优化的关键基础设施。没有评估集,你无法判断改动是变好还是变坏。

建议按以下来源构造评估集:

  • 线上日志:从 Agent 调用日志里抽取真实用户请求,覆盖不同表达方式和错误场景。
  • 触发示例:每个 Skill 的trigger_examples直接转换为评估用例。
  • 人工补充:邀请产品、运营和真实用户写出他们可能使用的表达。

一份评估集的最小格式如下:

[ { "id": 1, "input": "帮我根据日志生成这周的周报", "expected_skill": "weekly-report-generator", "source": "log", "difficulty": "easy" }, { "id": 2, "input": "把今天的进展整理成文档发给领导", "expected_skill": "daily-report-generator", "source": "manual", "difficulty": "hard" } ]

expected_skill必须是 Skill 注册表里的规范名称,评估脚本才能自动统计命中率。difficulty可以按“简单、中等、困难”划分,用于分析不同难度下的表现。

5.2 评估指标:命中率、召回率、误调用率

统计命中率时,需要把结果分成几类:

结果含义对用户的影响
正确命中选中的 Skill 与期望一致正常
错误命中选中的 Skill 与期望不一致用户得到错误功能
漏调用需要调用 Skill 但没有调用用户需求未满足
误调用不需要调用 Skill 但调用了执行了多余动作
执行失败选中正确 Skill 但内部执行报错用户看到错误信息

最终统计指标建议包括:

  • Skill 调用命中率:正确命中数 / 所有需要调用 Skill 的用例数。
  • 召回成功率:正确 Skill 出现在召回结果 Top K 中的比例。
  • 误调用率:误调用数 / 所有调用数。
  • 平均检索位置:正确 Skill 在候选集中的平均排名。

这些指标分别对应不同问题:命中率低但召回成功率高,说明问题在模型选择阶段;召回成功率低,说明问题在召回层。

5.3 最小评估脚本

下面是一个最小评估脚本示例:

import json import yaml def load_skill_registry(path): with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) def load_eval_set(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) def recall_candidates(skill_registry, user_query, top_k=5): # 这里替换成你自己的召回实现 # 返回值是 Skill 名称列表 return [] def run_eval(eval_set, skill_registry): total = len(eval_set) hit = 0 recall_success = 0 misinvoke = 0 for item in eval_set: expected = item["expected_skill"] query = item["input"] candidates = recall_candidates(skill_registry, query, top_k=5) # 检查召回阶段是否包含正确 Skill if expected in candidates: recall_success += 1 # 检查模型最终选择是否命中 selected_skill = model_select(query, candidates) # 替换成实际 Agent 调用 if selected_skill == expected: hit += 1 elif selected_skill is not None: misinvoke += 1 return { "total": total, "hit_rate": hit / total, "recall_success_rate": recall_success / total, "misinvoke_rate": misinvoke / total, } def model_select(query, candidates): # 这里应接入实际模型调用,并限定候选集 return candidates[0] def main(): skill_registry = load_skill_registry("config/skill_registry.yaml") eval_set = load_eval_set("config/eval_set.json") result = run_eval(eval_set, skill_registry) print(json.dumps(result, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()

这个脚本的结构提示了评估链路:召回和选择是两个独立阶段,可以分别统计。实际生产环境建议把每个评估用例的结果落盘,包括候选列表、最终选择、分数和日志,方便后续分析错误样本。

5.4 分析错误样本:从“命中率”到“为什么没命中”

评估结束后,要进入错误样本分析阶段。建议把错误样本按类型分组:

  • 语义映射失败:用户表达与 Skill 描述不在同一语义空间。
  • 描述覆盖不足:Skill 描述没有包含该场景的关键信息。
  • 候选召回失败:正确 Skill 不在召回 Top K 中。
  • 候选排序不佳:正确 Skill 在召回结果里,但排名靠后,模型选了其他项。
  • 规则优先级错误:某些规则强制把请求映射到了错误 Skill。

处理方式:

  • 对语义映射失败,增加对应 Skill 的触发示例,优化描述关键词。
  • 对召回失败,调整索引文本或引入向量召回。
  • 对排序不佳,调整混合召回权重或重新生成 Embedding 索引。
  • 对规则优先级错误,检查规则表和业务含义,必要时增加优先级字段。

评估不是一次性工作。建议每次增加 Skill、修改描述或调整召回逻辑后都重新运行评估集,并记录历史指标,避免“改一个 Skill 导致另一个 Skill 命中率下降”。

6. 运行时监控:日志、告警和持续迭代

6.1 记录每次调用链路的完整信息

评估集只能覆盖一部分情况,生产环境中的真实请求才是 Skill 管理的主要数据来源。每次 Agent 调用 Skill 时,至少记录以下字段:

字段说明
request_id请求唯一标识
timestamp调用时间
user_query用户原始请求
recall_candidates召回阶段返回的候选 Skill 列表
candidate_scores每个候选的分数
selected_skill模型最终选择的 Skill
execution_statusSkill 执行状态:成功、失败、超时
error_message如果执行失败,记录错误信息
latency_ms从请求到选择的耗时
model_name使用的模型名称和版本

日志格式可以使用 JSON,便于后续入 ClickHouse、Elasticsearch 或文件检索。下面是一个日志示例:

{ "request_id": "req_20250101_001", "timestamp": "2025-01-01T10:00:00Z", "user_query": "帮我根据日志整理本周周报", "recall_candidates": ["weekly-report-generator", "daily-report-generator"], "candidate_scores": [0.92, 0.61], "selected_skill": "weekly-report-generator", "execution_status": "success", "error_message": "", "latency_ms": 320, "model_name": "gpt-4o" }

6.2 建立命中率报表和告警规则

持续统计命中率时,按小时或按天聚合数据。比较简单的做法是把日志读入 DataFrame,然后按时间窗口统计:

import pandas as pd logs = pd.read_json("logs/skill_call.log", lines=True) logs["event_time"] = pd.to_datetime(logs["timestamp"]) # 每天命中率 logs["is_hit"] = logs["execution_status"] == "success" daily_rate = logs.groupby(logs["event_time"].dt.date)["is_hit"].mean() print(daily_rate) # 每个 Skill 的调用次数和失败率 skill_stats = logs.groupby("selected_skill").agg( call_count=("request_id", "count"), fail_count=("execution_status", lambda x: (x != "success").sum()), ) skill_stats["fail_rate"] = skill_stats["fail_count"] / skill_stats["call_count"] print(skill_stats.sort_values("fail_rate", ascending=False))

告警规则可以设置:

  • 命中率低于某个阈值,例如低于 85% 时触发告警。
  • 某个 Skill 连续失败超过 5 次。
  • 召回阶段覆盖率明显下降。
  • 用户请求中包含大量未知表达,说明 Skill 登记存在覆盖缺口。

告警不是目的,关键是告警后能定位到具体 Skill 和具体请求样本,判断是描述问题、执行问题还是模型问题。

6.3 Skill 生命周期管理:上线、下线、版本更新

Skill 数量过百后,必然会遇到下线、废弃、合并和版本更新问题。建议建立明确的 Skill 生命周期流程:

状态含义处理方式
草稿开发中,未进入正式索引不参与召回
已发布可在生产环境被调用进入召回索引和模型候选
已废弃不再推荐使用从召回层移除,保留日志用于历史分析
已下线当前不可用不参与调用,但记录名称防止冲突
Skill 生命周期状态迁移:草稿 -> 已发布 -> 已废弃 -> 已下线

实际项目很容易出现的问题是:某团队改了一个 Skill 描述,但线上索引没有更新;或者某个 Skill 已下线,但用户请求仍被规则强制路由到它。建立生命周期状态后,每次变更都要走更新任务,并同步更新注册表和召回索引。

6.4 持续迭代:以周为周期管理 Skill 质量

Skill 管理不是一次性建设,而是一个持续维护过程。建议按周执行以下循环:

  1. 从日志中抽取近一周的低命中率样本。
  2. 低命中率样本归类:描述问题、召回问题、执行问题、模型问题。
  3. 对描述问题更新SKILL.md和注册表。
  4. 对召回问题更新索引文本、触发示例或召回权重。
  5. 对执行问题查看 Skill 内部脚本和依赖。
  6. 更新评估集,加入新出现的表达方式。
  7. 重新运行评估脚本,对比历史指标。

如果团队规模允许,可以指定一个 Skill 仓库负责人,负责合并描述变更、处理冲突、审查新增 Skill。因为当 100 个 Skill 由多人维护时,命名冲突、语义重叠、索引覆盖缺失几乎必然发生。

7. 最佳实践与常见坑

7.1 至少三个与 Skill 过百相关的常见坑

坑一:只优化 System Prompt,不优化召回层。

有人在命中率下降后不断加长 System Prompt,把所有 Skill 描述都写进提示词,结果上下文被撑满,模型决策反而更差。正确做法是先做召回,缩小候选集,再让模型在小集合里选择。

坑二:触发示例只写“理想表达”,不写边界表达。

如果触发示例都是“生成周报”“周报生成”这类理想表达,模型遇到“我下周要跟领导汇报,帮我把内容整理下”时仍然可能选错。边界表达必须来自真实日志和用户反馈。

坑三:评估集和日志分离,导致无法定位问题。

只统计“命中率是否低于 90%”,却不记录具体是哪个 Skill、哪个用户请求、哪个召回阶段出错,就无法推进优化。评估和监控必须保留完整调用链路信息。

7.2 可复用的 Skill 质量检查清单

每次新增或修改 Skill 时,建议按以下清单检查:

  • [ ] Skill 名称是否使用动词开头,长度是否控制在 3 到 6 个单词。
  • [ ] 描述是否包含“做什么、什么时候用、不做什么”。
  • [ ] 是否配置了 5 条以上触发示例,且包含边界表达。
  • [ ] 分类和标签是否与其他 Skill 冲突。
  • [ ] 索引文本是否及时同步。
  • [ ] 是否新增了对应评估用例。
  • [ ] 本次修改是否会影响其他 Skill 的召回。
  • [ ] Skill 依赖的脚本、API 是否在目标环境可运行。
  • [ ] 是否有负责人和生命周期状态。
  • [ ] 是否登记到skill_registry.yaml,而不是只放在目录里。

7.3 命中率优化的优先级建议

如果资源有限,建议按以下顺序推进:

  1. 先建立评估集和日志采集,让命中率可度量。
  2. 再统一 Skill 元数据格式,重点补触发示例和边界描述。
  3. 然后引入召回层,先关键词召回,再向量召回。
  4. 最后用历史日志持续迭代评估集,形成周级别维护循环。

不要一开始就追求复杂的向量数据库和重排序模型。先把基础数据质量和日志链路做扎实,命中率提升会更稳定。

7.4 Agent 与 Skill 体系扩展方向

当 Skill 数量继续增长,比如超过 1000 个,还需要考虑更复杂的分层检索、Skill 组合编排、权限隔离和动态加载。Skill 之间如果存在依赖关系,例如某个 Skill 依赖另一个 Skill 的输出,还需要为这种依赖关系建模,而不是只做平面列表召回。

另一个方向是让 Skill 调用结果反过来参与召回。同一个用户请求如果多次调用某个 Skill,可以把该 Skill 的调用记录作为特征,影响后续召回排序。结合用户历史和业务偏好,召回准确率可以进一步提升。

对新手来说,最有价值的练习是在自己的 Agent 项目里构造 100 个 Skill,先跑出命中率,再复现本文提到的命名、描述、召回和评估优化流程。通过亲手复现命中率下降和回升的过程,会比只看任何一篇教程都更深刻。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 5:51:36

英国列车地图背后的技术链路:数据标准化与实时可视化实战

如果你做过交通类可视化,大概会同意一句话:画一张地图不难,难的是让地图上的每一个点都准确对应现实世界里正在发生的一趟车。最近 Hacker News 上SHOW HN: Substantial update to UK train mapping这个标题引起了不少讨论,标题本…

作者头像 李华
网站建设 2026/8/27 5:51:17

智能数据分析原型的交付验收

智能数据分析原型的交付验收 把输入与输出留在记录里 智能数据分析原型的交付验收这件事最怕只留下结论,没有留下判断过程。实际处理时,先选一条具体路径,把进入条件、经过的组件和结束状态写下来。正常场景当然要测,但更该看参数…

作者头像 李华
网站建设 2026/8/27 5:51:06

手写Python垃圾分类算法:基于PyTorch迁移学习的完整实战

简介:图像分类是计算机视觉领域的核心任务,其本质是通过算法自动理解图像内容并判断所属类别。卷积神经网络(CNN)作为主流技术,通过多层特征提取实现从边缘到语义的逐步抽象,但训练深层网络依赖海量标注数据…

作者头像 李华
网站建设 2026/8/27 5:48:42

C++函数探幽:从内联、引用、模板到函数指针的进阶实战解析

1. 项目概述:为什么函数探幽是C进阶的基石如果你正在啃《C Primer Plus》这本书,到了第八章“函数探幽”,可能会感觉有点不一样了。前面的章节讲变量、循环、控制结构,像是给你积木块,而这一章开始教你如何把这些积木搭…

作者头像 李华
网站建设 2026/8/27 5:46:59

脑网络通信:从静态连接到动态信息流的研究范式与模型解析

1. 项目概述:从“连接”到“通信”的脑网络研究范式跃迁在神经科学领域,我们谈论“脑网络”已经有些年头了。从早期的结构连接图谱,到后来的功能连接分析,研究者们绘制了大脑不同区域之间“谁与谁相连”以及“谁与谁的活动同步”的…

作者头像 李华