当前位置: 首页
AI教程
MCP+Function Calling实现模型自主驱动工具链多步推理

MCP+Function Calling实现模型自主驱动工具链多步推理

时间:2026-06-08
转载

一、前两篇做了什么,这篇做什么先说个背景,之前写过两篇相关文章。第一篇介绍了 AgentExecutor,让模型通过 ReAct 格式的纯文本输出来驱动工具调用——说白了,就是让模型输出类似 Action: xxx Action Input: yyy 这样的文本,然后我们去解析它。这套方案的好处

一、前两篇做了什么,这篇做什么

先说个背景,之前写过两篇相关文章。第一篇介绍了 AgentExecutor,让模型通过 ReAct 格式的纯文本输出来驱动工具调用——说白了,就是让模型输出类似 Action: xxx / Action Input: yyy 这样的文本,然后我们去解析它。这套方案的好处是不挑模型,什么模型都能用,但本质上还是在“读模型写的文章”来判断意图。

MCP + Function Calling:让模型自主驱动工具链完成多步推理

第二篇讲的是 MCP 的基础接入方式:用 McpManager 注册 HTTP 工具,手动调用 run() 执行,然后把结果拼回 Prompt 里。这个方式倒是能跑通,也能验证工具是否可用,但问题是——整个过程中,“调哪个工具”、“传什么参数”,模型根本就没有参与决策。

这一篇要解决的就是这个核心问题:让模型和 MCP 真正配合起来,实现一个完整的 Agent 功能。具体做法是把 MCP 的工具清单直接注册给支持 Function Calling 的模型。这样模型就不再输出文本格式的 Action,而是输出结构化的 ToolCall——包含函数名和 JSON 参数。开发者只需要负责执行这个 ToolCall,把结果写回 Prompt,然后让模型继续推理就行了。

这三种方式的核心区别,一句话说清楚:

方式谁决定调哪个工具工具参数格式模型要求
ReAct 文本驱动模型(文本输出)字符串 / JSON 文本任意
Function Calling模型(结构化输出)标准 JSON Schema需支持 Function Calling

二、场景:三步连贯推理,全程无人工介入

这一篇要做的场景挺有意思:自动检测当前环境下的公网 IP、定位城市、然后查询天气。整个过程需要用到三个工具,按顺序调用:

  1. get_export_ip:获取本机公网出口 IP
  2. get_ip_location:根据 IP 查询城市、经纬度、ISP 信息
  3. get_weather_open_meteo:根据经纬度查询实时天气

这三个工具是串联的——上一步的返回值就是下一步的输入参数。用户只需要说一句话,Agent 就会自主完成全部推理和工具调用,最终给出一个包含“位置 + 天气”的总结。而且这三个工具都在 mcp.config.json 的 default 分组里声明好了,不需要改任何代码,框架就能自动加载。

三、核心代码逐步拆解

第一步:构建 Prompt 并注入工具清单

// Prompt 模板:系统指令明确调用顺序和输出格式
BaseRunnable prompt = ChatPromptTemplate.fromMessages(List.of(
    BaseMessage.fromMessage(MessageType.SYSTEM.getCode(),
        """
        你是一名能够调用 MCP HTTP 工具的智能体,需要按以下顺序完成任务:
        1) 调用 get_export_ip 获取公网 IP;
        2) 将该 IP 传给 get_ip_location,获取城市、经纬度以及网络信息;
        3) 使用经纬度调用 get_weather_open_meteo,并设置 current_weather=true;
        4) 总结位置与天气(只输出结论,不暴露工具名称)。
        工具只在必要时调用,每个工具最多执行一次。
        """
    ),
    BaseMessage.fromMessage(MessageType.HUMAN.getCode(), "用户问题:${input}")
));

// manifestForInput() 把 mcp.config.json 转成模型所需的 JSON Schema 格式
List tools = mcpManager.manifestForInput().get("default");

// 把工具清单注册给 LLM,模型推理时会自动决定何时调用哪个工具
ChatAliyun llm = ChatAliyun.builder()
    .model("qwen3.6-plus")
    .temperature(0f)
    .tools(tools)
    .build();

注意这里的 System Prompt 写得比较细致——明确告诉模型要按照什么顺序调用工具。这样做有两个好处:一是能减少模型漏掉关键步骤的概率,二是调试起来更容易判断是哪个环节出了问题。

第二步:循环条件——有 ToolCall 就继续

