news 2026/10/28 12:33:56

智能体技能体系实战:从JSON Schema到Function Calling落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
智能体技能体系实战:从JSON Schema到Function Calling落地

最近社区里“agent-skills”这个热词反复被人提起,其实想想也不奇怪——智能体(Agent)要真正落地干活,靠的不是模型一个劲儿地“脑补”,而是一整套技能(Skills)体系。简单来说,技能就是给智能体预置的“可复用能力模块”,比如查天气、调度任务、写周报、拉取工单数据,每一个技能都让智能体在特定场景下能直接“上手操作”,而不是空谈。

这篇博文就是围绕“agent-skills”这个主题,把我实际做过的技能设计、编码实现、调试排坑的过程整理成一份可复用的经验。适合正在做AI应用落地的工程师、研究智能体框架的开发者,以及想自己搭一个实用个人助手的进阶玩家。我会从核心概念聊到具体代码,再聊到那些文档里不会写的坑。

1. 整体设计思路:技能为什么是智能体落地的关键

1.1 从“聊天”到“干活”,中间隔着一层技能

很多人第一次接触智能体时,都以为“给它一个强大的大模型就够了”。但真上手后会发现,模型再强,它也只能输出文本,没法真正帮你订会议室、改表格、发消息。要让智能体“动手干活”,就必须给它装上能对接真实世界的手脚——这就是技能。

技能本质上是一段可以被模型动态调用的“可执行能力描述”,它包括:这个能力叫什么、干什么用的、需要什么输入参数、实际执行时调用哪个函数。模型在对话中根据用户意图,自动匹配并调用最合适的技能,然后把函数的返回结果整理成用户能看懂的回答。

我见过不少团队做智能体,第一版都只是把prompt写得越来越长,希望模型“聪明点自己搞定”,结果在可控的真实任务上一败涂地。后来改成技能化设计后,效果立刻不一样了:模型不需要“知道”怎么执行任务,它只需要“知道”该调用哪个技能,剩下的交给确定性的代码去做。

1.2 技能化解决的核心痛点

我把智能体落地过程中最常见的痛点列了一下,技能这套方案几乎能把它们全部覆盖:

  • 幻觉减少:模型不再“编造”操作结果,而是由真实函数返回数据,比如查询库存、计算价格,返回什么就是什么。
  • 可维护性提升:每个技能都是独立的函数,单独测、单独改,不影响其他部分,不像改一段超长prompt那样牵一发动全身。
  • 组合能力增强:简单技能可以像乐高积木一样组合。比如“创建日程”加“发送会议邀请”加“生成会议纪要”三个技能组合起来,就是完整的会议管理流程。
  • 权限可控:技能是能力边界,系统只暴露需要的技能给模型。不该它做的操作,直接在技能层拦截,比在prompt里写一万句“你只能……”靠谱得多。

这里有一个很关键的设计理念:技能不是给模型“更多知识”,而是给模型“稳定的能力入口”。知识会过期,但能力接口一旦稳定,就能长期复用。

1.3 技能设计的核心原则

我在项目里总结出四个必须遵守的原则,违反任何一个都会在后期付出代价:

  • 单一职责:一个技能只做一件事,并且把它做好。宁可拆成两个细粒度技能,也不要揉成一个“万能函数”。细粒度技能更容易被模型准确理解和调用。
  • 参数化优先:技能的所有可变点都应该通过参数暴露出来,不要写死在代码里。比如“查询订单”技能,必须支持按订单ID、按用户ID、按时间范围过滤,而不是只返回最近一条。
  • 可测试性:每个技能都应该能在脱离对话上下文的情况下单独测试,也就是“独立于聊天记录可执行”。这样做单元测试和排错时都省心。
  • 可观测性:技能的执行应该留下日志,包括入参、出参、耗时、异常信息。否则用户反馈“答案不对”时,你根本不知道是模型选错了技能,还是技能本身跑崩了。

2. 核心细节解析:技能的结构与选型要点

2.1 技能的分类:三类技能,职责分明

根据我的实践经验,可以把技能分为三大类,分类的目的是为了在代码层面做不同的管理策略。

  • 系统级技能:这类技能负责智能体“自身生存”,比如记忆读写、对话历史管理、任务规划。它们通常由框架自动加载,不需要模型主动选择。
  • 工具级技能:对接外部系统和API,比如查天气、发HTTP请求、读写数据库、调用内部RPC服务。这是最常见的一类,也是我下面要重点讲的。
  • 领域级技能:把特定业务逻辑封装成能力,比如“财务报表分析”“简历解析”“客服工单分类”。它们往往依赖工具级技能,但包含领域校验和业务规则。

