1. 从零理解 agent-skills:它到底是什么,能解决什么问题
第一次看到 “agent-skills” 这个词,很多人会下意识地把它当成某个具体框架或者库的名字。但如果你真的去翻一圈资料,会发现它更像是一个概念集合,而不是一个可以直接pip install的包。简单来说,agent-skills 指的是围绕智能体(Agent)构建过程中,那些可以被复用、被组合、被独立评测的能力模块。你可以把它理解成智能体的“技能树”——每个技能单独拎出来都能干活,组合起来就能完成复杂任务。
我最早接触这个概念是在做一个自动化任务编排的小项目时。当时的需求很朴素:让一个智能体帮我从一堆非结构化文本里抽取关键信息,然后按照固定格式写入表格,最后再根据表格内容生成一段摘要。听起来不复杂,但真动手写的时候问题就来了——抽取、格式化、摘要这三个环节各自需要不同的提示词策略、不同的工具调用逻辑,甚至不同的错误处理方式。如果全部塞进一个巨大的提示词里,维护成本高得离谱,改一处就崩一片。后来我把这三个环节拆成独立的“技能”,每个技能有自己的输入输出定义、自己的测试用例、自己的失败重试逻辑,整个系统才变得可控。这就是 agent-skills 这个思路最朴素的价值:把复杂智能体拆成可独立开发、独立测试、独立替换的技能单元。
那它到底适合谁来参考?我的判断是三类人。第一类是正在做智能体应用开发但被“提示词越写越长、逻辑越理越乱”困扰的工程师;第二类是想系统化评估智能体能力短板的产品或运营同学,因为技能拆解之后你才能看清楚到底是“理解能力不行”还是“工具调用不行”;第三类是对智能体感兴趣但不知道从哪下手的新手,因为从单个技能入手比直接啃一个完整智能体要友好得多。不管你属于哪一类,理解 agent-skills 的组织方式都会让你在后续实践中少走很多弯路。
还有一个容易被忽略的点:agent-skills 不只是技术层面的拆分,它还涉及评测和迭代。一个技能好不好用,不能靠感觉,得有可量化的指标。比如信息抽取技能,你可以用准确率、召回率、格式合规率来衡量;摘要技能可以用压缩比、关键信息保留度来评估。没有评测的技能就是黑盒,今天能用明天可能就崩了。所以我在后面的章节里会重点讲怎么给技能设计评测方案,这部分是很多教程里不会细说的。
2. 核心设计思路拆解:为什么要把智能体拆成技能
2.1 单体智能体的三个致命问题
在讲拆解方案之前,先说说我踩过的坑。早期我做智能体的时候,习惯把所有逻辑写在一个系统提示词里,再加上几个工具函数,觉得这样最直接。但项目稍微复杂一点,三个问题就会同时爆发。
第一个问题是调试困难。当智能体输出不符合预期时,你根本不知道是提示词里哪一段起了冲突,还是工具返回的数据格式不对,又或者是模型本身在那个上下文里就是容易出错。你只能一遍遍改提示词、一遍遍重跑,效率极低。我印象很深的一次,一个文本分类任务准确率死活上不去,查了半天才发现是提示词里一句“请尽量简洁”导致模型把某些关键类别词给省略了。这种问题在单体结构里非常隐蔽。
第二个问题是复用性差。你在 A 项目里写了一套很好用的信息抽取逻辑,到了 B 项目想复用,发现提示词里耦合了大量 A 项目特有的上下文描述,工具函数也和 A 的数据结构绑死了。结果就是复制粘贴之后大改,改完还不如重写。
第三个问题是评测粒度粗。单体智能体你只能评“整体任务完成度”,但到底是哪个环节拖了后腿?不知道。是抽取错了,还是格式化错了,还是摘要漏了关键信息?说不清楚就没法针对性优化。
2.2 技能拆解的四个原则
基于这些问题,我总结出技能拆解要遵循四个原则,这也是 agent-skills 这个思路的核心。
原则一:单一职责。一个技能只做一件事,而且要把这件事的边界定义清楚。比如“从文本中抽取实体”是一个技能,“把实体列表格式化为 JSON”是另一个技能。不要在一个技能里既抽取又格式化,否则输入输出会变得很模糊。
原则二:接口稳定。每个技能要有明确的输入结构和输出结构,最好用 schema 定义死。这样做的好处是技能之间可以自由组合,今天用 A 抽取加 B 格式化,明天换成 C 抽取加 B 格式化,B 完全不用改。我在实际项目里会用 JSON Schema 或者 Pydantic 模型来定义每个技能的输入输出,虽然前期麻烦一点,但后期组合的时候非常省心。
原则三:独立可测。每个技能都要能脱离整个智能体单独运行和测试。这意味着技能内部不能依赖全局状态,也不能依赖其他技能的中间结果。你给它一个输入,它就应该能返回一个输出,干净利落。我通常会为每个技能准备一组测试用例,覆盖正常情况、边界情况和异常情况,每次修改技能后先跑测试再集成。
原则四:失败可隔离。一个技能失败了,不应该导致整个智能体崩溃。比如抽取技能返回了空结果,格式化技能应该能处理空输入而不是直接报错。这要求每个技能都要有健壮的异常处理逻辑,并且在技能组合层要有降级策略。
2.3 技能组合的两种模式
拆解之后怎么组合?我常用的有两种模式,各有适用场景。
串行流水线模式是最常见的。技能 A 的输出直接作为技能 B 的输入,B 的输出给 C,像工厂流水线一样。这种模式适合步骤明确、依赖关系清晰的任务。比如“文本清洗 → 信息抽取 → 结构化输出 → 摘要生成”就是典型的串行流水线。优点是逻辑简单、容易追踪;缺点是任何一个环节出错都会影响后续所有环节,所以每个环节都要有质量检查。
路由分发模式适合任务类型不固定的场景。先有一个“路由技能”判断当前输入属于哪类任务,然后分发给对应的处理技能。比如用户输入可能是在问事实性问题、可能是在要求生成内容、也可能是在要求执行某个操作,路由技能先分类,再走不同的技能链路。这种模式灵活但复杂度高,路由技能本身的准确率是关键瓶颈。
我在实际项目里通常是两种模式混用:顶层用路由分发,每个分支内部用串行流水线。这样既保证了灵活性,又让每个分支的逻辑足够清晰。
2.4 为什么不用现成的框架
有人可能会问,现在不是有很多智能体框架吗,为什么不直接用?我的看法是,框架解决的是“怎么快速搭起来”的问题,但 agent-skills 解决的是“怎么搭得可维护、可评测、可迭代”的问题。框架可以用,但技能拆解的思路是你自己的,框架换了你照样能迁移。而且很多框架的抽象层次太高,你想精细控制某个技能的行为反而更麻烦。我的建议是:新手可以先用框架快速体验,但一旦项目要长期维护,一定要把技能拆解这件事想清楚,哪怕不用任何框架,纯手写技能模块也比一团乱麻的框架代码强。
3. 核心技能模块详解与实操要点
3.1 输入解析技能:把乱七八糟的输入变成结构化数据
输入解析技能是整个链路的第一环,也是最容易被低估的一环。很多人觉得输入解析就是“把用户说的话读进来”,但实际上用户输入可能是自然语言、可能是半结构化文本、可能是 JSON、可能是表格截图 OCR 后的结果,格式千奇百怪。如果第一环没处理好,后面所有技能都在垃圾数据上干活。
我在设计输入解析技能时,会先定义清楚支持的输入类型和每种类型的解析策略。比如对于自然语言输入,解析技能要完成分词、意图初判、关键信息提取;对于结构化输入,解析技能要完成字段映射和类型校验。这里的关键是不要试图用一个通用解析器搞定所有格式,而是针对每种格式写独立的解析逻辑,然后用一个分发层根据输入类型调用对应的解析器。
实操中有一个细节很重要:解析技能要保留原始输入。我见过太多项目在解析后就把原始数据丢了,结果后面发现解析错了想回溯都回溯不了。我的做法是在解析结果的 metadata 里始终保留原始输入和解析时间戳,方便排查问题。
还有一个坑是编码问题。中文输入经常遇到全角半角混用、繁简混用、特殊符号乱码等情况。我的经验是在解析技能里加一层标准化处理:统一转半角、统一转简体、去除不可见字符。这一步看起来不起眼,但能避免后面很多莫名其妙的匹配失败。
3.2 信息抽取技能:从文本中精准捞出你要的东西
信息抽取技能是 agent-skills 里最核心也最难做好的一个。它的任务是从非结构化或半结构化文本中提取出预定义的字段。听起来简单,但实际做起来你会发现模型经常“自作聪明”——你让它抽公司名,它把产品名也抽进来了;你让它抽日期,它把“大约下个月”这种模糊表述也当成日期返回了。
我的做法是用 schema 约束输出。具体来说,给抽取技能定义一个严格的输出结构,每个字段标明类型、是否必填、取值范围。然后在提示词里明确告诉模型:只输出符合 schema 的内容,不确定的字段留空而不是猜测。这样虽然会损失一些召回率,但准确率会大幅提升。在实际业务里,准确率通常比召回率更重要,因为错误信息的修复成本远高于缺失信息的补充成本。
另一个关键点是少样本示例的选择。抽取技能的提示词里通常要放几个示例,示例的质量直接决定抽取效果。我的经验是示例要覆盖三类情况:典型正例、边界情况、容易混淆的反例。比如抽日期字段,正例放“2024年3月15日”,边界情况放“今年第一季度”,反例放“大约三到五天”。这样模型才能学会区分什么是真正要抽的内容。
还有一个实操技巧是分步抽取。如果字段很多,不要一次性让模型抽完,而是按字段分组分批抽取。比如先抽实体类字段(人名、地名、机构名),再抽数值类字段(金额、日期、数量),最后抽关系类字段(谁对谁做了什么)。每批抽取的结果可以互相校验,提高整体准确率。
3.3 工具调用技能:让智能体真正能干活
工具调用技能是智能体从“会说”到“会做”的关键。没有工具调用,智能体只能生成文本;有了工具调用,它才能查数据库、调 API、写文件、发请求。但工具调用也是最容易出问题的环节,因为涉及外部依赖,不确定性很高。
我在设计工具调用技能时,会强制要求每个工具具备三个要素:清晰的描述、严格的参数 schema、明确的错误返回格式。描述是给模型看的,要说明这个工具做什么、什么时候用、有什么限制;参数 schema 是给模型约束的,每个参数的类型、是否必填、取值范围都要写清楚;错误返回格式是给调用方处理的,要区分“参数错误”“网络错误”“业务错误”等不同情况。
实操中最大的坑是模型选错工具或传错参数。我的解决办法是在工具调用技能外面加一层“参数校验和修正”逻辑。比如模型传了一个字符串类型的参数但 schema 要求是整数,校验层尝试自动转换;如果转换失败,返回明确的错误信息让模型重新生成。这层校验逻辑看起来是额外工作,但能大幅降低工具调用的失败率。
还有一个经验是工具数量不要太多。我见过一个项目给智能体挂了三十多个工具,结果模型经常选错。后来我们精简到八个核心工具,准确率立刻上去了。如果确实需要很多工具,建议用路由技能先分类,每类只暴露相关的几个工具给模型选择。
3.4 结果校验技能:别让错误结果溜到下游
结果校验技能是我强烈建议每个项目都要有的一个环节。它的任务是在技能输出进入下游之前,检查输出是否符合预期。校验的内容包括:格式是否正确、必填字段是否齐全、数值是否在合理范围、逻辑是否自洽。
我通常把校验分成两层。第一层是结构校验,用 schema 验证输出结构,这层是自动化的,不通过就直接打回重做。第二层是语义校验,检查内容层面的合理性,比如抽取的日期是否在合理时间范围内、生成的摘要是否包含了原文的关键信息。语义校验有时候需要调用模型来判断,成本高一些,但对于关键任务值得做。
校验不通过怎么办?我的策略是有限重试加降级。先让原技能重试一到两次,如果还是不行,就降级到备用方案。比如抽取技能失败,降级到基于规则的抽取;摘要技能失败,降级到截取原文前几句。降级方案的效果肯定不如正常方案,但至少能保证流程不中断。
3.5 技能间的数据传递:别让格式问题毁掉整条链路
技能拆解之后,技能之间的数据传递就成了新的问题。A 技能输出的格式和 B 技能期望的输入格式不一致,这种问题在项目里太常见了。我的做法是在技能之间加一层适配器,专门负责格式转换。适配器不承载业务逻辑,只做数据映射和类型转换。
适配器的设计要遵循“宽进严出”原则:输入尽量兼容多种格式,输出严格符合下游技能的 schema。这样上游技能升级改了输出格式,只需要改适配器,下游技能完全不用动。适配器的代码通常很简单,但能省掉大量联调时间。
还有一个细节是数据传递要带上下文。每个技能的输入输出里都应该包含一个 context 字段,记录这条数据是从哪来的、经过了哪些技能、每个技能的处理时间和结果状态。这样出问题的时候可以快速定位是哪个环节的锅。context 字段会增加一些数据体积,但在调试和监控上的价值远超这点开销。
4. 完整实操流程:从零搭建一个技能化智能体
4.1 环境准备与依赖选型
动手之前先把环境理清楚。我的建议是最小化依赖,能用标准库就用标准库,必须用第三方库的时候优先选维护活跃、文档齐全的。以下是我常用的基础依赖清单,供参考。
| 依赖类型 | 推荐选择 | 用途说明 | 选型理由 |
|---|---|---|---|
| 语言运行时 | Python 3.10+ | 主开发语言 | 类型提示完善,生态成熟 |
| 数据校验 | Pydantic | 定义技能输入输出 schema | 与类型提示结合好,校验信息清晰 |
| HTTP 请求 | httpx | 调用外部 API | 支持同步异步,超时控制方便 |
| 测试框架 | pytest | 技能单元测试 | 插件丰富,参数化测试好用 |
| 日志 | loguru | 技能运行日志 | 开箱即用,结构化输出方便 |
| 配置管理 | pydantic-settings | 管理 API 密钥等配置 | 与环境变量集成好 |
安装命令很简单,一条搞定:
pip install pydantic httpx pytest loguru pydantic-settings这里我要特别说一下为什么选 Pydantic 而不是手动写校验函数。手动校验在技能少的时候还行,技能一多,每个技能的输入输出校验逻辑散落在各处,改一个字段类型要改好几个地方。用 Pydantic 把 schema 定义集中管理,校验逻辑自动生成,维护成本低很多。而且 Pydantic 的报错信息很详细,模型返回不符合 schema 的时候能直接看到是哪个字段出了问题。
4.2 定义技能基类与接口规范
为了让所有技能有一致的接口,我会先定义一个技能基类。这个基类规定每个技能必须实现run方法,输入是一个字典,输出也是一个字典,并且要包含状态标记。
from abc import ABC, abstractmethod from typing import Any from pydantic import BaseModel class SkillResult(BaseModel): success: bool data: dict[str, Any] = {} error: str | None = None context: dict[str, Any] = {} class BaseSkill(ABC): name: str = "base_skill" version: str = "0.1.0" @abstractmethod def run(self, inputs: dict[str, Any]) -> SkillResult: pass def validate_inputs(self, inputs: dict[str, Any]) -> None: pass这个基类看起来简单,但有几个设计点值得说明。SkillResult里强制包含success字段,这样调用方不用猜技能是成功还是失败。context字段用来传递上下文信息,比如处理耗时、使用的模型版本等。validate_inputs方法留给子类实现具体的输入校验逻辑。
有了基类之后,每个技能就是一个独立的类,实现自己的run方法。技能之间不直接互相调用,而是通过一个编排层来串联。这样做的好处是技能可以独立测试,编排层也可以独立测试,职责清晰。
4.3 实现一个信息抽取技能
下面以信息抽取技能为例,展示完整的实现过程。假设我们要从一段文本中抽取“项目名称”“负责人”“截止日期”三个字段。
from pydantic import BaseModel, Field from datetime import date class ExtractInput(BaseModel): text: str = Field(..., min_length=1, max_length=5000) class ExtractOutput(BaseModel): project_name: str | None = None owner: str | None = None deadline: str | None = None class ExtractSkill(BaseSkill): name = "extract_skill" def run(self, inputs: dict) -> SkillResult: try: parsed = ExtractInput(**inputs) except Exception as e: return SkillResult(success=False, error=f"输入校验失败: {e}") prompt = self._build_prompt(parsed.text) raw_output = self._call_model(prompt) try: output = ExtractOutput(**raw_output) except Exception as e: return SkillResult(success=False, error=f"输出校验失败: {e}") return SkillResult(success=True, data=output.model_dump()) def _build_prompt(self, text: str) -> str: return f"""从以下文本中抽取项目名称、负责人、截止日期。 只输出 JSON,不要输出其他内容。不确定的字段用 null。 文本:{text}""" def _call_model(self, prompt: str) -> dict: # 实际项目中这里调用模型 API # 这里用模拟数据演示 return {"project_name": "示例项目", "owner": "A同学", "deadline": "2024-06-30"}这个实现里有几个关键点。第一,输入和输出都用 Pydantic 模型约束,任何不符合 schema 的数据都会被拦截。第二,提示词里明确要求“只输出 JSON”,减少模型输出多余文本的概率。第三,_call_model方法被单独抽出来,方便替换不同的模型或做 mock 测试。
实际使用中,_call_model返回的原始文本可能包含 markdown 代码块标记,需要先清洗再解析。这个清洗逻辑我通常会写成一个独立的工具函数,因为很多技能都会用到。
4.4 技能编排与流程串联
技能写好了,接下来要把它们串起来。我通常用一个简单的编排器来实现,支持串行和条件分支。
class Orchestrator: def __init__(self): self.skills: dict[str, BaseSkill] = {} def register(self, skill: BaseSkill): self.skills[skill.name] = skill def run_pipeline(self, steps: list[str], initial_input: dict) -> list[SkillResult]: results = [] current_input = initial_input for step_name in steps: skill = self.skills.get(step_name) if not skill: results.append(SkillResult(success=False, error=f"技能不存在: {step_name}")) break result = skill.run(current_input) results.append(result) if not result.success: break current_input = result.data return results这个编排器非常轻量,但足够应付大多数场景。它的核心逻辑就是按顺序执行技能,把上一个技能的输出作为下一个技能的输入,遇到失败就停止。实际项目中你可能还需要加超时控制、重试逻辑、并行执行等,但那是后续优化的事,先把主流程跑通最重要。
编排的时候有一个经验:在关键技能后面插入校验技能。比如抽取技能后面跟一个校验技能,检查抽取结果是否合理。校验不通过就触发重试或降级。这样虽然多了一步,但能避免错误结果污染下游。
4.5 为每个技能编写测试用例
测试是保证技能质量的关键。我通常为每个技能准备三类测试:正常输入、边界输入、异常输入。
import pytest def test_extract_normal(): skill = ExtractSkill() result = skill.run({"text": "项目名称是示例项目,负责人是A同学,截止日期是2024年6月30日。"}) assert result.success assert result.data["project_name"] == "示例项目" def test_extract_empty(): skill = ExtractSkill() result = skill.run({"text": ""}) assert not result.success def test_extract_missing_field(): skill = ExtractSkill() result = skill.run({"text": "这个项目还没有确定负责人。"}) assert result.success assert result.data["owner"] is None测试用例不用多,但一定要覆盖关键路径。我的习惯是每个技能至少五个测试用例,跑一遍不超过十秒。每次修改技能代码后先跑测试,测试过了再集成到编排流程里。这个习惯能帮你省掉大量联调时间。
5. 常见问题与排查技巧实录
5.1 技能输出格式不稳定怎么办
这是最常见的问题。同一个技能,同样的输入,跑两次输出格式可能就不一样。原因通常是模型本身有随机性,或者提示词约束不够强。
我的解决办法分三步。第一步,在提示词里用最明确的格式要求,比如“输出必须是合法的 JSON,不要包含任何解释文字,不要用 markdown 代码块包裹”。第二步,在技能代码里加一层输出清洗,用正则把可能的代码块标记、前后缀文字去掉,再尝试解析 JSON。第三步,如果解析还是失败,把原始输出和错误信息一起返回,方便排查。
如果三步都做了还是不稳定,那就考虑换模型或者降低 temperature 参数。temperature 越低,输出越稳定,但创造性会下降。对于抽取类技能,我通常把 temperature 设成 0 或 0.1。
5.2 技能之间数据对不上怎么排查
技能 A 说输出了五个字段,技能 B 说只收到三个字段,这种问题通常是数据传递过程中丢了。排查思路是从后往前查:先看技能 B 实际收到的输入是什么,再看技能 A 实际输出的什么,对比差异。
我的经验是在每个技能入口和出口都打日志,记录完整的输入输出。日志用结构化格式,方便搜索和对比。loguru 的logger.bind(skill_name=..., step=...)很好用,可以把技能名和步骤号绑定到日志里,排查的时候一目了然。
还有一个常见原因是字段名不一致。技能 A 输出project_name,技能 B 期望projectName,这种问题在跨团队协作时特别常见。解决办法是在技能之间加适配器,或者在项目初期就统一命名规范。我倾向于用 snake_case,因为 Python 生态里更常见。
5.3 某个技能频繁失败怎么优化
先定位失败原因。是输入问题、模型问题、还是外部依赖问题?我的排查顺序是:先看失败日志里的错误信息,如果是输入校验失败,检查上游技能的输出;如果是模型调用失败,检查 API 配额和网络;如果是输出校验失败,检查提示词和 schema 是否匹配。
如果定位到是模型能力问题,比如某个抽取任务模型就是做不好,那可以考虑几个方向:换更强的模型、增加少样本示例、把任务拆得更细、或者加规则兜底。我通常先试增加示例和拆细任务,这两个成本最低。如果还不行,再考虑换模型。
还有一个容易被忽略的点是技能版本管理。技能改了之后,旧版本的输出和新版本的输出可能不兼容。我的做法是给每个技能加版本号,编排层记录每个步骤用的技能版本。这样出问题的时候可以快速回滚到旧版本。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 技能输出格式不稳定 | 提示词约束弱、temperature 高 | 检查提示词和模型参数 | 加强格式约束、降低 temperature |
| 技能间数据丢失 | 字段名不一致、适配器缺失 | 对比上下游输入输出日志 | 统一命名、加适配器 |
| 技能频繁失败 | 输入质量差、模型能力不足 | 查看失败日志和错误类型 | 优化上游、增加示例、换模型 |
| 整体流程慢 | 串行步骤多、模型调用慢 | 统计每个技能耗时 | 并行化、缓存、换更快的模型 |
| 错误结果溜到下游 | 缺少校验环节 | 检查是否有校验技能 | 插入校验技能、加降级策略 |
5.5 几个我踩过的坑和对应技巧
第一个坑是过度依赖模型做格式转换。我早期让模型直接把抽取结果格式化成 JSON,结果模型经常在 JSON 里加注释或者用单引号。后来我改成让模型只输出纯文本,格式转换用代码做,稳定性立刻上去了。能用代码做的事不要让模型做,这是我在多个项目里验证过的原则。
第二个坑是技能粒度太细。有一段时间我追求极致拆解,把每个字段抽取都做成独立技能,结果技能数量爆炸,编排逻辑复杂得没法维护。后来我调整成“一个技能处理一类任务”,比如所有实体抽取放一个技能,所有数值抽取放一个技能,平衡了复用性和维护成本。
第三个坑是忽略技能的执行成本。有些技能调用大模型,单次成本不低。如果编排流程里同一个技能被反复调用,成本会快速累积。我的做法是在编排层加缓存,相同输入直接返回缓存结果。对于抽取类技能,缓存命中率通常很高,能省不少钱。
第四个坑是测试用例覆盖不够。我见过一个项目上线后才发现某个技能对空输入会崩溃,而测试用例里全是正常输入。后来我强制要求每个技能的测试用例必须包含空输入、超长输入、特殊字符输入这三种边界情况。这个习惯帮我提前发现了很多潜在问题。
6. 技能评测与持续迭代
6.1 怎么给一个技能打分
技能评测不能只看“能不能跑通”,要有量化指标。不同类型的技能指标不一样。抽取类技能看准确率和召回率,生成类技能看人工评分或自动评分,工具调用类技能看调用成功率和参数正确率。
我通常为每个技能定义一个评测集,包含几十到几百条标注数据。每次技能修改后跑一遍评测集,对比指标变化。评测集不用很大,但要有代表性,覆盖各种输入类型和边界情况。评测集的标注质量很关键,标注错了会误导优化方向。
6.2 技能迭代的节奏
我的经验是小步快跑。每次只改一个技能的一个点,改完跑评测,指标涨了就保留,跌了就回滚。不要一次性改多个技能,否则出了问题不知道是哪个改动导致的。
迭代频率取决于项目阶段。开发初期可以每天迭代,稳定期可以每周迭代。关键是每次迭代都要有记录:改了什么、为什么改、指标变化多少。这些记录在后续排查问题和做决策时非常有用。
6.3 技能复用的经验
技能复用是 agent-skills 思路的一大优势,但复用不是简单的复制粘贴。我的做法是把通用技能抽成一个独立的包,通过版本号管理。不同项目引用同一个包的不同版本,互不影响。通用技能里不包含任何项目特有的逻辑,项目特有的部分通过配置或适配器注入。
复用时还要注意输入输出兼容性。通用技能的 schema 要尽量宽松,能接受多种输入格式;输出要尽量标准,方便下游处理。如果某个项目需要特殊格式,在适配器里转换,不要改通用技能本身。
7. 一些个人体会和后续扩展方向
做了一段时间技能化智能体之后,我最大的体会是:拆解本身不是目的,可维护和可迭代才是。技能拆得再漂亮,如果维护成本没降下来、迭代速度没提上去,那就是白拆。所以每次拆解之前我都会问自己:拆完之后,改一个功能需要动几个地方?加一个能力需要写多少新代码?如果答案不理想,那就说明拆解方式有问题。
后续如果要扩展,我觉得有几个方向值得尝试。一是技能自动发现和组合,让编排层根据任务描述自动选择合适的技能组合,而不是人工写死流程。二是技能级缓存和复用,相同输入直接返回缓存结果,降低成本和延迟。三是技能市场,把通用技能沉淀下来,不同项目直接引用,减少重复开发。
最后分享一个小技巧:给每个技能写一份“使用说明”,包括这个技能适合什么场景、不适合什么场景、输入输出示例、常见错误。这份说明不用很长,但能在团队协作时省掉大量沟通成本。我现在的习惯是技能代码和说明文档放在一起,改代码的时候顺手更新说明,保持同步。