Function calling到底有什么问题,MCP又是怎么解决的呢?

832 阅读7分钟

Function calling是什么:

Function calling provides a powerful and flexible way for OpenAI models to interface with your code or external services.。

个人理解下来就是LLM只有一张嘴,Function calling就是给这张嘴配上一些外部的能力(发邮件, 查db,搜网络等等)。

Function calling的流程:

这里附上一张OPENAI官方的流程图:

流程图中一共5个步骤,分别解析如下:

  1. 开发者将用户的 "问题" 和 “我这有什么工具” 告诉大模型。

  2. 大模型根据上一步中的“问题”分析出要调用哪几个工具,将工具调的参数及工具名字返回给开发者。

  3. 开发者根据参数调用对应的工具。

  4. 开发者将”调用工具的返回信息“ 和 “之前的所有信息”再次返回给大模型。

  5. 大模型综合所有信息回答最初的”问题“。

下面是openAI官网中的代码片段,展示了如何从代码层面接入一个function calling,代码中的注释对应着步骤1~5每个步骤。

from openai import OpenAI
import json

client = OpenAI()

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get current temperature for provided coordinates in celsius.",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {"type": "number"},
            "longitude": {"type": "number"}
        },
        "required": ["latitude", "longitude"],
        "additionalProperties": False
    },
    "strict": True
}]

input_messages = [{"role": "user", "content": "What's the weather like in Paris today?"}]

## 步骤1: 将问题和工具传给大模型(tools中定义了工具的信息)
response = client.responses.create(
    model="gpt-4.1",
    input=input_messages,
    tools=tools,
)

## 步骤2 大模型返回工具调用的信息
# 此时的response为:
#[{
#    "type": "function_call",
#    "id": "fc_12345xyz",
#    "call_id": "call_12345xyz",
#    "name": "get_weather",
#    "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
#}]

## 步骤3 根据步骤2 中的参数以及工具名称调用工具, latitude为48.8566; longitude为:2.3522
result = get_weather(args["latitude"], args["longitude"])


## 步骤4 将步骤3的执行结果和之前的所有信息再次传递给大模型
input_messages.append({                               # append result message
    "type": "function_call_output",
    "call_id": result.call_id,
    "output": str(result)
})

## 步骤5 大模型“综上所述”返回最终结果
response_2 = client.responses.create(
    model="gpt-4.1",
    input=input_messages,
    tools=tools,
)
print(response_2.output_text)
# "The current temperature in Paris is 14°C (57.2°F)."

部分平台内集成的可视化FC流程:(混元)

下图为function call的可视化配置,分为三部分:

  • 最上部分是选择“决策模型”,就是将原始问题和工具信息第一次要给的大模型,对应上面的步骤1和步骤2中的模型。
  • 中间部分是插件设置,可以添加多个,也就是需要执行的工具,对应步骤3(此处我开发了一个根据姓名查询员工基础信息的插件,hardcode了一个36岁年龄)。
  • 最下面是润色大模型,就是对应步骤4和5对应的大模型。

运行效果如下:

可以看到大模型根据我的问题,知道要调用我提供给他的能力“get_info_by_name”,查到用户的年龄之后,再给润色模型生成最终的答案。

Function calling的问题:

在步骤1中每个模型有自己的方式,数据格式,字段名称,sdk暴露出的方法等等。

Openai:

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get current temperature for provided coordinates in celsius.",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {"type": "number"},
            "longitude": {"type": "number"}
        },
        "required": ["latitude", "longitude"],
        "additionalProperties": False
    },
    "strict": True
}]

input_messages = [{"role": "user", "content": "What's the weather like in Paris today?"}]

response = client.responses.create(
    model="gpt-4.1",
    input=input_messages,
    tools=tools,
)

Germini:

get_product_sku = "get_product_sku"
get_product_sku_func = FunctionDeclaration(
    name=get_product_sku,
    description="Get the SKU for a product",
    # Function parameters are specified in OpenAPI JSON schema format
    parameters={
        "type": "object",
        "properties": {
            "product_name": {"type": "string", "description": "Product name"}
        },
    },
)

# Specify another function declaration and parameters for an API request
get_store_location_func = FunctionDeclaration(
    name="get_store_location",
    description="Get the location of the closest store",
    # Function parameters are specified in JSON schema format
    parameters={
        "type": "object",
        "properties": {"location": {"type": "string", "description": "Location"}},
    },
)

# Define a tool that includes the above functions
retail_tool = Tool(
    function_declarations=[
        get_product_sku_func,
        get_store_location_func,
    ],
)

# Initialize Gemini model
model = GenerativeModel(
    model_name="gemini-1.5-flash-001",
    generation_config=GenerationConfig(temperature=0),
    tools=[retail_tool],
)

# Start a chat session
chat = model.start_chat()

Llama:

api_request_json = {
  "model": "llama3.1-70b",
  "messages": [
    {"role": "user", "content": "What is the weather like in London?"},
  ],
  "functions": [
    {
        "name": "get_weather_info",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
            },
            "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"]
          }
        },
        "required": ["location"]
        }
    }
  ],
}

response = llama.run(api_request_json)

在步骤2中每个模型返回的关于调用的指令也不同:

openAI:

[{
    "type": "function_call",
    "id": "fc_12345xyz",
    "call_id": "call_12345xyz",
    "name": "get_weather",
    "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
}]

Germini

{
"candidates": [
  {
    "content": {
      "role": "model",
      "parts": [
        {
          "functionCall": {
            "name": "get_current_weather",
            "args": {
              "location": "New Delhi"
            }
          }
        },
        {
          "functionCall": {
            "name": "get_current_weather",
            "args": {
              "location": "San Francisco"
            }
          }
        }
      ]
    },
  }
],
}

Llama:  

{
  'role': 'assistant', 
  'content': None, 
  'function_call': {
    'name': 'get_email_summary', 
    'arguments': {
      	'value': 1, 
      	'login': 'mail@mail.com'
    }
  }
}

所谓_上梁不正下梁歪_,在完整的function calling步骤中,前面的步骤1和2的不同,导致后面的各个步骤都不同,尤其是在给LLM开发对应的外部能力的时候(步骤3中调用的服务),开发者需要对每个模型的function calling都做兼容。

MCP是什么:

2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而无法充分发挥潜力的难题,MCP 使得 AI 应用能够安全地访问和操作本地及远程数据,为 AI 应用提供了连接万物的接口。

网上到处都在说什么“开放标准”,像“usb”一样,其实就是在function calling的基础上,解决了“上梁不正下梁歪”的问题,

  1. 标准了大模型发现工具和使用工具的方式(Tool discovery)。

  2. 标准了外部能力的开发范式以及大模型调用外部能力的方式(Invocation)。

  3. 将调用外部工具执行的结果做了统一格式化,最终再喂给大模型进行润色(Response handling)。

  4. 为调用外部工具之前提供了审批。

MCP咋标准化的?(下面只分析了tools的标准化)

为此我浅看了一下mcp sdk中的内容,答案是他根本没标准化,他只提供了标准化过程中的一些基本方法。(此处我用了“只”,本人才疏学浅无意冒犯)

假设你现在开发一个查询天气的方法(api调用),python中只需要使用sdk/server暴露出的装饰器@mcp.tool()来装饰你的方法,这个方法就被“标准化了”,对应的在sdk/client中的list_tools()方法就能返回这个工具的所有信息,包括作用,名称,参数。 这个标准化的方法就能够轻易的被大模型理解发现并调用。

list_tools返回了工具的信息。

然后通过list_tools拼接到prompt中的方式告诉大模型,我这边有了什么样的工具,每个工具该怎么调用,每个工具是干什么的等等。(下图为mcp sdk中example中的client)

当有了list_tools能拿到工具的信息之后,再要如何告诉大模型呢,这里我看了cline和continuedev的源码,发现有两种方式:

1.通过万能的prompt,将list_tools返回的工具信息拼接到prompt中,要求输出的指令格式。

下图为(Cline)中的prompts,也是通过sdk/client中的list_tools拿到“标准化”之后的工具的信息,然后通过prompt再传给大模型:


为了验证上面的prmopt是否真的生效,我又本地利用ollama搭建了本地模型服务,cline指向本地服务之后打印出了喂给本地大模型的prmopt。
下图为cline指向本地ollama的服务配置:

下图为我在cline配置完成get_weather的mcp服务之后,询问"what is the weather like in Shenzhen"之后ollama打印出的被投喂的prompt内容:

**2.**做一层不同LLM之间的tools字段的格式映射。

我去看了continuedev中,没用使用prompt来进行tools的标准化,而是使用了更粗暴简单的方式,在不同大模型之间的字段映射,
下面是germini的:

下面是bedrock的:

下面是ollama的:

上面的这些标准化的逻辑,都发生在LLM应用,也就是MCP定义的“host”中,作为mcp的sdk只给你提供了像是“list_tool”或者“call_tool”这样的原子方法。

这再次验证了,mcp的sdk中只提供了标准化过程中的一些原子方法。真正的“标准化”或者说抹平各个模型调用和发现工具的差异性是作为LLM应用开发者的你来完成的。

一点想法

  • mcp提供的sdk并没有帮你去“抹平”差异,它做的是提供了一些工具方法让“开发者”去抹平这个差异。

  • 什么场景下某个应用是需要不断换大模型的?

  • 越来越多的可视化流程编辑,可以使得function calling做的足够傻瓜化,反而使用mcp接入服务流程上需要大量的开发。

references: 

blog.dailydoseofds.com/p/function-…

zhuanlan.zhihu.com/p/329600655…