ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

把技能装进智能体:agent-skills全栈落地实践与思考

把技能装进智能体:agent-skills全栈落地实践与思考 把“技能”装进智能体一次关于 agent-skills 的全栈落地思考你有没有过这样的感觉明明已经让大模型把某个流程跑通了可换一个项目、换一个场景同样的逻辑又得重新写一遍调工具、拼参数、处理返回、失败重试这些和“智能”基本无关的活却占掉了大量时间。我最近在重构一套基于大模型的自动化项目时被这个问题卡了很久最后让我真正改观的是一个看起来挺朴素的思路——把“技能”从智能体的主逻辑里独立出来用一套明确的契约去管理。顺着这个思路我梳理了一个名为agent-skills的完整结构这篇文章想把我踩过的坑、改过的设计、以及最后沉淀下来的做法从头到尾讲清楚。先说清楚这是什么agent-skills不是某个具体的库或框架而是一套关于“如何给智能体定义、装载、复用能力”的工程化思路。它解决的核心痛点是大模型推理不稳定但外部执行必须稳定技能如果和推理混在一起就没法测试、没法回归、没法跨项目复用。适合的读者群是那些已经在用大模型做实际自动化任务但逐渐觉得提示词和代码纠缠不清的开发者或者是想在一个团队里建立“可共享技能库”的技术负责人。我自己在重构前后对比非常明显——重构前三天两头改提示词重构后主流程几乎没动过动的都是技能包本身。1. 先从现状说起我们是怎么把一块智能体代码写成一团乱麻的很多人搭建智能体的第一步都是在系统提示词里塞一大堆说明比如“当你需要查询天气时调用/weather工具”“当用户给了地址之后先做标准化再调用地图接口”。听起来很顺但真正跑起来之后会发现两个问题。第一个问题是提示词里塞工具说明塞到一定程度就会互相打架。工具多了之后模型经常搞混参数的格式比如上一个工具要求date: 2026-01-01下一个工具要求time_start: 1699999999模型在字段转换上非常容易出错。我在一个项目里被这个问题搞崩过当时接入了六个外部服务每个服务平均三个参数提示词加上示例加起来有三千多个词结果模型在处理第七个请求时居然把上一个工具的返回结果当作下一个工具的输入对象直接传了过去整个链路瞬间断掉。第二个问题是代码和提示词边界模糊。很多人在主流程里写response await agent.chat(user_message)然后把所有工具调用都包在 agent 内部由模型自己决定何时调用。这样做短期内效果很好但一但业务逻辑变得复杂比如要同时维护多个会话上下文、要处理鉴权、要支持重试你就会发现你根本没法定位到底是模型选错了工具还是参数格式化出错还是外部服务本身抽风。我把这个问题形容为“一团不会报错的活代码”——它不崩溃但它会以一种非常隐蔽的方式产出错误结果。而 agent-skills 这个思路的出发点正是把“模型要做的判断”和“系统要做的执行”拆开模型只负责选哪个技能、填哪些参数剩下的交给一段确定性代码去跑。技能在这里有一个更好的名字——“能力契约”。1.1 技能的本质它不是“工具调用的别名”而是一个可以独立演进的单元我见过很多对“技能”的误解最常见的是把技能理解成 OpenAI Function Calling 里的 function schema。这个理解不能说错但太浅了。Function schema 只是“告诉模型有这个函数”而技能需要覆盖的能力包括这个能力什么时候该出现、参数如何做合法性校验、执行结果的格式如何统一、失败时应该暴露什么错误信息。它不是一纸声明而是一个完整的、可执行的单元。我自己最喜欢的一个类比是它像是一个经验丰富的助理手里的一本操作手册。助理知道“订会议室”这件事不只是“调用一个接口”还包括检查会议室占用、判断参会人数和会议室容量的匹配、如果冲突了应该推荐哪些备选。传统方案里这些知识全部要靠提示词描述给模型而在 agent-skills 的结构里这段知识固化在一个独立模块里可以由人严格测试也能被多个项目复用。关于“为什么单独拆出来”还有一个容易被忽视的点一旦把技能独立出来你事实上获得了“测试的自由”。你不需要每次都走一遍完整对话链路才能验证一个技能而是可以直接针对技能的输入参数、输出格式、异常分支写单元测试。这条路在后端开发里很成熟但在大模型应用里却常常被人忽略我后面会专门讲怎么给技能写回归测试。2. 明确边界哪部分该交给模型哪部分该固化成技能在开始设计 agent-skills 之前最值得花时间做的一件事是想清楚一条边界哪些事应该让大模型去做哪些事应该由代码确定性执行。我的判断标准很简单三条凡是“格式正确性”相关的事情一律交给代码。比如时间格式转化、数值类型校验、必填字段检查这些事大模型做得不差但时好时坏而代码只要写了就一定是稳定的。让模型去做“大概率对”的事是对资源和对结果都不负责任。凡是“选择与判断”相关的事情留给大模型。比如用户说“帮我把这封邮件回了吧”需要判断发件人身份、邮件语气、是否需要抄送经理这些判断没有固定规则适合让模型发挥。但判断之后的“执行”必须由技能去完成——填充模板、调用邮件接口、解析发件人地址全部做成确定性执行。凡是“需要事后解释和回溯”的事情放进技能的日志里。大模型的原生输出很难做到结构化回溯但技能模块可以详细记录输入是什么、处理到哪一步、调用了哪个外部接口、返回了什么、结果有没有被裁剪。有了这些日志你才能在出问题时快速定位。我在实践里把这个边界套到一个小例子上假设我们要做一个“查询对方时区并及时会议提醒”的功能。传统做法可能是让大模型直接去调一堆 API我的做法是定义一个timezone_converter技能它接收datetime和target_timezone两个参数返回一个已经转换好的带时区标记的时间字符串。大模型需要做的事只是从用户的自然语言里取出时间和时区名然后把参数传给技能。时区名到底是不是合法的夏令时要不要考虑全都在技能内部用zoneinfo库解决不需要模型做任何“判断”。2.1 狼群狩猎给我的启发推测领地放在主循环行动放在技能里说一个对我设计思路启发很大的自然现象——狼群捕猎。狼群中并非所有个体都做一样的决策头狼负责判断追哪一只猎物、什么时候该放弃但一旦决定追捕负责执行的狼会遵循一套高度固定的协作模式包围、驱赶、截击。判断的部分非常灵活但执行的部分极其固定。智能体也是同一个道理。主循环或者说“规划器”应该是一个灵活的推理器它负责理解意图、拆解步骤、决定下一步调用哪个技能。而技能本身应该是高度固定的——它的逻辑一旦写好就不该再被模型的随机性影响。我见过一个反面案例有人希望“更智能一点”在技能内部也接了一次大模型调用结果同一个输入两次执行给出的结果不一样导致下游依赖全部对不上。这就是典型的“把执行交给了推理”违背了狼群协作的基本原则。所以我的代码结构也很明确主目录下有一个orchestrator的概念它本质就是一个 while 循环不断问模型“下一步做什么”skills/目录下则是一堆不带任何模型调用的纯函数模块。只要可能技能内部的代码不允许出现 LLM 调用。2.2 技能的标准契约我用一个 YAML 骨架约束住所有技能为了让技能可以被多个项目复用我给它定义了一个标准结构。每个技能由一个描述文件和一个执行文件组成。描述文件用 YAML 写它告诉调度系统这个技能叫什么、干什么用、期望哪些参数、返回什么结构、有没有副作用。执行文件则是具体的代码实现。下面是一个最小化的技能描述文件我用的是我自己项目的示例字段名你可以按需调整name: check_calendar_conflict description: 检查一组会议时间是否冲突。需要传入会议的绝对时间列表 返回冲突对列表。注意本技能不负责时区转换上游需保证时间已统一。 parameters: type: object required: - meeting_slots properties: meeting_slots: type: array items: type: object properties: id: type: string description: 会议的唯一标识用于冲突结果引用 start: type: string format: date-time description: 会议开始时间ISO 8601 格式UTC end: type: string format: date-time returns: type: object properties: conflicts: type: array items: type: object properties: first_id: string second_id: string overlap_seconds: number side_effects: network_calls: false note: 纯内存计算无外部副作用这个 YAML 最关键的一点是把“description”写得很克制它明确写了“本技能不负责时区转换”。为什么特意写这句话因为有太多模型会把“检查会议冲突”隐式理解为“顺便把时间转成同一时区”结果传入的时间格式五花八门技能内部被迫做猜测处理。把边界写清楚其实是在技能层做一次防御性设计。2.3 为什么要让技能内部“没有智能”你可能会问既然这是个“智能体”项目为什么做一个看起来像普通函数的技能模块我想说正是这一步才是让智能体可维护的关键所在。普通函数最大的优点是确定性和可测试性。check_calendar_conflict只要给同样的输入每次返回结果都一致。你可以给它写十个测试用例覆盖边界条件跑一次测试就知道有没有问题。这是任何大模型应用都需要的“地基”。而智能体的“智能”体现在主循环对技能的组合和选择上而不是体现在技能内部的每一步计算里。我后来把项目里几乎所有的“工具调用”都改成了技能模式改完之后整体可观测性好了一个档次。说白了智能体应该像一个优秀的排长而不是一个什么事都亲力亲为的多面手——他要知道调哪支队伍、在什么时机调而不是自己也拿枪冲上去。3. 从零搭技能以“带时区的日程冲突检查”为例的实操记录说了这么多原则来点实际的。我挑一个我最近真正落地过的技能来拆解check_calendar_conflict也就是检查一组带时区标记的会议时间有没有重叠。这个场景在日程管理、会议助理、自动化排期里都非常典型。设置这个技能之前我先定义它的边界它接收已经统一到 UTC 的绝对时间。至于“用户说的下午三点”对应哪个 UTC 时间那是另一个技能名叫natural_time_to_utc的事本技能不越俎代庖。为什么要拆分因为“自然语言转时间”对模型的依赖度高不同用户表达习惯差异大而“检查冲突”是纯算法活。把两者拆开我就可以用单元测试把算法活彻底锁死而在自然语言转换那一层再去做提示词迭代。3.1 第一步设计技能目录与参数占位在我的项目目录里技能的物理位置是这样组织的agent-skills/ ├── registry/ │ ├── calendar_conflict_checker.yaml │ └── natural_time_parser.yaml ├── runtime/ │ ├── calendar_conflict_checker.py │ └── natural_time_parser.py └── tests/ ├── calendar_conflict_checker_test.py └── fixtures/registry里放描述文件runtime里放执行代码。执行代码里我拒绝任何大模型依赖只用标准库加zoneinfo。技能的参数设计有一个容易忽略的点不要在参数里使用嵌套太深的自由结构。比如让模型传一个“会议列表”如果字段结构设计得太灵活比如允许time: 下午3点这样的模糊输入那技能内部又要开始“猜”可测试性就打折扣。我的做法是在 YAML 描述里明确每个字段的类型、格式、编码并在代码里做严格的pydantic校验从源头拒绝脏数据。3.2 第二步实现冲突检查算法并保留对“边界重叠”的处理核心算法其实不复杂把所有会议时间按开始时间排序然后两两检查是否有重叠。但真正的工程细节在于“什么算重叠”。会议 A 是 9:00-9:30会议 B 是 9:30-10:00这算不算冲突业务上一般认为不算因为在 A 结束的瞬间 B 可以开始。所以判断重叠的条件应该是A.start B.end and B.start A.end而不是。我用 Python 写了一个简化实现代码里也加入了zoneinfo的时区比较# runtime/calendar_conflict_checker.py from datetime import datetime, timezone from zoneinfo import ZoneInfo from typing import List, Dict, Any def check_conflicts(meeting_slots: List[Dict[str, Any]]) - List[Dict[str, Any]]: # 把时间字符串解析成 datetime并统一转为 UTC 时间戳避免时区比较问题 normalized [] for slot in meeting_slots: dt datetime.fromisoformat(slot[start]) dt dt.astimezone(timezone.utc) normalized.append({ id: slot[id], start: dt, end: datetime.fromisoformat(slot[end]).astimezone(timezone.utc), }) # 按开始时间排序保证两两比较不会漏 normalized.sort(keylambda x: x[start]) conflicts [] # 只和后面的会议比较避免重复 for i in range(len(normalized)): for j in range(i 1, len(normalized)): # 一旦后一个会议的开始已经大于等于前一个会议的结束后续也没必要比了 if normalized[j][start] normalized[i][end]: continue overlap_start max(normalized[i][start], normalized[j][start]) overlap_end min(normalized[i][end], normalized[j][end]) if overlap_start overlap_end: conflicts.append({ first_id: normalized[i][id], second_id: normalized[j][id], overlap_seconds: int((overlap_end - overlap_start).total_seconds()), }) return {conflicts: conflicts}这段代码里我最想强调的不是算法本身而是最后那个overlap_seconds字段。传统实现可能只返回“有没有冲突”但加了这个字段之后下游比如模型生成给用户的文案就能说“这两个会议重叠了 25 分钟”而不是含糊地警告。技能的输出设计决定了上层推理的优雅程度。3.3 第三步参数校验与“主动告知”副作用在技能交付前我通常在外部包一个很薄的服务层。注意这一步要和算法核心分开原因很简单一套算法可能被 Python 调用、被 HTTP 接口调用也可能被未来的某个事件总线消费我不想把服务逻辑写死在算法函数里。# runtime/service.py from datetime import datetime from zoneinfo import ZoneInfo from calendar_conflict_checker import check_conflicts def handle_calendar_check(raw_payload: dict) - dict: # 简单校验缺失字段直接返回错误而不是让模型猜 if meeting_slots not in raw_payload: return {error: missing meeting_slots, valid: False} # 转换 ISO 字符串里可能带时区偏移 for slot in raw_payload[meeting_slots]: if start not in slot or end not in slot: return {error: fslot {slot.get(id, unknown)} missing time, valid: False} try: conflicts check_conflicts(raw_payload[meeting_slots]) except ValueError as e: # 时间格式错误需要提供可读的错误信息 return {error: str(e), valid: False} return {conflicts: conflicts, valid: True}你可能会注意到我在返回值里加了一个valid字段。这是给我自己看的“红绿灯”validfalse意味着上层模型在处理结果时必须先处理错误而不能直接解读conflicts字段。这一点比“抛出异常让模型自己猜”要可靠得多因为大模型处理 JSON 格式的错误比处理堆栈异常要顺手很多。3.4 第四步用 Pydantic 配置做“只要错就拒”的防线纯函数 手写 if 校验虽好但字段一多就累人。我后来引入了pydantic做模型把参数校验从“每个字段手写”变成了“声明式”。# runtime/schema.py from pydantic import BaseModel, validator from datetime import datetime from typing import List, Optional class MeetingSlot(BaseModel): id: str start: datetime end: datetime validator(end) def end_after_start(cls, v, values): if start in values and v values[start]: raise ValueError(end must be after start) return v class CalendarCheckRequest(BaseModel): meeting_slots: List[MeetingSlot]声明式校验的好处是技能的入参契约可以像数据库 schema 一样被版本管理。哪天想加一个“最小会议时长”参数只需要改 schema不用动任何业务逻辑。另外它还在“模型传参”和“函数执行”之间加了一道极厚的防火墙模型哪怕传了多余字段pydantic默认会拒绝而不是静默丢弃——这能帮你尽早发现提示词或调用方的问题。4. 调试与回归可复现、可回放、可测试的“技能护城河”技能模块独立出来之后调试方式跟以前完全不一样了。以前我调试智能体只能盯着对话日志看一会儿看看模型有没有理解用户意图一会儿看看工具返回长什么样现在我能直接对技能执行单元做黑盒测试把整个智能体的不确定性隔离在“主循环—模型选择”那一层。这个心得我觉得值得单独拿出来讲因为很多人对 agent-skills 有一个误解觉得它只是“整理代码”跟调试无关。但实际上它最大的收益恰恰在调试侧。4.1 场景还原一次时区误判引发的连环 BUG我之前做一个跨国会议助手的时候遇到过一个很经典的 bug用户在美国他输入“下周一上午 10 点开会”系统把它解析成了 UTC 时间然后跟一个欧洲团队的会议比较结果判定为“无冲突”实际上两个会议在用户本地时间是重叠的。问题不在算法核心——check_conflicts本身没错错在上游的时间归一化。旧代码里时间解析逻辑写在主流程的提示词里让模型自己去转换时区。模型不是不会转而是经常“默认按服务器时区转”导致一部分时间戳一致一部分不一致。这种 bug 在旧架构里特别难查因为你翻对话日志只能看到最终结果看不到中间哪个环节把时间弄错了。4.2 我搭的“快照日志”让每个技能调用都能重放解决思路其实很朴素每个技能调用时把入参、出参、耗时、错误信息全部记录成一条结构化日志。我把这种日志称为“快照日志”。在 agent-skills 模式里我把快照日志做成了装饰器加在技能入口上# runtime/logging_utils.py import json, time, hashlib from functools import wraps def snapshot_log(name): def decorator(func): wraps(func) def wrapper(*args, **kwargs): entry { skill: name, ts: time.time(), args: json.dumps(args, defaultstr, ensure_asciiFalse), kwargs: json.dumps(kwargs, defaultstr, ensure_asciiFalse), } try: start time.time() result func(*args, **kwargs) entry[result] json.dumps(result, defaultstr, ensure_asciiFalse) entry[latency_ms] int((time.time() - start) * 1000) except Exception as e: entry[error] str(e) entry[latency_ms] int((time.time() - start) * 1000) raise finally: # 落盘或上报到日志系统 print(json.dumps(entry, ensure_asciiFalse)) return result return wrapper return decorator snapshot_log(calendar_conflict_checker) def check_conflicts_remote(...): ...有了这些快照之后我再排查问题时思路就清晰了先去日志系统里按skill_name过滤看最近一次调用传入了哪些参数、返回了什么然后再看上游natural_time_parser在那一步生成了什么样的时间戳。两步一对比问题定位基本不会超过十分钟。4.3 回放不是重跑我是怎么用历史数据做“技能回归测试”的快照日志的第二个用途是构建“回放测试”。我写了一个小脚本读取历史快照中的args重新执行技能函数然后把结果跟当年的result对比。如果一致说明技能行为没有回退如果不一致说明在不经意间改动了行为逻辑需要人工确认。# replay_tests/replay.py import json, glob, importlib def replay_skill(skill_module, skill_name): for file in glob.glob(flogs/{skill_name}/*.json): with open(file) as f: record json.load(f) module importlib.import_module(skill_module) func getattr(module, skill_name) new_result func(**json.loads(record[kwargs])) old_result json.loads(record[result]) if new_result ! old_result: print(f[REGRESSION] {record[ts]}: {file}) replay_skill(runtime.calendar_conflict_checker, calendar_conflict_checker)这个方法听起来简单但在大模型应用项目里极其好用。因为大模型应用世界的逻辑变化太快大家普遍没有“回归”意识可一旦技能被做成确定函数回归的成本就降到跟传统后端一样低了。4.4 一个血泪教训不要在技能里返回“可读的文本”而不是“结构化数据”我想专门提醒一类设计错误有些技能会把结果拼成一段自然语言返回比如会议A和会议B在上午10:00-10:25存在重叠让模型直接引用。一开始看着很方便但后来我发现这是灾难的开始原因有三个结构化数据可以继续被加工文本不行。比如上层需要计算“总共有多少场冲突”如果是文本返回模型必须再做一次自然语言理解。文本里一旦出现时间措辞模型可能再次“转译”出错。模型常用“约”“左右”等模糊词导致下游排序和统计全部失去精度。没法做精确断言。你在回归测试里没法写assert result[conflicts][0][overlap_seconds] 1500但你完全可以断言字符串里包含“重叠”。正确做法是技能永远返回结构化 JSON如果希望模型引用也是在主循环里让模型基于 JSON 自己去生成自然语言。生成得怎么样那是语言能力问题但数据结构必须永远是数据的模样。5. 把技能当作乐高积木组合、复用与“编译期”检查当技能数量超过十个之后一个新的挑战浮出水面怎么让多个技能优雅地协作模型怎么知道在“解析时间”之后才能“检查冲突”如果技能之间存在隐式依赖调用顺序错了怎么办我第一次遇到这个问题是项目里同时有了“查询天气”“预订会议室”“检查日程冲突”“查询航班状态”四个技能之后模型开始频繁地把一首信息串起来用错。5.1 猫和狗的比喻主协调器只负责“选”每个技能负责“做”我很喜欢猫和狗的比方来解释系统设计狗会等人发指令主人让它去捡球它就去捡球猫则有自己的想法它不按指令行事。传统的大模型工具调用模式有点像“猫”——你期望模型按你预定的链路走但它经常不配合自由发挥。而 agent-skills 的“执行技能”部分其实就是“狗”——绝对执行指令绝不发挥。至于主协调器主循环它也应该是一只被训练良好的“狗”但它唯一需要发挥的地方在于判断下一步用哪一只“狗”。所以我把整个结构画得非常清晰主协调器读 YAML 注册表决定下一个执行哪个技能技能执行器负责实际调用每次执行完把结果返回给主协调器让它决定下一步。这个模式有点像微服务架构里的“API 网关 服务提供者”只不过“网关”本身是模型推理。5.2 一个“注册表”小工具让所有技能都规规矩矩我把技能的 YAML 描述文件做成一个注册表每次启动时加载成内存结构用来校验技能的参数、描述、副作用。 更关键的是我让主协调器的提示词只依赖注册表里的技能摘要而不是把所有技能的全部细节直接塞进系统提示词。这样既保证了模型“知道有哪些技能”又不会被十几个技能的冗长文档冲晕。# registry/loader.py import yaml, glob def load_skill_registry(registry_pathregistry): skills {} for filepath in glob.glob(f{registry_path}/*.yaml): with open(filepath) as f: skill_def yaml.safe_load(f) skills[skill_def[name]] skill_def return skills def render_skill_menus(skills) - str: lines [] for name, skill in skills.items(): lines.append(f- {name}: {skill[description]}) return \n.join(lines)这个时候 YAML 里的description就变得特别重要了。它像书籍目录不用完整写每章内容但要准确提炼“什么时候该翻到这一章”。我给团队定的规范是description 里必须写明输入的前置条件、输出的格式、和可能产生的副作用。比如check_calendar_conflict就明确写了“上游需保证时间已统一”逼着模型去先调用natural_time_parser。5.3 从“单技能”到“技能库”团队内部如何定发布标准当三五个项目都在用同一套技能时自然会产生版本和兼容性的问题。比如check_calendar_conflict之前返回的是overlap_minutes分钟后来为了精度改成overlap_seconds如果下游还有旧版依赖就会直接出错。这其实是后端断言的经典问题但在 agent-skills 的语境里容易被忽略因为“技能”听起来太软了像是提示词的一部分。我建议把技能的发布当“接口版本”来对待给returns字段加一个version字段另外一个团队在改字段名之前必须先跑一遍全量回归测试确认所有下游调用都能适配新结构。yaml name: check_calendar_conflict version: 1.2.0这一步非常小但它让跨团队协作变得可管理了。以前大家在一个项目里改来改去全靠口头约定现在技能就是正式契约任何变更都得走测试和版本确认流程。 ### 5.4 一个完整的调度示例从“用户一句话”到“技能组合”的全过程 为了让整个流程更清楚我贴一段在这个架构下主循环的实际代码轮廓简化版重点看“模型决定调用哪个技能”和“技能执行后返回结构化结果”是怎么衔接的 python # scheduler/runloop.py import json from registry.loader import load_skill_registry, render_skill_menus SKILLS load_skill_registry() MENU_TEXT render_skill_menus(SKILLS) def run_with_agent(user_input, llm_call): messages [{ role: system, content: f你是一个会议助手可用技能如下\n{MENU_TEXT}\n 请严格选择技能并按 JSON 输出 {{skill:技能名,parameters:{{...:...}}}} 如果无法确定参数请向用户追问。 }, {role: user, content: user_input}] for _ in range(5): # 限制最大循环次数防止死循环 decision llm_call(messages) decision json.loads(decision[content]) skill_name decision.get(skill) if not skill_name: return 无法确定下一步需要更多信息 skill_impl import_skill_runtime(skill_name) result skill_impl(**decision.get(parameters, {})) if result.get(valid): messages.append({role: user, content: f技能 {skill_name} 返回{json.dumps(result, ensure_asciiFalse)}}) else: messages.append({role: user, content: f技能 {skill_name} 返回错误{result.get(error)}请重新尝试或要求用户澄清。}) return 已达最大步骤限制这段代码并不长但它表达了一种理想的循环姿态一旦技能执行成功模型就能看到结构化的结果然后决定是继续调用下一个技能、还是直接给用户最终答复。整个流程里的每一步都有清晰的快照日志方便回放。6. 复盘更适合自己的 agent-skills从工具人到“技能库治理者”如果让我给刚开始接触 agent-skills 的人一句建议我会说不要一开始就追求“大而全的技能库”也不要急着把提示词里所有工具调用都改掉。先从一两个你天天在用、且经常出错的功能开始比如会议冲突检查、文件上传规范化、汇率换算之类的纯逻辑型功能。把它们做成技能配好 YAML、补好测试、加上快照日志跑两周看看效果。你大概率会发现模型犯蠢的概率并没有因为技能化而下降太多但定位蠢在哪里、修复蠢在哪里的速度会快上许多。这个项目的下一步我打算把技能注册表做成可视化面板方便团队里非工程角色也能看到哪些技能在跑、哪些最近报错了。另外给技能的description增加“典型示例”字段让模型在模糊场景下有更具体的参照物。不过这些都是增量优化核心架构已经稳定了——用 YAML 描述、用纯函数实现、用快照日志观测、用版本控制发布这套四件套我从目前项目里拿不出来去别的团队用了效果依旧立得住。
返回列表