news 2026/9/26 9:01:01

Agent技能体系实战:拆解、定义与调用优化,生产级落地关键

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent技能体系实战:拆解、定义与调用优化,生产级落地关键

做Agent落地这一年多,我最大的感受是:真正拉开差距的,往往不是模型选得多新、Prompt写得多花哨,而是被大多数人当成“附属品”的技能体系。很多项目Demo阶段跑得风生水起,一上生产就翻车,问题几乎都出在同一个地方——agent-skills。技能定义不清晰、粒度太粗、描述语义和模型理解对不上,导致智能体要么调用错工具,要么压根不知道该调用工具。这篇我想把agent-skills这件事从头到尾拆开聊一遍,从技能怎么拆、怎么定义、怎么被模型正确调用,到真实项目里踩过的坑和排查思路,一次性说透。适合正在做Agent应用、开始考虑生产级落地、或者被技能调用准确率折磨的朋友参考。

1. agent-skills到底在解决什么问题

1.1 从一个空壳Agent说起——为什么技能才是核心

先回想一下最早我们怎么做Agent:一个大模型,一个System Prompt,说“你是一个智能助手,可以帮用户查询天气、设置提醒、搜索资料”,然后上下文里塞一堆工具说明。这种空壳Agent有个很明显的问题——它其实是在“背台词”,并没有真正和外界交互的能力。用户让它查天气,它靠训练记忆里的城市气候知识硬编,用户让它打开某个网页,它只能抱歉地说“我没有浏览器的访问权限”。

技能(Skills)要解决的,恰恰是这个问题。技能的本质是给Agent装上“手”和“眼”,让它在推理之外,具备真实的执行能力。一个没有技能的Agent,就像一个只会动嘴的顾问,说得头头是道,但做不了任何事。而一旦你给它挂上“查天气”的技能,它就能通过天气API获取实时数据;给它挂上“执行SQL”的技能,它就能直接查数仓;给它挂上“调用内部服务”的技能,它就能操作业务系统。

我自己去年接过一个客服工单项目,一开始团队把精力全放在调Prompt上,试图用提示词让模型“更聪明”地回答问题。结果做了两周,准确率卡在65%上下不去。后来我们把重心转移到技能体系上——把工单查询、客户历史、库存状态、退款资格全部拆成独立技能,Prompt反而减了很多字数。准确率直接从65%拉到88%。那次之后我彻底想明白一件事:Agent能力的下限由模型决定,上限由技能决定。

1.2 技能不是“插件”,是“能力的最小可复用单元”

很多人把技能理解成插件,这两个概念在工程上差异很大。插件偏重系统集成,是一套已经写好的、独立运行的模块,Agent只是调用它的入口。而技能更强调“模型语义层”的调用——它既包含可执行的实现,也包含一段让模型理解“什么时候该用它”的描述。

举个例子,你写了一个“发送邮件”的工具函数,这是插件视角。但“发送邮件”作为技能时,你必须回答一系列问题:用户怎么表达才算触发这个技能?参数从哪来?“给张三发邮件”里的张三如何映射到邮箱地址?邮件正文缺了怎么办?所以技能本质上是一个“带使用说明书的能力单元”,说明书是给模型看的,实现是给系统跑的。两者缺一不可。

另外一个容易被忽略的维度是“可复用”。一个写好的技能,可以在不同Agent之间复用,甚至可以跨团队共享。我们团队现在维护了一个技能注册表,把公司内部常用的能力都沉淀成标准技能。新项目启动时,先查注册表,能复用的直接挂载,不能复用的再开发。这比每个项目组各写各的、实现一遍遍地复制粘贴,省了不止一半的工程量。

1.3 技能体系的三层模型:行为层、语义层、编排层

理解了技能是什么,再看技能体系就容易了。我习惯把一套完整的agent-skills拆成三层来看。

行为层是最底层,负责真实执行。这里包括API调用、脚本执行、数据库查询、消息推送等等,是“活”落到实处的部分。行为层的核心关注点是稳定性、安全性和参数校验。技能在下层执行时,出来的结果一定要结构化、可预期。

