首页/新闻资讯/正文详情

从提示词堆砌到技能模块化:AI Agent开发实战

发布时间:2026/9/24 22:37:01 来源:云帆数科 栏目:资讯中心
从提示词堆砌到技能模块化:AI Agent开发实战
我做了两年多 AI Agent 相关的开发踩过最大的坑就是把 Agent 的能力全堆在提示词里。一开始觉得挺爽prompt 里写清楚“你可以调用搜索、计算、写代码”模型好像真的听话。等任务稍微复杂点就原形毕露上下文被占满、工具调用格式飘忽不定、改一个功能的提示词还容易影响其他功能的表现。后来我把项目重构单独拆出来一套技能系统也就是现在的 agent-skills 项目。这个项目说白了就是给 Agent 做一套模块化的“能力插槽”每个技能独立定义、独立注册、独立执行由调度器根据任务描述自动选择要激活的技能。它适合正在做 Agent 应用、被“工具调用不稳定”和“提示词越写越乱”折磨的开发者和产品团队参考。先说明一下这篇文章不涉及某个现成框架的源码解读而是把我自己做 agent-skills 时的完整思路、踩坑记录和可复现的代码骨架分享出来。无论你是用 LangChain、CrewAI还是完全自己写编排逻辑这套设计思想都能直接落地。1. 项目概述agent-skills 是什么解决什么问题1.1 从一次真实翻车说起去年我做过一个内部知识库问答 Agent需求不复杂用户问问题Agent 先检索知识库再结合检索结果回答。第一版实现极其天真把检索逻辑、总结逻辑、判断逻辑全写进了 system prompt用类似“当你需要查找资料时请使用 search_docs(query) 这个函数”的句式。结果上线第一周就出问题。业务方反馈同一个问题隔一天问答案风格能变一个样。更麻烦的是某些问题模型会“自作聪明”跳过检索直接凭训练数据里的记忆编答案。我反复调提示词这个 bug 修好了另一个问题又冒出来。后来又加了几个新工具prompt 长度从 800 字涨到 3000 字每次请求光提示词就要吃掉一大截 token响应延迟肉眼可见地上升。这就是我决定做 agent-skills 的直接导火索把 Agent 的能力从“写在提示词里的约定”变成“代码里的实体”。1.2 agent-skills 的定位与边界agent-skills 这个项目核心定位是“Agent 能力的模块化管理与自动调度层”。它解决三件事能力注册每个技能有唯一的名称、描述、参数 Schema集中登记在技能注册表里。能力发现Agent 在运行时根据用户目标和上下文从技能注册表里选出候选技能。能力执行技能被选中后由统一执行器完成参数校验、调用、异常处理、结果归一化。这里需要划清一个边界agent-skills 不是 Agent 框架本身它不负责规划、不负责记忆、不负责多轮对话管理只专注在“技能”这一层。你可以把它理解为 Agent 的“工具箱管理员”而不是“大脑”。我见过不少团队试图自己做这套东西最后都掉进同一个泥潭把技能逻辑和 Agent 的规划逻辑耦合在一起。技能内部不该感知“我是被谁调用的”“现在在第几轮对话”它只需要暴露“我给你这些参数你返回这个结构的结果”。这个原则在后面实现时非常重要。2. 整体设计与思路拆解为什么技能必须模块化2.1 拆掉提示词里的“瑞士军刀”很多人觉得把工具说明写进提示词不也一样吗区别很大。提示词是文本模型对它只是“读取”不具备强约束力。模型可能漏看、可能理解偏、可能输出格式不对。而技能系统是代码它有类型检查、参数校验、错误分支模型只要选对了技能剩下的事情由程序兜底。我做一个类比把工具塞进提示词就像口头交代同事“有空帮我把数据整理一下”信息全靠默契用技能系统相当于提交一份标准工单字段齐全、流程固定、结果有验收标准。后者出错的概率天然低一个量级。2.2 技能、工具、插件概念边界怎么划做 agent-skills 之前我先把概念理清楚不然代码写起来会很乱概念定位粒度示例工具Tool单一原子操作最细执行一次 HTTP GET、读一个文件技能Skill围绕某个能力的完整逻辑中等查天气、做竞品分析、生成周报插件Plugin一组相关技能的打包分发最粗“数据分析插件”包含数据清洗、统计、可视化 3 个技能agent-skills 管理的是“技能”这一层。一个技能内部可以用多个工具技能对外是黑盒。这层抽象非常关键业务方只关心“这个 Agent 会不会查天气”不关心天气 API 用的是哪家服务商、返回格式是 JSON 还是 XML。2.3 技能注册表 执行器的双段式架构我最终选的是“注册表 执行器”的双段式架构整体流程分两步走选择阶段把技能列表名称 描述 参数 Schema交给 LLM由模型决定调用哪个技能、传入什么参数。这一步产出的是“调用意图”。执行阶段执行器根据意图从注册表取出对应技能做参数校验后真正执行。执行结果再回传给模型由模型组织最终回复。为什么分成两段不直接让模型执行因为模型不可靠直接让模型输出“最终答案”很难验证对错但让模型只输出“我要调什么、参数是什么”就简单多了。调用意图是结构化数据可以用 JSON Schema 严格校验执行结果来自真实代码可信度远高于模型的凭空生成。3. 核心细节解析与实操要点3.1 技能描述怎么写模型才容易命中技能系统里最容易被低估的是“描述description”字段。它不直接参与执行但决定了模型能不能在正确的时候选到正确的技能。我踩过的一个坑是描述写得太抽象。比如“weather”技能我一开始写的描述是“Get weather information”结果模型经常在该用的时候不用。后来改成“Get current weather and forecast for a city. Use this when the user asks about temperature, rain, wind, or weather conditions for a specific location.命中率明显提升。描述的关键是给出使用时机而且是带场景和使用条件的描述不是单纯的功能陈述。写描述的时候我总结了四条经验写清楚技能的触发场景用“当用户提到……时使用”的句式包含典型问法示例比如“用户可能问‘上海明天热吗’”说明不适用的边界比如“只支持中国城市不支持国外城市查询”控制长度在 50~150 字之间太短信息不够太长模型容易忽略3.2 参数 Schema 的合理设计别小看这一步参数 Schema 是技能与模型交互的“契约”。我遇到过最气人的场景技能写好了模型也选中了但传进来的参数完全没法用。比如查询天气需要“城市名”模型传了一堆“用户想去上海玩上海这几天热不热”这种自然语言而不是规范的城市名。这个问题靠一个技巧解决了大半在 Schema 的 description 里明确参数格式和示例值。比如城市名参数描述写成“City name in Chinese, e.g. 上海, 北京. Do not include extra words like 天气预报.”模型看到这样的约束输出规范程度会高非常多。再补充一个实操中很实用的设计给参数加默认值和容错归一化。我习惯在技能内部做一层输入清洗比如城市名先做去空格和别名映射“魔都”映射到“上海”这样就不会因为用户说法口语化而直接报错。3.3 技能执行的沙箱与安全边界Agent 技能一旦开放给用户调用安全就必须考虑。这里说的安全不只是防止恶意的提示注入也包括防止用户误操作触发副作用。我的原则是有副作用的技能必须带确认机制。比如“发送邮件”“删除文件”“下单支付”这类技能不能模型一判断就执行。markdown 里写了一个通知提示但代码层面更要强制确认。所以我在技能定义里加了一个confirm_required字段这类技能在执行前必须经过用户二次确认。另外执行环境也要尽可能隔离。涉及外部 API 的技能统一走网关涉及文件操作的技能限制在指定目录内涉及代码执行的技能强制放进沙箱容器。这些成本不高但能避免 99% 的“Agent 闯祸”事故。4. 实操过程与核心环节实现4.1 环境准备与基础骨架这个项目的语言我选了 Python主要因为生态成熟而且后续要接入不同的 LLM 都方便。依赖很少核心就两个Pydantic 做数据校验OpenAI SDK 做模型调用其他厂商的 SDK 也兼容后面会讲。先把技能的数据结构定义出来这是整个系统的地基from pydantic import BaseModel, Field from typing import Callable, Any, Optional class SkillDefinition(BaseModel): 技能元信息定义 name: str Field(description技能唯一名称如 weather_query) description: str Field(description技能描述包含使用时机、典型场景、边界说明) parameters_schema: dict Field( description参数 JSON Schema遵循 JSON Schema 规范, default_factorydict ) confirm_required: bool Field( description执行前是否需要用户二次确认, defaultFalse ) class Skill(BaseModel): 技能实体元信息 执行函数 definition: SkillDefinition handler: Callable[..., Any]这里 Pydantic 只是辅助核心思想是一个技能 元信息给模型看的契约 处理器给程序执行的函数。两者分开模型只接触元信息程序只执行 handler。4.2 技能注册表与执行器接着是注册表它负责维护“系统里有哪些技能”class SkillRegistry: 技能注册表登记、查找、列出技能 def __init__(self): self._skills: dict[str, Skill] {} def register(self, skill: Skill) - None: if skill.definition.name in self._skills: raise ValueError(fSkill {skill.definition.name} already registered) self._skills[skill.definition.name] skill def get(self, name: str) - Skill: return self._skills[name] def list_skills_for_llm(self) - list[dict]: 转成 LLM 友好的工具定义格式 return [ { type: function, function: { name: skill.definition.name, description: skill.definition.description, parameters: skill.definition.parameters_schema, } } for skill in self._skills.values() ]执行器我单独写了一个类职责很单一接收模型返回的工具调用意图找到技能校验参数执行兜底异常import json class SkillExecutor: 技能执行器负责参数校验与函数调用 def __init__(self, registry: SkillRegistry): self.registry registry def execute(self, skill_name: str, arguments: dict) - dict: skill self.registry.get(skill_name) # 参数校验这里可以基于 parameters_schema 做严格校验 # 我这里用 pydantic 的 TypeAdapter 动态校验失败时返回清晰错误 try: result skill.handler(**arguments) except TypeError as e: return { status: error, error_message: fInvalid arguments for skill {skill_name}: {e} } except Exception as e: return { status: error, error_message: fSkill execution failed: {e} } return {status: success, result: result}4.3 动手实现一个真实技能天气查询下面的示例是完整的技能创建过程。我以“天气查询”为例参数 Schema 写得比较讲究描述部分重点突出触发场景def get_current_weather(city: str, unit: str celsius) - dict: 模拟天气查询实际项目替换为真实 API 调用 # 这里假设调用了某个天气服务 mock_data { 上海: {temp: 28, condition: 晴, humidity: 65}, 北京: {temp: 24, condition: 多云, humidity: 50}, 广州: {temp: 31, condition: 雷阵雨, humidity: 88}, } data mock_data.get(city, {temp: None, condition: 未知, humidity: None}) if unit fahrenheit and data[temp] is not None: data[temp] round(data[temp] * 9 / 5 32, 1) return {city: city, **data} weather_skill Skill( definitionSkillDefinition( nameget_current_weather, description( 查询中国主要城市的当前天气和温度。 当用户询问温度、是否下雨、风力、天气状况时使用。 支持城市示例上海、北京、广州。 如果城市不在支持列表中明确告知用户暂不支持。 ), parameters_schema{ type: object, properties: { city: { type: string, description: 城市名用中文例如上海。不要携带语气词或多余文字。 }, unit: { type: string, enum: [celsius, fahrenheit], default: celsius, description: 温度单位默认摄氏。 } }, required: [city] } ), handlerget_current_weather ) registry SkillRegistry() registry.register(weather_skill)4.4 接入 LLM 完成自动调度技能注册好了怎么让模型自动选择呢这里利用 OpenAI 的 function calling 机制把技能列表作为 tools 传入模型返回的 tool_calls 就是“选中 参数”的意图from openai import OpenAI client OpenAI() def run_agent_with_skills(user_input: str, registry: SkillRegistry, executor: SkillExecutor): messages [{role: user, content: user_input}] response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolsregistry.list_skills_for_llm(), tool_choiceauto, ) response_message response.choices[0].message # 如果模型决定调用技能 if response_message.tool_calls: for tool_call in response_message.tool_calls: skill_name tool_call.function.name arguments json.loads(tool_call.function.arguments) # 对需要确认的技能先返回提示信息 skill_def registry.get(skill_name).definition if skill_def.confirm_required: return {type: confirmation_needed, skill: skill_name, arguments: arguments} # 执行技能 execution_result executor.execute(skill_name, arguments) # 把技能结果返回给模型让模型组织最终答案 messages.append({ role: assistant, content: response_message.content, tool_calls: response_message.tool_calls }) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(execution_result, ensure_asciiFalse) }) final_response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, ) return final_response.choices[0].message.content return response_message.content到这里一个最小的 agent-skills 闭环就跑通了。用户问“上海今天热吗”模型会选择 get_current_weather传入 {city: 上海}执行器返回真实天气数据模型再根据数据组织回答。4.5 多技能协同与任务编排当技能数量多起来你还会遇到“一个任务需要多个技能顺序执行”的情况。比如用户问“上海今天适合跑步吗”需要先查天气再参考空气质量也许还要查限行信息。我的做法是给模型加一个轻量的“计划器”在 system prompt 里提示模型“如果需要多个技能请先规划调用顺序分步调用”然后循环执行 tool_calls直到模型不再请求调用为止。每次循环把上一轮的工具结果附加到 messages 里模型就能看到之前的执行结果并决定下一步。这本质上是一个简单的 ReAct 循环但对多数任务已经足够了不需要上重型的规划图编排。5. 常见问题与排查技巧实录5.1 模型选了技能但参数一塌糊涂这是出现频率最高的问题。表现是技能选对了但传入的参数带着前后缀比如city字段是“帮我查一下上海的天气”而不是规范的“上海”。我的排查路径是三步先看parameters_schema 的 description是否写清楚格式要求没写清楚就让模型自由发挥必然飘再看模型返回的原始 arguments如果原始输出就是乱的那就是 Schema 约束不够如果原始输出是规范的那就是执行器侧解析的问题检查是不是用了错误的反序列化方式最佳实践是双保险Schema 层面尽量约束 执行器内部做归一化清洗。比如 city 字段解析后先做正则去杂质再匹配已知城市列表都不行才报错。5.2 多个技能互相“抢活”技能多了以后会出现 A、B 两个技能都能处理同一类请求模型时而选 A、时而选 B结果不稳定。比如“查天气”和“查穿衣建议”用户的“上海穿什么”就可能被选到天气技能。解决办法是明确技能的边界描述在描述里写“本技能只负责 X不处理 Y”。同时可以在技能描述里写明优先级“如果用户询问穿着建议请优先调用穿衣建议技能穿衣建议技能内部会自动查询天气。”这样模型在选择时就有了明确的优先级指引。5.3 技能调用超时与资源泄露技能是真实代码真实代码就会超时、就会挂起。之前我有个技能调外部 API对方服务抖动结果整个 Agent 卡了 40 多秒才响应。用户早跑了。后来给执行器加了统一的超时控制Python 里用concurrent.futures包一层就行from concurrent.futures import ThreadPoolExecutor, TimeoutError def execute_with_timeout(handler, timeout_seconds: int 10, **kwargs): with ThreadPoolExecutor(max_workers1) as pool: future pool.submit(handler, **kwargs) try: return future.result(timeouttimeout_seconds) except TimeoutError: pool.shutdown(waitFalse, cancel_futuresTrue) return {status: error, error_message: fSkill execution timed out after {timeout_seconds}s}另一个隐藏问题是外部 API 的 token 配额和并发限制。全局统一在技能外层加限流器rate limiter按技能分级控制调用频率避免一个技能把整个系统的配额打爆。5.4 技能更新后模型还在用旧行为我经历过一次很诡异的故障技能逻辑已经改了模型还是按照旧的行为方式调用。排查半天发现是缓存问题——LLM 调用层面没问题但技能注册表被加载成了旧版本进程没重启。这里要养成一个习惯技能注册表启动时做一次版本校验打印当前加载的技能清单和版本号。上线新技能后先跑一遍自检命令确认注册表内容再放开流量。6. 一点后续扩展的想法agent-skills 目前已经帮我把内部几个 Agent 项目的提示词体量压缩了 60% 以上工具调用的稳定性也有肉眼可见的提升。下一步我打算做两件事一是给技能加自动测试每个技能注册时附带几个 mock 用例冒烟测试通过才允许上线二是把技能的调用日志结构化存储用来分析哪些技能经常被模型误选、哪些描述还需要调优。如果你也在做 Agent 项目我的建议是从一个小场景开始别一上来就设计几十个技能。先做 3 个核心技能跑通注册、调度、执行、回传这个闭环再逐步扩展。等你把第一个技能系统的坑都踩完后面加技能就是纯粹的堆量了。

