新闻详情

新闻详情

首页 / 资讯中心 / 详情

Agent技能库实战:从Prompt膨胀到按需调用

发布时间:2026/9/25 7:21:19来源:尧图网络
Agent技能库实战:从Prompt膨胀到按需调用
做Agent开发快两年了我最大的体会是Agent能不能真正落地很多时候不取决于模型多聪明而取决于你给它准备的“技能”靠不靠谱。今天想聊的这个项目agent-skills就是一套把Agent能力拆成可复用技能、按需注册与调用的实战框架。它适合正在搞AI应用、想让大模型真正干活的开发者也适合想把自己的工具集沉淀成产品化能力的团队。我不打算只讲概念而是把整个项目的设计思路、核心代码和踩坑过程都摊开来说希望能给你一个可以直接抄作业的参考。1. 项目概述为什么Agent需要一份“技能库”1.1 agent-skills到底在解决什么问题先说背景。我在做Agent项目时最初的做法很简单把所有工具函数塞进System Prompt让模型自己看着办。结果就是Prompt越来越长模型越来越糊涂经常该调工具的时候不调不该调的时候乱调。后来我把项目拆成了agent-skills的形态——每个能力都封装成一个独立的、带元数据的技能由Agent按需调度。这里的关键词是“按需”。模型不是看到一长串工具列表再现场理解而是通过一套结构化的技能清单先判断“我现在需要哪个能力”再调用对应的技能执行。这个思路的本质是把大模型的推理能力和外部工具的执行能力解耦。推理归推理执行归执行中间用一份清晰的技能注册表来衔接。这个项目能解决的问题主要有三个解决Prompt膨胀问题工具描述不再全塞进System Prompt技能清单可以动态维护、按需注入。解决能力复用问题一个技能可以被多个Agent、多个场景共用不用每个项目重写一遍。解决调试困难问题技能调用有独立的输入输出结构出了问题可以精确地定位到是哪个环节挂了。1.2 技能层抽象比直接堆Prompt强在哪很多人会说我不搞什么技能框架直接把函数扔给LLM用Function Calling不就行了确实可以但只适合两三个工具的场景。一旦工具数量超过十个直接函数列表的方式就会暴露很多问题。我用一个生活化的类比来解释。你去餐厅吃饭菜单就是Agent的技能清单。如果餐厅把食材仓库的进货单也贴在菜单上你还能快速点菜吗不能。同样道理LLM需要看到的是“菜品”技能的简短描述和适用场景而不是每个函数背后的实现细节。agent-skills做的就是给Agent一份精简的、有结构的“点菜菜单”而不是把整个仓库清单丢给它。从工程角度看技能层抽象带来的好处很明显职责边界清晰每个技能只做一件事输入输出都被明确定义方便单测和迭代。调度逻辑统一所有技能走同一个注册、发现、调用、返回的链路新增能力不需要改主流程代码。可观测性强技能被调用一次就能记录调用参数、耗时、返回结果后续做分析、审计、优化都有数据支撑。1.3 适合谁用这套方案如果你属于下面任一类人这个项目值得花时间研究正在开发AI客服、AI助手、自动化工作流需要让模型调用外部API或内部服务的开发者。已经用过Function Calling但发现工具一多就开始乱、不好维护的团队。想把个人或团队沉淀的工具包做成可复用、可分享的能力库的人。这套方案对模型本身没有特殊要求OpenAI系的Function Calling可以用开源模型走JSON模式再加约束也可以用。后面我会讲到具体怎么适配。2. 技能体系的三大核心设计2.1 技能的定义一份让模型和人都不迷惑的元数据agent-skills项目的第一个核心就是定义技能的数据结构。这不是随便写个函数名字就完事而是要建立一套统一的元数据规范。我目前用的核心字段包括技能名称、用途描述、参数Schema、执行函数、标签和版本。from pydantic import BaseModel, Field from typing import Callable, Any, Optional class Skill(BaseModel): name: str Field(..., description技能的唯一标识使用英文小写加下划线) description: str Field(..., description对模型展示的技能用途说明必须包含能力和适用场景) parameters: dict Field(..., description参数Schema遵循JSON Schema规范) function: Callable[..., Any] Field(..., description实际执行的Python函数) version: str Field(1.0.0, description技能版本号) tags: list[str] Field([], description技能标签用于分类检索)这里面最容易被忽视的字段是description。很多人会随手写一句“获取天气”然后发现模型根本不知道怎么用。我的经验是description至少要包含三层信息技能是做什么的、什么情况下应该调用它、调用时需要注意什么边界条件。比如“获取指定城市的实时天气信息当用户询问天气、气温、降雨概率时使用城市名必须传中文全称而非缩写”。2.2 技能注册与按需加载别把家底全亮出来第二个核心设计是技能注册表。所有技能不是散落在代码里而是统一登记到一个注册中心由这个中心负责技能的发现和分发。这样Agent在对话过程中可以动态决定往上下文里注入哪些技能。class SkillRegistry: def __init__(self): self._skills: dict[str, Skill] {} def register(self, skill: Skill) - None: if skill.name in self._skills: raise ValueError(f技能 {skill.name} 已存在) self._skills[skill.name] skill def get_skill(self, name: str) - Optional[Skill]: return self._skills.get(name) def list_skills(self, tags: Optional[list[str]] None) - list[Skill]: if not tags: return list(self._skills.values()) return [s for s in self._skills.values() if set(tags) set(s.tags)]按需加载的意思是不要把所有技能的描述一次性全部塞给模型。比如你有二十个技能其中十五个跟数据分析相关五个跟信息检索相关那在用户问“帮我查一下天气”时就只需要给模型看天气相关的技能描述而不是把二十个技能全部铺开。这样做的好处是既节省Token又能减少模型选择技能时的注意力分散。我实际的做法是在对话的第一轮先通过关键词做一次粗筛选出候选技能列表然后将候选技能的元数据拼接到消息里。如果某轮对话没有命中任何技能就用默认的空结果。这相当于给Agent加了一层“路由感知”而不是让每次请求都在全量技能集里做选择。2.3 技能选择与参数映射让模型“正确动手”第三个核心设计是技能选择和参数映射。当模型决定调用某个技能后它回传的不只是技能名称还包括一份符合参数Schema的JSON对象。这个环节处理得好不好直接影响整个Agent系统的稳定性和准确性。我的做法是在调用模型接口时把所有候选技能的元数据直接映射为模型的tools参数。以OpenAI兼容接口为例每个技能都会转换成一个tool定义。关键在于parameters字段要与模型接口期望的JSON Schema格式保持一致尤其是required、type、enum这些字段缺一个都可能导致模型生成的参数不合法。{ type: function, function: { name: get_weather, description: 获取指定城市的实时天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称使用中文全称 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位 } }, required: [city] } } }这里有个细节容易踩坑模型返回参数时未必严格遵循Schema。比如枚举值它可能输成“C”而不是“celsius”整数字段可能输出成字符串“25”。所以我在执行技能之前会加一层参数校验和类型转换用Pydantic模型直接把原始输出解析成规范的参数对象解析不过就返回一条清晰的结构化错误让模型重新尝试。3. 从零搭建可复用的agent-skills实战3.1 基础架构与依赖准备这一节我会给出一个可以直接运行的完整架构技术栈选择相对通用方便大家迁移到自己的项目里。依赖很简单Python 3.10以上openai库或者其他兼容OpenAI接口的SDK、pydantic、fastapi和uvicorn用于起服务。如果只是想验证核心逻辑甚至连FastAPI都可以省掉直接跑命令行脚本看效果。项目目录我建议这样组织agent-skills/ ├── app.py # 入口编排主流程 ├── skills/ │ ├── __init__.py # 技能模块集合 │ ├── registry.py # 技能注册表 │ ├── time_skill.py # 时间查询技能 │ └── web_skill.py # 网页抓取技能 ├── core/ │ ├── llm.py # 模型调用封装 │ └── types.py # 技能元数据定义 └── config.py # 配置文件3.2 第一个技能时间查询与上下文注入我们从最简单的技能开始。这个技能的作用是获取当前时间初看没什么价值但它是验证整个调用链路最好的“最小可执行样本”。我把时间和时区都作为参数这样能测试模型对非必填参数的默认值处理能力。from datetime import datetime from zoneinfo import ZoneInfo def get_current_time(timezone: str Asia/Shanghai) - dict: 返回指定时区的当前时间当用户询问时间、日期、今天是几号等场景时使用。 now datetime.now(ZoneInfo(timezone)) return { timezone: timezone, date: now.strftime(%Y-%m-%d), time: now.strftime(%H:%M:%S), weekday: now.strftime(%A) }把函数注册到注册表time_skill Skill( nameget_current_time, description获取指定时区的当前日期和时间当用户询问时间、日期或星期几时调用, parameters{ type: object, properties: { timezone: { type: string, default: Asia/Shanghai, description: IANA时区名称如Asia/Shanghai、America/New_York } }, required: [] }, functionget_current_time, ) registry.register(time_skill)这里有个经验required不要随便加。如果一个参数的默认值对大多数场景都适用就把它设为非必填给模型足够的容错空间。我见过不少项目把每个参数都设为required结果模型为了填一个冷门参数反复出错。3.3 第二个技能网页内容抓取与总结实战中更有代表性的技能是网页抓取类因为这类技能能充分体现“Agent先决策、后执行、再总结”的价值。用户可能说“帮我看一下这个页面里讲什么”模型需要判断应该调用什么技能获取URL内容然后结合LLM的能力生成总结。抓取网页的核心实现import httpx from bs4 import BeautifulSoup def fetch_webpage(url: str, max_chars: int 5000) - dict: 抓取指定网页的正文文本当用户需要了解网页内容、页面信息时使用。 注意事项仅抓取静态页面动态渲染页面可能只能拿到部分内容。 headers {User-Agent: Mozilla/5.0 (agent-skills demo)} with httpx.Client(timeout10, follow_redirectsTrue) as client: resp client.get(url, headersheaders) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, nav, footer]): tag.decompose() text .join(soup.stripped_strings) return {url: url, content: text[:max_chars], length: len(text)}注意我在返回结果里加了length字段这是为了方便后续控制上下文长度。真实项目里网页内容动辄几万字符直接全量扔给模型会撑爆上下文窗口。我会在return前先截取前max_chars个字符并在描述里明确告诉模型这个截断逻辑避免它误以为页面只有这么长。3.4 技能编排器一次请求怎么走完调度链路现在我们把上面的技能串起来写一个精简但完整的编排器。这个是agent-skills的核心环节也是大家最需要理解的部分。import json from openai import OpenAI class AgentOrchestrator: def __init__(self, registry: SkillRegistry, client: OpenAI): self.registry registry self.client client def build_tools(self, skills: list[Skill]) - list[dict]: tools [] for skill in skills: tools.append({ type: function, function: { name: skill.name, description: skill.description, parameters: skill.parameters, } }) return tools def _execute_skill(self, name: str, args: dict) - dict: skill self.registry.get_skill(name) if skill is None: return {error: f技能 {name} 不存在} try: result skill.function(**args) return {name: name, result: result} except Exception as exc: return {name: name, error: str(exc)} def run(self, user_input: str): # 简化处理全量注入技能描述 skills self.registry.list_skills() messages [ {role: system, content: 你是智能助手根据用户问题选择合适的技能并调用。}, {role: user, content: user_input} ] # 第一轮让模型判断是否需要调用技能 response self.client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolsself.build_tools(skills), tool_choiceauto, ) msg response.choices[0].message messages.append(msg) # 如果模型决定调用技能执行并将结果回填给模型 if msg.tool_calls: for tool_call in msg.tool_calls: name tool_call.function.name args json.loads(tool_call.function.arguments) result self._execute_skill(name, args) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) # 第二轮让模型基于技能结果组织最终回复 final_response self.client.chat.completions.create( modelgpt-4o-mini, messagesmessages, ) return final_response.choices[0].message.content return msg.content这里我来解释几个关键设计。第一轮调用models接口的tool_choice我设置为auto让模型自主决定要不要调用技能。这是最灵活的方案但如果你明确知道用户一定会调用某个技能也可以强制指定。实战中我倾向于设置auto因为强迫模型调用不合适的技能反而会降低回答质量。技能执行结果我以roletool的身份回传并带上tool_call_id。这个字段是对应关系的关键它告诉模型哪次调用产生这个结果。很多初学者漏掉tool_call_id结果模型根本分不清结果对应的是哪个技能。第二轮调用时我不再传tools参数。这样做的原因是模型这时候的任务已经从“做选择”变成“做回答”继续传tools会让它陷入一种又要选工具又要回答的混乱状态。从我测过的多个模型来看这种“先决策、后总结”的两段式设计比一次性完成的效果更稳定。4. 真实踩坑记录与排查策略4.1 模型就是死活不调技能先查描述这个坑我踩得最深。有一阵子模型每当遇到数学计算就直接心算明明我提供了精确计算技能它就是不调用。一开始我以为是模型能力问题后来反复排查才意识到是技能的description写得有问题。我当时写的是“执行数学计算当需要计算时使用”。听着没毛病但模型认为自己的内置算术能力已经够用了。问题的根本在于我没有说清楚技能的优势和适用边界。后来改为“执行高精度数学运算支持大数、小数、复杂表达式当结果可能需要精确到多位或涉及大数乘除时优先调用不要依赖心算”模型才开始稳定地调用它。所以如果你发现模型不调用技能第一个该看的不是代码而是描述。描述写得像白开水模型就把它当可有可无的选项。要明确写出“什么场景用、为什么用它、别用什么方式替代”相当于给模型一个决策理由。4.2 参数解析不稳定Schema要“死板”一点模型生成参数时偶尔会出现“类型漂移”。比如定义了一个integer类型的字段模型却传了25这样的字符串。还有一种情况是缺字段明明required标记了city模型生成的参数里就是没有city。我的应对策略是三层防护第一层在Schema里把枚举值、格式约束写死不给模型自由发挥的空间。第二层拿到模型输出后用Pydantic模型做严格校验和类型转换解析失败就捕获异常。第三层解析失败时不直接把错误堆栈抛给模型而是返回类似“参数city不能为空请重新调用技能”的简洁消息让模型在下一轮自行纠正。这三层下来参数错误导致的失败率大概能从之前的百分之十几降到百分之一左右。这里有个心理准备要提前做好不管你怎么设计总会有极小比例的调度是错误的系统要做好“让模型重新试一次”的兜底逻辑。4.3 技能返回内容撑爆上下文怎么办网页抓取技能最容易遇到这个问题。用户丢一个超长URL页面文本抓回来可能有几万字如果直接把它回传给模型做总结上下文窗口会快速被占满而且成本也会飙升。我在处理这个问题时采用了“两级供给”的策略。第一级是技能本身先做截断只返回前5000个字符。第二级是如果模型需要更多信息可以在总结时使用另一个“获取网页片段”的技能按需取后面的内容。这个思路本质上是分页思想。技能不负责一次性把数据全倒给模型而是提供一种“按需取数”的能力。我还给技能结果加了长度标记让模型知道当前内容是完整还是截断的避免它基于不完整信息得出错误结论。4.4 多技能协作时的意图漂移问题多技能场景下的问题比单技能复杂得多。比如用户说“帮我查一下北京明天会不会下雨顺便把这篇文章的核心观点总结一下”这涉及天气和网页抓取两个技能模型很容易只调用了头一个却把第二个忘了。我的处理方案是引入一个“意图规划”步骤。在真正调用技能之前先让模型输出一份简短的任务拆解然后按顺序依次调用技能。每完成一个技能的执行都把结果追加到对话上下文再做下一步。这就类似于给Agent一个待办清单不至于做着做着就忘了后面还有事。对于需要多个结果交叉分析的场景我还会让模型最后输出一个汇总表或者结构化JSON。这种做法一方面方便前端展示另一方面也逼着模型把不同来源的信息做真正的整合而不是简单堆砌。5. 我的经验体会与扩展方向项目做到后面我越来越觉得agent-skills这类框架的核心不在代码量而在“技能设计”这件事本身。每新增一个技能我都会问自己几个问题这个技能和现有技能边界是否清晰描述是否足够明确参数是否容易让模型理解返回结构是否便于后续处理这些问题比写代码本身更能决定一个Agent项目的质量。现在我手里的新项目已经全面切换到这套模式所有能力都收拢成技能包不再写一次性脚本。新增一个接口时开发流程变成了写执行函数、填元数据、注册进表、跑一遍用例几乎没有额外成本。团队协作时大家维护各自的技能模块互不干扰代码评审也轻松了很多。如果你也想在这条路上继续深入我建议下一步可以研究这几个方向一是给技能加权限控制把用户会话维度引入调度逻辑二是做一个技能调用监控面板记录每个技能的调用次数、耗时和成功率三是探索自动生成技能描述让模型根据函数的docstring自动编写元数据。这条路走通之后Agent的能力边界就不再受限于Prompt技巧而会变成一个可持续生长、可度量的工程体系。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