语义层是中层,是模型和执行的桥梁。咱们在技能定义里写的name、description、parameters,本质上是给模型看的“界面”。模型不看你函数内部怎么实现,它只看这段描述来判断“这个技能能不能解决当前用户的问题”“参数要怎么填”。所以语义层的质量,直接决定了模型调用技能的准确度。

编排层是最上层,决定多个技能如何组合。有些任务是单技能就能解决的,更多任务需要多个技能的串行、并行或分支配合。编排层负责把这套流程组织起来,同时处理比如技能之间的参数传递、结果合并、互斥规则等。

日常排查Agent问题时,我第一件事就是把问题定位到某一层。模型不调用技能?大概率是语义层出了问题。技能调用后结果不对?看行为层。多个技能执行顺序混乱?问题在编排层。有了这三层框架,很多问题就不至于瞎猜了。

2. 核心技能怎么拆:从需求到技能定义

2.1 拆解任务的3个原则

技能设计的第一步不是写描述,而是“拆任务”。拆得好不好,直接决定后续所有环节的质量。我总结了三个拆解原则,结合项目经验逐个说。

第一个原则:以模型调用边界为边界。你的技能如果太粗,描述就写不清,模型就搞不清楚这个技能到底该不该调用;如果太细,技能数量爆炸,模型每轮要做大量选择,容易选错。举个例子,“知识库操作”这个技能就太粗,它下面有增删改查既有逻辑,模型没法从描述里判断“用户想新增文档”和“用户想搜索文档”分别对应什么操作。但反过来,如果把“发电邮”“发钉钉”“发企微”分别拆成三个技能,又太细了——它们的语义重叠度太高,模型经常混淆。合适的做法是拆成一个“发送消息”技能,参数里加channel字段。

第二个原则:以业务语义为边界。技能不是给机器看的,是给模型和业务场景看的。拆技能时要想一件事:站在用户视角,这个行为是不是完整的一步?用户说“帮我整理会议纪要给参会人”,这里就包含“生成纪要”和“发送邮件”两个独立业务步骤,拆成两个技能是合理的,因为后续可能有人只需要“生成纪要”、不需要“发送”。

第三个原则:以复用价值为导向。每个技能都需要维护成本,如果某个能力只有一个场景用、一次性的价值不大,那就集成到场景代码里,没必要单独拆技能。反过来,如果这个能力在很多场景都会用到,比如“查询订单”“获取用户信息”,那就值得拆成一个独立技能,并且在语义描述上尽量通用。

2.2 技能定义的标准动作:名称、描述、参数、返回

再来说技能定义的四个核心字段:name、description、parameters、returns。这四个字段不是随便填的,每一项都有讲究。

name要短、唯一、功能直白。建议用动词+名词的组合,比如fetch_weather、send_email、query_user_info。避免用抽象词,比如handle_data、do_stuff,模型看到这种名字根本不知道它是干嘛的。name一出,基本就能看出开发者有没有认真设计技能。

description是四项里最关键的,它决定了模型“该不该调用这个技能”。好的description至少要包含三部分:功能描述(这个技能做什么)、适用场景(什么情况下用)、负面条件(什么情况下不要用)。比如查询天气的技能,如果只写“查询天气”,模型在用户问“上海冷不冷”时,可能不会联想到天气查询。如果写成“查询指定城市当前天气和未来24小时预报,当用户问到天气、温度、降水、风力时使用”,模型就能准确匹配。

parameters使用JSON Schema格式定义。每个参数都要写明类型、描述、枚举值、默认值。参数描述也很重要,要让模型知道这个参数的值从哪里来。比如city参数的描述写“城市中文名,例如北京、上海”,模型就不会传成拼音。required字段只在真正不可缺省的参数上使用,能推断的参数尽量设为可选。

returns定义的是执行结果的返回结构。返回结构稳定非常关键,模型拿到结果后要继续推理,如果结果格式杂乱无章,模型难以从中提取信息。我一般返回一段结构化JSON,并且把核心结论放在靠前位置。

下面是一个“查天气”技能的完整定义示例,可以直接参考:

name: weather_query description: 查询指定城市当前天气和未来24小时预报。当用户问到天气、温度、降水、风力等气象问题时使用。如果用户没有明确指定城市,询问后再调用,不要用默认城市。 parameters: type: object properties: city: type: string description: 城市中文名,例如“北京、上海、广州” example: "北京" days: type: integer description: 预报天数,取值1到3,不传时默认1 enum: [1, 2, 3] default: 1 required: - city returns: current: temperature: 当前温度(摄氏度) condition: 天气现象描述 forecast: - time: 预报时间 temperature: 温度 condition: 天气现象

2.3 技能描述的语言学细节——差一个词,效果差一倍

技能描述不是写给人看的,是写给模型看的。这里面的措辞差异,影响比大多数人以为的大得多。同一个技能,描述写得行不行,调用准确率能差出20到30个百分点。

我见过最典型的反例就是把description写得“像函数注释”。比如:

# 差 description: 根据城市名查询天气信息

这种描述有三个问题:第一,没说清楚“什么时候用”,模型在模棱两可的场景下会犹豫;第二,没有负面条件,模型在用户只是闲聊“今天外面好热”时,也可能调用技能;第三,没有任何示例,模型对“城市名”这种抽象概念理解不充分。

我们后来迭代成:

# 好 description: 查询指定城市的当前天气与未来24小时预报。当用户明确询问天气、气温、降水、风力、出行穿衣建议时使用。如果用户只是感叹天气但没有请求信息,或没有指定城市,请先主动询问城市名,不要调用本技能。支持的城市以参数city为准。

这段描述里加了“什么时候用”“什么时候不用”“如何获取参数”三层信息。实测下来,盲目调用率和参数错误率都明显下降了。

再补一个经验:描述里不要用模糊程度高的词,比如“获取信息”“处理数据”。模型对这类词的触发非常不敏感。反而是“查询”“读取”“生成”“发送”“创建”这种动作明确的动词,触发准确率更高。另外,所有日期类参数,尽量用“今天”“明天”这种自然语言让系统折算,而不是让模型自己算时间戳——模型算错概率很高。

我建议一个新技能的描述,至少要过三轮迭代:先按直觉写,再用20条典型 query做调用测试,看哪些场景该调没调、不该调反而调了,然后针对失败样本调整措辞。三轮之后,调用准确率基本能稳定在90%以上。

3. 实操:从零搭一套agent-skills体系

3.1 最小闭环示例:使用JSON/YAML定义三个技能

理论说了一大堆,真正的价值还得落实到具体代码上。用一个实际项目来演示——会议纪要助手Agent,需要三个技能:检索会议记录、检查日程冲突、生成纪要模板。

先定义三个技能的YAML:

# 技能1:检索会议记录 name: retrieve_meeting_notes description: 按关键词或日期检索历史会议纪要。当用户询问“上次会议说了什么”“某天的会议记录”时使用。如果用户没有提供日期范围,默认搜索最近7天的记录。 parameters: type: object properties: keyword: type: string description: 检索关键词,例如“预算”“排期”“风险” start_date: type: string description: 开始日期,格式YYYY-MM-DD,默认取今天往前7天 end_date: type: string description: 结束日期,格式YYYY-MM-DD,默认取今天 required: [] returns: notes: - meeting_id, title, date, summary, action_items
# 技能2:检查日程冲突 name: check_schedule_conflict description: 检查指定参会人在某时段的日程是否冲突。当安排会议前需要确认大家有空时使用。传入参会人列表和会议时间,返回每个人在该时段的空闲/忙碌状态。 parameters: type: object properties: attendees: type: array items: type: string description: 参会人邮箱或姓名,例如["zhangshan@example.com"] start_time: type: string description: 会议开始时间,ISO 8601格式,例如2025-06-10T14:00:00 end_time: type: string description: 会议结束时间,ISO 8601格式,例如2025-06-10T15:00:00 required: - attendees - start_time - end_time returns: conflicts: - person, available, conflict_detail
# 技能3:生成纪要模板 name: generate_minutes_template description: 根据会议主题自动生成会议纪要的框架模板,包含背景、讨论要点、决议、待办事项等章节。当准备开会前需要空白模板时使用。 parameters: type: object properties: topic: type: string description: 会议主题 attendee_count: type: integer description: 预计参会人数,不小于1,默认2 default: 2 required: - topic returns: template: 纪要模板Markdown文本