int maxIterations = 5;
Function shouldContinue = round -> {
    if (round >= maxIterations) {
        return false; // 防止死循环
    }
    if (round == 0) {
        return true; // 第一轮必须执行
    }
    // 检查上一轮 LLM 输出是否包含 ToolCall
    AIMessage lastAi = ContextBus.get().getResult(llm.getNodeId());
    return lastAi instanceof ToolMessage toolMessage &&
           CollectionUtils.isNotEmpty(toolMessage.getToolCalls());
};

退出条件其实很直观:模型如果不再输出 ToolCall,说明它已经拿到了足够的信息,准备给出最终回答了。

第三步:核心处理器——执行 ToolCall 并写回 Observation

这是整个链路最核心的一段代码:

TranslateHandler executeMcpTool = new TranslateHandler<>(msg -> {
    ChatPromptValue promptValue = ContextBus.get().getResult(prompt.getNodeId());

    // 不是 ToolCall,说明模型已在生成最终回答,直接透传
    if (!(msg instanceof ToolMessage toolMessage)) {
        return msg;
    }
    if (CollectionUtils.isEmpty(toolMessage.getToolCalls())) {
        return toolMessage;
    }

    // 1. 把模型的 ToolCall 请求记入对话历史
    promptValue.getMessages().add(toolMessage);

    // 2. 解析 ToolCall:拿到工具名和参数
    AiChatOutput.ToolCall call = toolMessage.getToolCalls().get(0);
    Map args = parseArgs(call.getFunction().getArguments());
    String toolName = call.getFunction().getName();

    // 3. 用 McpManager 执行真实 HTTP 请求
    System.out.println("[ToolCall] " + toolName + " params -> " + JsonUtil.toJson(args));
    Object result = mcpManager.runForInput("default", toolName, args);
    String observation = result != null ? result.toString() : "工具无返回内容";
    System.out.println("[Observation] " + observation);

    // 4. 把执行结果以 ToolMessage 形式追加到对话历史,供模型下一轮参考
    appendToolMessage(prompt, call, observation);
    return ContextBus.get().getResult(prompt.getNodeId());
});

这里 appendToolMessage 的作用是把工具的返回结果以标准的 ToolMessage 格式写回 Prompt。这样模型在下一轮推理时就能看到完整的上下文——它知道自己调了什么工具、得到了什么结果。

第四步:组装完整链路

FlowInstance chain = chainActor.builder()
    .next(prompt)
    .loop(shouldContinue,
        llm,
        chainActor.builder()
            .next(
                Info.c(needsToolExecution, executeMcpTool),
                Info.c(input -> ContextBus.get().getResult(llm.getNodeId()))
            )
            .build()
    )
    .next(new StrOutputParser())
    .build();

ChatGeneration finalAnswer = chainActor.invoke(chain, Map.of(
    "input", "不要询问额外信息,自动检测我的公网 IP,推断所在城市并告知当前天气后统一回复。"
));
System.out.println(finalAnswer.getText());

整个链路的结构其实非常清晰:Prompt → 循环( LLM → [有ToolCall? 执行MCP : 透传] ) → 输出最终回答

四、完整推理过程

运行之后控制台会打印完整的推理轨迹:

> MCP Function-Calling ReAct 链开始执行...
[ToolCall] get_export_ip params -> {}
[Observation] 123.117.177.40
[ToolCall] get_ip_location params -> {"ip": "123.117.177.40"}
[Observation] {"country_name":"China","region_name":"Beijing Shi","city":"Dongcheng Qu", "latitude":39.9117,"longitude":116.4097,"org":"AS4134 Chinanet"}
[ToolCall] get_weather_open_meteo params -> {"latitude":39.9117,"longitude":116.4097,"current_weather":"true"}
[Observation] {"current_weather":{"temperature":18.3,"windspeed":6.1,"weathercode":1}}
> 链执行完成。
=== 最终回答 ===
检测到你的公网 IP 位于中国北京市东城区,当前温度约 18°C,天气晴朗,风速 6.1 km/h。

可以看到,模型精确地执行了三次 ToolCall,没有遗漏任何一步,也没有多余的调用。每次 Observation 被写回 Prompt 后,模型在下一轮推理中就能自动提取所需字段——从 IP 到经纬度再到天气,整个过程不需要开发者做任何字段映射。

五、和 ReAct 文本驱动的本质区别

很多开发者会问:AgentExecutor 也能完成多步工具调用,这两种方式到底有什么实质区别?

这么说吧,ReAct 文本驱动(AgentExecutor)的方式下,模型输出的是纯文本,比如:

Action: get_ip_location
Action Input: {"ip": "123.117.177.40"}

