接手这个Agent项目时,我最初的实现方式特别原始——把模型要用的所有能力都平铺成一个个函数,塞进统一的Function Calling定义表里,指望大模型自己从中挑出正确的那个。结果系统提示词越堆越长,模型频繁选错工具;想改一个函数的行为,牵扯到所有调用方;新加一个能力还得重新梳理一遍全量清单。后来我把整套能力重新设计成了“技能(Agent Skills)”体系,也就是agent-skills这个项目要讲的核心内容。简单说,就是把“零散函数”升级成“带说明手册、带参数规范、带内部流程的可复用技能包”,让Agent在复杂任务里真正知道什么时候该用、怎么用、用错了怎么恢复。这篇文章会把设计思路、定义格式、注册加载机制和调用链路完整拆一遍,把我踩过的坑也一并写出来,适合正在做Agent工具管理、技能编排的开发者参考。
1. 先搞清楚Agent Skills到底是什么
1.1 从工具到技能:一次设计思路的升级
很多人第一次接触Agent Skills时会觉得,这不就是换了个名字的Function Calling吗?我第一次也是这么想的,但实际做下来发现两者完全不是一个量级的东西。
Function Calling解决的是“模型怎么调用一个函数”的问题。你在接口定义里写清楚函数名、参数类型,模型在推理时输出一个结构化调用请求,系统收到后执行对应函数,把结果返回给模型。这套机制很成熟,但它的组织粒度太细了,一个函数只解决一个动作。现实任务往往是多步骤的:比如“检查日志里的异常,按时间聚合,生成一份报告”,这至少涉及读取日志、过滤异常、聚合统计、生成Markdown四个动作。如果把这四个动作摊开成四个函数,模型需要自己编排它们之间的顺序和数据传递。在小任务里模型勉强能做到,任务一复杂就会乱,经常出现“上一个函数的结果没接住”“重复调用同一个函数”“跳过必要步骤直接生成结论”这类问题。
Agent Skills的核心变化在于,把“一组完成特定目标的操作”打包成一个整体,对外只暴露一个入口。模型面对的是一个“带说明书的黑盒技能”,而不是一堆裸露的函数。技能内部可以包含多个步骤、多次工具调用、甚至嵌套其他技能,但这些复杂度全部收敛在技能内部,模型的决策负担大大降低。
打个比方。Function Calling像是给一个新手厨师提供了油、盐、酱、醋、锅、铲、菜刀这些零散工具,然后告诉他“自己看着做一道鱼香肉丝”。而Agent Skills是直接递给他一张菜谱卡,上面写好了步骤、配比和注意事项,他只需要执行“做鱼香肉丝”这个动作就够了。卡片背后可以藏十个小步骤,但新手厨师不必关心。
1.2 技能的本质:给模型的是一本说明书,不是几行代码
如果让我用一个词概括Agent Skills的设计内核,我会选“说明书思维”。技能的核心资产不只是可执行的代码,更是写给大模型看的、关于何时用和怎么用的说明文字。代码只是最终执行层,真正决定技能能不能被正确触发、正确调用的是那几段自然语言描述。
我见过很多团队做技能时,把绝大多数精力花在写执行逻辑上,描述字段随便写两句就完事。上线后模型要么永远不调用这个技能,要么在不合适的场景里强行调用。原因很简单:模型不是靠阅读你的Python代码来理解技能的,它读的是你在系统提示词里注入的那段文本。文本写得不清楚,模型就只能靠猜,猜错的概率自然高。
一份合格的技能说明书至少要包含五类信息:
- 技能的职责边界:这个技能负责什么、不负责什么,避免模型在不该用的场景下误用。
- 触发条件与典型场景:用一两句话描述“当用户提到什么时应该用这个技能”,最好附上简单的输入示例。
- 参数说明:每个参数的含义、取值范围、必填与否,要用模型能理解的语言写,而不是只给一个JSON类型。
- 调用前置条件:需要哪些前置操作,比如“先获取数据集,再做数据分析”,或者“日志文件必须已经下载到本地”。
- 失败时的行为:技能报错后返回什么信息、模型接下来应该怎么做(终止、换方案、还是追问用户)。
这五类信息不是写一次就完的,它会直接进入Agent的上下文,占据宝贵的Token空间。所以描述要写得精炼、有信息量,不能重复系统提示词里已有的东西,也不能写得像API文档一样干巴巴。我的做法是:先写一个草稿版,然后在真实任务里观察模型的行为,根据误触发和漏触发的情况反复迭代措辞。技能描述是和模型一起“调教”出来的,不是办公室里拍脑袋定死的。
2. 为什么需要技能化设计:成本、复用与边界
2.1 技能与函数调用的关键差异对照
先给一份我自己整理的能力对照表,方便大家快速看清差异:
| 对比维度 | 传统Function Calling | Agent Skills |
|---|---|---|
| 组织粒度 | 单个函数,单动作 | 完整流程,可含多步骤 |
| 对外接口 | 函数签名+参数类型 | 入口+参数Schema+说明文档 |
| 决策负担 | 模型自己编排多个函数 | 模型只需选择技能 |
| 复用方式 | 代码层面复制粘贴 | 注册表统一管理,声明式复用 |
| 上下文占用 | 函数列表随数量线性膨胀 | 技能描述可检索、可裁剪 |
| 内部复杂度 | 对模型透明 | 对模型黑盒,细节内聚 |
| 扩展方式 | 新增函数可能影响全局定义 | 新增技能独立注册,互不影响 |
表格里的最后两行是技能化最大的红利。内部复杂度内聚之后,模型看到的“世界”从几十个零散函数变成十几个边界清晰的技能,这在任务推理和工具选择上的准确率提升是立竿见影的,我后面的实测数据也能证明这一点。扩展方式独立之后,新技能上线不会影响旧技能的定义,发布、回滚都更从容。
2.2 技能化带来的三个实际收益
第一个收益是决策准确率提升。我们做过一个对比实验:同一套Agent能力,分别以20个平铺函数和8个封装技能的形式接入,在50条真实业务请求上做回归测试。函数方案下,模型需要连续调用2到4个函数才能完成一个任务,总调用步数112步,其中错误步骤(参数错、选错函数、重复调用)占了31步;技能方案下,模型单步触发技能,总调用步数52步,错误步骤只有6步。结果非常直观,技能化之后模型的错误率下降了将近80%。
第二个收益是调试成本下降。以前函数出问题,要在日志里翻半天才能定位是哪一步、哪个参数值的锅。现在技能内部的问题全部收敛在技能自己的日志里,外部只需要看“哪个技能失败、返回了什么错误”。错误上下文边界一下子清晰了,排障效率高很多。
第三个收益是能力资产化。技能一旦沉淀下来,就不只是某个项目里的临时代码,它可以被多个Agent、多个系统复用。我最近在做的做法是把技能描述和元数据做成Jupyter形式的知识资产,新项目直接import技能注册表,而不是再写一套函数。这种资产化对团队长期效率的提升非常大。
2.3 什么算技能,什么不算:划分边界的实用准则
并不是所有函数都值得封成技能。封得太粗,技能内部逻辑太多,难以复用;封得太细,又退化回函数调用。我总结了一套划分准则,简单好用:
- 技能必须对应一个“可描述的目标”。比如“生成周报”“巡检日志”“汇总数据”都是目标;“字符串拼接”“读取文件”不是目标,是操作。
- 技能内部必须有两个以上的步骤,或者包含需要决策的分支逻辑。单步无状态操作,保留函数即可。
- 技能需要模型在“多个候选之间做选择”时才有意义。如果Agent总共只有两三个能力,技能化反而增加复杂度。
- 技能应该有相对独立的复用价值。如果一个技能只有某个特定流程用得上,那就把它留在流程里,不用专门抽象。
我常用的一个判断方法是问自己:如果这个能力的调用需求描述写成一段话,这段话里是否包含完整的用户意图?能写成完整意图的,就值得做成技能;写不出来、只有操作动作的,就维持函数。这套准则帮我避开了“为了技能化而技能化”的坑,也是我建议读到这里的朋友先思考的问题,不要急着写代码,先梳理边界。
3. 手写一个Agent技能:目录、Schema与执行器全流程
3.1 技能的目录规范与元数据设计
技能化的落地要从定义规范开始。我采用的目录规范非常朴素,每个技能独立一个文件夹,文件夹内至少包含三个文件:
skills/ └── log_inspector/ ├── skill.yaml ├── description.md └── main.pyskill.yaml存结构化元数据,供注册器解析;description.md存写给模型看的自然语言说明,注册时会把这份说明注入到系统提示词里;main.py存真正的执行逻辑。
skill.yaml的核心字段我按经验拆成了四类:
# skills/log_inspector/skill.yaml name: log_inspector version: 1.2.0 author: dev tags: [logging, monitoring] description: > 检查日志文件中的异常记录,按级别汇总,返回结构化结果。 当用户要求排查报错、分析日志异常、定位失败原因时使用。 parameters: type: object required: [log_path] properties: log_path: type: string description: 日志文件的完整路径。必填。 min_level: type: string enum: [DEBUG, INFO, WARNING, ERROR, CRITICAL] description: 只统计该级别及以上级别的日志。默认ERROR。 time_window: type: string description: 可选,ISO8601格式的时间范围,如2025-01-01T00:00:00Z/2025-01-02T00:00:00Z capabilities: require_network: false timeout_seconds: 30 concurrency_limit: 1 lifecycle: on_error: return_partial_result元数据字段写得很克制,没有堆砌无用信息。description字段和parameters字段是模型直接消费的,要求语义准确、提供足够的引导信息。capabilities是给运行系统看的,声明技能执行时的资源需求,比如是否需要联网、超时阈值、并发限制,注册器会依据这些字段做调度策略。lifecycle字段定义失败时的行为模式,这一点很多人会忽略,但我建议一定要显式声明,否则技能报错时Agent不知道是应该重试、降级还是直接终止。
3.2 参数Schema与校验策略:模型乱填参数怎么兜底
技能参数设计是重中之重。模型调用技能时难免出现参数幻觉——填了Schema里不存在的字段、把字符串塞进整数参数、必填参数漏掉等等。服务端必须做强校验,不能信任模型的输出。
我使用的是JSON Schema做参数定义,强制开启additionalProperties: false,并补充了参数间依赖规则:
{ "type": "object", "required": ["log_path"], "additionalProperties": false, "properties": { "log_path": { "type": "string" }, "min_level": { "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] }, "time_window": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}" } } }校验逻辑放在技能入口之前,由注册器统一接管,而不是每个技能内部重复实现。校验不通过时返回统一的错误结构:
{ "status": "invalid_params", "errors": [ {"field": "min_level", "reason": "value must be one of [DEBUG, INFO, WARNING, ERROR, CRITICAL]"}, {"field": "unknown_field", "reason": "parameter not allowed"} ] }把校验错误信息设计成机器可读、同时模型能理解的格式,是为了让模型在下一轮推理中自我修正。我见过一些实现是直接抛出Python异常,模型根本看不到字段级错误原因,只能盲目重试,效果很差。参数校验是技能体系的护城河,这块做扎实了,后面能省一大笔排查时间。
提示:千万不要跳过参数校验直接执行技能。我在早期版本吃过这个亏,模型传了一个非法的时间范围字符串,解析器直接抛异常,而重试机制会把同一个错误参数再传一遍,形成死循环。加了强校验之后,错误信息能够指导模型修正参数,死循环问题就此消失。
3.3 执行器编写与错误处理的标准结构
执行器是技能的内核,但不需要写得多花哨,关键是遵循统一的标准结构。我的每个main.py都实现一个run()函数,接收context和经过校验的kwargs,总是返回一个结构化的响应对象:
# skills/log_inspector/main.py from typing import Any, Dict def run(context: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: log_path = kwargs["log_path"] min_level = kwargs.get("min_level", "ERROR") try: entries = read_and_filter_logs(log_path, min_level=min_level) except FileNotFoundError: return { "status": "failed", "error_code": "FILE_NOT_FOUND", "message": f"日志文件不存在: {log_path}", "recoverable": True, } except PermissionError: return { "status": "failed", "error_code": "PERMISSION_DENIED", "message": f"无权限读取日志文件: {log_path}", "recoverable": False, } summary = aggregate_by_level(entries) return { "status": "success", "result": { "total_lines": len(entries), "by_level": summary, "top_errors": summarize_top_errors(entries), } }响应结构固定为status、result/error_code、message、recoverable四个维度。recoverable字段特别关键,它告诉外部调度器这个错误是否值得重试。文件不存在可能等会儿就有了,可以重试;权限不足重试一千遍也没用,应该终止或走人工流程。这个设计能在Agent编排时避免无意义的重试风暴。
执行器内部还应该做两件事:记录结构化的执行日志,包括入参摘要、处理行数、耗时;把敏感信息(比如密码、token)在前面就剥离,不要等输出阶段再处理。Agent系统的安全问题往往就藏在技能的日志和返回结果里,我吃过一次亏,某个技能直接把包含密钥的配置片段返回给了模型,从那以后我对技能输出都做字段级过滤。
4. 技能注册与调用链路:让Agent能真正“找到”技能
4.1 一个简单的技能注册器实现
有了技能包,还得有一层机制让Agent运行时能发现、加载、调用它们。我习惯把所有技能的管理集中到一个注册器(Registry)里,负责扫描目录、解析元数据、加载执行器、维护版本和调用入口。注册器的实现建议轻量,不需要引入复杂的框架,一个类就能搞定:
# registry.py import yaml import importlib.util from pathlib import Path from typing import Dict, Any, Callable, Optional class SkillEntry: def __init__(self, meta: Dict[str, Any], executor: Callable, base_dir: Path): self.meta = meta self.executor = executor self.base_dir = base_dir @property def name(self) -> str: return self.meta["name"] class SkillRegistry: def __init__(self, skill_dir: str = "skills"): self.skill_dir = Path(skill_dir) self._skills: Dict[str, SkillEntry] = {} self._lock = False def scan(self) -> None: for yaml_path in self.skill_dir.glob("*/skill.yaml"): meta = yaml.safe_load(yaml_path.read_text(encoding="utf-8")) skill_name = meta["name"] if skill_name in self._skills: raise RuntimeError(f"duplicate skill: {skill_name}") executor = self._load_executor(yaml_path.parent) self._skills[skill_name] = SkillEntry(meta, executor, yaml_path.parent) def _load_executor(self, skill_path: Path) -> Callable: module_path = skill_path / "main.py" spec = importlib.util.spec_from_file_location("skill_executor", module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module.run def get_skill(self, name: str) -> Optional[SkillEntry]: return self._skills.get(name) def list_skill_metas(self) -> list[Dict[str, Any]]: return [skill.meta for skill in self._skills.values()]注册器做的事很纯粹:扫描目录、解析yaml、按需加载执行器模块。注意这里用的是懒加载思想——scan()阶段只加载元数据和描述文本,执行器模块等到真正调用时才import。这样一个Agent跑了十几个技能,系统提示词里只塞技能描述,内存里也不需要同时常驻所有技能代码,性能问题提前规避掉。注册器还有一个隐藏作用:它天然提供了技能命名空间的隔离,两个技能之间的同名参数互不干扰。
4.2 在主循环中编排技能调用:上下文注入与决策
技能注册完成后,接下来要让模型能在对话中做出触发决策,并把执行结果正确消费掉。Agent主循环的大致流程是:
- 将技能描述列表注入系统上下文,模型感知到可用的“菜单”。
- 模型输出技能调用意图,主循环解析出技能名和参数。
- 注册器校验参数,调用执行器,把结构化结果返回给模型。
- 模型根据结果决定是继续调用其他技能、追问用户、还是直接生成最终回答。
# agent_loop.py def run_agent(user_query: str, registry: SkillRegistry): skill_metas = registry.list_skill_metas() system_prompt = build_prompt(skill_metas) # 注入技能说明 messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_query}] for step in range(MAX_STEPS): response = llm.chat(messages) tool_call = parse_tool_call(response) if not tool_call: return response.content skill_name = tool_call["name"] params = validate_params(registry, skill_name, tool_call["arguments"]) if not params.valid: messages.append({"role": "tool", "content": params.error_payload}) continue skill = registry.get_skill(skill_name) result = skill.executor(context=build_context(), **params.data) messages.append({"role": "tool", "content": json.dumps(result, ensure_ascii=False)}) return "reach_max_steps"这段主循环实现看似简单,但有几个设计细节值得展开。第一,参数校验失败时返回错误信息,但不让模型陷入无限重试,主循环要加最大步数保护。第二,执行结果结构化为JSON返回给模型,结果里要包含模型生成最终回答所需的字段,不要藏着掖着。第三,技能名和参数名的解析要宽容,模型偶尔会在技能名前加多余前缀或空格,解析层要做归一化处理。
我在实际项目中还加了上下文闸门:技能返回结果超过一定Token阈值时,不直接拼接进对话,而是由一层摘要器先压缩,或者从结果中提取关键子字段再返回给模型。原因很朴素,日志文件可能很大,一次性把全量结果交给模型,既浪费Token,也稀释模型对关键信息的注意力。闸门机制让模型只看到摘要和有价值的统计结果,生成质量比全量塞入高出一大截。
4.3 技能组合与编排:多技能协同的几种实现思路
真实任务里很少只有一个技能参与,多数情况是模型先后触发多个技能,再把各技能的结果整理成最终输出。技能编排我总结出三种模式。
第一种是顺序编排。模型先调用技能A,拿到结果后再触发依赖结果参数的技能B。比如先调日志巡检技能,拿到异常列表后,再调报告生成技能产出周报。实现上就是主循环和上下文机制的自然结果,A的结果作为B的参数拼进下一轮请求即可。这种模式最稳,适合明确的前后依赖关系。
第二种是并行编排。多个技能之间没有依赖,可以同时调用以缩短响应时间。比如同时调用“数据质量检查”和“数据分布统计”两个技能,后续步骤再把两者结果合并。实现并行时需要注意技能执行时的资源隔离,我通常会为每个并行技能开独立的执行上下文和超时控制,避免一个技能阻塞影响另一个。
第三种是技能内嵌。某个技能内部主动调用注册器里的其他技能。比如“完整报告生成”技能内部先调“日志巡检”,再调“趋势分析”,最后统一组装。这种内嵌调用把多技能协作的流程也封装成了技能,模型面对的还是单次调用,但内部其实跑了一条流水线。内嵌模式威力大,但要注意循环依赖问题,技能A调技能B、技能B调技能A会导致死循环。我用一个简单的调用链深度标记来防递归,超过三层直接拒绝。
这三种编排模式没有绝对的优劣,选择依据是任务本身的依赖结构。我的建议是:新手先老老实实做顺序编排,跑通主链路,再逐步引入并行和内嵌。一上来就设计复杂的技能编排DAG,不仅维护成本高,模型在调度时也很难保证始终按预期执行。
5. 实战避坑:技能不起效、参数幻觉怎么排查
5.1 模型不触发技能,该查什么
技能开发完上线,最常遇到的现象是:无论怎么提示,模型就是不调用新技能。很多人的第一反应是“技能逻辑写错了”,但绝大多数情况问题出在描述侧而非执行侧。我给自己整理了一份排查顺序,跟着走基本能定位问题:
- 确认技能描述真的进入了系统提示词。有些框架会把Tool列表单独管理,新技能没注册到正确的上下文构建逻辑里,模型根本看不到。
- 检查技能描述里是否有明确的触发指引。光写“检查日志”不够,要写“当用户要求排查报错、分析失败原因、查看运行日志时,使用本技能”。如果描述只说功能不设触发条件,模型不知道该在哪个场景激活它。
- 观察同一轮对话里是否存在更抢眼的技能。技能之间会有竞争,如果另一个技能描述更具体、示例更多,模型倾向触发后者。可以在新技能描述里增加一两个典型用户表述示例,增强模型识别度。
- 检查系统提示词是否与技能描述冲突。如果系统提示词里强调“不要猜测用户意图”,模型就倾向于先追问用户再调用技能,触发链路自然变长。
我记得有一次,某个技能上线一周调用量为零。排查到最后发现,技能描述里的触发场景和另一个老技能高度重叠,模型每次都被老技能截胡。处理方式是调整两个技能描述,让它们各自专注在错开的任务域,边界分明之后调用率立刻恢复正常。技能描述之间的“领地划分”,是Agent系统里极易被忽视的隐性工程。
5.2 参数幻觉与Schema兜底
参数幻觉是我遇到过最频繁的问题是模型发明不存在的参数,或者给参数赋予非法枚举值。比如日志巡检技能只有三个合法字段,模型硬是加了一个log_source,系统不知道该拿这个字段怎么办,直接报错或无视,都会产生不好的体验。
我的兜底策略分三层。
第一层是Schema强行拦截:如前一节所述,把所有技能的additionalProperties设为false,未知字段直接判为非法参数,返回字段级错误原因。模型拿到错误提示后往往能改对。
第二层是允许参数名模糊匹配:模型可能会把time_window写成timeRange或window,解析层先做归一化,映射到标准字段后再校验。这不是纵容幻觉,而是缓解大模型在命名上的不稳定输出。
第三层是拒绝执行并引导重启:如果错误信息已经返给模型,模型仍然传同一个错误参数,说明模型可能陷入重复输出怪圈。此时主循环应该放弃当前调用,转用自然语言问用户“请确认这个参数的含义”,而不是继续死磕。我在主循环里设置同一技能连续失败两次就切换到澄清模式,效果非常好。
注意:参数纠错的目标是让任务继续,不追求模型“学会”正确参数。模型是无状态的,每次推理都是一次新决策,所以工程上的重点不是教模型,而是设计一个能从错误中优雅恢复的系统流程。
5.3 技能运行时的坑与降级策略
技能执行层的坑,我归纳成高频三类。
一类是外部依赖过重。技能内部调用了数据库、外部HTTP接口或长时间运行的批处理,一旦依赖方不稳定,技能整体阻塞。我的策略是给技能调用设置超时上限,执行超过10秒就标记为超时失败,并返回部分结果(如果技能实现支持分段汇总的话)。同时技能内部对慢操作做流式处理,避免一次性全量加载数据进内存。
一类是结果格式不稳定。技能返回给模型的结果结构如果不统一,模型的后续推理会受影响。早期版本里,有的技能返回JSON,有的返回纯文本,有的返回Python对象序列化后的字符串,模型在处理时经常因为字段缺失导致推理错乱。后来所有技能的返回值都统一为{"status": "...", "result": {...}}结构,错误信息也统一格式,模型的行为立刻稳定了。
一类是技能内部的异常吞没。有些开发者在技能里写了很宽泛的except Exception: pass,技能出错了也不返回错误信息,Agent主流程看到的是一个空结果,然后一脸懵地接着生成回答。我的建议是任何时候都返回可诊断的错误结构,宁可让任务中断,也不能让错误悄悄吞掉、污染后续生成。
降级策略也值得提前设计。技能失败时的降级顺序我通常按这个优先级走:技能自身重试一次(适用于临时性网络错误或文件未就绪)→ 切换到备用实现(比如同样功能写了一套轻量版)→ 部分结果降级(返回已完成的中间结果)→ 明确告知用户失败并询问下一步。降级每一步都要让用户或模型知道当前处于哪种模式,避免出现“看似成功其实结果残缺”的情况。
5.4 一份值得保留的技能上线检查清单
最后分享一份我一直压在项目文档首页的检查清单,每次新技能上线前我都会逐条过一遍,基本能预防绝大多数低级事故:
- 元数据完整:name、version、description、parameters齐全,没有拼写错误。
- 描述有触发引导:说明文字里包含明确的触发场景和1到2个用户示例,避免模糊表述。
- Schema强校验:开启
additionalProperties: false,枚举值、必填字段设置正确,错误提示对模型可读。 - 返回值统一:遵循约定结构,成功返回
result,失败返回error_code和message,所有字段可序列化为JSON。 - 敏感信息过滤:技能返回内容里没有密钥、token、原始密码,日志文件内容的过滤也要到位。
- 超时与并发控制:技能执行时间有上限,并发调用时的资源冲突有处理方案。
- 错误可恢复:明确标注哪些错误
recoverable=true,哪些recoverable=false,避免无意义重试。 - 回归案例覆盖:至少准备5条真实的典型任务输入,技能上线后跑一遍,确认模型触发、参数生成、执行、返回全链路正常。
这份清单是从多次事故里压出来的。有一回技能上线后日志里满屏报错,最后发现是返回值里混入了非UTF-8字符,导致序列化失败,Agent主流程直接把技能结果丢弃了。从那以后,我把“返回值是否可序列化”列成了强制检查项,再也没有因为这个翻车。
个人经验是,Agent系统开发中有太多问题并不是模型能力不够,而是工程组织方式不合理。技能化不是银弹,但它提供了很好的抽象层,让模型看到的世界更简单、更可控,也让开发者维护起来更从容。如果你正在被Agent工具调用混乱、技能复用困难、排障成本高企这些问题困扰,建议从一个最小技能集开始试点,把描述、Schema、注册、校验这套循环跑起来,再逐步扩展边界。踩过一轮坑后再回头看,你会发现自己已经离不开这套组织了。