1. 先说说我为什么想把这三者的边界彻底写清楚
最近一段时间,团队在做一个企业内部的智能客服项目,需求不算复杂:用户提问、系统检索知识库、生成回答、解决不了就转人工。一开始我们天真地以为,接入一个大模型API就能搞定。结果试了几天就发现完全不是那么回事——模型答非所问、查不到最新数据、更别提让它替用户去查订单状态了。
后来我们开始引入Agent框架,让模型能"调用工具",问题解决了一大半。但紧接着又踩了新坑:所有工具逻辑、业务流程、异常处理全堆在Agent的代码里,改一个需求要动整个编排层,项目越写越像一团乱麻。直到我们把工具层抽象成独立的Skill模块,整个架构才真正顺起来。
这段经历让我深刻意识到一件事:LLM、Agent、Skill这三个词,网上有大量文章在讲,但大多数要么停留在概念对比,要么是某个框架的文档翻译,真正把它们放在一个项目里讲清楚"各自解决什么问题、边界在哪、怎么协作"的内容太少。这篇文章就基于我自己的实操经验,把这三者的区别和关系揉碎了讲一遍。适合正在做AI应用开发、被Agent框架绕晕的朋友,也适合想从零理解这套架构的产品和技术同学。
先抛一个总的结论,后面逐个展开:LLM是大脑,负责思考和推理;Agent是躯干,负责感知、决策和行动;Skill是手脚,负责具体执行某个动作。听起来简单,但真到落地的时候,这三者的边界经常被模糊掉,导致架构越做越别扭。
2. LLM、Agent、Skill的定位差异
2.1 LLM:一个只会想、不会做的模型
大语言模型本质上是"根据上下文预测下一个词"的神经网络,经过海量训练后,它具备了理解、推理、生成、总结等能力。但请注意,它的输出永远只是文本。你问它"帮我查一下昨天订单的状态",它能给你一段回答,但这段回答要么是编的,要么基于训练数据里的旧信息,它没有能力真正去查你的数据库。
很多人把LLM类比成"大脑",这个类比没错,但容易产生误解。大脑如果不支配手脚,就只是个空想家。同样的道理,LLM如果不接上工具、不获得真实数据反馈,它就只能在训练过的知识范围内"脑补"。它的能力边界非常清晰:
- 擅长:语义理解、内容生成、逻辑推理、代码编写、多轮对话
- 不擅长:执行具体操作、访问实时数据、调用外部系统、保证结果可验证
从工程角度看,LLM本身只是一个被调用的"文本接口"。你给它一组输入(系统提示词+pPrompt+上下文),它返回一段输出。它没有目标意识,没有循环控制,不会自己决定"下一步做什么"。这个"只思考不行动"的特性,决定了它必须被某种框架包裹起来才能解决真实问题。
2.2 Agent:把思考变成行动的调度者
Agent的出现,就是为了补上"思考"和"行动"之间那条巨大的鸿沟。一个典型的Agent是一个软件实体,它具备以下特征:
- 有目标:要么来自用户指令,要么来自预设任务
- 能感知:可以获取外部状态,比如环境信息、工具返回结果
- 能决策:根据当前状态和LLM的推理,决定下一步调用什么工具、生成什么内容
- 能行动:真正去执行API调用、文件读写、数据库查询等操作
- 有循环:执行完一步后,观察结果,再决定下一步,直到任务完成
我习惯把Agent理解成"一个拿着LLM当大脑的程序"。它本身不是智能体,但它通过程序逻辑把LLM的智能"调度"出来。举个例子:用户说"帮我订一张明天去上海的高铁票,然后提醒我出发"。一个纯粹的LLM只能回复"好的,我帮你订票"——它什么也做不了。但一个Agent可以这样运作:
- 调用意图识别Skill,确认用户要订票+设置提醒
- 调用日期解析Skill,把"明天"转成具体日期
- 调用订票API,查询车次、下单
- 调用日历/通知Skill,创建提醒
- 汇总结果,返回给用户
每一步决策,Agent都会先问LLM"这一步该做什么",然后自己负责执行。LLM是决策脑,Agent是执行中枢,两者的职责完全不同。
这里有个很容易混淆的点:很多人把"能调用工具的LLM"直接等同于Agent。严格来说,如果只是给LLM挂上函数调用接口(function calling),但没有循环、没有状态管理、没有自主决策机制,那它只是一个"增强版的LLM",还不是真正的Agent。Agent最核心的特征是自主规划和多步执行,而不仅仅是"能调函数"。
2.3 Skill:可复用的能力单元
有了LLM和Agent,是不是就够了?还不够。你会发现Agent在决策时需要一个"能力清单"——它得知道有哪些事情可以做,每个事情怎么做,什么时候该做。这个"能力清单"里的每一项,就是Skill。
Skill是封装好的、具有明确输入输出接口的能力模块。它可以是:
- 一个函数(比如
query_order_status(order_id)) - 一个API调用(比如请求天气服务)
- 一段固定的工作流(比如"生成周报:拉数据→套模板→发送邮件")
关键不在于实现方式,而在于它的可描述性和可复用性。Agent需要通过Skill的描述来决定"什么时候该用它",所以一个合格的Skill必须包含清晰的名称、用途说明、参数定义、返回值格式。
我拿人来做类比:你会做饭、会开车、会写代码,这些是"技能";你的大脑负责判断"现在该做饭还是该修车",你的身体负责把技能落实。Skill就是那个"会做饭"的能力包——它不负责决策,只负责执行。
在工程实现上,Skill通常是独立于Agent逻辑的模块。Agent只负责"选哪个Skill",不关心Skill内部怎么实现。这样带来的直接好处是:
- 新增能力时,只需要添加一个新的Skill,不需要改动Agent的核心逻辑
- 同一个Skill可以被多个Agent复用
- Skill可以单独测试、单独部署、单独升级
从我实际经验来看,把Skill从Agent里拆出来,是整个项目从"能跑"走向"好维护"的关键一步。后面我会详细讲怎么设计。
3. 三者到底怎么协作:一个真实项目的完整拆解
3.1 从需求出发,倒推架构
我们那个智能客服项目,最初的需求只有一条:用户问问题,系统能答。到期中加了几个硬需求:
- 查订单状态(需要调内部订单系统)
- 查物流进度(需要调第三方物流API)
- 用户发"转人工"时自动创建工单(需要调工单系统)
面对这些需求,正确的做法不是一上来就写Agent代码,而是先把"能力清单"列出来。我们当时做了个简单表格,每个能力一行,标注:这个能力叫什么、输入是什么、输出是什么、调什么系统。这个表格,本质上就是Skill清单的雏形。
列完后发现:查询类的能力、操作类的能力、通知类的能力,它们的调用方式、错误处理逻辑、效果评估方式都不一样,如果全放在一个Agent里处理,代码会非常臃肿。于是我们确定了三层架构:
- 最底层:LLM,提供理解和推理能力
- 中间层:Agent,负责理解用户意图、规划步骤、调度能力
- 最上层:Skill集合,每个Skill负责一个具体的原子操作
这个架构不新鲜,但它解决了一个很实际的问题:每一层只关心自己的事,改动可以限制在最小的范围。比如物流API换了厂商,只需要改物流查询这个Skill;用户意图识别不准,只需要优化Agent的提示词和编排逻辑;模型从旧版本升到新版本,其他两层基本不用动。
3.2 Skill定义的关键点:让Agent"看懂"你的能力
Skill的接口设计是整个项目中我最想强调的部分。很多人写工具函数写得很好,但一变成Skill就失效,原因只有一个:Agent(实际上是LLM)看不懂这个Skill是干什么的。
举个例子,我们早期有个Skill定义为:
def get_info(param1, param2): ...功能是查库存,但描述写得很模糊。结果Agent在用户问"这个商品还有货吗"的时候,根本不知道怎么调用,最后还是走模型自由发挥,答了个"我建议您联系客服"。后来我们改成了这样:
def check_stock(sku_id: str, warehouse_code: str = "") -> dict: """查询商品在指定仓库的实时库存。 适用场景:用户询问"有没有货"、"是否缺货"、"什么时候补货"。 参数: sku_id: 商品编码,必填,格式如 SKU123456 warehouse_code: 仓库编码,选填,不填则查询所有仓库 返回: { "sku_id": "...", "stock_count": 123, "warehouses": [{"code": "...", "count": 55}], "status": "available" | "out_of_stock" | "low_stock" } """ ...这个改动看似简单,但对实际效果的影响极大。这里我总结几个自己踩过坑之后得出的经验:
第一,Skill的描述一定要写"适用场景"。LLM理解能力再强,也需要靠描述来做意图匹配。你只写"查询库存",它不知道什么用户话语对应这个功能;你补上"用户询问有没有货、是否缺货"这些触发场景例句,匹配准确率会明显上升。
第二,参数的命名和格式要符合直觉。用sku_id而不是a,用warehouse_code而不是b。LLM是从自然语言理解参数的,参数名越贴近业务语言,它越容易从用户话语中抽取正确的值。
第三,返回值必须结构化且包含状态字段。不只是返回数据,还要返回状态,比如status: out_of_stock。这样Agent在下一步决策时,可以直接根据状态判断该怎么回复用户,而不是让LLM去猜"这次查询成功没有"。
第四,一个Skill只做一件事。我们曾经写过一个"综合查询Skill",同时查订单、物流、库存,结果这个Skill在Agent决策的时候很难被精准触发,而且经常返回一堆无关数据,浪费token还误导模型。拆成三个独立Skill之后,一切清爽了。
3.3 Agent的编排逻辑:选对模型、写好提示词、定好循环
Skill定义好了之后,Agent层要做的事情就是"选Skill、排顺序、看结果、再选择"。这个编排逻辑目前主流有两种模式:
模式一:固定流程 + LLM辅助
适合业务流程明确、步骤变化不大的场景。比如我们的工单创建流程:识别意图→验证用户身份→收集必要信息→创建工单→通知用户。这个流程基本固定,我们直接在代码里定义好步骤,每一步调用一个对应的Skill,LLM只负责在中间环节解析用户输入、填充参数。
这种模式的好处是稳定、可控、容易调试。缺点是灵活性差,遇到流程之外的请求就处理不了。
模式二:完全由LLM驱动的动态规划
让LLM在每次对话中自主决定调用哪些Skill、以什么顺序调用。这种模式非常灵活,能应对各种开放性任务,但副作用也很明显:花更多的token、更容易出错、更难从代码层面去管控。
我们最终采用的是混合模式:对高频的、流程固定的需求走模式一;对开放式的、需要灵活应对的需求走模式二。这个权衡的准则是:越重要的操作,越不要交给LLM自由发挥。比如创建工单这种会产生实际影响的动作,宁可流程写死也不要让模型"灵机一动"走偏。
Agent层还有一个容易忽略的点:上下文和状态管理。Agent必须记住"当前执行到哪一步了""用户已经提供了哪些信息""哪些Skill已经调用过且结果如何"。这些状态如果全扔在对话上下文中,长了就乱,还会超token限制。我们的做法是维护一个独立的状态对象,Agent每次决策前先读取状态,每次执行后更新状态,只把关键信息同步给LLM,而不是把整个历史都灌进去。
4. 常见误区与排查实录
4.1 误区一:Skill就是普通函数,不用特别设计
这是我见过最多的问题。很多人直接把项目里现成的函数注册成Skill,描述就一行字,参数名全是缩写,return什么乱七八糟的都往上报。结果Agent要么不用这些Skill,要么用了也经常传错参数。
排查方法其实很简单:把Skill的注册信息完整打印出来,站在一个"完全不知道代码实现"的第三方视角看,问自己一句——"如果我是一个LLM,我看这段描述能知道什么时候用它、怎么用它吗?如果答案是否定的,那就重写描述。"
4.2 误区二:把业务逻辑全塞进LLM提示词
另一个高频问题:Agent能力不足的时候,第一反应是加长提示词,把各种业务规则、判断逻辑、应对策略全写进System Prompt里。这样做短期有效,但提示词会越来越长,决策会越来越不稳定,改起来也极其痛苦。
正确的做法是:尽量把"确定性逻辑"从提示词移到代码里。比如"订单金额超过1000元需要审批",这是确定性规则,写在代码里更可靠;"用户情绪激动时应该安抚"这类模糊判断,才适合交给LLM处理。Skill和Agent的设计原则,本质上是"把确定的交给代码,把不确定的交给模型"。
4.3 误区三:过度Agent化
本来一个简单的关键词匹配就能解决的问题,非要套一个Agent+LLM+三个Skill的架构。这是2024年下半年开始流行的一种"为了Agent而Agent"的风气。
我见过一个项目,用户点了个按钮,就为了改一个数据库字段,结果走了一遍"LLM理解意图→Agent规划→调用Skill→生成SQL→执行"的流程。延迟高、成本高、出错点多,完全没必要。引入Agent的唯一理由,是任务本身需要多步动态决策。如果任务是固定的、线性的,老老实实用传统代码,又快又稳。
4.4 常见问题速查
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| Agent完全不调用任何Skill | Skill描述模糊,LLM不知道该在何时使用 | 重写Skill描述,增加触发场景和示例 |
| Agent反复调用同一个Skill | 缺少退出条件;调用后状态没有更新 | 检查循环终止逻辑,确保调用后状态发生变化 |
| Skill参数总是传错 | 参数名不直观;缺少必填/选填说明 | 优化参数命名和描述,尽量和业务术语一致 |
| Agent执行到一半就不动了 | 上下文超长;某个Skill返回异常 | 增加异常重试和兜底提示;精简上下文内容 |
| 同样的输入,结果忽好忽坏 | LLM的随机性导致决策波动 | 降低temperature;对关键决策改用固定流程 |
| 新增一个Skill后其他功能变差 | 能力清单过长,LLM选择困难 | 对Skill分类分组,或为不同场景配置不同的Skill子集 |
4.5 排查思路:从结果反推每一层
Agent项目出问题的时候,最忌讳的是"一把抓"。我的排查习惯是一定要先定位问题出在哪一层:
- 先看用户输入是否被正确理解——这层问题通常是提示词和LLM的问题
- 再看Agent是否做了正确的Skill选择——这层问题通常是Skill描述和编排逻辑的问题
- 最后看Skill执行是否成功——这层问题通常是代码实现和接口返回的问题
每一层都有独立的日志是最好的。我们的实践是:LLM的输入输出记一份日志,Agent的决策和状态变更记一份日志,Skill的调用参数和返回结果记一份日志。出问题的时候,按时间线对齐三份日志,基本十分钟内就能定位问题源头。
5. 一点实操心得
整个项目做下来,我最大的体会是:LLM、Agent和Skill这三者的关系,不是并列的,而是递进的。LLM是基础能力,它决定了系统"聪不聪明";Agent是组织层,它决定了系统"会不会做事";Skill是能力单元,它决定了系统"能做多细、多广的事"。三层各司其职,才能构成一个真正可用的应用。
如果你现在正准备做一个AI应用,我的建议是从Skill开始想,而不是从LLM开始。先列出你希望系统"能做什么"——每个能力就是未来的一个Skill。然后考虑这些Skill之间哪些流程是固定的、哪些需要动态判断——固定流程写成代码,动态判断交给Agent。最后再选择LLM,根据你的任务复杂度、成本预算、响应速度要求挑合适的模型。
最后分享一个我们在后期优化时用的技巧:给每个Skill写一个"示例对话",放在Skill描述的末尾。比如:
示例:用户说"我昨天买的手机发货了吗", 应调用 check_logistics(sku_id="手机对应SKU", order_date="昨天对应日期")这个技巧最初是我无意中发现的——有一次加在描述里之后,Agent调用准确率突然高了一截。后来我查了一些资料才知道,LLM在语义匹配的时候,"示例"比"规则"更有效,有点像我们在新环境里做事,看一个别人做过的例子,比自己读十条规定要直观得多。这个细节虽然小,但在实践中带来的提升往往超预期,推荐你也试试。