1. 从"会写代码"到"会指挥AI写代码"的认知转变
Skill框架这个词最近在开发者圈子里被反复提起,但很多人第一次听到时的反应是:这不就是把提示词包装了一下吗?我一开始也这么想,直到在一个真实项目里被狠狠教育了一顿。
当时的情况是这样的:我需要用AI辅助完成一个数据处理模块,功能不复杂,大概涉及文件读取、字段清洗、格式转换、异常记录四个环节。按照以往的习惯,我打开对话窗口,把需求一股脑描述了一遍,AI也确实给出了代码。但问题来了——第一版代码跑不通,报了个字段类型不匹配的错;我把错误信息贴回去,它改了一版,能跑了,但输出格式和下游系统对不上;我再提要求,它又改,结果把之前能跑的部分改坏了。来回折腾了七八轮,代码越改越乱,最后我干脆自己重写了。
这个经历让我意识到一个关键问题:AI编程的核心瓶颈不在于模型能不能写出代码,而在于你能不能把复杂任务拆解成AI能稳定执行的单元,并且让这些单元之间有序协作。Skill框架要解决的,正是这个问题。
所谓Skill框架,你可以把它理解成一套"给AI编程助手定制的技能说明书体系"。它不是某个具体的软件产品,而是一种组织AI编程工作流的方法论和配套结构。核心思路是:把一个大任务拆成若干个独立的、职责单一的"技能单元",每个技能单元有明确的输入、输出、约束条件和验证标准,然后通过一个调度层把这些技能串起来,形成一个可复用、可调试、可迭代的编程工作流。
打个比方,传统用AI写代码就像你去餐厅点菜,跟服务员说"给我来个好吃的",厨师凭感觉做,做出来合不合口味全看运气。而Skill框架相当于你把菜谱写清楚了——主料是什么、辅料是什么、火候怎么控制、出锅标准是什么——厨师照着做,每次出品都稳定。
这套框架适合谁用?我的判断是三类人收益最明显:一是经常用AI辅助开发但总觉得"差口气"的独立开发者;二是团队里需要统一AI编程规范的技术负责人;三是虽然不写生产代码,但需要用AI处理大量结构化任务的数据分析人员。如果你只是偶尔让AI写个正则表达式或者解释一段代码,那确实用不上Skill框架,直接对话就够了。
接下来我会从框架的核心结构、搭建步骤、实战案例、常见坑和进阶技巧几个维度,把Skill框架这件事讲透。内容基于我在实际项目中的使用经验,涉及具体操作的地方会给出可直接参考的配置和代码。
2. Skill框架的骨架:技能单元、调度层与上下文管理
2.1 技能单元到底长什么样
技能单元是Skill框架的最小执行粒度。一个设计良好的技能单元,本质上是一份结构化的指令文档,它告诉AI三件事:你现在扮演什么角色、你要完成什么任务、你完成后要交出什么格式的结果。
我通常用一个JSON或YAML格式的配置文件来定义一个技能单元。以"数据字段清洗"这个技能为例,它的定义大概是这样:
skill_name: field_cleaner role: 数据清洗工程师 task: 对输入的CSV数据执行字段标准化处理 input: type: csv_string required_fields: [name, phone, email, register_date] output: type: json_array schema: - field: name rule: 去除首尾空格,统一为中文全角 - field: phone rule: 去除所有非数字字符,校验11位长度 - field: email rule: 转为小写,校验包含@符号 - field: register_date rule: 统一为YYYY-MM-DD格式 constraints: - 不修改原始数据行数 - 异常数据单独记录到errors数组 - 不猜测缺失字段的值这份定义看起来简单,但每一条都有讲究。role字段决定了AI的"思维模式"——你让它扮演数据清洗工程师,它就会更关注数据质量而不是代码优雅度。input和output的严格定义是保证技能可组合的关键,因为下一个技能单元需要知道上一个技能会吐出什么格式的数据。constraints则是最容易被忽略但最重要的部分,它划定了AI的自由度边界,防止它"自作主张"。
我踩过的一个坑是:早期定义技能时没写constraints,结果AI在处理手机号时,遇到一个10位数的号码,它自动补了一位"0"在前面凑成11位。从代码逻辑上看它"帮了忙",但从业务角度看这是灾难——你凭空造了一个不存在的号码。后来我加了一条约束:"遇到格式不符的数据,标记为异常并跳过,禁止自动修正",这个问题就再没出现过。
2.2 调度层:让技能按顺序干活
单个技能单元再强,也只能完成一个原子任务。真正的价值在于把多个技能串起来。调度层就是干这个的。
最简单的调度方式是线性流水线:技能A的输出直接作为技能B的输入,依次传递。比如数据处理场景:文件读取 → 字段清洗 → 格式转换 → 异常汇总,四个技能首尾相连。
但实际项目往往比这复杂。你可能需要条件分支——如果数据量小于1000行走快速通道,大于1000行走分批处理通道;也可能需要并行执行——字段清洗和格式校验可以同时进行,最后合并结果。
我的做法是用一个轻量的调度脚本(Python或JavaScript都行)来管理技能的执行顺序。核心逻辑不复杂,伪代码大概是这样:
def run_pipeline(input_data, skills_config): result = input_data context = {} # 存储中间状态和元信息 for skill in skills_config: if skill.condition and not eval(skill.condition, context): continue result = invoke_ai_skill( skill_name=skill.name, input_data=result, context=context ) if result.status == "error": handle_error(skill, result, context) break context[skill.name + "_output"] = result.data return result这里有个关键设计:context对象。它记录了每个技能执行后的输出和元信息,后面的技能可以引用前面技能的结果。比如"异常汇总"技能需要知道"字段清洗"阶段产生了哪些异常记录,这些信息就存在context里。
注意:调度层不要做得太重。我见过有人把调度层写成了一个几百行的状态机,结果调试成本比写业务代码还高。调度层的职责应该只有三件事:按顺序调用技能、传递数据、处理异常。业务逻辑全部下沉到技能单元里。
2.3 上下文管理:别让AI"失忆"
用AI编程最让人头疼的问题之一是上下文窗口限制。当一个任务涉及多个技能串联时,前面的对话历史会越来越长,最终超出模型的上下文容量,导致AI"忘记"前面的指令。
Skill框架的解法是:每个技能单元独立调用,不依赖对话历史。技能A执行完毕后,只把它的输出结果传给技能B,而不是把整个对话记录都带过去。这样做的好处是上下文始终干净,每个技能都在一个"新鲜"的环境里执行,不会被无关信息干扰。
但这也带来一个新问题:技能B可能需要知道一些技能A执行时的背景信息,比如"这批数据来自哪个业务系统""用户对日期格式有什么特殊要求"。这些信息不能靠对话历史传递,得显式地放在技能的输入参数里。
我的做法是维护一个"全局上下文对象",它只包含必要的元信息,不包含完整的对话记录。每次调用技能时,把这个全局上下文和上一个技能的输出一起传进去。全局上下文的内容控制在200字以内,只放最关键的约束和背景。
{ "global_context": { "data_source": "CRM系统导出", "date_format_requirement": "YYYY-MM-DD", "batch_id": "20240115-001", "strict_mode": true }, "previous_output": { "cleaned_records": 1523, "errors": 12 } }这个设计看起来简单,但它解决了AI编程中一个非常实际的问题:如何在多步骤任务中保持一致性。没有全局上下文,技能B可能把日期格式改成"DD/MM/YYYY",因为它不知道用户要求的是"YYYY-MM-DD"。有了全局上下文,每个技能都能看到统一的约束条件,输出自然就一致了。
3. 搭建一套可用的Skill框架:从零到跑通
3.1 先想清楚哪些任务值得做成技能
不是所有AI编程任务都值得封装成技能。我的判断标准是三个"如果":如果这个任务你会反复做、如果这个任务的输出格式有严格要求、如果这个任务出错后排查成本很高——满足任意两条,就值得做成技能。
举个例子。让AI"解释一段代码"这件事,虽然经常做,但输出格式没有严格要求,出错也无所谓,所以不值得做成技能,直接对话就行。但"根据数据库表结构生成CRUD接口代码"这件事,反复做、格式要求严格(要符合团队代码规范)、出错后排查成本高(接口不通会影响前端联调),就非常值得做成技能。
我通常会先列一个"任务清单",把日常用AI编程的场景都写下来,然后按上述标准打分。得分高的优先做成技能,得分低的继续用对话方式处理。不要一上来就想搭建一个大而全的框架,从两三个高频技能开始,跑通了再扩展。
3.2 技能定义的五个必备字段
经过多次迭代,我总结出一个技能定义至少需要包含五个字段,缺一个都会在后续使用中出问题。
第一个是角色设定。不要写"你是一个程序员"这种泛泛的描述,要具体到领域和风格。比如"你是一个注重代码可读性的Python后端工程师,遵循PEP8规范,偏好使用类型注解"。角色设定越具体,AI的输出越符合预期。
第二个是任务描述。用动词开头,一句话说清楚要做什么。避免模糊词汇,比如"优化"这个词就很模糊——是提升性能还是改善可读性?要说"将时间复杂度从O(n²)降低到O(n log n)"。
第三个是输入规范。明确输入数据的类型、格式、必填字段。如果输入是代码,要说明是什么语言的代码、大概多少行、有没有依赖。如果输入是结构化数据,要给出示例。
第四个是输出规范。这是最关键的。输出规范要精确到字段级别,包括字段名、类型、格式、取值范围。如果输出是代码,要说明代码结构、命名规范、注释要求。
第五个是约束条件。列出AI不能做的事情。比如"不修改输入数据的行数""不引入新的第三方依赖""不生成测试数据"等。约束条件的作用是防止AI"过度发挥"。
下面是一个完整的技能定义示例,场景是"根据API文档生成前端请求函数":
skill_name: api_to_frontend role: 前端工程师,熟悉TypeScript和Axios,注重类型安全 task: 根据API文档描述生成TypeScript请求函数 input: type: text content: API文档片段,包含URL、方法、请求参数、响应结构 output: type: typescript_code requirements: - 使用Axios实例,baseURL从环境变量读取 - 请求参数和响应数据都要有TypeScript类型定义 - 函数名使用驼峰命名,以fetch开头 - 包含错误处理,抛出结构化错误对象 constraints: - 不生成mock数据 - 不修改API文档中定义的字段名 - 不添加文档中没有的请求头 - 如果文档信息不完整,在代码注释中标注TODO这份定义用了大概两分钟写完,但它能让AI生成的代码质量提升一个档次。关键在于constraints里的最后一条——"如果文档信息不完整,在代码注释中标注TODO"。没有这条约束,AI遇到信息缺失时会自己编一个合理的值填进去,你拿到代码后还得逐行核对哪些是真实的、哪些是AI猜的。有了这条约束,所有不确定的地方都会被明确标注出来,省去了大量核对时间。
3.3 调度脚本怎么写才不添乱
调度脚本的复杂度要控制住。我的原则是:能用20行写完的,绝不写50行。下面是一个实际在用的调度脚本核心逻辑,用Python写的,大概30行:
import json import yaml class SkillRunner: def __init__(self, config_path): with open(config_path) as f: self.config = yaml.safe_load(f) self.context = {} def run(self, initial_input): current = initial_input for step in self.config["pipeline"]: skill = self.load_skill(step["skill_name"]) if "condition" in step: if not self.check_condition(step["condition"]): continue result = self.invoke(skill, current) if result.get("status") == "error": if step.get("on_error") == "skip": self.context[f"{step['skill_name']}_error"] = result continue else: raise SkillExecutionError(result) current = result["data"] self.context[step["skill_name"]] = result return current def invoke(self, skill, input_data): # 这里对接具体的AI调用接口 # 把skill定义和input_data组装成prompt发给模型 pass这个脚本的核心只有run方法里的那个循环。它做了四件事:按顺序遍历技能、检查执行条件、调用技能、处理错误。没有多余的设计模式,没有复杂的继承体系。
提示:调度脚本里的错误处理策略要提前想清楚。我的经验是,对于"数据清洗"这类技能,出错时可以选择跳过并记录;对于"代码生成"这类技能,出错时应该立即中断,因为后续技能依赖它的输出,带着错误往下走只会越错越离谱。
3.4 跑通第一个技能:从最简单的开始
搭建框架最忌讳一上来就搞复杂流程。我的建议是先用一个最简单的技能跑通全链路,确认调度脚本、AI调用、结果解析这几个环节都通了,再逐步增加技能。
最简单的技能是什么?我通常选"文本格式转换"——比如把一段非结构化的文本转成JSON格式。这个任务足够简单,AI几乎不会出错,适合用来验证框架的基础设施。
具体操作步骤:
- 创建一个
skills目录,在里面新建text_to_json.yaml,写入技能定义。 - 创建一个
pipeline.yaml,定义只有一个步骤的流水线。 - 写一个测试脚本,输入一段文本,调用
SkillRunner,看输出是不是符合预期的JSON。 - 如果跑通了,再加第二个技能,比如"JSON字段校验",让它接收第一个技能的输出。
这个过程大概需要半小时到一小时,但它是值得的。我见过有人跳过这一步,直接搭建包含十几个技能的复杂流水线,结果某个环节出问题后完全不知道从哪里排查。先用一个技能验证基础设施,再逐步扩展,这是最稳妥的路径。
4. 实战拆解:用Skill框架完成一个真实的数据处理任务
4.1 任务背景与技能拆分思路
假设你接到一个需求:从某个业务系统导出了一批客户数据,格式是CSV,但数据质量很差——姓名有全角有半角、手机号有带区号的有不带的、日期格式五花八门、还有重复记录。你需要清洗这批数据,转换成标准JSON格式,输出给下游系统使用。
这个任务如果直接丢给AI对话完成,大概率会经历"生成代码→运行报错→贴错误→改代码→又报错"的循环。但用Skill框架,我们可以把它拆成四个技能:
- 技能A:CSV解析与预览。读取CSV内容,识别列名和数据类型,输出数据概览(行数、列数、每列的非空数量、疑似异常值示例)。
- 技能B:字段标准化。对姓名、手机号、日期三个字段执行标准化处理,输出清洗后的数据和异常记录。
- 技能C:去重与合并。根据手机号或邮箱识别重复记录,保留最新的一条,输出去重后的数据。
- 技能D:JSON格式化输出。将清洗后的数据转换为目标JSON结构,包含元信息(处理时间、记录数、异常数)。
每个技能只做一件事,输出格式严格定义。这样即使某个环节出问题,也能快速定位是哪个技能的逻辑有误,而不是在一大段代码里大海捞针。
4.2 技能B的详细定义与调试过程
技能B是整个流程中最复杂的一环,因为它涉及多个字段的不同处理规则。我最初的定义是这样的:
skill_name: field_standardizer role: 数据清洗专家 task: 对客户数据的姓名、手机号、日期字段执行标准化 input: type: json_array fields: [name, phone, register_date] output: type: json_object schema: cleaned_data: json_array errors: json_array constraints: - 姓名统一为半角中文,去除首尾空格 - 手机号去除所有非数字字符,长度必须为11位 - 日期统一为YYYY-MM-DD格式 - 无法标准化的数据记录到errors数组,不中断处理第一次跑的时候,发现日期处理有问题。原始数据里有"2024年1月15日""2024/01/15""15-01-2024"三种格式,AI把前两种都正确转换了,但第三种"15-01-2024"被它理解成了"2024年1月15日"——实际上这个数据源里"15-01-2024"表示的是"2024年1月15日",但AI的判断逻辑是"日月年"格式,转换成了"2024年15月01日",明显错误。
这个问题暴露了技能定义的一个缺陷:没有明确日期的解析规则。我修改了约束条件,增加了一条:"日期格式按以下优先级解析:YYYY-MM-DD > YYYY/MM/DD > DD-MM-YYYY > YYYY年MM月DD日。如果无法确定,记录到errors数组。"
修改后重新跑,日期处理正确了。但这个调试过程让我意识到:技能定义不是一次写完就完事的,需要在真实数据上反复测试和修正。每遇到一个边界情况,就把处理规则补充到约束条件里。跑上十几轮之后,技能定义会变得非常完善,后续再遇到类似数据就能稳定处理。
4.3 技能间的数据传递格式设计
技能之间的数据传递格式非常关键。格式设计得不好,会导致信息丢失或者解析困难。我的经验是:统一使用JSON作为技能间的数据交换格式,并且每个技能的输出都包含一个固定的"信封"结构。
信封结构包含四个字段:
{ "status": "success", "data": { ... }, "errors": [ ... ], "metadata": { "skill_name": "field_standardizer", "execution_time_ms": 1250, "input_count": 1523, "output_count": 1511 } }status表示执行状态,只有success和error两种值。data是技能的核心输出。errors记录处理过程中的异常。metadata包含执行信息,方便排查问题和监控流程。
这个信封结构的好处是:调度层可以统一处理所有技能的返回结果,不需要为每个技能写不同的解析逻辑。如果status是error,调度层就知道这个技能执行失败了;如果errors数组非空,调度层可以选择记录日志或者触发告警。
4.4 完整流水线的运行与验证
四个技能都定义好之后,把它们串成流水线:
pipeline: - skill_name: csv_parser on_error: stop - skill_name: field_standardizer on_error: stop - skill_name: deduplicator on_error: skip - skill_name: json_formatter on_error: stop注意第三个技能deduplicator的on_error设为了skip。原因是去重逻辑即使出错,也不应该中断整个流程——最坏的情况是输出了包含重复记录的数据,下游系统可以再做一次去重。但其他三个技能如果出错,后续步骤就没法继续了,所以设为stop。
运行完整流水线后,我对比了手工处理和Skill框架处理的差异。手工处理(用AI对话方式)大概花了40分钟,中间经历了5次报错和修改。Skill框架处理花了15分钟搭建技能定义,之后每次运行只需要2分钟。如果这个任务需要反复执行(比如每天处理一批新数据),Skill框架的优势会非常明显。
注意:Skill框架的收益与任务重复次数成正比。如果这个数据处理任务只做一次,搭建框架的时间可能比直接对话还长。但如果每周都要做一次,第二次开始就是纯收益了。
5. 踩过的坑与对应的解决方案
5.1 技能粒度太粗导致调试困难
我最初设计技能时,倾向于把相关功能合并到一个技能里。比如把"字段清洗"和"格式转换"放在同一个技能中,理由是它们都是数据处理,放在一起可以减少技能数量。
这个决定让我在调试时吃了大亏。有一次输出格式不对,我花了很长时间才定位到问题出在"格式转换"部分的一个日期格式判断上。如果这两个功能是分开的技能,我只需要检查"格式转换"技能的定义和输出,排查范围会小很多。
后来我调整了策略:一个技能只做一件事,如果描述技能任务时需要用到"和"字,就说明应该拆成两个技能。比如"清洗和转换"应该拆成"清洗"和"转换"两个技能。虽然技能数量变多了,但每个技能的定义更简单、调试更容易、复用性也更强。
5.2 约束条件写得太宽松导致AI自由发挥
早期我写约束条件时比较随意,经常写"尽量保持数据原样"这种模糊的表述。结果AI的理解是"尽量",也就是说它认为在某些情况下可以修改数据。
最典型的一次是处理地址数据。原始数据里有"北京市朝阳区XX路123号"和"北京朝阳XX路123号"两种写法。我在约束里写了"尽量统一地址格式",AI把第二种也改成了"北京市朝阳区XX路123号"。从数据清洗的角度看这没问题,但业务方后来反馈说,第二种写法是他们系统里标记"非标准地址"的方式,不应该被自动修正。
这件事的教训是:约束条件必须用"必须""禁止""只能"这类明确的词汇,不能用"尽量""建议""最好"这类模糊词汇。修改后的约束是:"地址字段只做去除首尾空格处理,禁止修改地址内容中的任何字符。"这样AI就不会自作主张了。
5.3 上下文丢失导致技能间数据不一致
前面提到过上下文管理的重要性,但实际使用中还是踩了一个坑。有一次,技能A输出的日期格式是"2024-01-15",技能B在处理时把它改成了"2024/01/15",原因是技能B的角色设定里写了"偏好使用斜杠分隔的日期格式"。
问题出在技能B的角色设定上。我写那个设定时想的是"让AI输出更符合阅读习惯",但忽略了它会影响数据处理的一致性。后来我把所有数据处理类技能的角色设定统一改为"严格遵循输入数据的格式约定,不主动修改任何格式"。
这个坑的根源是:角色设定不仅影响AI的"态度",还会影响它的"行为偏好"。如果你让AI扮演一个"注重代码美观"的角色,它可能会为了美观而修改数据格式。所以在数据处理场景下,角色设定要强调"保守"和"严格遵循规范"。
5.4 错误处理策略不当导致流程中断
最初我在调度脚本里对所有技能都设置了on_error: stop,理由是"出错就应该停下来检查"。但实际运行中发现,有些错误是预期内的——比如数据清洗时遇到几条格式异常的数据,这是正常情况,不应该中断整个流程。
后来我根据技能的性质区分了错误处理策略:
| 技能类型 | 错误处理策略 | 理由 |
|---|---|---|
| 数据读取类 | stop | 读不到数据后续没法进行 |
| 数据清洗类 | skip | 个别数据异常不影响整体 |
| 数据转换类 | stop | 转换失败说明格式定义有问题 |
| 数据输出类 | stop | 输出失败说明目标不可达 |
| 日志记录类 | skip | 日志失败不影响主流程 |
这个策略表是我在实际运行中逐步总结出来的,不一定适用于所有场景,但提供了一个思考框架:判断一个技能出错时应该中断还是跳过,关键看它的输出是否是后续技能的必需输入。如果是必需输入,就中断;如果不是,就跳过并记录。
5.5 技能版本管理混乱
当技能数量增加到十几个之后,我遇到了版本管理的问题。有时候我修改了一个技能的定义,但忘记更新调度脚本里的引用,导致运行的是旧版本的技能。还有一次,两个不同的流水线引用了同一个技能,但我只在一个流水线里测试了修改后的版本,另一个流水线就出了问题。
解决方案是给每个技能定义加上版本号,并且在调度脚本里显式指定版本:
pipeline: - skill_name: field_standardizer version: "1.2" on_error: stop同时,每次修改技能定义时,在文件头部记录修改日志:
# 修改日志 # 2024-01-10 v1.0 初始版本 # 2024-01-12 v1.1 增加日期格式优先级规则 # 2024-01-15 v1.2 修复手机号长度校验逻辑这个做法看起来有点笨,但它避免了"改了A流水线的技能,B流水线莫名其妙出问题"的情况。技能版本管理不需要复杂的工具,一个版本号加一个修改日志就够了。
6. 让Skill框架真正好用的几个进阶思路
6.1 技能的市场化复用
当你积累了一定数量的技能之后,会发现很多技能在不同项目里都能用。比如"CSV解析""JSON格式化""日期标准化"这些技能,几乎每个数据处理项目都会用到。
我的做法是建立一个"技能库"目录,把所有通用技能放在里面,项目专用的技能放在项目目录下。调度脚本支持从多个目录加载技能,优先使用项目目录下的同名技能(覆盖通用技能)。
skills/ common/ csv_parser.yaml json_formatter.yaml date_standardizer.yaml projects/ project_a/ field_standardizer.yaml # 项目专用,覆盖common中的同名技能 project_b/ api_to_frontend.yaml这个结构的好处是:通用技能只需要维护一份,项目专用技能可以自由定制。当通用技能更新时,所有引用它的项目都能受益。
6.2 用AI来生成技能定义
写技能定义本身也可以借助AI。我的做法是:先用自然语言描述任务需求,让AI生成一份技能定义的初稿,然后我再根据实际需求修改约束条件和输出规范。
比如我可以对AI说:"帮我写一个技能定义,功能是根据数据库表结构生成TypeScript接口类型。角色是前端工程师,输入是SQL建表语句,输出是TypeScript代码,约束是不引入第三方类型库、字段名保持与数据库一致。"
AI会生成一份结构完整的技能定义,我只需要检查约束条件是否完整、输出规范是否精确。这个过程能把写技能定义的时间从十几分钟缩短到两三分钟。
但要注意:AI生成的技能定义不能直接使用,必须经过实际测试和修正。AI不知道你的具体业务规则,它生成的约束条件往往是通用性的,缺少针对你场景的特殊处理。比如它不会知道你的系统里"手机号字段可能包含国际区号"这种细节,需要你在测试中发现并补充。
6.3 技能执行日志的分析与优化
每次技能执行都应该记录日志,包括输入数据量、输出数据量、执行时间、错误数量。这些日志积累一段时间后,可以用来分析哪些技能是瓶颈、哪些技能经常出错。
我维护了一个简单的日志分析脚本,每周跑一次,输出三个指标:
- 技能执行时间排行:找出耗时最长的技能,考虑是否可以优化输入数据量或者拆分技能。
- 技能错误率排行:找出出错频率最高的技能,优先完善它的约束条件。
- 技能调用频率排行:找出最常用的技能,优先优化它的执行效率。
这个分析帮我发现了一个问题:某个"地址标准化"技能的执行时间特别长,原因是它的输入数据量太大(每次处理上万条地址)。后来我把这个技能拆成了"地址预筛选"和"地址标准化"两个技能,预筛选技能先用简单规则过滤掉明显不需要标准化的地址,只把复杂的地址传给标准化技能。拆分后整体执行时间减少了60%。
6.4 技能框架与版本控制系统的结合
技能定义文件应该纳入版本控制系统(如Git),和代码一起管理。这样做有三个好处:一是可以追踪技能定义的修改历史,知道每个约束条件是什么时候加的、为什么加的;二是可以在不同分支上测试不同的技能版本;三是团队协作时,每个人都能看到技能的最新定义。
我的做法是在项目根目录下建一个skills目录,和src目录平级。每次修改技能定义都提交一次commit,commit message写清楚修改了什么、为什么修改。比如:"field_standardizer: 增加日期格式优先级规则,修复DD-MM-YYYY解析错误"。
这样当某个技能出问题时,可以通过Git历史快速定位到是哪次修改引入的。我遇到过好几次"技能昨天还好好的,今天就不对了"的情况,都是通过Git历史发现是某次约束条件的修改导致的。
6.5 什么情况下不该用Skill框架
说了这么多Skill框架的好处,也得说说它的适用边界。根据我的经验,以下三种情况不适合用Skill框架:
第一种是探索性任务。比如你还不确定要用什么技术方案,需要和AI反复讨论、尝试不同的思路。这种场景下,固定的技能定义反而会限制AI的发挥,直接对话更合适。
第二种是一次性任务。如果某个任务只做一次,以后不会再重复,搭建技能框架的时间可能比直接完成任务还长。这时候直接用对话方式让AI生成代码,手动调整一下就行。
第三种是高度依赖上下文的任务。有些任务需要AI理解大量的背景信息,比如"根据这个项目的代码风格重构这个模块"。这种任务很难用结构化的技能定义来描述,因为"代码风格"这种东西很难用几行约束条件说清楚。
判断标准很简单:如果你发现自己在反复向AI解释同样的规则,那就值得做成技能;如果你每次都在向AI解释不同的背景,那就不适合。技能框架解决的是"重复性任务的一致性"问题,不是"创造性任务的能力"问题。