相关推荐

热修复原理与实战:从Android Tinker到后端Arthas
热修复原理与实战:从Android Tinker到后端Arthas

线上这周已经崩了三次,客户在群里拍桌子,你最想干的事是什么?大部分人第一反应是:赶紧把出问题的代码修掉,最好能立刻生效。这就是“热修复”这门技术存在的意义——在不下发完整新版本的情况下,让已经在线… · 2026/9/24 22:37:01

告别反复cd:用Gita一条命令管理多个Git仓库
告别反复cd:用Gita一条命令管理多个Git仓库

你手里如果同时管着十几个Git仓库,你一定懂我接下来要说的痛点:发版前挨个进目录敲git status、git log,一遍遍重复差不多的工作;想看看哪个仓库还有未提交的修改,得用find或脚本轮一遍;更别提多个前后端仓… · 2026/9/24 22:37:01

BlockNote 进阶表格实战:基于 onChange 事件实现带自动计算的表格列
BlockNote 进阶表格实战:基于 onChange 事件实现带自动计算的表格列

BlockNote 进阶表格实战:基于 onChange 事件实现带自动计算的表格列 【免费下载链接】BlockNote A React Rich Text Editor thats block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap. 项目地址: https://gitcode.com/gh_mirror… · 2026/9/24 22:36:54