分类管理有一些实际好处:可以给不同类别的技能设置不同的调用权限,比如系统级技能禁止模型直接调用,工具级技能需要鉴权,领域级技能则可以通过灰度发布逐步上线。

2.2 技能的“标准结构”:JSON Schema是你的好朋友

要让模型准确调用技能,技能的描述、入参、出参必须严格按照一种机器可读的标准来写。我推荐直接拥抱JSON Schema,它几乎是目前各大模型function calling功能的事实标准。

一个标准技能描述长这样:

  • name:技能名称,全局唯一,建议使用snake_case。
  • description:模型理解技能用途的描述,要具体,要说明“什么场景下用”。
  • parameters:JSON Schema对象,描述每个参数的名称、类型、是否必填、含义说明。
  • executor:实际执行的函数引用(在代码侧绑定)。

这里有一个细节:description的质量直接影响模型会不会调用这个技能。不要写“处理用户请求”这种废话,要写“当用户想创建日程、安排会议或添加待办事项时使用”。模型就是通过这段文本来做语义匹配的。

拿我做过的一个“日程管理”技能举例:

{ "name": "create_calendar_event", "description": "为用户创建一个新的日历日程,适用于用户提及安排会议、设置提醒或计划时间时", "parameters": { "type": "object", "properties": { "title": {"type": "string", "description": "日程标题,如'产品评审会'"}, "start_time": {"type": "string", "description": "开始时间,ISO8601格式,含时区"}, "end_time": {"type": "string", "description": "结束时间,ISO8601格式,含时区"}, "location": {"type": "string", "description": "地点,可选,如'A3会议室'"}, "attendees": { "type": "array", "items": {"type": "string"}, "description": "参会人邮箱列表,可选" } }, "required": ["title", "start_time", "end_time"] } }

有了这份JSON Schema,模型在对话中就能自动提取用户的自然语言信息,填充成参数,然后由代码侧调用真实的日历API。

2.3 技能设计里几个容易踩的坑

  • 参数名太抽象:比如用t0代表开始时间、loc代表地点,模型很难猜对。直接用start_time、location这种直观命名,模型几乎零成本映射。
  • 必填参数过多:必填参数越多,模型调用失败率越高。尽量把非核心字段设为可选,让模型能“粗调用”成功,再逐步补全细节。
  • schema描述为中文还是英文:这取决于你的模型主语言。实测下来,如果主对话是中文,用中文描述参数的description,模型对参数填充的准确率明显更高。英文反而容易在时间格式这类细节上踩坑。

3. 实操过程:从零实现一个日历技能

3.1 环境准备与最小框架

我习惯用Python来实现技能框架,不需要重型的第三方依赖,用标准库就能搭一个演示版本。核心思路是这样的:先写一个函数作为技能的“执行体”,再用一个注册器把函数和它的JSON Schema绑定起来,最后写一个调度器,把用户请求交给模型,由模型决定调用哪个技能。

下面是一个最小的技能注册框架:

# skills_registry.py import inspect import json class SkillsRegistry: def __init__(self): self._skills = {} def register(self, name, description, parameters_schema, executor): self._skills[name] = { "name": name, "description": description, "parameters": parameters_schema, "executor": executor } def get_skills_metadata(self): """返回给模型看的function列表""" return [ { "type": "function", "function": { "name": s["name"], "description": s["description"], "parameters": s["parameters"] } } for s in self._skills.values() ] def execute(self, name, arguments): if name not in self._skills: raise ValueError(f"Unknown skill: {name}") func = self._skills[name]["executor"] # arguments是JSON字符串,解析后传给函数 args = json.loads(arguments) if isinstance(arguments, str) else arguments return func(**args) registry = SkillsRegistry()

这个注册器里的get_skills_metadata()就是把所有技能描述发给模型的部分,模型根据这些描述返回一个结构化的调用指令,其中包含技能名和参数。然后我们的execute()负责执行并返回结果。

3.2 实现日程创建与查询技能

接下来注册两个具体的技能。创建日程时,我会把事件先存进内存列表,真实项目中替换为日历API即可。查询日程时支持按日期过滤。

