当前位置: 首页 > web >正文

MCP 第三波升级!Function Call 多步调用 + 流式输出详解

目录

前言

mcp调用

调用方式比较

function call方式调用

调用过程的流式输出

结语

下期内容预告


前言

前面两篇介绍了MCP基础调用与现有Webapi转MCP。接下来我们会继续改进:

1)通过function call方式实现mcp调用

这里我们也会介绍下,通过提示词与function call调用实现的比较

2)实现调用的流式输出,方便前端对接

mcp调用

调用方式比较

一开始我们是通过提示词的方式实现的mcp,后来改为了function call方式,这里我们做个比较。

比较提示词function call
简易度实现简单,直接提示词拼接tools即可相对简单,按照大模型调用格式,传递tools即可
可控性较差,可控性依赖大模型能力与提示词由大模型自己控制,不需要提示词干预,更可控
可扩展性较弱,随着tool越来越多,维护会越发困难较好,不需要做程序的改动,mcp tool自动转为大模型tools调用
token消耗较多,tools都在提示词里,会消耗大量token较多,大模型tools也会消耗token,不过就是节省了提示词的token消耗
大模型支持度相对更通用,但是依赖提示词的控制有些大模型不支持function call或支持不够好

function call方式调用

将mcp tool转为大模型的tools格式

def convert_mcp_tool_to_openai_tool(mcp_tool):"""将 MCP 工具转换为 OpenAI 的 function call 格式"""return {"type": "function","function": {"name": mcp_tool.name,"description": mcp_tool.description,"parameters": mcp_tool.inputSchema,},}

通过function call方式调用大模型

  async def chat(self, prompt, role="user"):"""与LLM进行交互,返回包含 tool_call 的完整响应"""if prompt != "":self.messages.append({"role": role, "content": prompt})response = await self.client.chat.completions.create(model=self.model,messages=self.messages,tools=self.f_tools,  # 使用工具定义stream=True,tool_choice="auto",  # 或者指定具体工具名)return response

判断与调用toll call

async def chat_loop(self, input) -> AsyncGenerator[Dict[str, Any], None]:"""运行交互式聊天循环"""response = await self.chat(input)while True:tool_call_response = Falsefunction_name, args, text = "", "", ""tool_call = {}async for chunk in response:delta = chunk.choices[0].deltaif delta.tool_calls:tool_call = delta.tool_calls[0]tool_call_response = Trueif not function_name:function_name = delta.tool_calls[0].function.nameargs_delta = delta.tool_calls[0].function.arguments# print(args_delta)  # 打印每次得到的数据if args_delta:  # 追加args = args + args_deltaelif delta.content:tool_call_response = Falsetext_delta = delta.contentyield {"type": "result", "content": text_delta}# text = text + text_deltaif tool_call_response:# tool_call = tool_calltool_name = function_nametool_args = json.loads(args)tool_call.function.name = function_nametool_call.function.arguments = args# 执行工具并处理流式输出async for event in self.exec_tool(tool_name, tool_args):# 如果是进度更新,则直接发送if event["type"] == "progress":yield eventelif event["type"] == "error":# 错误情况下也返回错误信息yield eventelif (event["type"] == "tool_start" or event["type"] == "tool_finish"):# 开始、完成调用yield eventelse:# 添加 tool_call 和 tool_response 到 messagesself.messages.append({"role": "assistant","content": None,"tool_calls": [tool_call.model_dump()],})# 添加 tool_response 到 messagesself.messages.append({"role": "tool","name": tool_call.function.name,"content": str(event["content"]),})yield eventresponse = await self.chat("")else:break

上面代码做下解释说明:

response = await self.chat(input) 这里是第一次调用大模型,然后在循环里判断,大模型返回内容(注意这里是流式输出模式,所以用到async for迭代器),如果是tool_calls,则从流式输出中获取调用function name和调用参数args。

然后就可以通过mcp调用tool了,调用结果再去调用大模型,如此反复。

网上关于mcp调用的案例,多数只能单步调用,在这里我们实现了多步调用。

代码中while True,就是要一直判断,直到大模型返回不是tool_calls(就是输出了最终结果,不需要再执行工具调用了)。这个时候就中断了循环。

这里还有一点要注意,在完成tool调用后,需要把工具调用信息、工具调用结果都拼到大模型messages里,这样大模型才能根据这些消息,进行下一步的推理。

调用过程的流式输出

从上面代码也可以看出来,调用工具的代码也是yield流式输出,而且设置了type,这样方便前端区分是思考调用过程、还是最终输出结果。

如下是我测试的的代码,已经是可以流式输出的效果。后续再和对话api对接到前端就行了。

async def main():host = Nonetry:host = Host()await host.connect_mcp_servers()# input = "查下北京的天气,再告诉我怎么从天安门去故宫"# await host.chat_loop("查下北京的天气,再告诉我怎么从天安门去故宫")# 改为可以多次对话while True:user_input = input("请输入:")if user_input == "/x":breakasync for event in host.chat_loop(user_input):print(event)except Exception as e:print(f"主程序发生错误: {type(e).__name__}: {e}")# 打印完整的调用堆栈traceback.print_exc()finally:# 无论如何,最后都要尝试断开连接并清理资源print("\n正在关闭客户端...")await host.disconnect_mcp_servers()print("客户端已关闭。")

结语

这就是今天讲的内容。主要就是对提示词和function call方式进行mcp调用做了一个比较、然后通过function call实现了mcp调用,再实现为流式输出效果,为对接前端做准备。

下期内容预告

整体功能整合,实现一个可用的mcp chat api,可以对接前端进行聊天,可以显示思考过程、最终结果等内容。 过程中遇到的问题与解决,也会做一分享。

不知面前的读者是否有类似的问题,欢迎关注与交流。

我们下期见~_~

http://www.xdnf.cn/news/15533.html

相关文章:

  • document.documentElement详解
  • LVS的集群技术和分布式
  • HTTP 四种常见方法
  • 飞桨AI Studio云编程环境搭建
  • redis实现红锁
  • MCP终极篇!MCP Web Chat项目实战分享
  • 【牛客刷题】小红的数字删除
  • 算法:投票法
  • VUE export import
  • MinIo快速入门
  • JJ20 Final Lap演唱会纪念票根生成工具
  • MIPI DSI (一) MIPI DSI 联盟概述
  • Oracle 学习笔记
  • Docker入门基础
  • GaussDB between的用法
  • 文心一言 4.5 开源深度剖析:中文霸主登场,开源引擎重塑大模型生态
  • 用基础模型构建应用(第九章)AI Engineering: Building Applications with Foundation Models学习笔记
  • # 检测 COM 服务器在线状态
  • python 双下划线开头函数
  • 网络协议和基础通信原理
  • Go泛型完全指南:从基础到实战应用
  • Fluent许可文件安装和配置
  • 车载诊断框架 --- 车载诊断GuideLine
  • 如何集成光栅传感器到FPGA+ARM系统中?
  • 如何更改Blender插件安装位置呢?
  • qt 使用memcpy进行内存拷贝时注意的问题
  • 硬盘爆满不够用?这个免费神器帮你找回50GB硬盘空间
  • EasyExcel实现Excel文件导入导出
  • [Nagios Core] 事件调度 | 检查执行 | 插件与进程
  • 解决Qt中“known incorrect sRGB profile“警告的Photoshop修改方法