基于SpringBoot的流浪猫狗救助领养管理系统开发指南
基于SpringBoot的流浪猫狗救助领养管理系统开发指南

做这类基于 SpringBoot 的流浪猫狗救助领养管理系统,看着是个典型的 Java 毕业设计题目,但真要做到能跑、能答辩、能扩展,里头的门道并不比企业级项目少。我前后带过几届毕业生做类似课题,也帮人 review 过不少代码,今… · 2026/9/24 23:56:41

学术论文图表规范全攻略:从选图到投稿的细节指南
学术论文图表规范全攻略:从选图到投稿的细节指南

图表规范这事儿,看着是“最后一公里”,其实是论文能不能过编辑法眼、能不能让审稿人一眼看懂工作量的关键一环。我见过太多人,做了非常漂亮的数据分析,图却画得像半成品:坐标轴字体小到要拿放大镜看,两个组… · 2026/9/24 23:56:41

STM32实战:一套可复现的开源工程,原理图+代码+仿真全解析
STM32实战:一套可复现的开源工程,原理图+代码+仿真全解析

1. 我为什么把整套STM32工程直接摊开:一个可复现项目的自我要求最近整理手头的一套STM32项目时,我做了个决定:把代码、原理图、仿真三样东西完整开源出来。身边不少朋友问我,开源就开源,丢个代码仓库不就行了&#xff… · 2026/9/24 23:56:41