基于VUE的食堂管理系统毕业设计 2026/9/25 7:57:47

基于VUE的食堂管理系统毕业设计

摘 要 针对传统厨房管理效率低、信息协同滞后、资源浪费严重等问题,本文设计并实现了一套基于Vue.js框架的智能厨房管理系统。系统采用前后端分离架构,前端以Vue 3组合式API为核心,结合Element Plus组件库构建响应式用户界面,通过…

阅读更多 →
Skia C++ 编码风格规范详解:命名约定、类设计模式与 clang-format 自动化落地 2026/9/25 7:57:46

Skia C++ 编码风格规范详解:命名约定、类设计模式与 clang-format 自动化落地

图形学图像处理 【免费下载链接】skia Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. 项目地址: https://gitcode.com/gh_mirrors/skia1/skia 点击查看 免费下载 本文基于 Skia 官方贡献文档 Coding Style Guidelines&#xf…

阅读更多 →
Learn-Algorithms 字符串修改专题:单词翻转、空格替换、左旋转、原地压缩与 strcpy 实战解析 2026/9/25 7:57:46

Learn-Algorithms 字符串修改专题:单词翻转、空格替换、左旋转、原地压缩与 strcpy 实战解析

教程 【免费下载链接】Learn-Algorithms 算法学习笔记 项目地址: https://gitcode.com/gh_mirrors/le/Learn-Algorithms 点击查看 免费下载 本篇技术指南以仓库内 1.3 字符串-修改.md 为骨架,系统讲解算法面试中最常出现的一类字符串操作题:…