# calendar_skills.py from datetime import datetime events = [] def create_event(title, start_time, end_time, location=None, attendees=None): event = { "id": len(events) + 1, "title": title, "start_time": start_time, "end_time": end_time, "location": location or "", "attendees": attendees or [] } events.append(event) return {"status": "success", "event_id": event["id"], "event": event} def list_events(date=None): if date: filtered = [e for e in events if e["start_time"].startswith(date)] else: filtered = events return {"count": len(filtered), "events": filtered} registry.register( name="create_calendar_event", description="为用户创建日程事件,适用于安排会议、设置提醒、创建待办时间节点", parameters_schema={ "type": "object", "properties": { "title": {"type": "string", "description": "日程标题"}, "start_time": {"type": "string", "description": "ISO8601开始时间"}, "end_time": {"type": "string", "description": "ISO8601结束时间"}, "location": {"type": "string", "description": "地点,可选"}, "attendees": {"type": "array", "items": {"type": "string"}, "description": "参会人邮箱列表,可选"} }, "required": ["title", "start_time", "end_time"] }, executor=create_event ) registry.register( name="list_calendar_events", description="查询用户的日程列表,可传入日期过滤,适用于用户问'今天有什么安排'", parameters_schema={ "type": "object", "properties": { "date": {"type": "string", "description": "查询日期,YYYY-MM-DD,可选,默认返回全部"} }, "required": [] }, executor=list_events )

这里有一个细节值得注意:list_events的date参数用了YYYY-MM-DD格式而不是ISO8601,因为用户问“明天有什么会”时,模型更容易正确推导出“2025-05-20”这种日期,而不是去推导带时区的完整时间戳。

3.3 模拟模型调用技能:从对话到执行

现在我们模拟一个完整的调用链路。假设用户说:“帮我在下周一上午10点安排一个产品评审会,参会人包括产品经理和开发组长。”

在实际的智能体系统中,你会把用户消息和registry.get_skills_metadata()一起发给大模型,让模型返回function call。这里我直接用代码模拟模型决策后的结果,演示执行链路:

# 模拟模型返回的执行指令 model_called_skill = "create_calendar_event" model_arguments = { "title": "产品评审会", "start_time": "2025-05-19T10:00:00", "end_time": "2025-05-19T11:00:00", "attendees": ["pm@example.com", "dev_lead@example.com"] } result = registry.execute(model_called_skill, json.dumps(model_arguments)) print(json.dumps(result, ensure_ascii=False, indent=2))

输出结果:

{ "status": "success", "event_id": 1, "event": { "id": 1, "title": "产品评审会", "start_time": "2025-05-19T10:00:00", "end_time": "2025-05-19T11:00:00", "location": "", "attendees": ["pm@example.com", "dev_lead@example.com"] } }

模型拿到这个返回值后,会整理成自然语言回复用户:“已为您安排产品评审会,时间是5月19日上午10点到11点,参会人包括产品经理和开发组长。”整个过程里,大模型只负责“理解意图”和“填参数”,真正确定性的操作全部由技能代码完成。

4. 常见问题与排查技巧实录

4.1 模型就是不调用技能,怎么办?

这个现象在初期的出现频率极高。排查时先看日志里模型到底返回了什么:

  • 如果模型返回的是普通文本而不是结构化调用指令,大概率是技能描述太模糊,模型不知道这个技能能处理当前问题。我之前把“获取天气”写成“天气相关操作”,模型死活不调;改成“当用户询问今天或未来几天的天气情况时使用,支持城市名与日期参数”后,调用率立刻上来了。
  • 如果模型返回了错误的技能名,检查技能名与描述的一致性,尽量在技能描述里重复一遍技能名,并且说明使用场景约束。
  • 如果技能已经调用,但参数严重错误,比如把“明天”解析成了今天,基本是必填参数的description不够明确。我把每个参数的描述都加上了示例值:“date: 查询日期,格式YYYY-MM-DD,例如2025-05-20”,准确率明显提高。

4.2 技能返回数据太长,导致上下文溢出

这是很实际的工程问题。一些查询类技能返回几十条甚至上百条记录,如果全部塞到上下文里,既浪费token又可能把模型“冲昏头”。

我的解决办法是“三段式裁剪”:

第一,分页返回。技能支持limit和offset参数,默认只返回前20条,并附上total_count提示还有更多数据。

第二,字段裁剪。查询列表时只返回模型需要汇总的字段,比如标题、时间、状态,不要返回数据库里的所有列。

