Function CAll和MCP

Function CAll和MCP

1 Function Call

1.1 基础概念

  早期的LLMs工作流程基本上是用户通过输入文本与模型进行交互,模型根据输入文本生成响应文本。但是这样的模型有个问题,模型根据用户输入生成对应的响应输出文本,只能利用模型本身的能力。为了突破这层壁垒,将现有大量的软件生态能力接入到LLMs中,让LLMs更具生命力,能够与外部软件生态进行交互。

  GPT提供了根据用户输入文本解析用户意图,生成对应函数调用响应的能力。这种能够根据用户输入生成对应函数调用信息的能力,就是Function Call。需要明确的是,Function Call是建立在模型推理能力和外部软件能力的基础上的,是二者共同交互协作的产物。LLMs本身只能进行推理,无法完成具体的任务,Function Call就是将传统文本推理能力进一步细化,调整成推理函数调用信息。因此,可以看到LLMs在任务中的角色从来没有改变,一直是扮演任务推理的角色只是任务本身变化了。

  这也是大模型对Agent相关的价值所在,其打破了用户和代码直接的界限,可以让用户直接和底层能力交互,让生产环境某些步骤更加简单高效。

1.2 工作流程

  Function Calling最基本的工作流程是:用户提出请求,LLM 理解意图并选择合适的函数,生成参数并调用该函数,接收函数执行结果后整合到最终回复中呈现给用户,从而实现 LLM 与外部工具的交互,扩展其能力并完成更复杂的任务。

  其中LLMs主要参与用户的意图理解,输入是用户期望的promot,输出是相关函数的调用信息。而具体的函数调用是由外部软件完成的。

  最基本的工作流程就是:输入------推理------调用------返回结果。但是通常情况下为了处理更加复杂的任务流程会更加复杂,推理流程也会更加复杂。比如需要区分用户的诉求是咨询诉求还是执行诉求,根据不同的诉求执行不同的单元等等。

1.3 示例

  简单的描述比较干燥,下面用一个大模型Function Call的例子来说明LLMs Function Call是如何工作的。首先,我们需要实现期望运行的函数,这里实现了两个含简单的函数,一个是显示图片,一个是图片重设大小。

需要注意的是这里展示的只是最基本的原理,而实际上很多开元的function call会对工具进行二次封装,比如openai的示例。但是大体流程是一致的。

python 复制代码
response = client.responses.create(
    model="gpt-4.1",
    input=[{"role": "user", "content": "What is the weather like in Paris today?"}],
    tools=tools
)
python 复制代码
from PIL import Image
import ollama
import json
import re  # Import the regular expression module

def display_image(image_path):
    """Displays an image from the given path."""
    try:
        img = Image.open(image_path)
        img.show()  # This will open the image using the default image viewer
        return f"Image displayed successfully from {image_path}"
    except FileNotFoundError:
        return f"Error: Image not found at {image_path}"
    except Exception as e:
        return f"Error displaying image: {e}"


def resize_image(image_path, scale=0.5):
    """Resizes an image to half its original size and saves it with '_resized' suffix."""
    try:
        img = Image.open(image_path)
        width, height = img.size
        new_width = int(width * scale)
        new_height = int(height * scale)
        resized_img = img.resize((new_width, new_height))
        new_image_path = image_path.replace(".", "_resized.")  # Add '_resized' before the extension
        resized_img.save(new_image_path)
        return f"Image resized successfully and saved to {new_image_path}"
    except FileNotFoundError:
        return f"Error: Image not found at {image_path}"
    except Exception as e:
        return f"Error resizing image: {e}"

  然后我们需要告诉大模型,这些函数能干什么,参数是什么,返回值是什么。

