ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Function Calling、MCP、Skill 到底啥关系?Java 落地 Agent 开发实战

Function Calling、MCP、Skill 到底啥关系?Java 落地 Agent 开发实战 1. 三个概念的真实定位别被缩写绕晕了1.1 从一个真实困惑说起前阵子团队里有个做 Java 后端的兄弟问我“Function Calling、MCP、Skill 这三个词天天在群里刷屏到底谁是谁是不是同一个东西换了个马甲”这个问题其实特别典型。我刚开始接触 Agent 相关开发时也被绕进去过因为这三个词经常出现在同一篇文章里甚至同一段话里但它们描述的压根不是同一个层面的东西。打个比方你就懂了。假设你要开一家餐厅Function Calling是你跟厨师之间的“点菜机制”——你说“来份宫保鸡丁”厨师能听懂并去做MCP是餐厅和供应商之间的“标准采购协议”——规定了食材怎么下单、怎么配送、用什么格式对接Skill是厨师手里的“菜谱合集”——每道菜怎么做、放多少盐、火候怎么控都写在里面。三者各管一摊缺一不可但绝对不能混为一谈。我见过太多人把这三个概念搅在一起讲结果越讲越糊涂。所以这篇我打算用最直白的方式把三者的边界、关系、以及怎么在 Java 项目里落地一次性讲透。不管你是刚接触 Agent 开发的新手还是已经在用 Spring AI 做集成的老手看完都能有个清晰的认知框架。1.2 一句话定义三者先把定义摆出来后面再逐个展开Function Calling大模型的一种能力让模型能“决定调用哪个函数、传什么参数”本质是模型输出结构化 JSON 来触发外部工具。MCPModel Context Protocol一套标准化协议规定了 AI 应用和外部工具/数据源之间怎么通信解决的是“对接格式不统一”的问题。Skill一个封装好的能力单元通常包含提示词、工具调用逻辑、知识库等是面向具体任务的高层抽象。看出来了吗Function Calling 是模型层的能力MCP 是通信层的协议Skill 是应用层的封装。它们不在一个维度上所以不存在“谁替代谁”的说法。1.3 为什么这三个词总被放在一起因为它们经常在同一个技术栈里协同工作。一个典型的 Agent 系统可能是这样的用户说“帮我查一下上个月的销售数据并生成图表”模型通过Function Calling决定调用查询函数这个函数通过MCP 协议连接到数据库工具而整个“查数据画图”的流程被封装成一个Skill。所以它们是一条链上的三个环节不是三个竞品。理解这一点后面的内容就好展开了。2. Function Calling模型怎么学会“打电话叫人”2.1 从“只会聊天”到“能干活”的关键一步早期的语言模型就是个“嘴炮王者”你问它今天天气怎么样它要么说“我不知道”要么编一个听起来很像真的答案。Function Calling 的出现改变了这个局面——它让模型学会了“遇到搞不定的事知道该找谁帮忙”。具体怎么实现的你在调用模型 API 时除了传用户的问题还可以传一份“工具清单”告诉模型“你有这几个函数可以用分别是干嘛的需要什么参数。”模型在理解用户意图后如果判断需要调用某个函数就会输出一段结构化的 JSON比如{ name: get_weather, arguments: { city: 杭州, date: 2025-01-15 } }你的程序拿到这段 JSON去实际执行查询再把结果塞回给模型模型最后用自然语言告诉你“杭州明天晴气温 3 到 12 度。”2.2 核心机制拆解模型到底做了什么很多人以为 Function Calling 是模型真的“调用了函数”其实不是。模型做的事情只有一件根据上下文判断该不该调、调哪个、参数填什么。真正的函数执行是你的代码干的。这个区分特别重要因为它决定了你的系统设计。模型只负责“决策”不负责“执行”。所以你需要定义清晰的函数描述名称、用途、参数 schema接收模型返回的调用意图在自己的代码里执行实际逻辑把执行结果回传给模型这个循环可能来回好几轮直到模型认为不需要再调工具了给出最终回答。2.3 Java 里怎么接Spring AI 的 Function Calling 实践Spring AI 对 Function Calling 的支持算是比较友好的。你可以用Bean定义一个Function然后用Description注解描述它的用途Configuration public class WeatherConfig { Bean Description(查询指定城市指定日期的天气情况) public FunctionWeatherRequest, WeatherResponse getWeather() { return request - { // 实际查询逻辑 return weatherService.query(request.city(), request.date()); }; } }然后在调用 ChatClient 时注册这个函数String response chatClient.prompt() .user(杭州明天天气怎么样) .functions(getWeather) .call() .content();Spring AI 会自动把函数描述转成模型能理解的格式并在模型返回调用意图时帮你执行函数、回传结果。这套流程封装得比较干净省去了手动拼 JSON 的麻烦。注意函数描述写得越清楚模型判断越准确。我踩过的坑是描述太模糊导致模型该调的时候不调、不该调的时候乱调。建议把参数含义、适用场景都写进去。2.4 实操心得什么时候该用 Function Calling不是所有场景都适合上 Function Calling。我的经验是适合需要实时数据天气、股价、库存、需要执行操作发邮件、建订单、改状态、需要精确计算数学运算、单位换算不适合纯知识问答、文本生成、创意写作——这些模型自己就能搞定硬加函数反而增加延迟和成本还有个细节函数数量别太多。我试过一次注册二十几个函数模型选择困难症都犯了准确率明显下降。建议单次对话控制在 5 到 10 个函数以内多了就分组或者用路由层先筛一遍。3. MCP让 AI 和工具说同一种语言3.1 MCP 要解决的真正问题在没有 MCP 之前每个 AI 应用要对接一个工具都得自己写一套适配代码。你对接数据库写一套对接文件系统写一套对接浏览器又写一套。工具那边也一样今天适配 A 平台明天适配 B 平台重复劳动极其严重。MCP 的思路很简单定一套标准协议大家都按这个来。AI 应用这边实现 MCP 客户端工具那边实现 MCP 服务端双方通过标准化的消息格式通信。这样工具写一次所有支持 MCP 的 AI 应用都能用AI 应用写一次所有支持 MCP 的工具都能接。这个思路跟当年 USB 接口统一各种外设是一个道理。USB 之前鼠标、键盘、打印机各有各的接口USB 之后一个口全搞定。MCP 就是 AI 工具领域的“USB 标准”。3.2 协议长什么样核心概念拆解MCP 的核心概念不多主要就几个Server服务端提供能力的一方比如一个数据库查询服务、一个文件操作服务Client客户端使用能力的一方通常是 AI 应用Tool工具Server 暴露出来的具体功能比如query_database、read_fileResource资源Server 提供的可读取数据比如文件内容、数据库表结构Prompt提示模板Server 预定义的提示词模板方便客户端直接使用通信方式上MCP 支持多种传输层常见的有标准输入输出stdio和 HTTP 两种。stdio 适合本地工具HTTP 适合远程服务。3.3 Java 落地用 Spring AI 接 MCP ServerSpring AI 从某个版本开始提供了 MCP 客户端支持。假设你有一个现成的 MCP Server比如一个提供数据库查询能力的服务在 Java 里接入大概是这样Configuration public class McpConfig { Bean public McpClient mcpClient() { return McpClient.builder() .transport(new StdioTransport(path/to/mcp-server)) .build(); } Bean public ListFunctionCallback mcpFunctions(McpClient client) { return client.listTools().stream() .map(tool - FunctionCallback.builder() .name(tool.name()) .description(tool.description()) .function(args - client.callTool(tool.name(), args)) .build()) .toList(); } }这段代码做的事情是连接 MCP Server拉取它暴露的所有工具然后把每个工具包装成 Spring AI 能识别的 FunctionCallback。这样模型就能像调用本地函数一样调用远程 MCP 工具了。提示MCP Server 的启动方式要配好stdio 模式下进程管理容易出问题。我建议加个健康检查Server 挂了要能自动重启。3.4 MCP 生态现状哪些工具已经支持了目前 MCP 生态发展挺快的常见的支持 MCP 的工具类型包括工具类型典型能力适用场景数据库类查询、表结构读取数据分析、报表生成文件系统类读写、搜索文件文档处理、代码助手浏览器类页面操作、截图自动化测试、信息采集开发工具类代码分析、调试开发辅助、代码审查这个表格只是举例实际生态里的工具远不止这些。选型的时候重点看两点一是工具本身稳不稳定二是 MCP Server 的实现质量怎么样。我遇到过一些第三方 Server 实现得很粗糙参数校验都不做用起来很糟心。4. Skill把能力打包成“即插即用”的模块4.1 Skill 到底是什么一个更上层的抽象如果说 Function Calling 是“零件”MCP 是“接口标准”那 Skill 就是“组装好的模块”。一个 Skill 通常包含提示词模板告诉模型这个 Skill 是干嘛的、怎么用工具集合这个 Skill 需要用到的 Function Calling 或 MCP 工具执行逻辑什么条件下触发、执行顺序是什么、异常怎么处理知识库特定领域的背景知识帮助模型更好理解任务举个例子“生成月度销售报告”这个 Skill 可能包含一个查询销售数据的 MCP 工具、一个生成图表的函数、一段描述报告格式的提示词、以及一些业务规则知识。用户只需要说“帮我生成上个月的销售报告”Skill 就会自动编排这些资源完成任务。4.2 Skill 和 Function Calling、MCP 的关系用一张表说清楚维度Function CallingMCPSkill层级模型能力层通信协议层应用封装层解决什么模型怎么调工具工具怎么标准化对接任务怎么完整编排粒度单个函数单个工具服务完整任务流程谁定义应用开发者协议制定方业务开发者可复用性低绑定具体应用高跨平台通用中绑定业务场景从表里能看出来三者是层层递进的关系。Function Calling 是最底层的机制MCP 在它之上解决了标准化问题Skill 再往上封装成面向业务的完整能力。4.3 Java 里怎么实现一个 SkillSpring AI 本身没有“Skill”这个显式概念但你可以用组合的方式实现。核心思路是把提示词、工具、执行逻辑打包成一个服务类。Service public class SalesReportSkill { private final ChatClient chatClient; private final McpClient mcpClient; public SalesReportSkill(ChatClient chatClient, McpClient mcpClient) { this.chatClient chatClient; this.mcpClient mcpClient; } public String execute(String month) { // 1. 通过 MCP 查询销售数据 Object salesData mcpClient.callTool(query_sales, Map.of(month, month)); // 2. 构造提示词让模型生成报告 String prompt 你是一个销售分析师。根据以下数据生成月度报告 %s 要求包含同比环比分析、Top 5 产品、异常说明。 .formatted(salesData); // 3. 调用模型生成报告 return chatClient.prompt() .user(prompt) .call() .content(); } }这个类就是一个简单的 Skill 实现。它把数据查询、提示词构造、模型调用串成了一条完整的流程对外只暴露一个execute方法。业务代码调用它的时候不需要关心内部用了什么工具、什么模型。4.4 Skill 设计的几个关键原则做了几个 Skill 之后我总结了几个设计原则单一职责一个 Skill 只做一件事。“生成销售报告”是一个 Skill“生成销售报告并发送邮件”应该是两个 Skill 组合。这样复用性更好也更容易测试。幂等性同样的输入应该得到同样的输出在数据没变的前提下。这要求 Skill 内部不要有随机性操作或者把随机性控制在可复现的范围内。可观测Skill 执行过程中要打日志记录调用了哪些工具、传了什么参数、返回了什么结果。出问题的时候能快速定位。超时控制Skill 内部可能调用多个外部服务每个都要设超时。我见过一个 Skill 因为某个 MCP 工具卡住整个请求挂了五分钟。实操心得Skill 的提示词建议单独放在配置文件里不要硬编码在 Java 代码里。这样调整提示词不需要重新编译改完配置重启就行。5. 三者协同一个完整的 Java 落地案例5.1 场景设定餐饮 SaaS 的智能助手假设我们在做一个餐饮 SaaS 系统想加一个 AI 助手能回答老板的各种经营问题。比如老板问“上周哪家门店的营业额下滑最严重帮我分析下原因。”这个需求涉及查询多门店数据、对比分析、生成自然语言解释。我们看看怎么用 Function Calling MCP Skill 三者协同实现。5.2 架构设计分层拆解整体架构分三层Skill 层定义“门店经营分析”Skill负责编排整个流程MCP 层对接数据仓库的 MCP Server提供标准化的数据查询能力Function Calling 层模型通过 Function Calling 决定调用哪些工具、传什么参数数据流是这样的用户提问 → Skill 接收 → 模型通过 Function Calling 决定调用 MCP 工具 → MCP Client 转发请求到 MCP Server → 数据返回 → 模型分析 → 生成回答。5.3 关键代码实现先定义 MCP 工具包装Configuration public class RestaurantMcpConfig { Bean public McpClient restaurantMcpClient() { return McpClient.builder() .transport(new HttpClientTransport(http://data-warehouse/mcp)) .build(); } Bean public ListFunctionCallback restaurantFunctions(McpClient client) { return List.of( FunctionCallback.builder() .name(query_store_revenue) .description(查询指定门店指定时间段的营业额) .parameter(storeId, 门店ID) .parameter(startDate, 开始日期格式 yyyy-MM-dd) .parameter(endDate, 结束日期格式 yyyy-MM-dd) .function(args - client.callTool(query_store_revenue, args)) .build(), FunctionCallback.builder() .name(list_stores) .description(列出所有门店及其基本信息) .function(args - client.callTool(list_stores, args)) .build() ); } }再实现 SkillService public class StoreAnalysisSkill { private final ChatClient chatClient; public StoreAnalysisSkill(ChatClient chatClient) { this.chatClient chatClient; } public String analyze(String question) { return chatClient.prompt() .system( 你是餐饮经营分析助手。你可以调用工具查询门店数据。 分析时请遵循 1. 先获取门店列表 2. 查询相关门店的营业额数据 3. 对比分析找出异常 4. 给出可能的原因和建议 ) .user(question) .functions(query_store_revenue, list_stores) .call() .content(); } }5.4 执行过程实录当老板问“上周哪家门店的营业额下滑最严重”时实际执行过程是这样的第一轮模型判断需要先知道有哪些门店调用list_stores拿到门店列表。第二轮模型对每个门店调用query_store_revenue查询上周和上上周的数据。第三轮模型拿到所有数据后进行对比分析发现某门店下滑 35%然后生成回答“XX 门店上周营业额环比下滑 35%主要原因是……”整个过程模型自动编排我们只需要把工具准备好、把 Skill 的提示词写好。这就是三者协同的威力——Function Calling 负责决策MCP 负责通信Skill 负责编排。5.5 性能与成本考量这套架构跑起来效果不错但有几个点要注意Token 消耗多轮工具调用会消耗大量 token。我实测一个复杂查询可能来回五六轮token 用量是普通对话的十倍以上。建议对简单查询做缓存或者用更小的模型做路由。延迟每轮工具调用都有网络往返整体延迟可能到几秒甚至十几秒。用户体验上要加 loading 提示或者用流式输出让用户先看到部分结果。错误处理MCP 工具调用可能失败模型可能传错参数。要做好重试和降级不能让一个工具挂了整个 Skill 就崩了。6. 常见问题与排查技巧实录6.1 模型不调用工具怎么办这是最常见的问题。模型该调工具的时候不调直接编答案。排查思路先检查工具描述是否清晰。描述太模糊模型判断不了该不该用。我一般会把“什么时候用”也写进描述里比如“当用户询问实时数据时使用此工具”。再检查提示词。系统提示词里要明确告诉模型“你有工具可以用遇到不确定的信息要调工具查”。有些模型比较“自信”不提醒就自己编。最后看模型选择。不同模型的 Function Calling 能力差异很大。实测下来专门优化过工具调用的模型准确率明显更高。6.2 MCP 连接失败排查MCP 连接问题一般出在几个地方现象可能原因排查方法连接超时网络不通或 Server 没启动先 ping 通再检查 Server 进程工具列表为空Server 注册工具失败看 Server 日志确认工具注册逻辑调用返回错误参数格式不匹配对比 schema 定义和实际传参频繁断连心跳机制没配检查 keepalive 配置避坑技巧stdio 模式的 MCP Server进程管理特别容易出问题。建议用进程守护工具挂了自动拉起。HTTP 模式相对稳定但要注意超时设置。6.3 Skill 执行结果不稳定同样的输入有时候结果好有时候结果差。这通常是提示词的问题。我的经验是提示词要具体不要抽象。“分析销售数据”不如“对比上周和上上周的营业额找出下滑超过 20% 的门店分析可能原因”。给例子。在提示词里放一两个输入输出示例模型模仿能力很强有例子比没例子稳定得多。控制温度参数。分析类任务温度调低一点0.1 到 0.3创意类任务可以高一点。6.4 成本失控怎么破Agent 系统跑起来成本很容易失控因为多轮调用太费 token 了。几个控制手段缓存相同查询缓存结果别每次都调模型路由简单问题用小模型复杂问题才用大模型限制轮次设置最大工具调用轮数防止无限循环精简上下文历史消息别全带上只带相关的我试过一个优化把工具返回结果做了摘要再回传给模型token 用量直接降了 40%效果几乎没影响。7. 选型建议什么时候用什么7.1 从需求出发的决策树不是所有项目都需要三者全上。我的建议是如果只是想让模型能查个天气、算个数Function Calling 就够了不用上 MCP 和 Skill。如果需要对接多个外部系统而且希望这些对接能复用加上 MCP。标准化带来的长期收益很可观。如果业务逻辑复杂需要多步骤编排、需要封装成可复用的能力单元再上 Skill。一句话Function Calling 是必选项MCP 是规模化选项Skill 是复杂业务选项。7.2 Spring AI 还是 LangChain4jJava 生态里这两个框架都挺火。我的使用体验Spring AI 和 Spring 生态集成好如果你项目本来就是 Spring Boot选它上手快。Function Calling 和 MCP 支持都比较完善。LangChain4j 的抽象层次更高Skill 编排类的功能更丰富。但学习曲线陡一些和 Spring 集成需要额外配置。我个人的选择是Spring Boot 项目用 Spring AI独立 Agent 项目用 LangChain4j。没有绝对优劣看场景。7.3 给新手的上手路径如果你刚接触这块建议按这个顺序来第一步先用 Spring AI 跑通一个最简单的 Function Calling 例子理解模型怎么调函数。第二步找一个现成的 MCP Server 接进来理解协议怎么工作。第三步把前两步的东西封装成一个 Skill理解编排逻辑。第四步做一个完整的小项目比如“智能天气助手”或者“文档问答助手”把三者串起来。这个路径走下来基本就通了。别一上来就搞复杂架构容易劝退。8. 我踩过的坑和最后几句实在话做这块一年多踩的坑不算少。最大的一个坑是过早抽象。一开始就想着搞一套通用的 Skill 框架结果业务还没跑通框架先复杂得没法维护。后来推倒重来先用最土的办法把功能实现跑通了再抽象反而顺利得多。第二个坑是过度依赖模型。有段时间我把所有判断逻辑都交给模型结果模型偶尔抽风该调工具不调不该调乱调。后来加了规则层做兜底关键路径不依赖模型判断稳定性一下子上来了。第三个坑是忽视可观测性。Agent 系统出问题特别难排查因为链路长、环节多。后来加了详细的日志和追踪每个工具调用都记录输入输出排查效率提升明显。最后说句实在话Function Calling、MCP、Skill 这三个概念理解它们的关系不难难的是在实际项目里用好。我的建议是别追求一步到位先从 Function Calling 开始遇到问题了再引入 MCP业务复杂了再封装 Skill。技术选型永远服务于业务需求别为了用新技术而用新技术。还有个小技巧分享调试 Agent 的时候把模型的思考过程打出来看。Spring AI 支持返回中间步骤能看到模型为什么决定调这个工具、为什么传这个参数。这个信息对排查问题特别有用比看最终结果强多了。
返回列表