第三,让模型自己选择是否继续获取。返回结果中附上一句“提示模型:如需查看全部记录,请调用第2页”,模型就能自主决定是否翻页。

4.3 两个技能功能相似,模型总是选错

我遇到过“查订单”和“查物流”两个技能,模型经常把查物流的需求命中去查订单。后来我发现问题不在于模型笨,而在于两个技能的description边界不清晰。解决办法是在描述里明确写“互斥场景”:

  • 查订单:当用户询问“订单列表、订单状态、订单金额”时使用,不包含物流轨迹信息。
  • 查物流:当用户询问“包裹到哪了、快递进度、物流轨迹”时使用,需要订单号作为参数。

同时我还给每个技能加了一个场景示例字段,在description里带上两个典型的用户问法。实测下来,技能选错的概率降低了一个数量级。

4.4 排查工具箱:日志与回放

我给每个技能都加了统一的日志装饰器,记录每次调用的完整入参和出参,耗时,以及异常堆栈。这里的关键是把日志打全——不仅仅是打技能内部,还要打模型发过来的原始function call指令。很多问题其实发生在模型侧,比如参数类型传错、字段名拼错,如果没有原始指令日志,你很难区分是模型的问题还是代码的问题。

另一个非常有用的技巧是录制回放:把用户对话和模型输出录制下来,离线重放时可以反复修改技能代码,验证修复效果。在复杂项目中,这比在线联调节省的时间多得多。


最后再分享一个经验:技能设计不要一蹴而就。我最初给智能体一口气注册了三十个技能,结果模型选择负担很重,很多技能成了摆设。后来我改成“最小可用集”策略,每次只暴露当前场景需要的5到8个技能,准确率明显上升。等基础技能稳定后,再根据用户真实请求的分布逐步增加新技能。这种“从少到多、以用定增”的思路,才是agent-skills落地的正确打开方式。

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

C语言复试高频陷阱:运算符优先级、指针副作用与文件I/O底层机制

简介:本资源是电子科技大学研究生C语言复试试题的权威整理与深度解析资料,专为备战该校计算机类专业复试的考生设计,覆盖循环、指针、函数传参、switch流程控制、数组遍历、文件I/O、递归及位运算等核心考点,直击历年回忆版真题难…

作者头像 李华
网站建设 2026/10/28 12:31:35

Windows 上 Docker 可视化:Portainer 部署与运维实战指南

Windows 上跑 Docker,很多人第一步就卡在“看不见摸不着”这件事上——命令行敲docker ps出来一堆字符,容器到底健不健康、端口有没有冲突、日志里在刷什么,全靠脑补。Portainer 这个可视化面板就是来解决这个痛点的:它把 Docker …

作者头像 李华
网站建设 2026/10/28 12:31:10

深入解析Git核心:.git目录结构与实战应用

1. .git目录:Git版本控制的核心引擎 当你第一次在项目目录下执行 git init 命令时,一个名为 .git 的隐藏文件夹就悄然诞生了。这个看似普通的文件夹,实则是Git版本控制系统的神经中枢。就像汽车的发动机舱,外表平平无奇&…

作者头像 李华
网站建设 2026/10/28 12:30:01

驱动级内存操作与云特征下发技术解析

1. 项目背景与核心价值在软件安全领域,内存操作技术一直是个双刃剑。我从业十年来见证了无数内存读写方案的兴衰,从最早的直接指针操作到后来的API Hook,再到现在的驱动级交互。这个项目标题提到的"驱动读写云下发特征无痕操作"组合…

作者头像 李华
网站建设 2026/10/28 12:29:18

鲁大师电脑版验机全攻略:四层闭环核验硬件真伪与配置

电脑城装机、二手平台淘本、公司批量采购验机,这几个场景里最容易踩的坑,几乎都绕不开同一件事:机器到底是不是它说的那个配置,硬件是不是原装正品,硬盘是不是被人动过手脚。我这些年帮朋友验过的机器没有一百也有八十…

作者头像 李华
网站建设 2026/10/28 12:29:18

React Caveman模式:用useMemo实现离线token调试

1. 项目概述:为什么“caveman”突然成了React生态里的高频暗语?最近在多个前端技术群、React面经整理帖和CI/CD故障排查记录里,频繁看到一个看似原始、实则暗藏玄机的词——caveman。它既不是React官方文档里的API,也不是任何主流…

作者头像 李华