python 复制代码
# Function descriptions (adapt to the model's expected format)
function_descriptions = [
    {
        "name": "display_image",
        "description": "Displays an image from a given file path.",
        "parameters": {
            "type": "object",
            "properties": {
                "image_path": {
                    "type": "string",
                    "description": "The path to the image file.  Must be a valid path to an image file (e.g., PNG, JPG, JPEG).",
                },
            },
            "required": ["image_path"],
        },
    },
    {
        "name": "resize_image",
        "description": "Resizes an image to half its original size and saves the resized image with a suffix.",
        "parameters": {
            "type": "object",
            "properties": {
                "image_path": {
                    "type": "string",
                    "description": "The path to the image file to resize.",
                },
                "scale": {
                    "type": "number",
                    "description": "The scale factor to resize the image. Default is 0.5 for half the size.",
                    "default": 0.5,
                },
            },
            "required": ["image_path"],
        },
    },
]

  有了上面的函数描述,我们就可以将其告诉大模型我们的意图和相关的函数描述,大模型会根据输入来判断我们期望调用的参数。需要注意的是,在prompt中尽量标准化输出方便后续解析输出。

python 复制代码
def run_conversation_ollama(user_prompt, model="deepseek-r1:1.5b-qwen-distill-fp16"):
    """
    Interacts with the local LLM via the ollama API, attempts to identify function calls, and executes them.
    """
    messages = [{"role": "user", "content": user_prompt}]

    # Include function descriptions in the prompt (you might need to adapt this based on the model)
    system_prompt = f"""你是一个图像处理专家,根据给定的函数列表分析出用户期望调用的函数名和对应的参数,返回的格式为:
    ```json
    {{
        "function": "function_name",
        "args": {{
            "image_path": "1.jpg",
            "scale": "0.1"
        }}
    }}
    ```
    可用的函数列表如下:\n{json.dumps(function_descriptions)}
    """
    messages.insert(0, {"role": "system", "content": system_prompt}) # Add system prompt

    try:
        response = ollama.chat(model=model, messages=messages)
        llm_response = response['message']['content']

        print(f"LLM Response: {llm_response}")

        # Attempt to parse the response to identify a function call
        function_name, arguments = extract_function_call(llm_response)  # Implement this function!

        if function_name:
            print(f"Identified function call: {function_name} with arguments: {arguments}")

            # Execute the function
            function_response = execute_function(function_name, arguments)  # Implement this function!

            print(f"Function response: {function_response}")

            # Integrate the function response into a final response
            messages.append({"role": "assistant", "content": llm_response}) # Add assistant response
            messages.append({"role": "function", "name": function_name, "content": function_response}) # Add function response

            # Get a new response from the model with the function result
            second_response = ollama.chat(model=model, messages=messages)
            final_response = second_response['message']['content']
            return final_response
        else:
            return llm_response

    except Exception as e:
        return f"Error during ollama interaction: {e}"

  拿到输出后就是从输出中解析出我们需要的参数,比如我本地返回的输出为如下,后续解析参数和调用比较简单就不说了。

python 复制代码
<think>
好的,我现在要分析用户提供的信息,并根据给定的函数列表来确定应该调用哪个函数以及对应的参数。
首先,用户提供了一个示例:显示图片,图片路径是1.jpeg。看起来这是一个简单的请求,要求将指定的图像显示出来。根据可用的函数列表,有两个可能相关的函数:display_image 和 resize_image。  
我需要判断用户的需求属于哪种情况。用户明确提到"显示图片",而不是" resized image"。因此,应该使用 display_image 函数来完成操作。
接下来,我查看函数的参数。对于 display_image 函数,主要参数是 image_path,这是一个字符串,表示要显示的图像文件路径。用户提供的例子中,image_path 是1.jpeg,所以参数应该是 { "image_path": "1.jpg" }。
不需要使用 resize_image 的函数,因为用户没有提到需要调整大小或缩放。因此,在 args 中不包括 scale 参数。
总结一下,应该调用 display_image 函数,参数是 image_path,"1.jpg"。
</think>

```json
{
    "function": "display_image",
    "args": {
        "image_path": "1.jpg"
    }
}
```json

2 MCP

