使用FastMCP构建 MCP

这里的代码来源是FastMCP,构建 MCP 的 python 框架,比官方 SDK 更好用!

原代码使用的是通过提示词的方式调用tools,这里改用兼容openai接口中使用tools的方式。

除使得openai接口和使用factmcp接口外,不使用langchain之类的框架,展示基础的内部调用机制。

McpServer的代码与原文一样:

from fastmcp import FastMCP

mcp = FastMCP(name="MyAssistantServer")


@mcp.tool()
def add(a: float, b: float) -> float:
    """加法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之和
    """
    return a + b


@mcp.tool()
def subtract(a: float, b: float) -> float:
    """减法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之差 (a - b)
    """
    return a - b


@mcp.tool()
def multiply(a: float, b: float) -> float:
    """乘法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之积
    """
    return a * b


@mcp.tool()
def divide(a: float, b: float) -> float:
    """除法运算

    参数:
    a: 被除数
    b: 除数

    返回:
    两数之商 (a / b)

    异常:
    ValueError: 当除数为零时
    """
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b


if __name__ == "__main__":
    mcp.run(transport='sse', host="127.0.0.1", port=8001)

客户端的代码:

import asyncio
import json

from fastmcp import Client
from fastmcp.tools import Tool
from openai import OpenAI
from openai.types.chat import ChatCompletionMessage


def mcp_tools_2_openai_tools(tools: list[Tool]):
    """
    将mcp返回的tools对象,转为适合openai接口的格式
    Args:
        tools:

    Returns:

    """
    result = []
    for tool in tools:
        res = {
            "type": "function",
            "function": {}
        }
        res["function"]["name"] = tool.name
        res["function"]["description"] = tool.description
        parameters = {}
        parameters["type"] = "object"
        parameters["properties"] = tool.inputSchema["properties"]
        parameters["required"] = tool.inputSchema["required"]
        res["function"]["parameters"] = parameters
        result.append(res)

    return result

class LLMClient:
    """LLM客户端,负责与大语言模型API通信"""

    def __init__(self, model_name: str, url: str, api_key: str) -> None:
        self.model_name: str = model_name
        self.url: str = url
        self.client = OpenAI(
            base_url=url,
            api_key=api_key,
        )

    def get_response(self, messages: list[dict[str, str]], tools) -> ChatCompletionMessage:
        """发送消息给LLM并获取响应"""
        response = self.client.chat.completions.create(
            model=self.model_name,
            messages=messages,
            tools=tools,
            tool_choice="auto",
            stream=False
        )
        return response.choices[0].message

class ChatSession:
    """聊天会话,处理用户输入和LLM响应,并与MCP工具交互"""

    def __init__(self, llm_client: LLMClient, mcp_client: Client, ) -> None:
        self.mcp_client: Client = mcp_client
        self.llm_client: LLMClient = llm_client
        self.function_names = []

    async def process_llm_response(self, llm_response: ChatCompletionMessage) -> list:
        """处理LLM响应,解析工具调用并执行"""
        messages = []
        tool_call = llm_response.tool_calls[0]
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)
        # 检查工具是否可用
        if function_name in self.function_names:
            result = await self.mcp_client.call_tool(
                function_name, arguments
            )

            # 将LLM回复追加到会话历史中
            messages.append({
                "role": "assistant",
                "tool_calls": [
                    {
                        "id": tool_call.id,
                        "type": "function",
                        "function": {
                            "name": tool_call.function.name,
                            "arguments": tool_call.function.arguments
                        }
                    }
                ]
            })

            # 再将tool执行结果追加到会话历史中
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result[0].text)
            })

        return messages

    async def start(self, system_message) -> None:
        """启动聊天会话的主循环"""
        messages = [{"role": "system", "content": system_message}]
        tools = await self.mcp_client.list_tools()
        self.function_names = [tool.name for tool in tools]
        openai_tools = mcp_tools_2_openai_tools(tools)
        while True:
            try:
                # 获取用户输入
                user_input = input("用户: ").strip().lower()
                if user_input in ["quit", "exit", "退出"]:
                    print('AI助手退出')
                    break
                messages.append({"role": "user", "content": user_input})

                # 获取LLM的初始响应
                llm_response = self.llm_client.get_response(messages, openai_tools)
                while hasattr(llm_response, 'tool_calls') and llm_response.tool_calls:
                    # LLM返回需要调用function,而且可能要多次调用
                    result = await self.process_llm_response(llm_response)
                    messages.extend(result)
                    # 将带有function执行结果的消息发给LLM,看是否还需要继续执行tool_call,还是可以显示最终回复
                    llm_response = self.llm_client.get_response(messages, openai_tools)

                messages.append({"role": "assistant", "content": llm_response.content})
                # 显示最终回复
                print("助手: ", llm_response.content)
            except KeyboardInterrupt:
                print('AI助手退出')
                break

async def my_chat():
    async with Client("http://127.0.0.1:8001/sse") as mcp_client:
        # 初始化LLM客户端,使用通义千问模型
        llm_client = LLMClient(model_name='model_id', api_key="abc",
                               url="http://192.168.1.1:9008/v1")

        # 系统提示,指导LLM如何使用工具和返回响应
        system_prompt = "你是一个智能助手,名叫小满。"
        # 启动聊天会话
        chat_session = ChatSession(llm_client=llm_client, mcp_client=mcp_client)
        await chat_session.start(system_message=system_prompt)

if __name__ == "__main__":
    asyncio.run(my_chat())

这里主要的修改是通过LLM调用tools的方式,内部有循环,处理多次tools请求的问题。

运行结果:

用户: 现在要购买一批货,单价是 1034.32423,数量是 235326。商家后来又说,可以在这个基础上,打95折,折后总价是多少?
助手:  折后总价为 231,233,214.56 元。
用户: 我和商家关系比较好,商家说,可以在上面的基础上,再返回两个点,最后总价是多少?
助手:  最终总价为 226,608,550.27 元。
用户: quit
AI助手退出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值