Agent八股文-LLM 工具调用篇
Function Calling 篇
首先抓住重点:LLM 的工具调用的原理是 LLM 输出相关调用工具的JSON格式代码,由人类应用程序执行代码并完成相关API的调用,LLM 只是考虑是否使用,而真正调用工具的是应用程序。
那么,LLM学会调用工具分类两个部分:1 怎么学会调用的,2 具体调用的过程是什么
- 如何学会调用的
通过 SFT(监督微调) 和 RLHF(给予人类反馈的强化学习)完成学会的过程
SFT:给大模型大量实例,让大模型学会模仿调用工具的过程,具体过程就是一开始说的,详细的讲,一个合格的训练样本应该是有:
- tool schema 工具说明书(告诉LLM调用的方式)
- use 消息(用户提出的问题)
- LLM 调用(通过问题思考是否使用说明书中的工具并返回json代码)
- tool 消息(应用代码执行json代码,调用相关API返回真实数据)
- LLM 整合回答(接收数据并整合回答)
RLHF(基于人类反馈的强化学习):通过强化学习告诉大模型哪些回答人类更喜欢,给每一个回答打分,按照打分表选择回答方式,解决LLM 在合适的情况下调用工具,详细过程:一个问题,让LLM有三种解决方案,调用工具,直接回答,参数错误,然后人类根据回答进行打分,并将打分员的风格进行蒸馏,生成打分AI员(具体叫训练奖励模型,实现对于一个问题一个回答,推测出人类可能会打的分数),然后将打分结果对LLM进行强化学习,让LLM知道这类问题那种回答方式更好,避免出现 问1+1的简单问题,都需要调用计算器的问题。
- 如何运行的
这其实就是调用工具的原理,和训练样本完全一样。
总结:
LLM 并不是因为训练参数多,规模大而导致的自发学会工具调用,它最终不过是一直在读书,不可能学会工具的使用,而学会的原因是告诉了 LLM 可以通过json格式的代码发送请求,由应用程序代替它实现API调用,从而实现工具调用。
补充:
- Function calling = SFT + RLHF
- 大模型的工具调用和agent的工具调用是不一样的理解:一般的,我们说起大模型的工具调用特指的是,模型发起工具调用的指令,而不是实际的调用过程,因为对于大模型而言,它最初只是一个聊天工具,在不知道有工具这个概念的时候是不会自主发出调用工具的指令的,就像人不知道未知生物,就不会说我要去杀掉某个生物一样,当我们给大模型灌输模型调用的概念的时候(SFT)此时他才会了解并提出工具调用的指令,所以平时说的大模型调用是如何学会的,特指大模型是如何知道可以自主发出工具调用指令的,对于刚学习或者单纯从语义上理解,确实普遍会认为问的是大模型是如何实现调用工具得到数据的整个过程的,这就是为什么在该篇文章的问题下,回答的是如何训练模型学会调用工具,也就是 Function Calling 机制,而不是具体调用的过程。
MCP 篇
MCP 解决的是 工具怎么标准化接入、一次实现到处复用
我们从问题出发,明白了最底层的工具调用逻辑,但是这种方式会存在维护困难的情况,比如,
- api提供方更新了api文档,我就需要同样更换工具描述(description),这就需要拿出格外的时间去维护文档,也就是说,我需要实时更新关于工具api的描述以及响应数据的处理,一个两个还可以,但是当维护一个大的系统,存在很多个工具,对于一个api来讲,它负责的厂商可能很久更新一次,但是当工具数量多起来,各个厂商不定时更新,造成的结果和表象就是我作为开发方需要每天关注哪些apil更新,是否还能用,十分麻烦
- 还有就是api返回格式各式各样,毕竟一个api调用不会只是为了单独给我设计的,所以返回的内容中必然会存在很多无关数据。因此为了获得准确数据,我还需要做数据提取的工作,那么当api发生变化带来的问题可能就是我在对于返回数据处理的代码因为格式不一致可能就会直接报错,获取不到数据等等情况,
总的来说就成为维护的烦恼,而mcp解决了这个维护难的问题,
- 第一个就是工具更新问题,如果厂商工具更新,会主动发送工具更新情况,也就是表转化的工具列表
- 其次,因为不同的调用返回格式不同,需要额外花时间处理数据提取问题,mcp直接规定要想使用我这个协议,双方必须按照规定的格式调用和返回,
所以,mcp解决了更新工具内容和响应回来的数据格式不一致问题,
这里补充一点,mcp的感觉更像是http协议,规定了双方通信的约定,他本质是解耦,让开发者不用在关注数据格式这种维护问题,而是专心的放在业务逻辑上,大大降低了维护成本。
在这里,其实我才明白mcp的作用,以前学的总是懵懂,听到的都是mcp,没人提起工具调用,当时就认为工具调用就是mcp,但是当学到mcp的时候又不理解为什么他的名字却是模型上下文协议。
以前就很疑惑,现在明白了,工具调用是指的agent实现工具调用的流程,而mcp专指如何维护多个工具,他就更像一个容器,因为所有的工具都是基于这个生态创建的,类似于springboot,所以大家平常会说工具调用就会说mcp,就像写个java程序我们不说用java写的,我们说用的springboot写的。
这里让ai完善一下内容
-
关于“工具更新”:从“被动维护”变为“主动感知”
- 你的痛点:你需要每天去盯着几十个厂商的 API 文档,看他们有没有改参数、改接口。一旦他们改了,你的代码就报错,你得连夜改 Prompt 和解析逻辑。
- MCP 的解法:正如你所说,MCP 实现了“动态发现(Dynamic Discovery)”。
- 在 MCP 的架构里,MCP Server(也就是对接具体厂商 API 的那一端)是“工具的主人”。
- 当厂商更新了 API,MCP Server 的维护者只需要在 Server 端更新一下
tools/list的描述信息。 - 作为调用方的大模型(Client),在下次发起请求时,会自动拉取最新的工具列表。你作为中间的开发方,根本不需要去改任何代码,也不需要去读厂商的文档。 大模型会自动根据最新的描述,生成新的调用指令。
-
关于“数据提取与格式不一致”:从“脏活累活”变为“标准化封装”
- 你的痛点:API 返回的数据往往带着大量无关信息(比如 HTTP 状态码、复杂的嵌套 JSON),你需要写大量的
if-else和正则表达式去“抠”出你想要的那几个字段。一旦厂商返回的 JSON 结构微调,你的提取代码就崩溃。 - MCP 的解法:MCP 规定了“数据清洗的边界”。
- 在 MCP 协议中,
tools/call返回的内容必须是经过 MCP Server 处理过的、相对干净的标准格式(通常是一个TextContent或ImageContent数组)。 - 这意味着,“数据提取”这个脏活累活,被从你的 Agent 主逻辑中剥离了,转移到了 MCP Server 端。
- 你的 Agent 主程序永远只接收标准化的 MCP 响应,至于这个响应背后是调用了淘宝的 API 还是微信的 API,返回了多么复杂的嵌套数据,都由 MCP Server 在内部消化和清洗。你的主系统变得极其稳定。
- 在 MCP 协议中,
MCP底层通信协议
应用层消息格式:JSON-RPC 2.0
应用层,用JSON格式来表达调用,服务端返回一个JSON相应,包含执行结果和错误信息,好处就是易读易调试
传输层,两种传输方式
- 本地传输 stdio Server 作为子进程,通过管道和Client 通信,从stdin读消息,将结果写到 stdout
- 管道:操作系统在内存给这两个进程分配的一段先进先出的小缓冲区,Client往一头塞一行JSON,Server从缓冲区另一头读取然后处理,反过来也是同理,好处就是这个过程不经过网卡,没有TCPIP链接,网络延迟低响应快。
- 远程传输 Streamable HTTP,本质依旧是HTTP+SSE,短对话用HTTP响应,长对话就用SSE流式对话响应,实现的原理是由原先的post发送http请求,然后建立getSSE链接,由于两个端点是分开,导致SSE链接必须一致链接,无法做到按需发送请求然后再建立连接返回答案,现在是将两个端点合并成一个,响应数据的时候根据任务类型(一次性结果/流式输出)选择不同的建立方式。
MCP三层架构
- Host :AI应用本身
- Client:Host 中负责通信的部分
- Server:工具提供方的服务器
MCP的三种能力
- Tool 工具调用,指的是可以改变数据的操作
- Resource 专指只能读的数据
- Prompt 预定义提示词模板
FC与MCP的对比和选型篇
MCP 和 Function Calling 有什么区别?有没有实际跑过 MCP?
认真理解过MCP 和 FunctionCalling的就很容易认出区别。
Function Calling 通json格式的方式发送工具调用指令,不仅便于读取工具说明书,并且发送的json格式指令也容易被代码解读,从而让ai具备了发起工具调用的指令
MCP 协议最初存在的意义是为了维护多个工具,不同工具来自不同厂商,更新时间不一致,需要不定时去查看公司API文档进行更改,消耗人力物力,并且不同厂商的调用和返回数据格式不同,需要单独对数据提取编写代码,导致最终难以维护,总的来说,还是维护工具的问题,如何保证后续过程中调用工具调用还能正常运行
所以MCP 通过json-RPC 2.0的方式规定了消息格式,统一规定用json格式请求响应,并且用CS架构解决断层更新问题,客户端不需要再次维护工具的工具说明书,由服务端主动发送工具说明书,解决了频繁更新工具的问题。
总的来说,FC确保AI可以发送正确的工具调用指令,MCP 则是保证工具标准化接入,降低维护成本。
请问什么场景下使用 Function Calling,什么场景下使用 MCP?
使用最单纯的Function Calling 其实就是指的最初本地工具调用方法,手动写工具说明书,然后实现本地工具调用,一般适用于工具个数少,且来源少的情况,MCP 主要使用于多个工具且厂商不同的情况,或者是跨团队复用
为什么有些特定的推理模型不支持 MCP 协议?
我以为所有的模型都是支持FC,结果还有不支持FC的,有一些推理模型在给出答案之前必须跑一遍完整的思维链,在思考的过程中会反复自言自语推演验证反驳推翻重来的那个情况,类似与一些深度思考,这种情况好像不允许在推理过程中停止然后去查数据,再将数据输入重新推理,大概是因为上面的推理方式是隐式推理,属于模型本身的能力,无法按照prompt的方式记录他的推理过程,导致调用工具后无法继续推理。不支持FC,自然也就不需要MCP去维持工具了
小林在状态保持上给出的解释是这样的
如果推理模型停下来去进行工具调用,由于这种推理模型推理的内容十分复杂,一般会有一个缓存的思考草稿本,里面记录了所有的推理过程,加KV Cache,是模型缓存注意力计算结果的结构,体积十分庞大,如果进行工具调用,这个草稿本就会原封不动的占GPU显存,一直占用不给其他的请求,导致整体延迟上升,不适宜多请求的处理。
Skill 篇
Skill 又称 “技能”,他的作用是赋予agent专业知识,让agent具备相关领域的能力或者学会相关工作步骤
我们从物理层面和逻辑层面来了解 Skill
物理层面
本质上skill就是一个文件夹,里面包含核心文件 skill.md 以及其他可选的指令,更加详细的讲,md文件中其实就是一些 prompt,里面可能记录某人的性格,对ai风格的要求,也许是一个代码审查的完整流程,详细记录了具体需要检查代码的哪些部分,但是总来看,他只不过是把prompt封装到一个md文件里面。
Skill 文件夹
-
skill.md 文件:里面包括元数据(名称和描述)和详细步骤指令
-
--- name: tech-weekly-reporter description: 专门帮助程序员和工程师将零散的工作记录转化为格式标准、用语专业的技术周报。当用户需要写周报、日报或工作总结时触发。 --- # 技术周报生成器 ## 你的角色 你是一个拥有10年经验的技术团队Team Leader,擅长将工程师零散、口语化的工作记录,润色成逻辑清晰、重点突出、用语专业的正式周报。 ## 核心工作流程 1. **接收输入**:引导用户把这一周做的杂事、代码提交记录、修复的Bug等零散信息发给你。 2. **读取模板**:从 `templates/weekly_report_template.md` 中读取标准的周报格式。 3. **参考规范**:在润色语言时,参考 `references/writing_guide.md` 中的“周报黑话”和用词规范,确保语气专业。 4. **生成报告**:将用户的信息填入模板,输出最终的周报。 ## 注意事项 - 保持客观,不要夸大工作量,但要突出技术难点和解决思路。 - 如果用户提供的信息太少,主动追问细节(比如:“这个功能上线后带来了什么具体的性能提升?”)。
-
-
可选项
-
参考文档或者知识库(references)
-
# 技术周报用语规范指南 ## 推荐使用的“专业黑话” - 不要说“改完了代码”,要说 **“完成了代码重构与优化”**。 - 不要说“修了几个Bug”,要说 **“提升了系统稳定性与健壮性”**。 - 不要说“跟产品经理对了一下”,要说 **“完成了需求对齐与可行性评审”**。 - 不要说“正在做”,要说 **“目前处于开发阶段,进度符合预期”**。 ## 写作原则 - **数据驱动**:尽量用数字说话(如:QPS从100提升至500)。 - **结果导向**:重点描述产出和价值,而不是罗列流水账。
-
-
执行脚本(scripts)
-
模板文件(templates)
-
### 📅 本周工作周报 **姓名:** [你的名字] **日期:** [填写日期范围] #### 1. 重点项目进展 (Key Progress) - **[项目名称/模块]**: - 完成情况:[用百分比或状态表示,如:已完成 100%] - 核心产出:[简述做了什么,例如:完成了用户登录接口的重构] - 技术亮点:[选填,例如:引入Redis缓存,接口响应速度提升50%] #### 2. 问题与修复 (Bug Fixes & Issues) - 修复了 [具体Bug描述],原因是 [简述原因],目前已 [上线/待验证]。 - 协助 [某同事/某部门] 解决了 [某环境问题]。 #### 3. 下周工作计划 (Next Week Plan) - [计划事项1] - [计划事项2] #### 4. 风险与求助 (Risks & Help Needed) - [如果有阻碍进度的风险,在这里提出,没有则写“无”]
-
-
静态资源(assets)
-
逻辑层面(意义是什么)
- 首先从问题出发,prompt 具有临时性,每次都需要粘贴描述,否则会影响质量,所以十分繁琐,因此不如直接封装到有一个“共享文档”里面,需要的时候粘贴使用。
- 只是作为共享文档还是十分单薄,skill还具备 “渐进式披露” 的自主选择调用的强大功能,这样就不需要每次粘贴使用,agent 会自动按需加载,实现这功能的原因属于agent 开发中的skill机理,简单的讲就是通过配置agent会自动取识别skill相关文件,然后按照 “渐进式披露” 的方式实现按需加载
agent实现原理——渐进式披露
渐进式披露又叫渐进式加载,一共分为三个步骤:
- 当skill文件夹导入到agent配置后,在对话创建的初始化阶段,agent 会主动识别skill文件,并且会读取skill.md文件中的元数据(frontmatter),从而了解skill的名字(name) 和功能描述(description),快速了解各个skill
- 通过用户问题选择合适skill,详细阅读skill.md文件内容
- 根据提示词内容按需调用skill文件加中的可选项,包括模板或者脚本等
几个小问题:
- 存在意义:直接加载所有的skill会消耗大量 token,一个skill大约在 2000 token左右,上下文容量在 20 万左右,完全加载一个 Skill 还是十分消耗资源并且还会造成白加载。而只加载元数据 frontmatter 消耗量会缩小 10 倍
- 为何第三步才加载脚本或者模板:对于可选项,里面包含的脚本或者模板内容会很长,非必要直接加载会导致消耗大量token,所以必须要在确认所需加载时才会选择,节约资源
A2A篇
之前在 agent篇中有一篇内容提到关于多agent的内容,里面详细提到了单agent的局限性和多agent的必要性,但是那篇文章多指的是同一框架内的多agent,本质上是通过langgraph的建图去实现。
而A2A协议专门指的是来自于不通过厂商的agent之间通信的协议,它实现的将不同能力的agent分配合适的任务,共同完成一个主任务。
最明显的区别在一个是通过公共变量来获取prompt,一种则是由agent自发的向某一agent发送prompt
由于我并没有尝试过A2A,比如让codex去调用claude,所以这里只写一下相关的八股知识
A2A协议中如何保证agent之间的通信?
每一个 A2A agent都会在一个约定的位置存储一张 JSON 格式的名片 Agent Card,类似于SKILLmd中的yml格式简介,用来介绍自己的擅长领域,具备哪些SKILL,方便其他agent认识。
Task
这里还有一个新的定义,就是A2A的任务协作单位是Task,一个agent发起任务就算是一个Task创建,然后他还有生命周期
- Submitted 状态,表示已提交,等待处理
- Working 状态,表示正在执行
- Completed 状态表示执行成功
- Failed 状态
这样设计的原因是为了异步执行,负责等待的agent不会一直等待该task状态变换,而是通过轮询或者任务完成主动通知(push notification)的方式判断上一次操作是否完成,这样的好处就是不需要一直等待,而是先处理其他响应,提高整体响应效率
通信协议篇
说说 WebSocket 和 SSE 通信的区别及局限性?
- 通信方向的区别
- SSE:服务端单向推送,客户端想发消息要另起 HTTP 请求。
- WebSocket:全双工,双方都可以随时主动发消息。这是两者最本质的差异,不是「简单 vs 复杂」的关系。
- 各自的局限性(面试官追问重点)
- SSE 的三个坑:HTTP/1.1 下同域名连接数上限(6条)、只支持文本格式(传二进制要 Base64 编码膨胀 33%)、单向性导致的双通道架构复杂度。
- WebSocket 的三个坑:有状态导致横向扩展麻烦(需要 Redis 等共享存储做连接状态外移)、容易被企业代理和防火墙拦截(Upgrade 握手被当异常请求拒绝)、没有内置的请求-响应配对机制(需要自己维护请求 ID 映射)。
- 选型原则
- 单向推用 SSE,真正需要双向才上 WebSocket。LLM 文字对话场景绝大多数用 SSE 就够了,这也是 OpenAI 和 Anthropic 的选择。能说出这个判断依据,比单纯罗列功能差异更有说服力。

整理小林coding的学习笔记
浙公网安备 33010602011771号