
如果你跟我一样这段时间一直在折腾 AI Agent 开发那你应该已经发现了一个很尴尬的现象Agent 看起来什么都能聊可一旦让它去解决某个具体问题比如批量处理文件、按固定流程走业务、调用一堆外部接口它就总在关键环节翻车。原因很简单——大模型擅长“理解”和“生成”但天生不擅长“稳定执行一套复杂流程”。解决这个问题的热门方向之一就是 agent-skills。这套思路说白了就是给 Agent 装上一个个“技能包”让它不再靠临场发挥而是像人一样调用自己早就学会的技能来干活。我这篇文章想把 agent-skills 这个概念拆开揉碎从底层原理讲到工程落地最后再给一套可以直接抄走的实操方案。不管你是刚接触 Agent 开发的新手还是已经在生产环境里被各种工具调用折磨过一轮的工程师这篇文章应该都能帮你省下不少试错时间。1. 先说清楚agent-skills 到底在解决什么问题1.1 从“告诉模型怎么做”到“直接给模型一套完整工具”过去我们让 AI 干活主要靠的是提示词。你在一段 Prompt 里把所有规则、步骤、示例都写清楚指望模型照着执行。这个方法在小任务上确实有用但任务一复杂就绷不住了步骤一多模型容易忘中间状态一变模型容易乱涉及外部交互模型根本控不住。agent-skills 的思路完全不同。它不再要求模型“知道怎么做”而是直接给它一个封装好的、可以调用的技能模块。这个模块里面既有清晰的描述文件也有实际的代码逻辑甚至还带着运行所需的依赖配置。模型要做的不是重新理解整个流程而是判断“当前这个请求应该调用哪个技能”然后调用它、拿结果、再继续回答用户。我打个比方以前你请了个实习生你每次都得事无巨细地告诉他流程现在你直接给了他一套带说明书的工具箱他只需要看清楚当前情况该用哪个工具工具自己会把活干完。Agent 从“什么都要自己想”变成了“知道该调用什么”稳定性一下子就不一样了。1.2 一次完整调用的背后到底发生了什么要真正理解 agent-skills你得知道一次技能调用在系统层面经历了什么。完整链路大概是这样的用户输入传到 Agent 主模型同时系统会把当前可用的技能列表或技能描述注入上下文。主模型根据用户意图判断是否匹配某个技能的触发条件。如果匹配它不会自己去硬解而是向调度层发出一个“调用技能”的请求请求里带上技能名和必要的输入参数。调度层找到对应的技能实现执行代码拿到结果。这个结果可能是结构化数据、文件内容、接口响应或者另一个待处理的中间状态。结果返回给主模型主模型基于这个结果继续组织语言回复用户。这个过程有点像把函数调用扩展成了一个更粗粒度的完整模块。区别在于函数调用往往是“一次请求一次响应”而技能调用里面包含的可能是多步骤的完整流程甚至内部还会再调用多个函数。这就是 agent-skills 和单纯 tool calling 的最大不同它把复杂流程的稳定性从模型身上转移到了代码里。2. Skills 和 Function Calling 是两码事2.1 Function Calling单次调用的“翻译官”很多团队一开始都是走 Function Calling 的路线也就是给模型注册一堆 JSON 格式的函数模型通过结构化的参数输出来调用它们。这套机制本身没有问题尤其适合场景简单、参数清晰的调用比如查天气、算价格、查数据库。但 Function Calling 有个天然的边界它本质上还是“一次意图识别加一次函数调用”。如果某个业务需要连续调用三个接口、中间还要做判断和分支处理模型每一步都要自己决定下一步调什么。这就像让实习生每走一步都要停下来问你“下一步怎么办”效率低不说还容易走错。而且函数一多光是把几百个函数的描述全部塞进上下文Token 消耗就已经很可观了。2.2 Skills带流程、带知识、带依赖的“完整工种”Skills 聪明的地方在于它把“应该怎么做”的全部内容都封装在技能内部了。模型只需要知道“什么时候该用这个技能”和“该给技能传什么参数”剩下的流程、规则、代码逻辑、异常处理全都不需要模型操心。举个例子你就明白了。假设你要做一个“多语言文档翻译审校”技能。如果走 Function Calling你可能要注册十几个函数读取源文档、翻译成目标语言、检查术语一致性、生成审校报告、统计错误率……模型面对这么一堆函数很容易在中间环节选错、漏调或者把一个步骤的输出误传给另一个函数。但如果你用 Skills 来实现整个流程会被打包为一个 doc-review 技能。模型只需要知道“用户要翻译审校文档时调用 doc-review传路径和语言参数”剩下的读文档、翻译、比对术语、出报告全部由技能内部的代码完成最终只把审校结果返回给模型。模型不需要了解中间每一步自然也就不会在中间环节犯错。2.3 什么时候用哪个我个人的经验是两类任务的分界非常清晰对比维度Function CallingAgent Skills任务粒度单次、独立的数据请求多步骤、有流程、有状态的整套操作流程稳定性依靠模型逐步决策存在累积误差流程固化在代码里执行稳定Token 消耗函数多时上下文压力大按需加载只在触发时注入完整技能依赖管理不适合携带外部依赖和资源可以打包脚本、配置、资源文件适用场景查询天气、数据检索、简单工具文档处理、代码审计、自动化测试、业务编排一句话总结凡是“单次拿数据、单次做动作”的用 Function Calling 就够凡是“需要按固定流程走完、中间有状态有分支”的直接上 Skills 才稳妥。3. 手把手搭一个可运行的 Agent Skills 工程3.1 目录结构怎么设计先给出一份我在实际项目里验证过多次的目录结构你可以直接照搬后续再按自己的项目调整project/ agent/ main.py # Agent 主入口负责加载技能、处理对话 config.json # 全局配置模型参数、技能路径、调度策略 skills/ doc-review/ SKILL.md # 技能描述文件模型靠它判断何时触发 scripts/ review.py # 技能核心实现 requirements.txt # 技能运行所需的 Python 依赖 resources/ term_base.txt # 术语库等静态资源 tests/ case_01.md # 测试用例文件 case_02.md logs/ run.log # 运行日志方便排查这个结构的关键点是“一个技能一个目录”所有跟这个技能绑定的描述、代码、资源、测试都在同一个目录下互不干扰。后续要增删技能、要版本管理、要打包分发都方便很多。3.2 写好 SKILL.md 是成败关键我见过太多团队花了大量精力写技能代码结果在 SKILL.md 上草草了事最后模型就是不触发技能或者总是误触发。你要记住一个原则技能代码决定“能不能干完活”技能描述决定“模型会不会正确使用它”。后者出错前者写得再好也白搭。一份合格的 SKILL.md 至少包含这几部分# 技能名称DocReview文档审阅 ## 功能简介 对传入的文档内容进行审阅基于内部术语库检查术语一致性、格式规范、敏感信息并输出结构化审阅报告。 ## 触发时机When to Use - 用户要求对文档进行审阅、质检、校对、术语检查时。 - 用户上传了文档并要求给出改善建议时。 - 用户要求批量检查多份文档的合规性时。 ## 不适用场景When NOT to Use - 用户只是询问文档内容不需要审阅时。 - 用户要求写一篇新文档而不是检查已有文档时。 ## 调用参数 - input.document_path: 需要审阅的文档路径必填 - input.review_level: 审阅级别可选值为 strict / normal / quick默认 normal ## 返回值 - output.report: 审阅报告文本包含问题清单、严重级别、修改建议。这里有几个容易踩坑的地方触发条件必须具体。只写“用户需要审阅文档”是不够的要把用户可能会说的话都覆盖进去比如“帮我看看这篇稿子”“检查一下这份合同有没有问题”“校对一下这个大纲”都属于同一个技能场景。必须写清楚“何时不要用”。这点很多文档都忽略了。不写清楚模型就很容易在模糊场景下过度触发。参数说明要明确到类型和取值。最好直接写成 JSON Schema 风格或者像上面例子这样把枚举值都列出来。3.3 绑定代码实现让技能真正“会动手”SKILL.md 写完后下一步就是把实现代码写出来。技能实现本身可以很简单就是一个普通的函数或者脚本。继续用上面的审阅技能举例# skills/doc-review/scripts/review.py import sys import json from pathlib import Path def load_term_base(resource_path: str) - list[str]: 加载术语库每个术语占一行。 if not Path(resource_path).exists(): return [] return Path(resource_path).read_text(encodingutf-8).splitlines() def review_document(document_path: str, review_level: str normal) - dict: doc_text Path(document_path).read_text(encodingutf-8) term_base load_term_base(skills/doc-review/resources/term_base.txt) issues [] # 简单示例检查术语库中的词是否被大小写混用或误替换 for term in term_base: # 这里可以做真正的匹配逻辑 pass return { status: completed, issue_count: len(issues), issues: issues, report: json.dumps(issues, ensure_asciiFalse, indent2) } if __name__ __main__: # 命令行入口方便调试和接入 path sys.argv[1] if len(sys.argv) 1 else level sys.argv[2] if len(sys.argv) 2 else normal result review_document(path, level) print(json.dumps(result, ensure_asciiFalse, indent2))这段代码看起来简单但它体现了技能实现的一个核心设计原则技能内部尽可能自己做完整件事最后只返回一个结构化的、对模型友好的结果。模型不关心你内部怎么读文件、怎么比对术语它只需要拿到一个清晰的 JSON 或文本然后基于这个结果回复用户。3.4 三种测试方式上线前必须跑一遍技能写完后不要直接上线我建议至少做三层测试第一层是单测直接跑脚本本身传入准确的参数看输出是否符合预期。这一步是为了验证核心逻辑没有 bug。第二层是触发测试。让 Agent 分别输入明确意图的句子、模糊意图的句子、完全不相关的句子观察技能是否在正确时机触发、在错误时机保持沉默。如果发现模糊场景下频繁误触发那就要回到 SKILL.md 里补“不适用场景”的说明。第三层是端到端测试。模拟真实的用户完整对话流让 Agent 连续多次调用技能中间还要穿插追问、澄清、改参数等交互看整个流程是否稳定。这一步最容易暴露问题我先提醒一句参数透传是端到端测试里最常见的坑。模型可能在第一次调用时报了正确的参数类型第二次就给你传成字符串了或者干脆漏传必填参数。设计技能接口时尽量做参数校验和默认值兜底不要依赖模型传参“总是很规范”。4. 实战拆解给 Agent 添加一个文档审阅技能4.1 需求拆解与技能边界下面我用一个完整的案例带大家把前面讲的这套流程走一遍。假设我所在的团队要做一个「内容合规审阅助手」需求是运营同学上传一份文档到系统Agent 能自动审阅文档检查里面的术语使用是否统一、格式是否规范、有没有明显的敏感内容最后输出一份问题清单。这个需求如果让模型硬做最大的问题是模型每次看文档都只生成了“看起来像那么回事”的意见但不同文档、不同时间的结果差异很大。后来我把这个场景收敛成一个 doc-review 技能模型不直接输出意见而是调用技能去执行审阅逻辑再基于技能返回的问题清单做解释。技能边界我控制在三个功能点术语一致性检测、格式合规检测、敏感信息扫描。超过这个范围的事情技能直接返回“不在能力范围内”避免模型拿技能结果去过度发挥。4.2 技能代码判断、查规则、输出评分代码设计上我把技能拆成三个模块读取器、检查器、报告生成器。读取器负责把不同格式的文档统一转成纯文本检查器负责跑规则报告生成器负责输出结构化结果。# 读取器将 docx / pdf / txt 统一转为文本 def read_document(path: str) - str: suffix Path(path).suffix.lower() if suffix .txt: return Path(path).read_text(encodingutf-8) elif suffix .docx: # 用 python-docx 解析这里省略详细实现 pass elif suffix .pdf: # 用 pypdf 提取文本 pass else: raise ValueError(fUnsupported file type: {suffix})这里我提醒一个实际项目里很常见的坑文件读取并不像看起来那么简单尤其是 PDF 里有扫描图片的情况直接抽出来可能是空文本。所以技能里提前要做两件事一是加文件格式白名单二是对读取结果做最小长度检查如果文本为空就要向上返回“无法读取”而不是继续跑检查避免后面所有检查结果都是基于空内容得出的假阳性。检查器就比较直接了设计成一组可扩展的规则函数def check_terminology(text: str, term_base: list[str]) - list[dict]: 检查术语是否统一返回问题清单。 problems [] for term in term_base: # 正常情况下的术语写法 canonical term.strip() # 这里可以维护一个常见错误变体列表 variants load_variants(canonical) for variant in variants: if variant in text: problems.append({ type: terminology, severity: warning, message: f术语 {canonical} 疑似被写为 {variant}, position: text.find(variant) }) return problems这个规则函数的好处是足够透明——每个问题都能说清楚是依据哪条规则判出来的模型可以照着这个结果向用户解释也可以在此基础上提供更详细的修改建议。报告生成器就是把所有问题清单聚合起来生成一个带摘要的结果。def generate_report(doc_path: str, level: str) - dict: text read_document(doc_path) if not text.strip(): return {status: empty_document, report: } term_base load_term_base(skills/doc-review/resources/term_base.txt) issues [] issues.extend(check_terminology(text, term_base)) issues.extend(check_format(text)) issues.extend(check_sensitive(text)) return { status: completed, doc_path: doc_path, issue_count: len(issues), critical_count: len([i for i in issues if i[severity] critical]), issues: sorted(issues, keylambda x: x[position]), }4.3 接入层如何让主 Agent 按需加载技能代码写完了最后到接入层。我最开始的做法是简单粗暴地扫描整个 skills 目录把每个技能的 SKILL.md 全部拼到系统提示词里。结果技能一多系统提示词长到离谱模型反而不聚焦了。后来我改成“白名单 按需激活”的方式# agent/main.py 里做技能加载 def load_active_skills(config: dict) - list[str]: skills [] for skill_name in config[enabled_skills]: skill_path Path(config[skills_dir]) / skill_name desc_file skill_path / SKILL.md if desc_file.exists(): skills.append(desc_file.read_text(encodingutf-8)) return skillsconfig.json 里维护一个 enabled_skills 白名单默认只加载两三个高频核心技能。这样系统提示词的体积控制住了模型对每个技能的感知也更清晰。至于低频技能可以走另一种方案只把技能名和一句话描述注入上下文等模型决定调用时再动态加载完整 SKILL.md。这种“懒加载”的思路在生产环境里非常实用。{ model: your-model-name, skills_dir: ./skills, enabled_skills: [doc-review, code-linter], max_context_skills: 3, log_level: INFO }5. 常见问题与排查技巧实录5.1 技能死活不触发先查这三个地方技能不触发是出现频率最高的问题我以前排查的时候毫无头绪后来总结出固定的三板斧。第一检查 SKILL.md 里的描述是否太抽象。如果你写的是“处理文档审阅”模型可能根本不知道什么场景算“处理”。解决办法是写成一个触发词清单最好是直接从真实用户测试里收集回来的说法比如“帮我看看这篇稿子有什么问题”“检查一下这篇软文有没有违规词”等等。第二检查系统提示词对技能的介绍是否足够短。模型对技能的感知方式是“先扫描一遍有没有可用的工具再判断该不该用”如果每个技能都被你用三百字介绍了一遍模型容易看晕反而什么技能都触发不了。我的经验是系统提示词里每个技能最多只放一句话概括详细描述放在 SKILL.md 里等模型决定调用后再给它看。第三检查调度层有没有把技能的触发权真正交给模型。有些团队喜欢在调度层里自己写规则比如“只要用户提到文档就直接调用 doc-review”结果把模型的选择权拿走了反而导致后续对话无法根据上下文做判断。调度层只做合法性校验触发判断尽量让模型做。5.2 技能之间互相干扰怎么办技能数量一多容易出现两个技能都声称自己该处理当前请求的情况。最典型的场景是“文档审阅”和“文本润色”——用户说“帮我改改这篇稿子”两个技能都觉得该自己上模型随机选一个结果可能跑偏。我的解法是加一层“优先级规则”在配置里给每个技能定义一个优先级字段并且在 SKILL.md 里明确写出行使条件。比如润色技能的描述里写明“仅当用户明确要求改写措辞、提升表达时触发若用户要求的是检查问题请返回不适用”。这看起来像在描述里加了一句话但实际效果非常明显模型在模糊场景下的误触发率会降一大截。另外技能拆分的粒度也要注意。我刚做 skills 时习惯把一个大技能拆成十几个小技能后来发现维护成本反而更高。正确的做法是一个技能至少要能独立完成一项用户可感知的任务。那种只有一两行逻辑、还要跟另一个技能联动才能出结果的“微技能”不如直接合并到大技能里。5.3 输入输出的坑比你想的要多技能调用的输入输出往往是生产环境里最容易翻车的地方我挑三个最常见的说。第一个坑是参数类型不一致。模型调用技能时传的参数经常不符合你定义的 Schema比如你要求传整数它给你传了个字符串 “3”。所以技能入口处一定要做类型校验和转换不要抱侥幸心理。def robust_int(value, default: int 0) - int: try: return int(value) except (TypeError, ValueError): return default第二个坑是输出格式不稳定。有些技能返回的是纯文本模型对纯文本的解读非常主观同一个结果可能这次被总结成“文档基本合格”下次被总结成“有若干问题”。要想输出稳定建议所有技能统一返回 JSON 结构字段名、取值枚举都提前定义好模型只要照着字段解释就行。第三个坑是长文本输出把上下文塞爆了。有的技能往返回结果里塞了整份文档内容Agent 再把结果贴回大模型一轮对话直接消耗几万 Token。正确做法是技能只返回“问题清单 关键片段”不要返回完整内容。模型需要知道细节的时候再按需发起第二次技能调用去取对应位置的上下文。常见问题根本原因排查优先级技能不触发SKILL.md 描述抽象或触发词不全高误触发/过度触发缺少“不适用场景”的说明高技能互相争夺请求技能职责边界不清晰中参数类型不匹配模型传参不规范缺少校验中输出不稳定返回格式不结构化中上下文膨胀返回数据冗余低6. Skills 落地到真实应用场景6.1 哪些场景最适合先引入 Skills如果你正在评估自己的业务要不要做成 Agent Skills我建议优先看这三类任务。第一类是知识密集、规则复杂的任务。比如合同审阅、财务合规检查、代码规范检查。这些场景的难点不在“理解语义”而在于“每一句话都要对照大量规则去判断是否违规”纯靠模型记忆规则根本不可靠但用技能把规则库和判断逻辑固化下来效果立竿见影。第二类是多步骤、多接口串联的任务。比如用户问“帮我分析这个产品的库存、销量、评价数据生成一份周报”这里涉及多个数据接口、多轮中间处理、还有格式转换。与其让模型临场发挥不如把所有步骤写进一个 report 技能输入是产品 ID 和时间范围输出直接是周报初稿。第三类是对输出格式有强要求的任务。比如生成 PDF 报告、批量生成结构化数据、格式固定的工单。模型直接生成这些格式很容易出错但技能里可以用模板引擎和专用库来渲染模型只负责填参数输出质量就有保证。6.2 和现有 AI 应用的三种集成方式Skills 可以嵌进三种常见的架构里你可以按自己的现状选。第一种是单体 Agent 内嵌。把技能列表挂在一个 Agent 里所有技能共享同一个主模型和上下文。这种方式最简单适合工具数量少于十个、流程不复杂的场景但技能多了以后上下文管理和模型决策负担都会上升。第二种是编排层 多技能仓库。单独做一个调度服务维护一个技能仓库不同 Agent 可以根据需要加载不同的技能组合。这种方式适合已经有服务化架构的团队技能的发布、更新、灰度都能通过仓库来管理。第三种是技能市场模式。把技能作为独立模块通过 API 对外暴露不同业务线可以像装插件一样装技能。这个模式适合平台型的团队也是 agent-skills 最终形态的一个方向——未来的 Agent 不再是一堆代码写死的助手而是一个可以按需加装各种技能的运行环境。最后再分享一个小技巧。在技能配置里加一个“使用日志”字段每次技能被调用时记录触发时的用户原话、技能入参、返回结果摘要。坚持积累两周你会一眼看出哪些技能描述需要调整、哪些技能实际使用的频率远超预期、哪些技能该被合并或拆掉。这比任何压测数据都更能说明问题。我个人在实际操作中体会最深的还是那句话——agent-skills 好不好用七成取决于你的描述文件和各技能边界设计而不是代码本身的复杂度。把这两件事做好剩下的事情都会顺很多。