阅读更多 →
Atlas 300V 24G部署YOLOv5实战:从环境配置到推理调优全流程 2026/9/25 7:57:33

Atlas 300V 24G部署YOLOv5实战:从环境配置到推理调优全流程

1. 先搞清楚:Atlas 300V 24G到底是一张什么卡我在过去半年里陆续接手过几个CV项目,从最开始在GPU服务器上跑YOLO,到后来被客户要求落地到国产加速卡上,可以说踩了不少坑。Atlas这个名字,很多人第一次听说时都会有个困惑…

阅读更多 →
Ariakit Tab 组件完全指南:基于 WAI-ARIA Tabs Pattern 的可访问标签页实现 2026/9/25 7:57:26

Ariakit Tab 组件完全指南:基于 WAI-ARIA Tabs Pattern 的可访问标签页实现

UI组件前端 【免费下载链接】ariakit Toolkit with accessible components, styles, and examples for your next web app 项目地址: https://gitcode.com/gh_mirrors/ar/ariakit 点击查看 免费下载 本文围绕 Ariakit 仓库中 components/tab.md 所定义的 Tab 组件展…

阅读更多 →
S905L3B 电视盒子安装 Armbian 到 eMMC 完整指南 2026/9/25 7:57:26

S905L3B 电视盒子安装 Armbian 到 eMMC 完整指南

S905L3B 电视盒子安装 Armbian 到 eMMC 完整指南 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk3588, rk3568, rk3399, …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