这里的代码来源是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助手退出