MCP之大模型Function Calling开发与原理

1. 相关总结

总结内容链接
MCP之大模型Function Calling开发与原理https://blog.youkuaiyun.com/a82514921/article/details/147860120
MCP概念与server开发及调试-使用Python+SSE传输机制https://blog.youkuaiyun.com/a82514921/article/details/147860221
MCP client开发与日志分析-使用Python+SSE传输机制https://blog.youkuaiyun.com/a82514921/article/details/147860518
MCP细节与原理分析-使用Python+SSE传输机制https://blog.youkuaiyun.com/a82514921/article/details/147860541
MCP server支持在不同Python脚本中实现工具方法https://blog.youkuaiyun.com/a82514921/article/details/147860559
MCP client支持同时连接多个MCP server-使用Python开发https://blog.youkuaiyun.com/a82514921/article/details/147860587

2. MCP 与大模型 Function Calling

虽然 MCP 不是一定需要依赖大模型 Function Calling(工具调用)功能,但 Function Calling 确实是 MCP 具体实现时可选的一部分

在理解 MCP 实现原理之前,首先掌握 Function Calling,有助于按领域分层学习相应内容

3. 工具调用工作流程示意图

阿里云百炼“工具调用”文档中有关于工具调用工作流程示意图

https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/6226954471/CAEQURiBgMDNmePMnhkiIDAxMzcyNDk4OGU1MzQ0MTBiNTE2NDEyMGY4YjY3Y2Yw4358988_20240409160230.951.svg

应用程序负责收集工具的信息、执行工具,并将结果发送给大模型

大模型负责选择工具、提取参数、运行工具并总结工具的输出

通过工具调用,大模型可以接收到所有可用的工具信息,根据问题选择合适的工具要求应用程序执行,根据工具调用结果进行分析,直到得到答案或无法得到答案时结束

4. Function Calling 相关接口

OpenAI 在 2023 年 6 月 13 日发布了 Function Calling,可参考 https://openai.com/index/function-calling-and-other-api-updates/

以下 Function Calling 相关说明参考 OpenAI 及阿里云百炼文档

由于 OpenAI 的文档 https://platform.openai.com/无法正常访问,因此参考 OpenAI Cookbook 的文档

5. Chat Completions API 请求接口

Function Calling 通过 Chat Completions API 调用

阿里云百炼的通义千问 API 与 OpenAI 是兼容的,可以参考其文档

也可以参考 OpenAI Cookbook 的以下文档

https://cookbook.openai.com/examples/named_entity_recognition_to_enrich_text

https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models

https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models

https://github.com/openai/openai-cookbook/blob/main/examples/data/oai_docs/function-calling.txt

https://github.com/openai/openai-cookbook/blob/main/examples/data/oai_docs/tool-function-calling.txt

也可以参考 OpenAI Python SDK 相关源码,例如 ChatCompletionToolParam 及 FunctionDefinition 对应的源码如下

https://github.com/openai/openai-python/blob/main/src/openai/types/chat/chat_completion_tool_param.py

https://github.com/openai/openai-python/blob/main/src/openai/types/shared/function_definition.py

5.1. Chat Completions API 的 role 分类

Chat Completions API 在请求中接收 messages 数组,每个元素需要通过 role 指定对应的角色,role 包含 system、user、assistant、tool

以下参考阿里云百炼文档通义千问 API 对 role 的说明

https://help.aliyun.com/zh/model-studio/qwen-function-calling#d3166f28c2r8i

此时的 messages 数组为:

[
  System Message -- 指引模型调用工具的策略
  User Message -- 用户的问题
  Assistant Message -- 模型返回的工具调用信息
  Tool Message -- 工具的输出信息(如果采用下文介绍的并行工具调用,可能有多个 Tool Message)
]

5.2. messages.role = system

messages.role = system 时的对象为 System Message

System Message 本身类型及说明如下

System Message object (可选)