这三个技能的选择是有讲究的。第一个和第三个技能功能差异大,语义边界清晰,模型不会混淆;第二个技能和第一个存在天然的前后依赖——先检索历史记录,可能拿到上次的参会人和待办,再检查这些人在新时间是否冲突。这正好演示了技能之间的组合关系。

3.2 技能如何被Agent调用:基于LLM的工具选择机制

技能定义好了,接下来是Agent如何识别并调用它们。目前主流方式是基于function calling的机制:模型根据对话内容和技能定义,输出一个结构化的调用意图,系统负责执行并返回结果。

流程是这样的:用户query进入后,系统把技能列表(包括name、description、parameters)注入上下文,模型判断当前是否应该调用技能,以及调用哪个、参数是什么。如果模型决定调用,返回一个包含函数名和参数的JSON结构。系统执行对应函数后,把结果作为工具消息回传给模型,模型再基于结果生成最终回答。

用一个简化版示例看关键代码逻辑:

def run_agent(user_input, skills): messages = [ # system prompt里注入了技能列表 {"role": "system", "content": build_system_prompt(skills)}, {"role": "user", "content": user_input} ] for step in range(MAX_STEPS): response = llm.chat( messages=messages, tools=[skill.schema for skill in skills], # 把技能定义为tools tool_choice="auto", temperature=0.2 ) if response.tool_calls: for call in response.tool_calls: # 执行对应技能 result = execute_skill(call.name, call.arguments) messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False) }) else: # 没有再调用技能的意图,直接返回最终回答 return response.content return "已达到最大推理步数,请尝试简化问题"

这段代码有几个值得注意的细节。temperature设置得偏低,是让模型在“是否调用技能”这个问题上保持稳定,减少随机性——毕竟调用错了比不调用还麻烦。tool_choice="auto"表示让模型自主判断是否需要调用。如果我们能确定某轮必须调用某个技能,可以临时把它设置成required,强制调用。

实际项目里还有几个容易踩的坑。其一,技能执行结果一定要转成JSON喂给模型,而且JSON里最好带一个“status”字段表明成功或失败。模型看到失败结果才知道怎么向用户解释,或者尝试换个参数重新调用。其二,技能返回内容如果过长,会被后面模型输入截断,导致模型丢失关键信息。所以行为层实现技能的返回时要做摘要是非常必要的,只把最关键的信息返回给模型。

3.3 编排层:技能之间的组合与冲突消解

单个技能的调用搞定之后,真正让Agent产生价值的是技能的“组合”。还是拿会议纪要助手举例,用户连续输入“帮我把上周产品评审会的纪要找出来,然后看看周五下午大家有没有空,最后按模板生成一份新会议纪要”。这个任务需要依次调用三个技能。

最简单的编排方式是“串行”,即上一个技能的结果作为下一个技能的输入参数。retrieve_meeting_notes执行后,把返回内容里的action_items整理出来,作为check_schedule_conflict的attendees参数,再把topic等信息传给generate_minutes_template。这种一个接一个的流程,在代码里就是一个状态机,每一步根据上一步的结果决定下一步的调用参数。

更复杂的场景需要“并行”和“条件分支”。并行是指在多个技能互不依赖时同时调用,比如用户同时问“北京天气怎么样、上海呢、机票价格如何”,这三个查询没有依赖关系,没必要串行等待。条件分支则是指根据参数判断走哪条路,比如用户问“如果明天开会,帮我看看会议室能不能用”,这时候先判断明天是否工作日,然后再调用会议室查询技能——判断逻辑可以在技能内部做,也可以在编排层写规则。

编排层还需要处理技能的“冲突消解”。两类冲突最常见:一是多个技能功能重叠,描述彼此包含,模型不知道该选哪个;二是某些技能不能在同一轮里被调用,比如“发送邮件”和“草稿模式”,一旦触发前者,后者就该被抑制。

关于重叠问题,我的建议是如果两个技能语义重叠度超过60%,就把它们合并成一个技能,通过参数区分行为;如果一定要分开,就要在彼此的description里写清楚“不要和某技能混用”。关于互斥问题,可以在编排层做一层规则拦截:模型如果真的同时把两个互斥技能都选上了,我们就在执行前把后一个拦截掉,并给模型回一条信息说明原因,让模型重新决策。这类拦截逻辑不建议直接交给模型自行判断,模型在复杂对话里很容易顾此失彼。

4. 技能系统的常见问题与排查实录

4.1 典型问题速查表

把这一年多被问得最多的问题整理成一张速查表,遇到类似情况可以直接对号入座。

现象可能原因排查方法解决建议
Agent不调用技能,直接硬答description和用户query语义距离远,模型没意识到技能可用打印模型实际看到的技能列表和用户输入,看语义是否匹配重写description,加入触发词和适用场景示例
技能调用了,但参数总是填错参数描述太模糊,或没有枚举约束查看模型实际传的参数JSON,对比预期格式参数描述里给示例值,日期等统一用自然语言格式,定义enum取值范围
多个技能容易混淆,选错技能边界重叠,描述没有互斥提示构造一批典型query,看模型选技能的分布合并重叠技能,或互相写清使用边界
执行成功但结果模型不用返回结果结构复杂,模型提取信息困难查看返回内容里关键信息是否清晰、靠前精简返回结构,核心结论放前面,附带简要摘要字段
一轮调用经常触发到上限技能编排串行步骤过多,或每轮都有无关调用打开调用日志,统计每轮调用步数和触发原因把固定流程固化成编排模板,减少模型自由决策步数
技能偶尔失联,报错被吞行为层异常处理不足,错误没有归一化返回检查底层API异常是否被捕获并转成结构化错误行为层统一做try-catch,任何异常返回含error字段的JSON

这张表里最值得说的是第一行。我们线上环境里出现“模型不调技能”的概率,比“调错技能”还高。排查思路很简单:把模型真实看到的技能描述打印出来,再把用户query在旁边,逐条看语义。很多时候是你觉得description写清楚了,但模型读了就是理解不了。这时候不要急着怪模型,回到描述本身找问题。

4.2 我踩过的三个坑

第一个坑是把“功能模块”当“技能”,粒度太粗。早期做知识库Agent时,我图省事把整个知识库操作封装成一个技能“knowledge_base”,内部支持增删改查。结果上线后模型频繁把这个技能当成“万能入口”,用户问“我要上传一个文档”它也调、用户问“帮我查一下某条记录的创建时间”它也调。但每次调用时模型给出的参数意图完全不同,技能内部根本分辨不了。后来拆成“kb_add_document”“kb_search_document”“kb_update_document”“kb_delete_document”四个技能,调用准确率立刻从70%左右涨到近95%。教训很直接——一个技能必须对应一个清晰的动词意图,包得越多,意图越模糊。

第二个坑是description写得太“文艺”。我一开始给“日程检查”技能写的描述是“帮助用户确认时间安排的合理性”,模型每次遇到用户问“我下午有没有空”就调这个,但用户问“帮我约个时间”它反而不知道调。后来改成“检查指定参会人在指定时间段内是否有日程冲突,当日程安排、预约会议、调整会议时间时使用”,立刻就好了。描述文案一定要包含“动词+宾语+触发场景”,不要写形容词。

第三个坑是参数Schema必填字段过多,导致多轮对话体验极差。我们有个“创建工单”技能,一开始把客户姓名、联系方式、问题分类、优先级、描述全设成required。结果用户来一句“我要报修”,模型因为没有拿到客户姓名和联系方式,一路追问用户,用户烦得要死。后来我们把所有能从上下文推断的字段全部改成optional,只有“问题描述”是必填。需要时让模型先去查用户信息技能,再自动填充到参数里。这样用户体验好了不止一个档次。

4.3 技能质量的评估与迭代方法

技能做出来不是一劳永逸的,要持续迭代。我的建议是给技能体系建一套“评估基准”,就像给大模型跑评测集一样,只不过这里评测的是技能调用质量。

具体做法分三步。第一步,构造测试集。从真实用户会话日志里抽取20到50条典型query,尽量覆盖技能的所有触发边界,包括正例(该调用的场景)和反例(不该调用的场景)。比如“今天天气怎么样”属于正例;“我好热”这种情绪表达,如果没有降温建议技能,就属于反例。每条query标注期望行为:调用哪个技能、期望参数值、还是不调用技能。

第二步,定义评估指标。我主要看三个:调用准确率,即该调的时候调了、不该调的时候没调的概率;参数准确率,即调用的技能参数是否正确;任务成功率,即从用户query到最终回答是否真正解决了问题。前两个是过程指标,第三个是结果指标,三个一起看才能反映真实水平。

第三步,做迭代闭环。每次修改技能定义,先在这套测试集上跑一遍回归,看指标有没有提升。如果修改后调用准确率下降,就回滚,重新分析原因。标注失败样本时,重点关注两类:一类是模型没有调用任何技能但实际应该调用的,另一类是把技能A错成了技能B。这两类问题的修复手段通常不一样,前者改description,后者拆技能或增加互斥描述。

另外强烈建议给技能定义做版本管理。技能的描述、参数Schema只要一改,模型的行为就可能立即变化。我们团队后来每次改技能,都会记录“改动前调用准确率—改动后调用准确率—失败样例—结论”这一条完整链路。积累几个月,这就是团队最有价值的经验库,任何新同学接手Agent项目,从这套记录里就能快速知道每个技能该怎么维护。

我个人在实际操作中还有一个习惯:每个技能上线前,先用它跑50条模拟用户输入,人工检查模型的调用表现。这个过程不花太多时间,但能挡住绝大多数低级问题。等技能运行一段时间后,再用真实日志里筛选出来的失败案例做二次优化。先把跑通再谈优化,不要一开始就追求完美,这是做Agent项目最实际的路子。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 9:00:11

JsonCpp在C++项目中的稳定、轻量与可控性实践

1. 为什么我坚持在C项目里用JsonCpp,而不是自己手写解析器或换其他库JsonCpp这个名字听起来平平无奇,但在我过去八年带过的十几个嵌入式通信模块、桌面客户端和游戏工具链项目里,它几乎从没让我失望过。不是因为它功能最全——它确实不支持JS…

作者头像 李华
网站建设 2026/9/26 8:59:37

金融科技项目落地:从零搭建可扩展的金融服务架构

金融科技项目落地的那些事:从零搭建一套可扩展的 financial-services 服务架构做金融科技这几年,我最大的感受是:很多人一提“financial-services”就先想到合规、牌照、资本金这些门槛,却忽略了它首先是个工程问题。一个能扛住真…

作者头像 李华
网站建设 2026/9/26 8:59:23

反无人机技术硬参数解析:激光/雷达/射频三大系统实战标定

简介:本资源是一份聚焦军事科技前沿的深度分析报告,面向国防科研人员、军事爱好者、安全领域从业者及高校相关专业师生,系统梳理国外反无人机技术发展现状与趋势,助力读者把握电磁对抗、激光拦截、网络攻防等新型防御手段的核心逻…

作者头像 李华
网站建设 2026/9/26 8:57:55

LibreChat自托管AI聊天平台:从Docker部署到多模型统一接入实战

先聊聊LibreChat是个什么项目 如果你用过一段时间的ChatGPT网页版,又折腾过几次API,大概率会冒出这样一个念头:官方网页版虽好,但模型切换麻烦、历史记录散落、团队协作基本靠复制粘贴,想把OpenAI、Azure、Anthropic这…

作者头像 李华
网站建设 2026/9/26 8:57:39

H桥逆变器Simulink仿真:从MOSFET开关特性到LC滤波设计

1. 项目概述:这不是一个“调个参数就能跑”的简单仿真,而是一次对DC-AC逆变本质的硬核推演 你看到这个标题——【DC-AC】使用了H桥MOSFET进行开关,电感器作为滤波器,R和C作为负载目标是产生150V的双极输出和4安培(双极…

作者头像 李华
网站建设 2026/9/26 8:57:30

Qt内存泄漏检测工具:基于VLD的C++泄漏定位与工程集成指南

简介:基于Qt与MSVC开发环境,结合VLD内存泄漏检测库而成的工具及源码,面向需要排查C程序内存问题的中初级开发者。资源体积仅3KB,共6个文件,涵盖2个C源文件、1个头文件、1个Qt界面文件、1个工程配置文件与1个Git属性文件…

作者头像 李华