通过fastmcp手搓可调用工具的循环聊天机器人
fast mcp
- fastMCP地址。相比于MCP官方的SDK,fastMCP在调用和实例化上都显得要简单一些。
安装
- 安装uv,python
- cd到路径
uv init .把文件夹python工程化uv venv --python 3.12- 安装fastmcp和openAI:
uv pip install fastmcp openai;openai用在需要做大语言用户模型时。 - 依赖安装好后,在uv环境下可以正常启动。
mcp server和 mcp client
- 最简单的mcp server,命名为
server.py- 参数数据类型需要明确,这是给AI做数据填入用。
mcp.run()方法可以执行mcp服务器。- 方法的描述必不可少,AI依靠描述得知方法的作用。
"""
1. 创建FastMCP实例
2. 创建函数,添加文档
3. mcp.tool
4. 运行服务器
"""
from fastmcp import FastMCP
mcp=FastMCP()
@mcp.tool()
def get_weather(city:str):
"""
获取对应城市的天气
:param city:城市
:return:城市天气的描述
"""
return f"{city}今天天气晴,18度"
if __name__=='__main__':
mcp.run()
- 最简单的mcp client,命名为
client.py- 这是最简单stdio通信
- 需要在异步函数中执行client
- 需要用
async with client:才能记录上下文 client.call_tool()执行工具
"""
1. 创建客户端
2. 获取工具和资源和prompt
3. 执行工具
"""
import asyncio
from fastmcp import Client
async def run():
client=Client('server.py')
async with client: # 必要,用来进入上下文管理器
tools = await client.list_tools()
tool = tools[0]
tool_result = await client.call_tool(tool.name,{"city":"成都"})
print(tools)
print(tool_result)
if __name__=='__main__':
asyncio.run(run())
- 运行
client.py效果:
[Tool(name='get_weather', description='获取对应城市的天气\n:param city:城市\n:return:城市天气的描述', inputSchema={'properties': {'city': {'title': 'City', 'type': 'string'}}, 'required': ['city'], 'type': 'object'},
annotations=None)]
[TextContent(type='text', text='成都今天天气晴,18度', annotations=None)]
LLM+MCP server
- 可以作为模板参考新文件,命名为
app.py- 此时能和LLM对话但是不能调用工具:
- 例程中的异步函数和格式和提示词都是必要的,mcp_client也是参考上面的例子拓展的。进一步功能可以在此基础上更改。
"""
用户client:
1. 调用大语言模型,client如OpenAI client
2. 调用MCP server,client如MCP client
3.
"""
from typing import List, Dict
import asyncio
from fastmcp import Client
from openai import OpenAI
class UserClient:
def __init__(self,script="server.py",model="qwen2.5:latest"):
self.model=model # 传入Model名
self.mcp_client=Client(script) # mcp_client
self.openai_client=OpenAI( # 基于ollama部署的本地大模型
base_url="http://*********/v1",
api_key="*********"
)
self.messages=[ # 给大模型的系统消息(默认消息)
{
"role":"system",
"content":"你是一个AI助手,你需要借助工具,回答用户问题。"
}
]
async def prepare_tools(self): # 用异步方法把工具拿到
tools = await self.mcp_client.list_tools() # 工具
tools = [
{ # 拿到工具信息,以下是固定结构
"type":"function",
"function":{
"name":tool.name,
"description":tool.description,
"input_schema":tool.inputSchema
}
}
for tool in tools
]
return tools
async def chat(self,messages:List[Dict]): # 聊天,需要传入一个字典的列表
response=self.openai_client.chat.completions.create(
model=self.model, # 传入参数
messages=messages,
)
print(response)
async def loop(self): # 循环聊天
while True:
question=input("user:") # 首先需要用户输入内容
message = {
"role":"user", # 用户消息
"content":question
}
self.messages.append(message)
reponse_message = await self.chat(self.messages)
print("AI: ",reponse_message.get('content'))
async def main(): # 调用
user_client = UserClient()
await user_client.chat([
{"role":"user","content":"hello."}
])
if __name__=='__main__':
asyncio.run(main())
- 大模型回复:
ChatCompletion(id='chatcmpl-756', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='Hello! How can I assist you today? Feel free to ask me any questions or let me know if you need help wi
d help with anything specific.', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None))], created=1750863190, model='qwen2.5:latest', object='chat.completion', service_tier=None, systerint='fp_
m_fingerprint='fp_ollama', usage=CompletionUsage(completion_tokens=29, prompt_tokens=31, total_tokens=60, completion_tokens_details=None, prompt_tokens_details=None))
- 加入工具调用相关代码:
"""
用户client:
1. 调用大语言模型,client如OpenAI client
2. 调用MCP server,client如MCP client
3.
"""
from typing import List, Dict
import asyncio
from fastmcp import Client
from openai import OpenAI
class UserClient:
def __init__(self,script="server.py",model="qwen2.5:latest"):
self.model=model
self.mcp_client=Client(script)
self.openai_client=OpenAI(
base_url="http://*********/v1",
api_key="*********"
)
self.messages=[
{
"role":"system",
"content":"你是一个AI助手,你需要借助工具,回答用户问题。"
}
]
self.tools=[] # 建立工具调用列表
async def prepare_tools(self):
tools = await self.mcp_client.list_tools()
tools = [
{
"type":"function",
"function":{
"name":tool.name,
"description":tool.description,
"input_schema":tool.inputSchema
}
}
for tool in tools
]
return tools
async def chat(self,messages:List[Dict]):
async with self.mcp_client: # 上下文管理,不加要报错
if not self.tools: # 如果工具列表是空的,就执行准备工具
self.tools= await self.prepare_tools()
response=self.openai_client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools, # 把工具列表传入模型
)
print(response)
async def loop(self):
while True:
question=input("user:")
message = {
"role":"user",
"content":question
}
self.messages.append(message)
reponse_message = await self.chat(self.messages)
print("AI: ",reponse_message.get('content'))
async def main():
user_client = UserClient()
await user_client.chat([
{"role":"user","content":"成都今天天气怎么样?"} # 更改问题
])
if __name__=='__main__':
asyncio.run(main())
- 执行后大模型回复如下,像比于之前的回复内容:
content='',content是空的- 多了
tool_calls以及列表里面的内容,证明大模型知道自己需要调用工具 function=Function(arguments='{"city":"成都"}', name='get_weather')大模型知道自己调用的工具名和参数
ChatCompletion(id='chatcmpl-905', choices=[Choice(finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, too
l_calls=[ChatCompletionMessageToolCall(id='call_b7ju7kgk', function=Function(arguments='{"city":"成都"}', name='get_weather'), type='function', index=0)]))], created=1750867239, model='qwen2.5:latest', object='chat.completion', s
ervice_tier=None, system_fingerprint='fp_ollama', usage=CompletionUsage(completion_tokens=20, prompt_tokens=158, total_tokens=178, completion_tokens_details=None, prompt_tokens_details=None))
- 完善工具调用,实现在循环中提问:
"""
用户client:
1. 调用大语言模型,client如OpenAI client
2. 调用MCP server,client如MCP client
3.
"""
import json
from typing import List, Dict
import asyncio
from fastmcp import Client
from openai import OpenAI
class UserClient:
def __init__(self,script="server.py",model="qwen2.5:latest"):
self.model=model
self.mcp_client=Client(script)
self.openai_client=OpenAI(
base_url="http://*********/v1",
api_key="*********"
)
self.messages=[
{
"role":"system",
"content":"你是一个AI助手,你需要借助工具,回答用户问题。"
}
]
self.tools=[]
async def prepare_tools(self):
tools = await self.mcp_client.list_tools()
tools = [
{
"type":"function",
"function":{
"name":tool.name,
"description":tool.description,
"input_schema":tool.inputSchema
}
}
for tool in tools
]
return tools
async def chat(self,messages:List[Dict]):
if not self.tools:
self.tools= await self.prepare_tools()
response=self.openai_client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools,
)
if response.choices[0].finish_reason != "tool_calls": # 不等于说明仅仅是普通消息
return response.choices[0].message # 直接返回消息
# 执行工具
for tool_call in response.choices[0].message.tool_calls: #执行工具方法
response = await self.mcp_client.call_tool(tool_call.function.name,
json.loads(tool_call.function.arguments)) # arguments由大语言模型传入。要求是字典,所以要用json.loads()转换一下
self.messages.append({
'role':'assistant',
'content':response[0].text
})
return await self.chat(self.messages) # 把回复拼接进消息中
async def loop(self):
async with self.mcp_client: # 把这段代码从chat放到loop中,简化流程
while True:
question=input("user:")
message = {
"role":"user",
"content":question
}
self.messages.append(message)
reponse_message = await self.chat(self.messages)
print("AI: ",reponse_message.content) # 返回message对象
async def main():
user_client = UserClient()
await user_client.loop() # 在循环聊天中提问
if __name__=='__main__':
asyncio.run(main())
- 和大模型的交互内容:
user:你好
AI: 你好!有什么我可以帮助你的吗?
user:重庆今天天气怎么样?
AI: 到26度之间。注意适时添加衣物以免感冒。
user:你可以调用城市温度相关的工具吗?
AI: defaultManager = 0 htmlFor = 52889 json = '{"city":"成都","temperature":[{"date":"今天","text":"多云","low":16,"high":24}]}'
重庆今天的气温是18度,天气晴朗。你可以在出行时做好防晒措施哦!其他地方的天气情况也可以随时询问我哦。
user:
调试中遇到的问题记录
-
调用自己写的工具时出现字符类型不匹配问题:
fastmcp.exceptions.ToolError: Error calling tool 'double_click_software': 'charmap' codec can't encode characters in position 0-8: character maps to <undefined>- 这个问题不止出现在会读写文件的工具函数中。一些看起来和读写文本不太相关的工具函数也会导致这个问题,可能是因为AI或者fastmcp在背后记录日志或者其他文本信息导致的。
- 解决办法:打开控制面板,找到时钟和区域,打开区域,选择更改系统区域设置,勾选
Beta版:使用unicode UTF-8提供全球语言支持。 - 总之,就是让自己电脑支持utf-8的格式。
-
AI的回复总是为空:
'content':response[0].text~~~~~~~~^^^IndexError: list index out of range- 解决办法:注意自己写的工具函数是否具有
return,返回值是必要的。 - 如果函数没有返回值,AI就会回复空数组导致出错。
- 如果AI回复还是为空, 可以尝试
try:execpt Exception as e:,捕获错误。
- 解决办法:注意自己写的工具函数是否具有

浙公网安备 33010602011771号