模型的目标或角色。如果设置系统消息,请放在 messages 列表的第一位。

System Message 下的属性结构如下

content string (必选)

消息内容。

role string (必选)

固定为 system。

role = system 的 messages 的 content 通常用来指定 Prompt 的内容

5.3. messages.role = user

messages.role = user 时的对象为 User Message

User Message 本身类型及说明如下

User Message object (必选)

用户发送给模型的消息。

User Message 下的属性结构如下

content string 或 array (必选)

消息内容。如果您的输入只有文本,则为 string 类型;

role string (必选)

固定为 user。

role = user 的 messages 的 content 通常用来指定用户提出的问题

5.4. messages.role = assistant

messages.role = assistant 时的对象为 Assistant Message,结构如下

Assistant Message 本身类型及说明如下

Assistant Message object (可选)

模型对用户消息的回复。

Assistant Message 下的属性结构如下

content string (可选)

消息内容。仅当助手消息中指定 tool_calls 参数时非必选。

role string (必选)

固定为 assistant。

partial boolean (可选)

是否开启 Partial Mode。使用方法请参考前缀续写。

tool_calls array (可选)

在发起 Function Calling 后,模型回复的要调用的工具和调用工具时需要的参数。包含一个或多个对象。由上一轮模型响应的 tool_calls 字段获得。

5.4.1. tool_calls

role = assistant 的 messages.tool_calls 结构如下

id string

本次工具响应的 ID。

type string

工具的类型,当前只支持 function。

function object

需要被调用的函数。

index integer

工具信息在 tool_calls 列表中的索引。
5.4.1.1. tool_calls.function

tool_calls 数组元素中的 function 结构如下

name string

需要被调用的函数名。

arguments string

需要输入到工具中的参数,为 JSON 字符串。

5.5. messages.role = tool

messages.role = tool 时的对象为 Tool Message,结构如下

Tool Message 本身类型及说明如下

Tool Message object (可选)

工具的输出信息。

Tool Message 下的属性结构如下

content string (必选)

消息内容,一般为工具函数的输出。

role string (必选)

固定为 tool。

tool_call_id string (可选)

发起 Function Calling 后返回的 id,可以通过 completion.choices[0].message.tool_calls[0].id 获取,用于标记 Tool Message 对应的工具。

5.6. tools

tools 本身类型及说明如下

tools array (可选)

可供模型调用的工具数组,可以包含一个或多个工具对象。一次 Function Calling 流程模型会从中选择一个工具。

tools 下的属性结构如下

type string (必选)

tools 的类型,当前仅支持 function。

function object (必选)

5.6.1. tools.function

tools.function 结构如下

name string (必选)

工具函数的名称,必须是字母、数字,可以包含下划线和短划线,最大长度为 64。

description string (必选)

工具函数的描述,供模型选择何时以及如何调用工具函数。

parameters object (必选)

工具的参数描述,需要是一个合法的 JSON Schema。JSON Schema 的描述可以见链接。如果 parameters 参数为空,表示 function 没有入参。
5.6.1.1. tools.function.parameters

“工具调用”文档的“创建 tools 数组”部分有关于 tools.function.parameters 的结构说明

type 字段固定为"object";

properties 字段描述了入参的名称、数据类型与描述,为 Object 类型,Key 值为入参的名称,Value 值为入参的数据类型与描述;

required 字段指定哪些参数为必填项,为 Array 类型。

5.7. parallel_tool_calls

参考通义千问 API 文档

parallel_tool_calls boolean (可选)默认值为 false

是否开启并行工具调用。参数为 true 时开启,为 false 时不开启。

参考工具调用文档

您可以在发起 Function Calling 时,将请求参数 parallel_tool_calls 设置为 true,这样返回对象中将包含所有需要调用的工具函数与入参信息。

默认情况下,大模型每次进行处理时只会返回一个工具的信息;应用程序在接收到返回后,也只会执行一个工具;应用程序将当前工具调用的结果再次发送给大模型后,大模型此时只会处理一个工具调用的结果