基于LiteRT.js的浏览器端收据扫描器:WebAssembly与WebGPU加速实战
基于LiteRT.js的浏览器端收据扫描器:WebAssembly与WebGPU加速实战

浏览器里跑OCR这件事,我从Tesseract.js刚出来那会儿就在折腾,当时的体验说实话挺劝退的——加载慢、识别率一般、大图直接卡死主线程。后来PaddleOCR的Web版本出来,精度上去了但包体积又成了新问题。直到LiteRT.js进入视野,配合We… · 2026/9/24 23:56:41

Matlab支持向量机仿真实战:从数据准备到参数调优
Matlab支持向量机仿真实战:从数据准备到参数调优

简介:支持向量机(SVM)在电力系统短期负荷预测中的MATLAB仿真资源,面向电力预测与回归建模方向的初学者和研究人员,帮助读者通过实际案例掌握SVM模型构建、数据预处理与预测效果评估。压缩包共12个文件,以6个… · 2026/9/24 23:56:41

WorkBuddy实战指南:10个AI技能重塑工作流,会议纪要、周报与邮件效率翻倍
WorkBuddy实战指南:10个AI技能重塑工作流,会议纪要、周报与邮件效率翻倍

用了大半年 WorkBuddy,说实话,最早我也觉得这类 AI 助手就是“聊天窗口加个知识库”,但真正把它嵌进日常工作流之后,改变最大的是我处理那些“琐碎但必须做”的事情的方式。以前一个上午耗在会议纪要、周报、邮件回复上&#xff0… · 2026/9/24 23:56:34

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码