框架需要通过字符串解析来提取工具名和参数——本质上是“读模型写的文章”。

而 Function Calling(本篇采用的方式)下,模型输出的是结构化的 JSON:

{
    "toolCalls": [
        {
            "function": {
                "name": "get_ip_location",
                "arguments": "{\"ip\":\"123.117.177.40\"}"
            }
        }
    ]
}

框架直接解析 JSON,工具名和参数都是强类型字段,不存在格式歧义的问题。

这两种方式各有各的适用场景:

场景推荐方式原因
模型不支持 Function CallingReAct 文本驱动唯一可用选项
参数复杂(嵌套 JSON、数组)Function Calling结构化输出更可靠
需要精确控制推理文本ReAct 文本驱动Thought 内容完全可读
对接标准 MCP 工具生态Function Calling工具 Schema 天然匹配
模型稳定性要求高Function Calling减少格式解析失败

六、工具调用失败怎么处理

真正上线之后,你就会发现生产环境里什么意外都可能发生:网络超时、参数错误……HTTP 工具调用失败是家常便饭。框架的处理方式是这样的:把错误信息同样以 ToolMessage 的形式写回 Prompt,让模型感知到“这次工具调用失败了”,然后由模型自己决定是重试、跳过还是向用户说明原因:

try {
    Object result = mcpManager.runForInput("default", toolName, args);
    String observation = result != null ? result.toString() : "工具无返回内容";
    appendToolMessage(prompt, call, observation);
} catch (Exception e) {
    log.error("调用 MCP 工具 {} 失败: {}", toolName, e.getMessage(), e);
    // 把错误信息写回 Prompt,模型会在下一轮 Thought 中处理
    appendToolMessage(prompt, call, "调用失败:" + e.getMessage());
}

这种做法比直接抛出异常要友好得多——模型可以在最终回答中说“天气数据获取失败,已为您提供位置信息”,而不是让整个链路直接崩溃。

七、总结

这一篇展示的是一个完整的“MCP + Function Calling”多步推理链路。和之前手动调用的方式相比,这里的工具执行顺序和参数完全由模型决定;和 ReAct 文本驱动的方式相比,这里用的是模型原生的 ToolCall 输出,参数解析更可靠。

核心思路其实就一句话:把 MCP 工具清单交给模型,让模型决定调什么,开发者只负责执行和回写结果。

也许你觉得这段循环逻辑还是有点绕。没关系,下一篇文章会介绍 McpAgentExecutor——用一行代码搞掂整个流程。

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
CAD零基础入门教程:坐标输入、图层管理与基础绘图命令

CAD零基础入门教程:坐标输入、图层管理与基础绘图命令

本文面向CAD零基础学习者,系统讲解坐标输入、图层管理与基础绘图命令的核心用法。通过分步实操与常见问题排查,帮助新手建立精确绘图习惯,掌握规范出图的基础能力。

时间:2026-09-01 16:53
CAD从入门到项目交付:绘图、标注、图块与实战工作流

CAD从入门到项目交付:绘图、标注、图块与实战工作流

掌握CAD的核心在于建立“画得准、标得清、复用快、交付稳”的工作流。本文提供从环境设置、高频命令组合、标注规范、图块标准化到项目分阶段交付的完整路径,帮助初学者避免常见返工陷阱,独立完成可检查、可复用、可打印的工程图纸。

时间:2026-09-01 16:52
Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤

Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤

本文详细解析 Claude Code 登录前的账号类型区分方法,涵盖个人订阅、Teams 席位与企业 Enterprise 席位的授权路径差异。提供终端登录命令、环境变量排查及常见异常处理步骤,帮助用户快速完成正确授权并避免登录路径混淆。

时间:2026-09-01 14:27
Claude Code 文件修改前的权限模式配置与命令审批指南

Claude Code 文件修改前的权限模式配置与命令审批指南

本文详细介绍Claude Code在修改文件前的权限模式配置方法,包括defaultMode可选值、permissions allow与deny规则设置、多层级配置文件管理以及 status验证技巧,帮助开发者安全高效地使用AI编程助手。

时间:2026-09-01 14:12
Claude Code接入VS Code后先测扩展和终端命令

Claude Code接入VS Code后先测扩展和终端命令

在VS Code中接入Claude Code后,建议优先验证扩展面板与集成终端两条入口。本文提供标准检查顺序、关键命令与常见故障排查路径,帮助你快速确认环境就绪,避免后续开发受阻。

时间:2026-09-01 14:10
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全