Agent-Skill 设计:从概念到工程落地 Agent-Skill 设计从概念到工程落地基于 Anthropic 官方工程博客 LangGraph 实现案例 行业实践的综合思考参考资料见文末一、Skill 是什么为什么需要它1.1 核心问题Agent 有能力工具≠ Agent 知道怎么用好工具。一个 Agent 可以挂载几十个工具MCP Server但它不知道哪个工具该先调用参数怎么填才对生产环境中什么会出错你的业务规则和偏好是什么Skill 就是解决这个问题的它是给 Agent 的「入职手册」——把你的领域知识、操作流程、最佳实践打包成结构化文件让 Agent 在需要时动态加载。1.2 Skill vs 直接塞 System Prompt维度全部塞 System PromptSkill 动态加载Token 成本每次请求都消耗全部 token10个 Skill × 2000 token 20000 token/请求只加载需要的~500 token 目录 按需加载信号清晰度长 prompt 中相关指令被无关内容淹没聚焦当前任务的指令可观测性看不出 Agent 用了哪段知识每次 load_skill 都是可追踪的 tool call更新方式改代码重新部署改 markdown 文件即可扩展性随 Skill 增多线性膨胀知识量无上限渐进式披露但注意Vercel 的实验表明在某些窄域、稳定场景下一个精心压缩的单文件 prompt如 AGENTS.md可能比分模块 Skill 效果更好100% vs 79%。选择取决于问题的变异性、规模和运维需求。1.3 一个关键心智模型渐进式披露Progressive Disclosure这是 Skill 系统的核心设计原则类似一本组织良好的手册Level 1: 目录~500 token → 始终在 System Prompt 中 ↓ Agent 判断相关性 Level 2: 章节正文~2000 token→ 按需 load_skill() ↓ Agent 需要更多细节 Level 3: 附录/参考文档 → 按需 read_skill_file()类比你不会把整本手册塞进脑子里。你先看目录找到相关章节需要时再查附录。Skill 让 Agent 以同样的方式工作。二、Skill 的组成结构2.1 文件结构skills/ └── pdf/ # Skill 目录 ├── SKILL.md # 必需核心指令文件 ├── reference.md # 可选参考文档 ├── forms.md # 可选表单填写指南 └── scripts/ └── extract_fields.py # 可选可执行脚本2.2 SKILL.md 的两部分--- name: pdf description: 处理 PDF 文档读取、提取表单字段、填写表单 tags: [pdf, document, forms] version: 1.0 --- ## 核心指令 当用户需要处理 PDF 时按以下步骤操作 1. 先用 extract_fields.py 提取表单字段 2. 根据用户输入填写字段 3. 生成填写后的 PDF ### 注意事项 - 对于扫描版 PDF先用 OCR 处理 - 表单填写详见 [forms.md](forms.md) ### 参考文档 - [reference.md](reference.md) — PDF 格式参考 - [forms.md](forms.md) — 表单填写详细指南YAML FrontmatterLevel 1nameSkill 唯一标识Agent 用它来调用description最关键的字段——Agent 靠它决定是否加载这个 Skilltags辅助分类version版本管理Markdown BodyLevel 2给 Agent 的具体操作指令引用 Level 3 的文件2.3 Skill 中的代码Skill 不仅是文本还可以包含可执行脚本。Agent 收到 填写 PDF 表单 的请求 ↓ Agent load_skill(pdf) → 读到 SKILL.md 中的指令 ↓ Agent 发现需要先提取字段 → 运行 scripts/extract_fields.py ↓ 脚本是确定性的不需要 LLM 推理 → 稳定可靠 ↓ Agent 用提取结果 用户输入 → 填写表单关键洞察代码既是可执行工具也是文档。LLM 可以读懂代码来理解它应该做什么也可以直接运行代码来完成任务。对于排序、解析、格式转换等确定性操作代码比 LLM 生成更可靠。三、Skill 提示词怎么写3.1 总体原则「正确高度」的提示词Anthropic 提出了一个重要概念——正确高度Right Altitude过于具体硬编码 if-else 过于模糊高层指导 ←—————————→ 脆弱、难维护 模型不知道具体怎么做 ↑ 正确高度 具体到能引导行为 灵活到能适应变化错误示例过于具体如果用户说帮我查天气先判断城市如果城市是北京就调用 weather_beijing 如果是上海就调用 weather_shanghai其他城市调用 weather_general...错误示例过于模糊根据用户需求选择合适的工具完成任务。正确示例正确高度当用户需要查询信息时 1. 先判断信息类型天气、新闻、知识等 2. 选择对应的搜索工具 3. 如结果不够精确尝试换关键词重新搜索 4. 最终用简洁的自然语言回答用户 注意优先使用结构化搜索而非全量列表。3.2 SKILL.md 的写作规范描述description—— 决定 Agent 是否触发 Skill# ❌ 太模糊description:帮助处理文档# ❌ 太具体把实现细节暴露给 Agentdescription:使用 PyPDF2 库读取 PDF 文件并提取表单字段支持 AcroForm 格式# ✅ 恰好描述能力边界和适用场景description:处理 PDF 文档读取内容、提取和填写表单字段。 适用于需要操作而非仅阅读PDF 的场景。 不适用于扫描版 PDF 的文字识别需要 OCR Skill。描述越精准Agent 触发率越高误触发率越低。指令正文 —— 告诉 Agent “怎么做”好的指令应该包含触发条件什么时候该用这个 Skill操作步骤清晰的步骤序列边界说明什么不该做和其他 Skill 的区别错误处理常见失败场景和应对策略示例few-shot 示例是最有效的信号## 使用场景 当用户要求对 PDF 进行以下操作时触发本 Skill - 填写 PDF 表单 - 合并/拆分 PDF 页面 - 提取 PDF 中的结构化数据 ## 操作流程 ### 填写表单 1. 运行 scripts/extract_fields.py file 获取字段列表 2. 向用户确认每个字段的值如果用户未提供 3. 运行 scripts/fill_form.py file fields_json 生成填写后的 PDF 4. 验证输出文件存在且大小合理 ### 常见错误 - 如果 extract_fields.py 返回空列表 → 可能是扫描版 PDF建议用户使用 OCR Skill - 如果 fill_form.py 报 field not found → 检查字段名是否与提取结果完全一致 ## 示例 用户帮我把这个 W-9 表格填了公司名是 Acme Corp → 运行 extract_fields.py → 发现字段company_name, ein, address... → 自动填入 company_nameAcme Corp其余字段向用户确认 → 生成填写后的 PDF3.3 工具描述Tool Description—— 最被低估的工程环节Anthropic 在构建 SWE-bench Agent 时发现他们花在优化工具描述上的时间比优化主 prompt 还多。“We actually spent more time optimizing our tools than the overall prompt.”工具描述的黄金法则1. 像给新人写文档一样写工具描述# ❌ 机器友好的描述defsearch(query:str)-str:Search for information....# ✅ Agent 友好的描述defsearch_documents(query:str,doc_type:strall,max_results:int10)-str:在知识库中搜索文档并返回最相关的结果。 使用场景 - 用户问了一个你无法从已有上下文回答的问题 - 需要查找特定产品/功能的文档 不使用场景 - 用户只是在闲聊 - 问题可以通过已有信息直接回答 Args: query: 自然语言搜索查询。用具体问题比关键词效果更好。 例如 React 18 的并发模式怎么用 优于 React 并发 doc_type: 文档类型过滤。可选 api | guide | example | all max_results: 返回结果数量默认 10。建议用较小值3-5快速定位。 Returns: JSON 格式的搜索结果每条包含 title, content_snippet, url, relevance_score 注意事项 - 如果第一次搜索没找到尝试换一种表述方式 - 避免在 query 中添加年份如 2025除非用户明确要求 2. 参数名要无歧义# ❌ 模糊defget_user(user:str):...# user 是 ID名字邮箱# ✅ 明确defget_user(user_id:str):...# 一目了然是 IDdefsearch_user(user_name:str):...# 一目了然是名字3. 命名空间Namespacing—— 工具多了必须做当 Agent 可用工具超过 15 个时命名冲突和选择困难就会出现# ❌ 工具名互相竞争tools[search,create,update,delete,search,create,update,delete]# 两套 search# ✅ 用命名空间区分tools[asana_search,asana_create_task,jira_search,jira_create_issue]# 更进一步按资源细分tools[asana_projects_search,asana_users_search,asana_tasks_create]前缀 vs 后缀命名对不同 LLM 效果不同需要通过评测来选择。4. 返回值要「高信号」# ❌ 返回低信号信息{uuid:a1b2c3d4...,256px_image_url:...,mime_type:image/png}# ✅ 返回高信号信息{name:John Smith,avatar_url:...,role:Engineering Manager}# 更好的做法提供 concise/detailed 两种模式defget_customer(customer_id:str,response_format:strconcise):... response_format: concise 返回核心信息~70 token detailed 返回完整信息含技术ID~200 token 为什么UUID 等不透明标识符会增加 Agent 的幻觉率。把a1b2c3d4解析为John Smith能显著提升检索准确性。5. 错误信息要「可操作」# ❌ 无用错误{error:400 Bad Request}# ✅ 可操作错误{error:参数 validation 失败,details:date 格式必须是 YYYY-MM-DD收到的是 01/15/2026,example:正确格式: 2026-01-15}四、Skill 如何与 Agent 的工具绑定4.1 架构总览┌─────────────────────────────────────────────┐ │ System Prompt │ │ ┌──────────────────────────────────────┐ │ │ │ Agent 基础指令 │ │ │ │ Skill 目录Level 1 元数据 │ │ │ │ 工具定义tool schemas │ │ │ └──────────────────────────────────────┘ │ ├─────────────────────────────────────────────┤ │ Agent Loop (ReAct) │ │ │ │ 用户请求 → LLM 推理 → 决定调用工具 │ │ ↑ ↓ │ │ └──── 工具结果反馈 ←────────────┘ │ │ │ │ 可用工具包括 │ │ ├── load_skill(name) ← Skill 加载 │ │ ├── read_skill_file(...) ← Skill 文件读取 │ │ ├── search_documents(...) ← 业务工具 │ │ ├── create_task(...) ← 业务工具 │ │ └── ...其他工具 │ └─────────────────────────────────────────────┘4.2 两种工具绑定模式模式 ASkill 作为 Tool Call推荐Skill 的加载本身就是一个工具调用tooldefload_skill(skill_name:str)-str:Load expert knowledge for a skill. Available skills: pdf, code-review,>store.load(skill_name)ifparsedisNone:returnjson.dumps({error:fSkill {skill_name} not found,available_skills:list(store.get_skill_names())})returnjson.dumps({skill_name:skill_name,instructions:parsed.content,available_files:store.list_supporting_files(skill_name),})优势每次加载都是可追踪的事件在 Langfuse 等工具中可见Agent 自主决定何时加载无需额外路由逻辑加载后的内容进入消息历史后续轮次可直接引用模式 BSkill 内容绑定到 Agent 工具Skill 中的指令会告诉 Agent 如何组合使用已有的业务工具# SKILL.md — 数据分析 Skill ## 操作流程 1. 使用 database_query 工具获取原始数据 2. 使用 data_transform 工具进行数据清洗 3. 使用 chart_generator 工具生成可视化 4. 使用 report_writer 工具生成分析报告 ### 数据库查询技巧 - 优先使用 database_query(query..., limit100) 而不是全量查询 - 对于大表先用 COUNT(*) 确认数据量Skill 不创建新工具而是教 Agent 如何更好地使用已有工具。4.3 LangGraph 实现中的关键代码# SkillStore发现和缓存classSkillStore:defscan(self)-dict[str,SkillMetadata]:只解析 YAML frontmatter不读全文 → 快速发现forskill_fileinself.skills_dir.rglob(SKILL.md):metadataparse_metadata_only(skill_file)self._metadata_cache[metadata.name]metadatadefload(self,skill_name:str)-ParsedSkill|None:首次访问时才读全文 → 懒加载parsedparse_skill_file(metadata.path)self._content_cache[skill_name]parsedreturnparsed# Agent 节点每次迭代重建 prompt 绑定工具asyncdefagent_node(state:State,runtime:Runtime[Context])-dict:ctxruntime.context# 动态构建 system prompt包含最新 Skill 目录system_messageSYSTEM_PROMPT.format(current_timedatetime.now(tzUTC).isoformat(),skill_catalogctx.skill_store.get_skill_catalog(),)# 绑定工具包括 load_skill, read_skill_file 业务工具modelload_chat_model(...)model_with_toolsmodel.bind_tools(ctx.tools)responseawaitmodel_with_tools.ainvoke([{role:system,content:system_message},*state[messages]])return{messages:[response]}4.4 动态 Docstring —— 减少无效调用defcreate_skill_tools(store:SkillStore)-list[BaseTool]:available, .join(store.get_skill_names())ornonetooldefload_skill(skill_name:str)-str:Load expert knowledge for a skill....# 动态注入可用 Skill 名称到工具描述中load_skill.__doc__(fLoad expert knowledge for a skill.\n\nfAvailable skills:{available}\n\nfArgs:\n skill_name: Exact name of the skill to load.)return[load_skill,read_skill_file]为什么有效Agent 在推理时会看到工具 schema 中列出的合法 Skill 名称大幅减少拼写错误和无效调用。4.5 自纠错机制当 Agent 调用了一个不存在的 Skill 时错误信息中包含合法名称ifparsedisNone:names, .join(store.get_skill_names())returnjson.dumps({error:fSkill {skill_name} not found,available_skills:names# ← 让 Agent 自我纠正})五、工程经验总结5.1 Skill 开发的正确顺序1. 从评测开始 ↓ 跑代表性任务观察 Agent 哪里表现不好 2. 识别能力缺口 ↓ Agent 不知道怎么用用错了工具参数填错 3. 增量构建 Skill ↓ 先写最小 SKILL.md观察效果 4. 迭代优化 ↓ 让 Agent 自己反思哪里出了错把经验写回 SkillAnthropic 的建议“Ask Claude to capture its successful approaches and common mistakes into reusable context and code within a skill.”5.2 不要构建多阶段流水线LangGraph 实践中的一个重要教训❌ 第一版规划节点 → 选择 Skill → 加载 Skill → 执行 问题规划节点选了 A Skill执行时发现需要 B Skill路由逻辑越来越复杂 ✅ 最终版一个 ReAct 循环 系统提示词中的行为引导 Agent 自己决定何时加载 Skill图只负责硬约束重试次数、终止条件关键认知LLM 本身就能链式调用工具。用图结构做软行为引导是过度工程。图应该只用于硬约束重试限制、超时、错误处理软行为交给 prompt。5.3 可观测性必须从第一天做起因为 Skill 加载是 tool call所以天然可观测Trace 示例 Turn 1: user: 帮我审查这个 PR Turn 2: load_skill(code-review) → 成功 (120ms) Turn 3: read_skill_file(code-review, checklist.md) → 成功 (45ms) Turn 4: search_code(...) → 找到 3 个文件 Turn 5: 生成审查报告可以追踪的信息哪些 Skill 被加载了哪些从没被用过 → 考虑删除加载了多少额外上下文token 消耗哪些 tool call 频繁失败→ 改进描述或实现Skill 使用和任务成功率的关联5.4 Skill vs MCP 的关系维度SkillMCP Server本质领域知识 操作指南工具接口 数据源形式Markdown 文件代码实现的协议服务加载方式Agent 通过 tool call 动态加载文本框架注册工具 schema 始终可见擅长教 Agent “怎么做和为什么”给 Agent “能做什么”关系互补Skill 教 Agent 如何更好地使用 MCP 工具5.5 上下文工程的三种长时任务策略对于 Agent 需要长时间运行的任务上下文会超窗口策略适用场景实现方式压缩Compaction需要保持对话流的任务将历史消息总结后替换保留关键决策和最近 5 个文件结构化笔记有明确里程碑的迭代开发Agent 维护 NOTES.md跨上下文重置时读取子 Agent 架构复杂研究/分析任务子 Agent 深度探索后只返回 1000-2000 token 的总结5.6 防御性工程清单□ 文件路径验证防止目录遍历 □ tool_call / ToolMessage 不变量保证即使工具崩溃也要返回 ToolMessage □ 重试检测解析异常消息中的状态码 □ Error Summary 节点重试耗尽后卸载所有工具让 LLM 只能输出文本总结 □ 动态 docstring注入合法 Skill 名称减少幻觉 □ Skill 目录缓存scan 只解析 frontmatterload 才读全文 □ 工具返回值 token 限制Claude Code 默认 25000 token 截断六、核心认知提炼认知 1Agent-Computer Interface (ACI) 与 HCI 同等重要“Think about how much effort goes into human-computer interfaces (HCI), and plan to invest just as much effort in creating good agent-computer interfaces (ACI).”工具定义、描述、参数设计不是附带工作而是 Agent 系统的核心工程。认知 2工具不是越多越好“More tools don’t always lead to better outcome.”常见错误把每个 API endpoint 包装成一个工具。正确做法按用户工作流设计工具一个工具可以封装多个 API 调用。# ❌ 粒度太细list_users → list_events → create_event# ✅ 按工作流合并schedule_event内部自动查人 → 查空闲 → 创建事件认知 3Skill 的真正价值是「捕获隐性知识」Agent 已经知道怎么用工具通过工具描述。Skill 补充的是你才知道的东西你的业务规则生产环境中的常见坑你的偏好和品味历史经验什么方案行得通、什么行不通认知 4迭代 预设不要试图一次性写出完美的 Skill。正确的做法是1. 最小可用 Skill → 跑真实任务 2. 观察 Agent 失败/困惑的地方 3. 把成功经验和失败教训写回 Skill 4. 重复让 Agent 参与优化自己的 Skill把评测 trace 喂给 Claude Code让它分析问题并改进 Skill 内容。Anthropic 的大部分工具优化建议就是这样产生的。认知 5上下文是有限资源“Context must be treated as a finite resource with diminishing marginal returns.”每个 token 都消耗 Agent 的「注意力预算」。Skill 系统的渐进式披露本质上是一种上下文节约策略——只在需要时加载需要的信息。