2.1 简介

  模型上下文协议 (MCP) 是一种旨在标准化和管理大语言模型 (LLM) 上下文信息的协议。它定义了 LLM 如何接收、处理和利用上下文信息,以提高生成结果的相关性、准确性和一致性。MCP 的目标是解决 LLM 在处理长文本、多轮对话和复杂任务时遇到的上下文理解和利用问题。

  所以总体上MCP只是一套协议,相比这就意味着可以通过这个协议调用网络上的LLM能力。Function Call是用来和LLM交互的,用来表示如何通过LLMs实现某些功能,更加和native贴近,相比之下MCP则是将这些能力通过协议开放到网络上让用户或者其他C端使用。这里只描述了MCP是什么,具体MCP细节不赘述了。

2.2 简单实现一个MCP

  简单实现一个MCP,首先是服务器,服务器是用来处理具体事务的。下面的代码比较简单就是接受客户端的请求然后通过llm解析然后执行具体的调用(这里也用到了function call解析,只不过没有那么标准,没有写function描述)。

python 复制代码
from flask import Flask, request, jsonify
from PIL import Image
import io
import base64
import os
import threading
import time
import ollama
import json

app = Flask(__name__)

# 身份验证 (简单示例)
API_KEY = "your_secret_api_key"

# Ollama 模型名称
OLLAMA_MODEL = "llama2"  # 替换为你实际使用的 Ollama 模型

def authenticate(request):
    api_key = request.headers.get('X-API-Key')
    if api_key == API_KEY:
        return True
    else:
        return False

# 健康检查
@app.route('/health', methods=['GET'])
def health_check():
    return jsonify({"status": "ok"}), 200

@app.route('/process_image', methods=['POST'])
def process_image():
    print("processing request...")
    if not authenticate(request):
        return jsonify({"error": "Unauthorized"}), 401

    if 'image' not in request.files:
        return jsonify({"error": "No image provided"}), 400

    image_file = request.files['image']
    instruction = request.form.get('instruction', 'Resize the image to 200x200')  # 默认指令

    try:
        img = Image.open(io.BytesIO(image_file.read()))

        # 调用 Ollama 模型
        prompt = f"""你是一个图像处理专家,能够对图像进行缩放(scale),灰度化(grayscale),裁剪处理(crop)。分析输入的指令返回执行的结果格式为(输出结果除了这个json多余的内容不要加)
        ```json
        {{
            "function": "function_name",
            "args": {{
                "width": "200",
                "height": "200"
            }}
        }}
        ```
        "用户的输入为: {instruction}
        """
        response = ollama.generate(model=OLLAMA_MODEL, prompt=prompt, stream=False)
        res = response['response']
        i = res.find("{")
        re = res[i:]
        j = re.rfind("}")
        re = re[:j + 1]
        operation_data = json.loads(re)
        # 根据 Ollama 模型的输出进行图像处理
        if operation_data:
            operation = operation_data.get('function')
            if operation == 'scale':
                width = operation_data.get('width', 200)
                height = operation_data.get('height', 200)
                img = img.resize((width, height))
            elif operation == 'grayscale':
                img = img.convert('L')
            elif operation == 'rotate':
                angle = operation_data.get('angle', 90)
                img = img.rotate(angle)
            elif operation == 'crop':
                left = operation_data.get('left', 0)
                top = operation_data.get('top', 0)
                right = operation_data.get('right', 100)
                bottom = operation_data.get('bottom', 100)
                img = img.crop((left, top, right, bottom))
            else:
                return jsonify({"error": "Invalid operation from LLM"}), 400
        else:
            print("No operation needed based on the instruction.")

        # 将处理后的图像转换为 base64 编码
        buffered = io.BytesIO()
        img.save(buffered, format="JPEG")
        img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')

        return jsonify({"image": img_str}), 200

    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True, port=5001)

  然后是客户端,客户端比较简单就是请求-响应。

python 复制代码
import requests
import base64
from PIL import Image
import io

