
1. 为什么Agent要“技能化”而不是堆Prompt先聊一个很现实的问题很多人做智能体第一步就是把所有指令塞进System Prompt里告诉模型“你是一个擅长写周报的助手”“你是一个数据分析师”“你会写Python代码”……结果呢Prompt越写越长模型越来越“拧巴”遇到稍微复杂一点的任务就开始胡说八道改一个功能要动全局换个场景又要从头调Prompt。我大概是在做第三个Agent项目时彻底想明白这件事的LLM是“大脑”但它需要“手脚”——这就是agent-skills存在的理由。所谓skills就是把智能体需要执行的具体任务拆成一个一个可复用、可组合、可独立升级的能力单元。每个技能都有明确的名字、触达条件、输入输出规则和实现逻辑Agent在运行时会根据用户意图动态挑选技能去执行。这个思路的来源并不神秘它借鉴了软件工程里的模块化和微服务思想。你写代码不会把所有功能堆在一个main函数里而是拆成service、utils、handlers构建Agent同样不该把几十条行为准则塞进一个System Prompt而应该把“技能”作为一等公民对待。网上搜agent-skills会发现很多框架都在做这件事。OpenAI的function calling是基础形态LangChain的tools是中间形态而真正完整意义上的skill体系应该是“结构化任务定义 可执行逻辑 元信息管理与调度机制”的组合。我后面会用一套完整的实战案例来说明这个体系怎么落地。这套方案适合谁参考首要是那些已经跑通了最基本的LLM调用但觉得Agent行为不稳定、能力边界模糊的开发者和AI产品经理。其次是想给团队搭建Agent能力复用体系的架构师。如果你只是写个demo玩一玩那直接堆Prompt也无妨——但只要你开始认真做Agent产品技能化是绕不开的那道坎。1.1 agent-skills到底解决了什么问题简单说它同时解决了四个痛点。第一是能力复用困难。我最早做的一个客服Agent里面有一个查订单的功能。后来要做一个新的售前咨询Agent也想用这个查订单逻辑结果发现原来的代码写死在某个业务流程里面根本抠不出来。技能化之后查订单就是一个独立的skill谁要用谁注册调用十分钟接完。第二是Prompt膨胀导致的稳定性坍缩。LLM对长上下文的关注力是有限的指令越多每条指令被遵循的概率就越低。实测下来如果把节假日值班调整、退款规则、物流异常处理等七八项能力全部写进一个Prompt里模型在第五条和第七条之间开始“打架”——它会自己编出互相矛盾的规则。拆成独立技能后每个技能只负责一个职责Prompt短了准确率直接回升。第三是无法观测、无法调试。我见过太多人和我一样最开始调试Agent全靠“感觉”——说错了就改Prompt没说错但结果不满意也不知道是哪一步出了问题。技能化的结构天然支持分层日志Agent选了什么技能、技能执行时输出了什么、哪一步校验失败了一眼就能看到。这个价值在实际项目里怎么强调都不过分。第四是能力治理与迭代困难。有了技能清单你才能回答这些问题这个Agent到底会哪些能力哪些技能一个月没人调用新版本技能上线后效果变好还是变差没有技能体系这些问题几乎无从谈起。2. 设计一套agent-skills体系前先把这几个概念掰开不要急着写代码。我见过太多人上来就写skill函数写到最后横七竖八互相调用乱成蜘蛛网。在设计之前有几个关键概念必须搞清楚技能和工具的区别是什么技能和Prompt的区别是什么技能之间的依赖关系如何处理2.1 技能、工具、Prompt三者的本质差异先上结论这三者的关系类似“业务能力”、“技术组件”和“行为风格”。在LangChain等框架里tool通常是一个函数输入参数返回结果。它擅长做的是那些“不需要模型思考”的确定性操作比如查数据库、调API、算个数学题。而skill在tool之上加了一层“模型理解和编排”的能力——它需要LLM来判断“当前场景是否适用本技能”“参数应该怎么填”“技能输出如何反馈给用户”。Prompt则是纯粹的文本指令它让模型“懂得”一件事但不赋予模型“执行”一件事的能力。比如你可以用Prompt教会模型“遇到退款咨询你要安抚用户并给出政策说明”但你没办法用Prompt让它真的去调用退款接口。真正的动作需要一个可执行层来完成。我的理解可以概括成一句话Prompt定义行为准则tool定义原子操作skill定义完整的业务能力单元。做个类比。一个餐厅里服务员的大脑是LLM后厨的设备是tool烤箱、灶台、冰箱都是工具每个只能干一件事而skill是一整套“做一道菜”的能力——它包含菜谱操作流程、用料参数schema、判断标准什么时候算做熟以及必要的时候调用某个厨房设备的逻辑。2.2 skill的标准结构一张“技能卡”应该包含什么我做了一个内部规范要求所有skill按照下面的结构来定义缺一不可。技能元信息技能ID、名称、版本号、作者、创建时间。这个容易被偷懒略过但等到技能数量超过20个你就会跪谢当初写了元信息。没有ID和版本你根本没法做灰度发布和回滚。技能描述description这是一段给LLM看的文字用来帮助模型在运行过程中判断“当前任务是否应该调用本技能”。这段文字的写法非常有讲究我后面单开一节专门讲。参数Schema定义技能执行需要哪些输入参数、每个参数的类型、是否必填、取值范围。推荐直接用JSON Schema格式通用性好可校验。参数设计的好坏直接决定了技能能被多少场景复用——参数太具体技能就僵化参数太抽象模型又不会填。技能体implementation实际执行的逻辑。可以是代码函数、外部API调用、一段工作流描述甚至是一段包含具体指令的嵌套Prompt。校验与兜底逻辑技能执行完如何判断结果是否合法如果失败如何降级。这个我最开始没做导致不少技能“表面成功、实际失败”给用户返回一堆编造的JSON。2.3 技能描述怎么写LLM才愿意“选它”这个细节太重要了必须单独拿出来说。同一套skills体系描述写得好不好直接表现在技能调用的准确率上能差出20到30个百分点。先说反例。我见过很多技能描述是这样的“这是一个查询天气的技能。”——这句话给人类看没问题但给LLM看它很难判断“今天上海适不适合去迪士尼”和“帮我看看明天的天气”这两句话应该选哪个技能因为前者和查询天气的关联性并不直观。我总结了四条经验第一用第三人称描述“技能的适用场景”不要用祈使句。写“当用户询问未来某一时段的天气情况、降水概率、气温变化等内容时使用本技能”而不是“你要查询天气”。第二主动写出“边界”。很多技能误调用都是因为没有画清边界。比如你有一个“搜索商品订单”的技能描述里一定要带上“如果用户询问的是快递物流状态请勿使用本技能”。这种反例描述对降低误选率极其有效。第三给出参数填写的提示。在描述里顺带写清楚关键参数怎么填比如“location参数请填写中文城市名支持市级单位”。模型看到这个提示多半能准确填参。第四保持描述精简。太长的描述会增加模型在技能检索时的注意力开销。依我的经验一段好的技能描述控制在80到150个字符之间既能说明白适用场景又不至于喧宾夺主。3. 从零开始实做一个可运行的slills体系理论部分聊够了下面进入正题我用Python从零构建一套最小可用的agent-skills体系。不需要任何重量级框架核心代码量控制在200行以内。设计目标就一个——让LLM能够根据用户问题自动选择合适的技能并执行。3.1 环境准备与目录结构先说一下技术选型LLM用OpenAI兼容接口方便你随便换底座工具链只用Python原生的json模块、typing模块和一个轻量的embedding接口做技能召回。不需要LangChain不需要Pydantic减法优先。项目的目录结构如下agent-skills/ ├── skills/ │ ├── __init__.py │ ├── registry.py # 技能注册中心 │ ├── base.py # 技能基类 │ ├── weather.py # 天气查询技能示例 │ ├── calculator.py # 计算器技能示例 │ └── note.py # 便签记录技能示例 ├── agent/ │ ├── __init__.py │ ├── selector.py # 技能选择器 │ └── runner.py # 技能执行器 └── main.py # 演示入口这个结构适合一个中小型项目。如果你的技能数量超过50个可以考虑按业务域拆目录比如skills/finance、skills/logistics再在registry上做二级命名空间。3.2 定义技能基类所有技能的统一“壳”不管什么技能都必须实现同一个接口这是整个体系能跑起来的基石。我用一个抽象基类定义标准# skills/base.py from abc import ABC, abstractmethod from typing import Any, Dict, Optional class BaseSkill(ABC): 所有技能的抽象基类统一元信息和执行接口 skill_id: str # 技能唯一ID name: str # 技能显示名 version: str 1.0.0 # 版本号 description: str # 给LLM看的技能描述 tags: list[str] [] # 用于索引的标签 def __init__(self) - None: if not self.skill_id or not self.name or not self.description: raise ValueError(f技能必须定义 skill_id、name、description当前缺失) abstractmethod def get_parameters_schema(self) - Dict[str, Any]: 返回JSON Schema格式的参数定义 ... abstractmethod def execute(self, **kwargs) - str: 执行技能输入参数字典返回给用户的文本结果 ... def validate_params(self, params: Dict[str, Any]) - Dict[str, Any]: 参数预校验缺参补默认值多余参数剔除。 这一步看似不起眼实则避免了LLM乱填参数导致的运行时崩溃。 schema self.get_parameters_schema() required schema.get(required, []) properties schema.get(properties, {}) cleaned {} for key, item in properties.items(): if key in params and params[key] is not None: cleaned[key] params[key] elif key in required: raise ValueError(f技能 {self.skill_id} 缺少必填参数: {key}) else: cleaned[key] item.get(default) return cleaned这个基类有几个设计细节值得说说。**第一参数预校验。**我见过太多Agent项目死在“模型给execute传了个None”这种低级问题上。提前在validate_params里把缺省值补上、把多余参数剔掉执行函数里就不用写一堆防御性代码了。**第二返回类型统一为str。**这很重要。技能执行完之后的结果可能要反馈给用户也可能要传给LLM做进一步推理统一成字符串最省心。你要是返回一坨字典后面处理起来到处是坑。3.3 注册中心所有技能的“户口本”打住前面代码有个小错误——基类里我check了self.skill_id是空字符串的情况但类属性是写在class body的如果子类正好忘记定义skill_id这里查的是类属性抛错能兜住但如果子类在__init__里没调用super().__init__()校验也不会触发。实操中我建议把技能注册动作放到registry里统一处理别依赖基类的__init__。下面就是registry# skills/registry.py from typing import Dict, List, Optional from skills.base import BaseSkill class SkillRegistry: 技能注册中心负责登记、检索、获取技能实例 def __init__(self) - None: self._skills: Dict[str, BaseSkill] {} self._metadata: List[Dict] [] def register(self, skill: BaseSkill) - None: if skill.skill_id in self._skills: raise KeyError(f技能ID {skill.skill_id} 重复注册) self._skills[skill.skill_id] skill self._metadata.append({ skill_id: skill.skill_id, name: skill.name, version: skill.version, description: skill.description, tags: skill.tags, }) def get_metadata(self) - List[Dict]: 返回所有技能元信息供LLM技能选择时使用 return self._metadata def get(self, skill_id: str) - Optional[BaseSkill]: return self._skills.get(skill_id) def unregister(self, skill_id: str) - None: 反注册技能下架、功能废弃时使用 self._skills.pop(skill_id, None) self._metadata [m for m in self._metadata if m[skill_id] ! skill_id] # 全局单例 registry SkillRegistry()3.4 技能选择器让LLM学会读“菜单”技能定义好了、注册好了接下来最核心的问题来了怎么让LLM在拿到用户消息时选对技能现在业界主流有两条路线embedding相似度检索召回和让LLM直接读meta列表做选择。两条路各有优劣我目前用的是“先embedding粗筛再LLM精排”的混合方案。纯embedding的问题是它只能看语义相似度看不出“边界条件”纯LLM读列表的问题是一旦技能数量超过30个一是上下文太长费token二是模型开始“眼花”。下面我给出一个完整的selector实现# agent/selector.py import json from typing import List, Optional from openai import OpenAI class SkillSelector: def __init__(self, client: OpenAI, model: str gpt-4o-mini): self.client client self.model model self.system_prompt 你是技能调度器。根据用户的请求在给定技能列表中挑选最合适的技能并提取调用参数。 规则 1. 必须且只能选择一个技能除非所有技能都不匹配此时返回空。 2. 严格按照技能描述中的边界条件判断避免误选。 3. 参数必须从用户请求中提取提取不到且非必填时使用默认值。 4. 输出必须是JSON格式形如 {skill_id: ..., reason: ..., params: {...}}params内每个参数键必须是技能的参数schema允许的键。 def select(self, user_message: str, skills_meta: List[dict]) - Optional[dict]: # 第一步embedding粗筛候选省略了向量化实现细节核心思路是按相似度取Top3 candidates self._embedding_recall(user_message, skills_meta, top_k3) # 第二步让LLM在候选技能中精挑并提取参数 payload { messages: [ {role: system, content: self.system_prompt}, {role: user, content: self._build_select_prompt(user_message, candidates)} ], temperature: 0, response_format: {type: json_object}, model: self.model, } resp self.client.chat.completions.create(**payload) result json.loads(resp.choices[0].message.content) return result if result.get(skill_id) else None def _build_select_prompt(self, user_msg: str, candidates: List[dict]) - str: 构造精排Prompt只放候选技能缩小模型的选择范围 lines [f用户请求{user_msg}, , 候选技能列表] for skill in candidates: # 注意这里把参数schema也一并放进候选列表LLM才能正确提取参数 lines.append(json.dumps(skill, ensure_asciiFalse, indent2)) return \n.join(lines) def _embedding_recall(self, user_msg: str, skills_meta: List[dict], top_k: int 3) - List[dict]: 用embedding向量粗筛候选技能。此处为简化演示实际用向量库计算余弦相似度 # 推荐使用 text-embedding-3-small 或 bge-m3按照你的部署环境选择 # 这里略过向量计算细节直接按描述包含关键词的命中度做粗筛示例 query user_msg.lower() scored [] for meta in skills_meta: desc meta[description].lower() tags .join(meta.get(tags, [])).lower() score self._keyword_match(query, desc tags) scored.append((score, meta)) scored.sort(keylambda x: x[0], reverseTrue) return [m for s, m in scored[:top_k] if s 0]这一步里面有几个关键决策值得展开。**为什么让LLM只从候选里选而不是从全部技能里选**因为token成本和准确率不可兼得。技能超过10个后你把整个列表丢给LLM它很容易把两个相似技能搞混。先粗筛掉必然无关的让它集中精力区分那几个最像的准确率明显提升。我实测过一组数据技能总数28个全部丢给LLM时选择准确率大约85%先embedding粗筛Top3再让LLM精排准确率提升到了96%左右单次调用token也从900多降到了不到400。**第二用了response_format字段强制JSON输出。**不求稳的可以不加但我是强烈建议加上的否则流式返回里时不时给你飘一句“好的”。3.5 两个示例技能天气查询和计算器有了框架还得有几个能干活的实体技能不然没法演示。我写两个有代表性的技能。第一个是带外部API调用的“天气查询”第二个是纯粹本地计算的“算式计算器”。# skills/weather.py import json, urllib.request from typing import Any, Dict from skills.base import BaseSkill class WeatherSkill(BaseSkill): skill_id weather_query name 天气查询 version 1.0.0 description 当用户询问某个城市当前天气、气温、降雨情况、空气质量或未来天气预报时使用本技能。若用户询问的是历史天气数据请勿使用本技能。location参数需要填中文城市名可精确到区县级。 tags [天气, 气象, 温度, 降雨, 生活] def get_parameters_schema(self) - Dict[str, Any]: return { type: object, properties: { location: {type: string, description: 城市中文名如 北京、上海、杭州余杭区}, date: {type: string, description: 查询日期格式YYYY-MM-DD默认今天, default: None}, }, required: [location] } def execute(self, **kwargs) - str: location kwargs[location] # 此处简化实际项目请替换为真实天气API # 生产环境建议用高德/和风天气需要申请API Key return f【天气查询】{location}多云24°C~31°C东南风2级空气质量良降水概率20%。 # skills/calculator.py import ast, operator from typing import Any, Dict from skills.base import BaseSkill class CalculatorSkill(BaseSkill): skill_id calculator name 算式计算器 version 1.0.0 description 当用户给出一个数学表达式并希望得到计算结果时使用本技能支持加减乘除、括号和乘方运算。仅处理四则运算不处理单位换算或方程求解。expression参数直接传表达式字符串例如 12*(53)^2。 tags [计算, 数学, 四则运算] ALLOWED_NODES (ast.Expression, ast.Constant, ast.BinOp, ast.UnaryOp, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow) def get_parameters_schema(self) - Dict[str, Any]: return { type: object, properties: { expression: {type: string, description: 数学表达式字符串}, }, required: [expression] } def _safe_eval(self, expr: str) - float: 安全的表达式求值仅允许白名单AST节点防止任意代码执行 tree ast.parse(expr, modeeval) for node in ast.walk(tree): if not isinstance(node, self.ALLOWED_NODES): raise ValueError(f非法表达式节点: {type(node).__name__}) return eval(compile(tree, filenamecalc, modeeval), {__builtins__: {}}, {}) def execute(self, **kwargs) - str: expr kwargs[expression] try: result self._safe_eval(expr) return f计算成功{expr} {result} except Exception as e: return f计算失败{e}# skills/note.py - 便签记录技能 import json, datetime, os from typing import Any, Dict from skills.base import BaseSkill class NoteSkill(BaseSkill): 将用户交代的待办事项或备忘信息追加保存到本地文件 skill_id note_take name 便签记录 version 1.0.0 description 当用户希望记录、保存一条备忘、待办事项、临时灵感或稍后处理的任务时使用本技能。note内容为用户要记录的文字可包含时间、地点等细节。标签tag用于分类默认值general。 tags [备忘, 待办, 便签, 提醒, 记录] def get_parameters_schema(self) - Dict[str, Any]: return { type: object, properties: { note: {type: string, description: 要记录的备忘内容}, tag: {type: string, description: 分类标签, default: general} }, required: [note] } def execute(self, **kwargs) - str: note kwargs[note] tag kwargs.get(tag, general) # 追加写入本地备忘录文件 with open(memo.txt, a, encodingutf-8) as f: f.write(f[{tag}] {note}\n) return f已记录便签标签{tag}。代码里的ALLOWED_NODES那一坨是我之前在公开分享里强调过的安全底线。凡是要让Agent执行代码类技能的都必须做AST白名单校验不然LLM一旦被恶意Prompt注入传一个__import__(os).system(rm -rf /)进来你就知道什么叫做“事故”了。这条线必须死守。3.6 组装与运行让Agent真正跑起来主流程就简单了把所有技能注册进registry然后启动一个循环接收用户输入调用selector选择拿到结果后走runner执行返回给用户。# agent/runner.py from typing import Optional, Dict from skills.registry import registry class SkillRunner: def __init__(self) - None: self.history [] def run(self, selector_result: Optional[Dict]) - str: if not selector_result or skill_id not in selector_result: return 抱歉我没有找到合适的技能来处理你的请求。 skill_id selector_result[skill_id] params selector_result.get(params, {}) skill registry.get(skill_id) if not skill: return f技能 {skill_id} 已失效请联系管理员排查。 try: cleaned skill.validate_params(params) result skill.execute(**cleaned) self.history.append({skill_id: skill_id, params: cleaned, result: result}) return result except Exception as e: return f技能执行异常{str(e)} # main.py 演示入口完整流程 from openai import OpenAI from agent.selector import SkillSelector from agent.runner import SkillRunner from skills.registry import registry from skills.weather import WeatherSkill from skills.calculator import CalculatorSkill from skills.note import NoteSkill client OpenAI() # 请配置你的API Key与BaseURL registry.register(WeatherSkill()) registry.register(CalculatorSkill()) registry.register(NoteSkill()) selector SkillSelector(client) runner SkillRunner() if __name__ __main__: print(Agent技能演示已启动输入q退出) while True: user_input input( ) if user_input.strip().lower() q: break selected selector.select(user_input, registry.get_metadata()) print(选择结果, selected) print(执行结果, runner.run(selected))跑起来之后体验是这样的 明天上海什么天气 选择结果 {skill_id: weather_query, reason: 用户询问上海未来天气匹配天气查询技能, params: {location: 上海, date: 2025-06-14}} 执行结果 【天气查询】上海多云24°C~31°C东南风2级空气质量良降水概率20%。 帮我算一下 (235*7 88) / 13 选择结果 {skill_id: calculator, reason: 用户给出数学表达式期望计算结果, params: {expression: (235*7 88) / 13}} 执行结果 计算成功(235*7 88) / 13 133.30769230769232 记住明天下午三点开会 选择结果 {skill_id: note_take, reason: 用户希望记录一个待办事项, params: {note: 明天下午三点开会}} 执行结果 已记录便签标签general。到这里一个最小闭环就通了。三个技能一个调度器一个执行器——但你已经能从中看到agent-skills的全部骨架元信息驱动选择、Schema约束参数、统一执行接口。后面的数据分析你会发现这套骨架天然支持各种扩展。4. 技能运行中的常见问题与排查实录写了这么多框架和代码如果只让这套体系跑demo大概率遇不到什么坑。但一旦上了生产、技能多了、用户话术复杂了问题就一个接一个往外冒。下面这些是我自己在项目中真实踩过的坑和排查出的解决方案按优先级排列。4.1 技能描述引发的“误选”怎么排查先说症状用户明明问的是“快递到哪了”Agent却调用了“查询订单”技能然后自信满满地说“您的订单正在加紧配送”。我第一次遇到时第一反应是改Prompt跟模型说“你要仔细判断”。效果有一点但不稳定。后来我用了一个更系统性的排查方法思路类似于软件工程里的根因分析第一步先把selector输出的reason打出来看模型是怎么想的。然后翻技能描述原文逐字比对。排查结果通常是这三类原因一是描述边界不清晰没有写明“哪些场景不要用本技能”二是技能之间描述存在交叉重叠比如“订单查询”和“物流查询”在语义空间里本来就挨得很近三是候选列表里出现了多个语义相近的技能,把模型搞迷糊了。解决手段有三种给重叠技能之间互相补充“边界互斥说明”把易混淆技能合并成一个并新增内部路由逻辑或者增加一个“兜底空技能”——当所有候选技能匹配度都低于阈值时干脆不选让Agent回答“超出我的能力范围”而不是硬选一个。4.2 参数错乱与幻觉填入的防御参数这个坑比技能误选还要隐蔽。我遇到过最离谱的一次是用户说“查下北京天气”模型选对了技能但把location填成了“天气”——因为用户问题里“天气”这个词紧挨着“北京”LLM在做参数提取时张冠李戴了。防御策略分三层。第一层是在selector的system prompt里明确写“参数必须从用户请求文本中提取原词不得改写或联想”。第二层是在技能参数Schema的description里给出非常具体的参数样例例如location的description里写“填城市中文名比如北京/上海/广州不许填天气现在这类词”。模型在看到带例子的描述时填参准确率会显著提升。第三层是在validate_params里做合法性校验。城市名在白名单里就放行不在就用模糊匹配纠偏纠不了就报错让Agent重问用户。宁可让Agent多问一句“您说的是哪个城市”也不能让它拿着一个错参去调用外部API。4.3 技能膨胀当技能数量超过50个怎么办技能数量少的时候一切都是美好的。但当技能超过50个你会碰到一个全新的瓶颈embedding粗筛开始出现漏召某些偏冷门的技能永远进不了候选列表。我处理这个问题的实践方案是给技能增加“路由标签”和“优先级”两个维度。路由标签是粗粒度的分类比如“销售域”“售后域”“工具类”先按标签把技能分成几个分桶再在桶内做embedding粗筛。这相当于把一道“50选1”的问题拆成了两道小题先判断用户意图属于哪个域再在域内挑技能。实测下来漏召率从12%降到了3%左右。另外我还会定期清理调用次数过低的僵尸技能。技能也是需要维护的资产不是越多越好。与其放50个平均只用5次的技能不如砍到20个精品每个都能被精准召唤。4.4 执行结果不可信的兜底策略最后说一个最“要命”的问题技能执行了但结果不够准。比如天气接口因为上游源更新慢返回的是昨天的数据而Agent却当新鲜数据汇报给用户。兜底策略就两条但缺一不可。一是数据时效校验。凡是涉及外部数据源的技能在execute返回前必须判断数据的时间戳是否在合理范围内超龄数据直接标注“该数据可能过期请重新查询”。二是结果置信度反馈。让每个技能在执行完时自报一个“完成度”比如“数据获取成功但仅有80%字段非空可能存在信息缺失”。selector拿到这个信号后可以决定是重试其他技能还是如实告诉用户。这个机制听起来简单但大多数项目都没做因为大家习惯于默认“技能返回了就是可信的”直到被坑了一次才长记性。5. 这套体系还能怎么长从单技能到技能编排最后想聊一个扩展方向。前面演示的都是单技能调用——一个技能干完一件事就把结果交出去。但真实业务里复杂任务往往需要多个技能协同。比如“帮我把今天的销售数据整理成周报并发送到群里”这个任务至少涉及三个子能力查数据、写摘要、发消息。对此我的思路是引入技能编排层它不直接执行任何技能而是一个“导演”负责把一个复杂任务拆解成多个步骤每步选择合适技能并管理步骤之间的数据流转。目前我用的是两种编排策略按任务复杂度分档。一种是顺序流水线适合有明显流程顺序的任务。前一个技能的输出直接作为后一个技能的输入。实现很简单就是在编排层定义一个步骤清单依次执行上一步的返回文本塞进下一步的参数里。另一种是动态规划式编排适合没有固定顺序、需要模型现场拆解的任务。流程是把任务描述交给LLM让它输出一个DAG有向无环图式的步骤计划每个节点绑定一个技能然后再去执行。这个复杂度上来不少但对真正有挑战的任务效果惊艳。我站在个人角度预测往后agent开发的重点会从“单技能调准”转向“技能生态编排质量”。谁能在自己的业务领域沉淀出高质量、低耦合的技能库并且有一套靠谱的编排机制谁就能在LLM落地的路上少走很多弯路。如果你也想在这条路上做点东西我给的建议是先别急着上微调别急着重量级框架先把10个以内的必要技能用这套思路跑通、调准、上线然后再慢慢扩张。技能化是一个越早做越省事的架构决策等Prompt堆到几千行再重构那个代价远比你想象中要高昂得多。