多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Agent技能化架构:从Prompt混沌到可插拔技能编排实践

Agent技能化架构:从Prompt混沌到可插拔技能编排实践 1. 为什么你的Agent用起来像“人工智障”技能化是第一性原理我跟不少做Agent的朋友聊过大家普遍卡在同一个地方单轮对话Demo跑得挺顺一旦要处理真实业务流模型的表现就开始飘。要么漏掉关键工具调用要么参数传错要么在同一个循环里反复横跳就是不给你一个结果。很多人第一反应是换更大的模型、调Prompt折腾半天效果依然不稳定。问题的根子往往不在模型能力而在你把所有逻辑都灌进了一个巨大的Prompt里让模型在无边界的语义空间里瞎猜。我在项目里验证过一套完全不同的思路——agent-skills把Agent的能力拆成一个个独立、可注册、可调度的技能模块让模型在清晰的边界里做选择而不是在混沌里创造。这套思路对我手头几个项目的稳定性提升是质的飞跃这期就把它拆开讲透。1.1 把逻辑全塞进Prompt的代价先看一个最常见的反面教材。假设你要做一个内部运维机器人功能包括查日志、查服务器状态、重启服务、发告警。很多人的做法是这样的PROMPT 你是一个运维助手你可以做以下事情 1. 用户想看日志就调用get_logs函数 2. 用户想看服务器状态就调用get_server_status 3. 用户想重启服务就调用restart_service 4. 用户想发告警就调用send_alert ... 请根据用户的请求调用相应函数如果用户没有明确说明请询问清楚。 这个Prompt在功能列表短的时候还能勉强跑通一旦超过10个工具问题就集体爆发上下文膨胀。每个工具描述都占几十到上百个token塞得越多模型对真正关键信息的注意力被稀释得越厉害。实测超过15个工具描述时工具选择准确率明显下降。参数传递混乱。工具多起来后模型经常把上一个工具的参数套到下一个工具上比如把服务名传给了日志路径报错以后还会反复尝试同样的错误方式。行为边界模糊。模型不知道什么情况该拒绝、什么情况该请示、什么情况该直接执行于是经常自作主张做出超出权限的操作。完全无法测试。所有逻辑耦合在一个巨大的文本里改一处可能影响全局。你想加一个新能力得祈祷它不会让旧能力失效。这个问题的本质是你要求模型同时解决“理解任务”和“执行任务”两个问题而执行路径的定义是松散且模糊的。技能化的思路正是要把“执行”部分从Prompt里彻底拆出去固化成工程单元。1.2 技能化的三层结构与核心定义我的项目里对“技能”的定义是一个能够被大模型识别、选择和调用的最小可执行能力单元它拥有明确的输入输出契约、独立的运行逻辑和自包含的错误处理机制。一个完整的技能体系拆成三层层级名称职责应用层技能描述层告诉模型“我是什么、我干什么、什么时候该用我”逻辑层技能执行层真正干活的部分调用API、执行脚本、读写数据基础设施层注册与路由层统一管理技能清单做匹配分发、鉴权、统计描述层解决“模型如何知道我”执行层解决“选中后如何可靠地做”路由层解决“这么多技能如何快速找到我”。三者缺一不可。用生活化的类比来说这就像一个大型餐厅的后厨。菜单上的每一道菜是“描述层”后厨厨师的烹饪动作是“执行层”前台的点菜系统和传菜员则是“路由层”。顾客模型不需要亲自下厨只需要看着菜单点菜就能获得标准化的结果。如果菜单写得含糊、厨房各自为政、传菜混乱餐厅就会翻车——这就是不技能化Agent的现状。1.3 Skill与Function Calling的边界辨析很多人会问“这不就是Function Calling吗OpenAI早就有这个能力了。” 其实这两者的抽象层级完全不同。Function Calling是模型侧的一种接口规范——它定义了模型如何输出一个函数调用请求。它是一个“点”。比如模型说“我要调用restart_service这个函数参数是nginx”。而Skill是一个“面”。它是围绕某个完整能力域的封装内部可能包含多次Function Calling、多次API请求、甚至内嵌一套完整的状态机。举个例子Function Calling调用get_cpu_usage(hostweb-01)返回CPU使用率。Skill执行“服务器健康巡检”。内部逻辑包括SSH批量连接多台机器→采集CPU/内存/磁盘数据→比对阈值→生成体检报告→如果异常则触发告警通道。这是一个完整的可复用工作流不是一次函数调用能搞定的。实际上在agent-skills的架构里Function Calling只是技能执行时的一个底层通信手段。技能隐藏了内部的复杂性对模型暴露一个干净的入口你告诉我目标和必要参数我保证给你结构化结果。把这个边界划清楚你就不会在设计时陷入“把所有函数都做成技能”的误区。技能是业务能力单元不是技术方法单元。get_cpu_usage是方法论健康巡检才是技能。2. 技能注册中心与路由分发构建可插拔能力底座技能化最怕的一件事是“各自为战”。团队里三个人分别开发了查天气、查股票、做翻译的技能每个技能写在自己的文件里但没有人统一管理它们。等到模型需要调用的时候你甚至没有一个地方能看到“当前Agent总共有哪些可用技能”。这跟没有技能化没什么区别。所以技能架构必须有一个统一的注册中心所有技能都必须在中心登记、上线、下线。这个中心是整个agent-skills体系的心脏。2.1 技能清单的标准化设计每个技能在注册时都有一份标准化的“技能清单”Manifest。我用YAML格式定义示例如下name: server_health_check version: 1.2.0 description: 检查一台或多台服务器的健康状态包括CPU使用率、内存占用、磁盘空间和服务存活情况。 当用户提到服务器大屏、体检、宕机排查、性能瓶颈分析时使用。 不适合用于查看应用日志或配置修改。 trigger_keywords: - health - 体检 - cpu - 内存 input_schema: type: object properties: hosts: type: array items: type: string description: 服务器主机名或IP列表 check_items: type: array items: type: string enum: [cpu, memory, disk, service] default: [cpu, memory, disk] required: - hosts output_schema: type: object properties: overall_status: type: string enum: [healthy, warning, critical] details: type: array generated_at: type: string timeout: 30 permission: scope: read_only allow_audit: true这份清单有几个关键设计点值得说。第一是description的写法它必须包含三个信息这个技能是什么、什么场景该用它、以及什么场景不该用它。第三点尤其重要它给了模型一个“负向选择”的依据能有效减少技能误召。我见过很多清单里只写“用于检查服务器健康状态”结果用户问“服务器上跑了什么服务”也被模型匹配到这个技能上就是因为没写边界。第二是trigger_keywords。这不是给模型看的是给路由层做快速预筛用的。它不需要覆盖所有场景但必须覆盖最高频的触发语境。2.2 注册中心的数据结构设计注册中心在实现上就是一个技能注册表服务。我用SQLite做本地单机版本用PostgreSQL做团队共享版本核心表结构如下CREATE TABLE skills ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, version TEXT NOT NULL, description TEXT NOT NULL, manifest_json TEXT NOT NULL, -- 完整的Manifest内容 status TEXT DEFAULT active, -- active / deprecated / disabled owner TEXT, -- 技能负责人 call_count INTEGER DEFAULT 0, -- 累计调用次数 error_count INTEGER DEFAULT 0, -- 累计错误次数 avg_latency REAL DEFAULT 0, -- 平均延迟 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );除了技能元数据表还需要一个技能状态表记录每次调用的轨迹这是后面做评估和灰度发布的数据基础。注册中心提供三个核心接口register(skill)技能登记校验Manifest格式是否完整、Skill类是否实现了统一接口、是否与既有技能冲突。route(query, context)路由分发根据用户请求和上下文返回最匹配的技能。deregister(name, reason)技能下线不是删除而是置为disabled状态保留历史数据。2.3 路由策略从关键词匹配到语义向量调度路由是整个技能体系的调度大脑。最粗糙的版本是关键词匹配——用户问“查一下CPU”就匹配触发词cpu。但这样有几个致命问题用户说话千变万化“我的机器最近有点卡是不是CPU满了吗”这种自然表达关键词匹配根本覆盖不到。我在实际项目里用了两阶段路由策略第一级关键词粗筛。用触发词和正则把技能范围缩小。比如用户请求中包含“重启”“宕机”“服务起不来”就先把候选集锁定到运维相关技能不用整个技能库做全量匹配。第二级语义精排。把用户请求的向量表示与技能描述向量表示做相似度计算取Top N作为最终候选。这一步通常用embedding模型实现。代码上的伪逻辑是这样的def route_query(query, skill_registry): coarse_candidates [] for skill in skill_registry.active_skills(): if any(keyword in query for keyword in skill.trigger_keywords): coarse_candidates.append(skill) continue if re.search(skill.trigger_pattern, query): coarse_candidates.append(skill) # 粗筛结果少于2个时直接用向量模型做全量语义匹配 if len(coarse_candidates) 2: query_vec embed_query(query) scored [] for skill in skill_registry.active_skills(): skill_vec embed_model(skill.description) score cosine_similarity(query_vec, skill_vec) scored.append((skill, score)) scored.sort(keylambda x: -x[1]) return [s for s, _ in scored[:3]] # 粗筛结果较多时在候选集内做精排 query_vec embed_query(query) scored [] for skill in coarse_candidates: skill_vec embed_model(skill.description) similarity cosine_similarity(query_vec, skill_vec) # 叠加一个关键词命中加分 bonus sum(1 for kw in skill.trigger_keywords if kw in query) * 0.1 scored.append((skill, similarity bonus)) scored.sort(keylambda x: -x[1]) return [skill for skill, _ in scored[:3]]两阶段路由的价值在于准确率和成本之间的平衡。全量语义匹配精度高但每次都要给几百个技能做embedding计算调用量大时既费钱又增加延迟。关键词粗筛先砍掉大部分无关技能再在少量候选中做精排速度能快一个数量级。路由层还要有一个兜底机制如果Top N的相似度分数都低于阈值不要强行返回一个技能。正确做法是返回“无法匹配技能”的应答让模型回问用户澄清需求。这个兜底机制能避免大量误调用。我在一个45个技能的Agent项目里做过对比纯关键词路由的准确率只有61%纯向量路由的准确率是79%但每次路由延迟多了300毫秒两阶段路由的准确率是82%延迟只多了30毫秒。这个方案几乎成了我所有Agent项目的标配。3. 手写一个真实技能安全代码执行器的完整实现前面把理论和架构讲清楚了接下来动手写一个真实技能。我选“安全代码执行器”作为案例——这是我在项目里踩坑最多、也是收益最明显的一个技能。它允许用户提交一段Python代码在沙箱环境里执行并返回结果。这个技能任何一个做过Agent的人都会需要但很多人直接让模型用本地环境跑代码安全隐患极大。得到一个随时可以复用的技能是本节的目标。3.1 技能边界什么该接、什么坚决不接动手前先画边界。这个技能需要“接”的是用户的Python片段计算请求比如“计算一下Fibonacci数列前20项”“帮我处理一下这段JSON数据”数据清洗、格式转换、数学计算等纯逻辑类任务坚决“不接”的涉及文件系统读写任意路径只能写在沙箱的工作目录里网络请求沙箱里默认禁外网除非明确开启白名单安装任意第三方包只能使用预装的白名单库任何提权操作、系统命令调用无限循环和超长任务设置CPU时间上限和内存上限这个边界判断应该在技能内部强制实现而不是靠模型的自觉。规则写进沙箱配置里物理封死不管模型怎么想都突破不了。3.2 Skill主类统一接口设计所有技能都需要实现一个统一的Skill基类。这个基类约定了一个技能必须提供什么方法、返回什么格式from abc import ABC, abstractmethod from typing import Dict, Any class BaseSkill(ABC): abstractmethod def get_manifest(self) - Dict[str, Any]: 返回技能清单Manifest pass abstractmethod def execute(self, params: Dict[str, Any], context: Dict[str, Any]) - Dict[str, Any]: 执行技能核心逻辑返回标准化结果 pass def cleanup(self, params: Dict[str, Any]): 可选技能执行后的清理工作 pass基类Contract之所以要统一是因为路由层和编排层不需要关心具体技能的实现细节它们只需要知道“你实现了execute方法我传入规范化参数你返回规范化结果”。这样整个技能库就像一组USB设备即插即用。3.3 代码执行技能的实现与沙箱配置这个技能我最终用的是Docker容器隔离而不是exec直接执行。选择Docker而不是纯Python沙箱的原因exec可以通过__subclasses__()等技巧逃逸较多保护安全网不够密。Docker的隔离是内核级的权限、网络、资源限制都非常干净。有现成的镜像管理机制可以复现环境。技能执行流程import docker import uuid import json import time class CodeExecutionSkill(BaseSkill): def __init__(self): self.client docker.from_env() self.IMAGE_NAME python:3.11-slim self.TIMEOUT_SECONDS 20 self.MEMORY_LIMIT 256m self.CPU_LIMIT 1.0 def get_manifest(self): return { name: python_code_runner, version: 1.0.0, description: 在隔离沙箱中执行用户提供的Python代码片段返回标准输出、错误信息和执行状态。适合数据计算、文本处理、算法实现。不适合文件IO、网络请求、任意库安装。, trigger_keywords: [代码, python, 计算, 运行, 脚本], input_schema: { type: object, properties: { code: {type: string, description: 需要执行的Python代码}, timeout: {type: integer, default: 10} }, required: [code] }, timeout: 25, permission: {scope: isolated_sandbox} } def execute(self, params, context): code params.get(code, ) if not code: return {status: error, error: code参数不能为空} # 沙箱内预装的白名单库列表用pip在镜像构建时安装即可 allowed_pkgs [] if import requests in code: return {status: error, error: 沙箱内禁止网络请求请使用本地计算能力} container_name fcode-runner-{uuid.uuid4().hex[:8]} container_cmd [python, -c, code] try: container self.client.containers.run( imageself.IMAGE_NAME, commandcontainer_cmd, namecontainer_name, detachTrue, mem_limitself.MEMORY_LIMIT, nano_cpusint(self.CPU_LIMIT * 1e9), network_disabledTrue, read_onlyTrue, # 容器内文件系统只读 tmpfs{/tmp: size64m}, # 仅允许/tmp有临时写权限 environment{PYTHONDONTWRITEBYTECODE: 1} ) result container.wait(timeoutself.TIMEOUT_SECONDS) logs container.logs().decode(utf-8).strip() status_code result.get(StatusCode, -1) return { status: success if status_code 0 else runtime_error, stdout: logs, exit_code: status_code } except docker.errors.ContainerError as e: return {status: container_error, error: str(e.stderr.decode())} except docker.errors.APIError as e: if Timeout in str(e): # 超时时间到了直接把容器杀掉 try: self.client.containers.get(container_name).kill() except Exception: pass return {status: timeout, error: 代码执行超时已终止} return {status: docker_error, error: str(e)} finally: try: self.client.containers.get(container_name).remove(forceTrue) except Exception: pass这里面有几个关键的坑要说明network_disabledTrue把整个容器的网络栈关掉这是比在代码里检查import requests更可靠的安全边界。因为就算模型用了别的方式发起网络请求比如urllib也一样会被网络禁用拦截。read_onlyTrue让根文件系统只读配合tmpfs给一个临时的可写目录。这样代码如果乱写文件只能写在62MB的临时目录里容器销毁时自动消失。超时处理要先wait(timeout)然后kill()而不是在run的时候直接设timeout参数。直接设timeoutDocker SDK会把异常抛出来但容器不一定被清理掉容易残留僵尸容器。3.4 挂载到Agent主循环与实测表现技能类写好后注册到Agent的主循环里。这里给一个简单的集成示例假设你用的是OpenAI SDKfrom my_skills.code_executor import CodeExecutionSkill skill CodeExecutionSkill() routes skill_registry.register(skill) tools [] for s in routes: tools.append({ type: function, function: { name: s.manifest[name], description: s.manifest[description][:1024], parameters: s.manifest.get(input_schema, {}) } }) completion client.chat.completions.create( modelgpt-4o-mini, messagesuser_messages, toolstools, ) # 模型选择调用code_executor时把工具参数传给skill.execute()我拿这个技能做了几轮实测。让模型运行“用蒙特卡洛方法估算圆周率”模型在多轮对话中正确选择了code_executor并将代码传入执行沙箱返回结果模型再把数值公式化解释给用户整个链路表现稳定。让模型执行__import__(os).system(ls /)时由于沙箱只读且网络禁用即使命令被执行也拿不到外部文件更无法外传数据。这里我特别提醒一下千万不要为了省事用exec内嵌到Agent进程里跑用户代码。一次“不小心”就可以让你Agent的整个内存空间暴露给恶意代码。Docker隔离虽然多了一层容器调度开销但在安全性上值得。4. 技能编排与组合从“能用”到“好用”当技能数量上到两位数以后你会发现另一个难题单个技能负责的任务太单一了用户要的是一个端到端的完整服务。比如“帮我把这个CSV文件统计分析后做成图表发给我”这个动作至少涉及读文件/入库、数据统计分析、图表生成、结果输出四个环节。靠模型自己一步步想调用哪个技能相当于每次都做一次实时编排不稳定是常态。我建议在技能之上增加一个显式的编排层用流程定义串起多个技能。4.1 顺序编排与条件分支最基础的编排是顺序执行。组织编排的配置文件可以选择YAML或JSON格式。这里我尝试用JSON来定义一个自动化的数据处理工作流{ workflow: csv_analysis_report, version: 1.0.0, steps: [ { id: csv_parser, skill: csv_parser, input: { file_path: {user_input.file_path} } }, { id: statistics, skill: data_statistics, input: { data: {steps.csv_parser.output.data}, metrics: [mean, median, max, min, std] } }, { id: chart_generator, skill: chart_generator, input: { statistics: {steps.statistics.output}, chart_type: auto } }, { id: report_writer, skill: markdown_report, input: { title: 数据分析报告, sections: { 概述: {steps.csv_parser.output.summary}, 统计结果: {steps.statistics.output}, 图表: {steps.chart_generator.output.image_markdown} } } } ] }Python端用一个简易的编排引擎来执行这类流程def execute_workflow(workflow_definition, user_input): step_results {} for step in workflow_definition[steps]: # 解析本步骤的输入参数支持引用前序步骤输出 parsed_input resolve_placeholders(step[input], {**step_results, user_input: user_input}) skill skill_registry.get_skill(step[skill]) result skill.execute(parsed_input, {workflow_id: workflow_definition[workflow]}) step_results[step[id]] result if result.get(status) ! success: workflow_context.save_progress(step[id], result) return {status: failed, failed_step: step[id], error: result.get(error)} return {status: success, final_output: step_results[workflow_definition[steps][-1][id]]}条件分支同样可以用JSON来表达比如“如果统计数据中有null值则先执行清洗再继续否则直接生成报告”。把这种逻辑固化进编排定义里模型只负责判断整体目标不再为每个中间步骤的调用细节操心。这既降低了延迟也减少了模型随意选错技能的概率。4.2 技能间数据契约中间状态的流转编排的本质是让技能A的输出作为技能B的输入。那这个“输出”长什么样我推荐一个统一的结果信封格式{ status: success, output: {...}, # 技能的私有输出按技能定义的结构 metadata: { skill_name: csv_parser, latency_ms: 240, truncated: false, verdict: confident # public 或 low 置信度 } }verdict字段是后来加上的很有用。某些技能执行时可能因为上游数据质量差而对自己输出的置信度不高。比如数据统计技能发现CSV里有30%的单元格是空值它仍然输出了统计结果但会把verdict标记为low。编排引擎看到low时可以决定是否插入一个数据清洗步骤或者通知用户结果仅供参考。这样数据流转过程不再是盲人摸象。4.3 编排冲突的消解优先级与超时熔断多技能并行时冲突不可避免。两个技能可能同时争抢同一个资源或A技能的运行会让B技能的输入过时。我的处理原则是同一技能编排链中禁止自循环防止无限递归。每步骤单独设置超时上限超时后直接将该步骤标记为failed同时终止后续步骤避免资源浪费。如果两个技能都需要写同一个临时文件为每个步骤分配独立的命名空间目录物理隔离从根上杜绝冲突。全局增加一个熔断开关——如果某个技能在连续3次执行中失败编排引擎直接将其标记为faulty后续工作流跳过该技能。这个熔断设计能在生产环境里防止雪崩避免一个坏技能阻断所有任务。4.4 实战案例自动复盘日报生成这个编排思路我做了一个具体应用自动生成团队每日复盘日报。原来团队每天下班前要花半小时聚在一起说今天做了啥、卡在哪、明天干啥然后有专人整理成文档。我把它做成了一条技能编排链从项目管理系统拉取今日所有任务状态拉取任务技能从Git仓库提取今日提交记录和分支合并信息Git日志技能从IM工具导出今日讨论的高频关键词消息聚合技能将前三个步骤的数据交给汇总生成技能让大模型生成复盘点、风险点、明日计划三段式日报草稿推送日报草稿到IM群由人在线确认或修改后再正式发布人机协同技能这个流程上线后原本半小时的复盘压缩到5分钟而且日报格式统一、统计口径一致。更重要的是每步的输出都被记录下来月底还能跑一次月度趋势分析进一步验证了技能编排在规模化信息处理上的价值。5. 技能资产的生命周期管理测试、版本与灰度技能不是写完就完了。作为一个长期运转的Agent系统技能库就是你的核心资产需要像对待代码服务一样对待它的生命周期。很多团队把技能写出来就跑结果一周后某个技能悄悄退化现象是用户请求开始持续报错或返回模糊结果。排错时一要查半天才发现是某个技能底层请求的目标接口变了。这些坑建立一套完整的管理机制就能尽量避免。5.1 技能评估集用数据说话我强烈建议从第一天就给技能建立评估集。评估集不是功能测试而是针对“模型-技能匹配”和“技能执行质量”的联合评测。具体分两部分技能选择评估集Routing Test一批构造好的用户语句每条标注了正确答案——应该路由到哪个技能。例如用户说“帮我算一下这段代码的时间复杂度”应该路由到代码分析技能而不是代码执行技能。技能执行评估集Execution Test针对每个技能的输入样例集每条包含参数和期望输出用于验证技能在给定参数下是否正确完成任务。评估类型样例数通过标准统计口径路由准确率200条/技能库≥90%正确路由数/总请求数执行成功比例100条/技能≥95%状态为success的次数/总调用次数结果正确率100条/技能≥80%人工抽检人工判断正确的次数平均延迟—≤3s总延迟/调用次数异常处理率40条极端输入100%返回错误而非崩溃的次数每个新技能上线前先跑一轮评估集。低于标准的技能再打磨一下不要直接上线。这个习惯让我的项目避免了大量线上返工。5.2 版本化与回滚技能也是要发版的技能跟普通代码服务一样也需要版本管理。大部分技能是纯函数式逻辑基础版本version: 1.0.0每次修改文档、优化描述、调整参数、修复错误后版本号递增。如果新版本表现不佳可以一键回滚到上一版。我推荐简单可用的版本策略主版本号Major技能接口契约发生破坏性变更时递增。比如改变了输入参数结构旧调用方式不再兼容。次版本号Minor新增能力或参数但保持向后兼容时递增。补丁版本号Patch修复错误、优化文档、调整内部实现但对外行为不变时递增。版本切换在注册中心里完成。注册中心持续维护多个版本的技能定义但对外仅暴露当前active版本。线上每次路由到该技能时都查询最新的active版本号一旦执行异常率超标或人工确认失败立即切换回上一个稳定版本号。5.3 灰度发布与人机协同确认高风险的技能变更我不想“全量切换”。灰度是更好的选择。实现方式很简单注册中心增加一条流量规则例如version: 1.2.0承接整体调用量的10%其余90%仍然走version: 1.1.0。观察一周的评估数据若错误率没有上升再逐步提升至30%、50%、100%。人机协同确认是另一个重要防线。对于高影响技能发告警、删数据、改配置、付款即便技能本身是自动化的我要求它在执行前输出一个“执行预案”由人在IM群点确认按钮后才真正执行。这个确认机制在Agent早期阶段能极大降低事故率——因为模型再聪明也无法完全替人承担后果责任。人工审核是在能力与责任之间找平衡。5.4 从维护成本看技能收敛该合并就合并技能库膨胀到一定规模后维护成本会陡增。每多一个技能就意味着路由评估集需要多覆盖它、灰度版本需要兼顾它、故障时多一个排查对象。我见过一个项目把“读取Excel”和“读取CSV”分为两个技能又加了“读取JSON文件”和“读取YAML文件”四个技能互相几乎不共享代码。维护它们各自版本的痛苦远超收益。我遵循一个“能力域收敛”原则同属于一个数据读取领域的底层能力合并为一个大技能用参数区分文件类型如果两个技能的description在描述同一个工作流的不同阶段它们也应该合成一个完整的技能而不是拆开如果一个技能在两周内没有一次路由命中记录说明它的description或触发设计有问题只有一个原因是它确实没用——这时就该考虑删除或合并。技能的收敛不是减少功能而是让Agent的决策空间更干净让模型选择的准确率更高。这是用工程的克制换取产品的稳定。我自己的体会是做Agent项目最难的往往不是把单技能跑通而是让技能库整体运转得像一个训练有素的团队。技能之间配合有序、路由清晰、版本可控、演进有章法这才是agent-skills这套架构真正沉淀下来的长期价值。踩过几次坑之后我现在每接手一个新Agent项目都会先从技能盘点开始而非直接调模型写代码——这个顺序一旦反了后期返工的代价远超你想象。
返回列表