class MCPClient:
    def __init__(self, base_url, api_key):
        self.base_url = base_url
        self.api_key = api_key
        self.headers = {'X-API-Key': self.api_key}

    def process_image(self, image_path, instruction="Resize the image to 200x200"):
        url = f"{self.base_url}/process_image"
        try:
            with open(image_path, 'rb') as image_file:
                files = {'image': (image_path, image_file, 'image/jpeg')}
                data = {'instruction': instruction}
                print("sending request...")
                response = requests.post(url, files=files, data=data, headers=self.headers)
                response.raise_for_status()
                return response.json()
        except requests.exceptions.RequestException as e:
            print(f"Error processing image: {e}")
            return None

def display_image_from_base64(base64_image):
    try:
        image_data = base64.b64decode(base64_image)
        image = Image.open(io.BytesIO(image_data))
        image.show()  # 使用默认图像查看器显示图像
    except Exception as e:
        print(f"Error displaying image: {e}")

# 示例用法
if __name__ == '__main__':
    # 替换为你的 MCP 服务器地址和 API 密钥
    mcp_client = MCPClient("http://localhost:5001", "your_secret_api_key")

    image_path = "1.jpg"
    instruction = "Enter the image processing instruction: 缩放图片到200x200"

    result = mcp_client.process_image(image_path, instruction=instruction)
    if result:
        processed_image = result['image']
        display_image_from_base64(processed_image)
    else:
        print("Image processing failed.")

3 总结

  Function Calling 是一种 LLM 的能力,用于生成调用外部函数的指令,而 MCP 是一种自定义的消息传递协议,用于在客户端和服务器之间传递消息。 Function Calling 侧重于利用 LLM 的推理能力来驱动外部系统的执行,而 MCP 侧重于建立可靠的通信通道,以便客户端和服务器能够协同工作。 在你的图像处理应用中,你可以同时使用 Function Calling 和 MCP: 使用 Function Calling 来让 LLM 决定如何处理图像 (例如,选择合适的滤镜或调整参数),然后使用 MCP 将图像数据和 LLM 生成的指令传递给服务器进行处理。

特性 Function Calling (LLM) MCP (自定义消息传递协议)
核心功能 LLM 生成函数调用指令 客户端和服务器之间的消息传递
执行函数 LLM 不 执行函数,而是将指令返回给应用程序 服务器执行函数 (例如图像处理)
控制权 应用程序控制函数的执行 客户端和服务器共同控制协议的实现
通用性 可以与各种外部工具和服务集成 通常用于特定的应用程序场景
自定义程度 函数的定义和实现由开发者控制,但 LLM 的行为受到预训练模型的限制 协议的各个方面 (消息格式、状态码、错误处理等) 都可以自定义
应用场景 扩展 LLM 的能力,使其能够完成更复杂的任务 在客户端和服务器之间建立可靠的通信通道,用于特定的应用程序需求 (例如图像处理)

4 参考文献

相关推荐
玫瑰互动GEO12 分钟前
GEO优化学习九级模型:开发者从认知层切入
人工智能·ai·ai搜索·gem·生成式引擎优化·gem优化
VIP_CQCRE2 小时前
用 Ace Data Cloud 的 gpt-image-2,把 AI 图像生成接入真实业务场景
ai·aigc·api·图像生成·acedatacloud
满脑子技术的前端工程师2 小时前
第六章 TypeScript MCP Server:独立综合项目与能力验收
ai
EchoMind-Henry3 小时前
Muse外设两条接入路,成本该怎么算
人工智能·ai
桃西西呀4 小时前
LangChain 之七:回调与可观测
人工智能·langchain·llm
桃西西呀4 小时前
LangChain 之六:记忆与历史
人工智能·langchain·llm
Jo乔戈里6 小时前
免费本地搜图软件
图像处理·python·搜索引擎·ai
deepseek2311 小时前
硬预算帽默认值拆解:AWS 九月上线支出上限、GCP 七月跟进,Agent 时代按量付费必须默认断供
人工智能·llm·云计算·agent·aws
全栈练习生12 小时前
大模型原理之 Softmax
python·ai
唯鹿12 小时前
Jev初体验记录
人工智能·ai·jev