将 parallel_tool_calls 参数设置为 true 时,会开启并行工具调用,大模型每次进行处理时可以返回多个工具的信息;应用程序在接收到返回后,会执行多个工具;应用程序将当前所有工具调用的结果再次发送给大模型后,大模型此时只会处理多个工具调用的结果

6. Chat Completions API 返回接口

6.1. choices

choices 本身类型及说明如下

choices array

模型生成内容的数组,可以包含一个或多个 choices 对象。

choices 下的属性结构如下

finish_reason string

有三种情况:

因触发输入参数中的 stop 条件,或自然停止输出时为 stop;

因生成长度过长而结束为 length;

因需要调用工具而结束为 tool_calls。

index integer

当前响应在 choices 数组中的序列编号。

logprobs object

该参数当前固定为 null。

message object

本次调用模型输出的消息。

6.1.1. choices.finish_reason

通过返回的 choices.finish_reason 可以判断是否还需要继续进行工具调用,若该字段值为 tool_calls,则说明还需要进行工具调用;若该字段值为 stop,则说明大模型已经得到结论,不需要再进行工具调用

6.1.2. choices.message

choices.message 结构如下

content string

本次调用模型生成的文本。

refusal string

该参数当前固定为 null。

role string

消息的角色,固定为 assistant。

audio object

该参数当前固定为 null。

function_call(即将废弃)object

该值默认为 null,请参考 tool_calls 参数。

tool_calls array

在发起 Function Calling 后,模型回复的要调用的工具以及调用工具所需的参数。可以包含一个或多个工具响应对象。
6.1.2.1. choices.message.tool_calls

choices.message.tool_calls 结构如下

id string

本次工具响应的 ID。

type string

工具的类型,当前只支持 function。

function object

需要被调用的函数。

属性

index integer

工具信息在 tool_calls 列表中的索引。
6.1.2.1.1. choices.message.tool_calls.function

choices.message.tool_calls.function 结构如下

name string

需要被调用的函数名。

arguments string

需要输入到工具中的参数,为 JSON 字符串。

由于大模型响应有一定随机性,输出的 JSON 字符串并不总满足于您的函数,建议您在将参数输入函数前进行参数的有效性校验。

7. 工具调用代码示例

工具调用相关示例代码可参考以下内容

https://help.aliyun.com/zh/model-studio/qwen-function-calling#69a684e145962

参考“完整代码”部分,需要注意请求中的 required 需要在 tools.function.parameters 中,而不是在 tools.function 中,以上文档中的示例有误

https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models#executing-sql-queries

参考“Executing SQL queries”部分

对阿里云百炼提供的工具调用示例代码进行修改,支持通过 Python 脚本执行时的参数 1 选择是否使用并行工具调用,链接为 https://github.com/Adrninistrator/MCP-DEMO/blob/master/function_calling_example_aliyun/function_calling_example_aliyun.py

8. 工具调用流程

以下对工具调用流程进行说明

Chat Completions API 支持进行非工具调用与工具调用,请求与返回格式相同,但非工具调用每次只调用一次大模型后就结束;工具调用可能需要调用大模型多次

8.1. 首次访问大模型

应用程序首次访问模型时,与调用 Chat Completions API 非工具调用的情况类似,需要在 messages 中指定数据,在 role=user 的数组元素中指定问题,可在 role=system 的数组元素中指定 Prompt,

在 tools 中指定应用程序可以使用的工具信息

若模型支持一次返回多个选择需要执行的工具,及一次分析多个工具的执行结果,可将 parallel_tool_calls 设为 true,指定使用并行工具调用

8.1.1. 首次访问大模型请求示例

首次访问大模型请求示例如下,向大模型提交了问题,以及可以使用的两个工具 get_current_time、get_current_weather

{
  "messages": [
    {
      "content": "深圳现在天气怎么样,几点了",
      "role": "user"
    }
  ],
  "model": "qwen2.5-7b-instruct",
  "parallel_tool_calls": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_current_time",
        "description": "当你想知道现在的时间时非常有用。",
        "parameters": {}
      }
    },
    {
      "type": "function",
      "function": {
        "name": "get_current_weather",
        "description": "当你想查询指定城市的天气时非常有用。",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "城市或县区,比如北京市、杭州市、余杭区等。"
            }
          },
          "required": [
            "location"
          ]
        }
      }
    }
  ]
}

8.1.2. 首次访问大模型返回示例

首次访问大模型返回示例如下,大模型无法得到结论,因此返回了选择需要执行的工具 get_current_weather、get_current_time,以及执行工具时使用的参数

由于在请求中设置了 parallel_tool_calls=true 开启并行调用,大模型支持并开启了并行调用,且大模型认为需要调用多个工具,因此大模型能够返回多个工具信息

{
  "choices": [
    {
      "message": {
        "content": "",
        "role": "assistant",
        "tool_calls": [
          {
            "index": 0,
            "id": "call_6bc3f82256db43989b799e",
            "type": "function",
            "function": {
              "name": "get_current_weather",
              "arguments": "{\"location\": \"深圳市、"}"
            }
          },
          {
            "index": 1,
            "id": "call_aad5a1cc024a487cacf349",
            "type": "function",
            "function": {
              "name": "get_current_time",
              "arguments": "{}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls",
      "index": 0,
      "logprobs": null
    }
  ],
  "object": "chat.completion",
  "usage": {
    "prompt_tokens": 297,
    "completion_tokens": 31,
    "total_tokens": 328
  },
  "created": 1745638221,
  "system_fingerprint": null,
  "model": "qwen2.5-7b-instruct",
  "id": "chatcmpl-4df4d8bb-242c-9193-9e1a-62845c318431"
}

8.2. 大模型返回与判断

若大模型判断需要继续进行工具调用,会向应用程序返回 choices.finish_reason=tool_calls

同时在 choices.message.tool_calls 中返回大模型选择的需要执行的工具信息,包括工具名称,及调用工具时使用的参数名称及参数值,id 是本次大模型为需要调用的工具生成的 ID,后续会有使用

通过大模型返回的 choices.finish_reason 是否为 tool_calls,以及 choices.message.tool_calls 是否非空,可以判断是否需要继续进行工具调用,以下为根据大模型返回判断需要终止流程的代码示例:

    # response 为访问大模型后的返回
    assistant_output = response.choices[0].message
    
    if assistant_output.content is None:
        assistant_output.content = ""
    
    if response.choices[0].finish_reason != "tool_calls" \
            or assistant_output.tool_calls is None \
            or len(assistant_output.tool_calls) == 0:
        print(f"无需调用工具,我可以直接回复:{assistant_output.content}")
        return

若在请求中有指定并行调用且大模型支持并行调用,则返回的 tool_calls 中可能包含多个工具的信息

8.3. 非首次访问大模型

若前一次大模型返回还需要继续调用工具,则应用程序需要根据模型返回的工具信息调用指定的工具

基于上次请求大模型使用的 messages 进行修改,用于本次继续访问大模型的请求

8.3.1. 除 messages 之外的其他字段内容保持不变

除 messages 之外的其他字段内容保持不变,包括代表工具信息的 tools、model、parallel_tool_calls 等

这是因为现阶段的大模型本身不会记忆历史对话的内容,一次工具调用的后续请求需要包含之前访问的请求与返回内容

8.3.2. messages 中 role=system、user 的元素保持不变

对于 messages 中 role=system、user 的元素,需要继续指定为首次访问时使用的值,即一开始使用的 Prompt 与问题

8.3.3. messages 中 role=assistant 的元素增加数据

对于 messages 中 role=assistant 的元素,需要继续保持之前的值,并在之前的基础上,增加上一次请求大模型返回的 choices 数组第一个元素的 message 元素

因为 Chat Completions API 中返回的 choices 数组中的 message 元素,与请求的 messages 中 role=assistant 的元素结构及含义都是相同的

  • 代码示例
        # response 是调用大模型的返回
        assistant_output = response.choices[0].message
        if assistant_output.content is None:
            assistant_output.content = ""
        messages.append(assistant_output)

8.3.4. 调用工具并在 messages 中 role=tool 的元素指定结果

本次请求大模型的 messages 中已有的 role=tool 的每个元素都需要保留

处理上一次请求大模型后返回的 choices 数组第一个元素中的 message 元素,遍历 tool_calls 数组,即大模型指定的需要执行的工具数组,依次调用每个工具

处理调用工具的结果,生成一个用于添加到 messages 中的元素,role=tool,content 字段使用工具返回的结果,tool_call_id 字段设置为当前处理的 tool_calls 数组元素的 id

这样当大模型接收到请求后,可以根据 id 将大模型要求调用的工具信息,与实际调用工具后的结果关联起来

  • 代码示例
        for tool_call in assistant_output.tool_calls:
            # 遍历所有工具调用
            tool_name = tool_call.function.name
            arguments = json.loads(tool_call.function.arguments)

            # 执行对应工具
            # result 为执行工具的结果

            # 处理调用工具的结果,tool_call_id 字段设置为当前处理的 tool_calls 数组元素的 id
            messages.append({
                "role": "tool",
                "content": result,
                "tool_call_id": tool_call.id
            })
            if not parallel:
                break

8.3.5. 非首次访问大模型请求示例

非首次访问大模型请求示例如下,上一次访问大模型的请求内容都有保留,并添加了本次调用大模型要求的工具的结果

{
  "messages": [
    {
      "content": "深圳现在天气怎么样,几点了",
      "role": "user"
    },
    {
      "content": "",
      "role": "assistant",
      "tool_calls": [
        {
          "id": "call_6bc3f82256db43989b799e",
          "function": {
            "arguments": "{\"location\": \"深圳市、"}",
            "name": "get_current_weather"
          },
          "type": "function",
          "index": 0
        },
        {
          "id": "call_aad5a1cc024a487cacf349",
          "function": {
            "arguments": "{}",
            "name": "get_current_time"
          },
          "type": "function",
          "index": 1
        }
      ]
    },
    {
      "role": "tool",
      "content": "深圳市今天是晴天。",
      "tool_call_id": "call_6bc3f82256db43989b799e"
    },
    {
      "role": "tool",
      "content": "当前时间:2025-04-26 11:30:21。",
      "tool_call_id": "call_aad5a1cc024a487cacf349"
    }
  ],
  "model": "qwen2.5-7b-instruct",
  "parallel_tool_calls": true,
  "tools": [
    // 与之前相同,省略
  ]
}

8.3.6. 非首次访问大模型返回示例

非首次访问大模型返回示例如下,大模型已经得到了结论

{
  "choices": [
    {
      "message": {
        "content": "深圳现在的天气是晴天。当前时间是 2025 年 04 月 26 日 11 点 30 分 21 秒。",
        "role": "assistant"
      },
      "finish_reason": "stop",
      "index": 0,
      "logprobs": null
    }
  ],
  "object": "chat.completion",
  "usage": {
    "prompt_tokens": 373,
    "completion_tokens": 35,
    "total_tokens": 408
  },
  "created": 1745638222,
  "system_fingerprint": null,
  "model": "qwen2.5-7b-instruct",
  "id": "chatcmpl-3989aabe-6ffb-9382-a779-5d86314350e5"
}

9. 日志示例

以下日志使用通用的日志记录方式,将访问大模型的请求数据,及大模型的返回数据,都记录到日志中,便于观察数据与分析问题

示例日志可参考

https://github.com/Adrninistrator/MCP-DEMO/blob/master/log_example/function_calling_example_aliyun_20250426_113017.log

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值