MCP我知道:手搓代码学原理到应用

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
数据管理 DMS,安全协同 3个实例 3个月
推荐场景:
学生管理系统数据库
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
简介: MCP火爆异常,目前大量资料介绍了基本概念,与LLM联动这块通常是讲如何集成在Claude、Cursor这些系统,隐藏了其底层细节原理。本文将从0编写client、Server代码、搭建QwQ-32B大模型、接入云数据库,讲解通过联动外围工具来解决LLM“知识茧房”问题。最后总结并展望了MCP未来的发展。

前言

学习一个东西,有必要先弄清楚它的应用场景是什么,它为什么会出现,究竟能解决什么问题,具体是如何解决的,这样才能避免盲目,也能深入理解本质。

MCP主要应用在LLM场景,我们先搭建一个LLM环境用于测试验证和DEMO开发。然后,为探究MCP原理,从LLM的一个固有短板:知识茧房问题着手展开讨论。

针对MCP,目前大量资料介绍了基本概念,也有一些范例,但不少资料写的神乎其玄,在原理这块却有些模糊不清,调用流程、与LLM互动这块通常是讲如何集成在Claude、Cursor这些系统,隐藏了其细节原理,本文将从0手搓client、Server代码,详解底层原理,使读者能知其然并知其所以然。最后给出一个应用示例:LLM通过MCP联动数据库查询,来实现涉及私域知识(私域信息存放在数据库中)的问答。

此外,对机器学习、深度学习、神经网络、LLM知识库等感兴趣的AI技术爱好者,此前编写了一本AI启蒙书籍,有需要的可以免费下载:https://developer.aliyun.com/ebook/8435

环境准备及QwQ-32B简介

因为最终需要实现MCP+LLM的完整示例,所以在准备环境时就需要考虑LLM安装对GPU的配置要求。本例选择QwQ-32B量化模型为示例。

2025.3.6日阿里云发布了QwQ-32B LLM,支持在消费级显卡部署大模型,再次惊艳了行业。业界一方面在不断做大模型,提高基准大模型的泛化能力,另一方面以中国Deepseek、阿里通义等厂商,正在进一步发展模型的小型化低成本推理部署,卷出了新方向新赛道,今年或有望成为大模型大规模应用的普惠年。低成本部署后,很多产品都可以便捷独立集成大模型了,线下场景的私域大模型、RAG等系列方案推广和落地起来不再有阻力。此后,各产品与大模型的联动能力或将成为基本要求,使得MCP等LLM工具对接生态发展也迎来了一拨热潮。

QwQ-32B模型数学代码等核心指标(AIME 24/25、livecodebench)以及部分通用标(IFEval、LiveBench等)达到DeepSeek-R1 满血版水平,各指标均显著超过同样基于 Qwen2.5-32B 的 DeepSeek-R1-Distill-Qwen-32B。

QwQ-32B开源链接:

魔搭开源链接:https://modelscope.cn/models/Qwen/QwQ-32B

huggingface开源链接:https://huggingface.co/Qwen/QwQ-32B

 

ecs要求:CPU 16核以上,内存64GB+,硬盘50GB+,显卡24GB+显存本例选择ecs.gn7i-c16g1.4xlarge:16核(vCPU) 60 GiB  GPU:NVIDIA A10,显存24GB注意:显存过小,将会导致LLM无法正常启动)。

以在Ubuntu 22.04 64位系统(该系统有对应的cuda驱动,使用较方便)为例:

  1.  创建GPU实例。设置用户名及登录密码,同时选择安装CUDA。
  2.  设置安全组配置,配置出方向端口22,并在源IP中加入本机IP。
  3.  本机ssh到云ECS:sudo apt-get update。如果是root登录,系统会提示正在安装CUDA。待安装完毕,查看GPU详情(若命令无法正常运行,则说明CUDA没安装好):nvidia-smi


下载QwQ-32B

  1.  pip 换源加速下载并安装依赖包:

sudo python3 -m pip install --upgrade pip

pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip3 install modelscope==1.22.3

pip3 install transformers==4.48.2

pip3 install accelerate

pip3 install tqdm==4.67.1

pip3 install autoawq

 

  1. 模型下载(本例部署量化版QwQ-32B-AWQ):modelscope download --model=Qwen/QwQ-32B-AWQ --local_dir ./QwQ-32B

此外,推荐使用 modelscope 中的 snapshot_download 函数下载模型。新建 model_download.py 文件,输入以下内容:

from modelscope import snapshot_download

model_dir = snapshot_download('Qwen/QwQ-32B-AWQ', cache_dir='/home/ecs-user/QwQ-32B', revision='master')

说明:第一个参数为模型名称(也可以部署QwQ-32B,需要留足空间),参数 cache_dir 为模型的下载路径(修改为自己模型下载的路径)。

然后在终端中输入: nohup python3 model_download.py &

执行下载(建议使用nohup方式执行,文件下载会大量占用带宽,可能导致ssh断连),需要等待约40分钟,直到模型下载完毕。


部署兼容 OpenAI API 接口的服务

LLM联动tools常用做法是采用OpenAI的Chat Completion API,OpenAI的API中提供了函数调用流程和规范(function specifications)。vLLM、ollama、SgLang等均支持OpenAI API的tool参数,本例使用vLLM进行模型的OpenAI API服务部署,结合参数配置,实现基于OpenAI规范的Function call接口,默认会在http://localhost:8000 启动服务器(端口号支持在服务创建时自定义)。服务器当前一次托管一个模型,并实现completions和chat completions端口。

- completions:是基本的文本生成任务,模型根据输入的提示词生成一段文本,常用于生成文章、文案、写作、故事、邮件等。

- chat completions:是面向对话的任务,模型需要理解和生成对话,常用于构建聊天机器人或者对话系统。

在创建服务器时,可以指定模型名称、模型路径、聊天模板、服务端口等参数。

- --host 和 –port:地址和服务端口。

- --model:模型名称。

- --chat-template:聊天模板。

- --served-model-name:服务模型的名称。

- --max-model-len:模型的最大长度。

 

服务部署:

  1. 安装vLLM:pip3 install vllm 
  2. vLLM服务部署命令:python3 -m vllm.entrypoints.openai.api_server --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ --served-model-name QwQ-32B --max-model-len 2048 --gpu_memory_utilization 0.9 --max_num_seqs 32 --enable-auto-tool-choice --tool-call-parser hermes

系统会打印详细的启动过程和日志,以及系列参数配置,可以拷贝一份保存下来,以备后用。此外,服务一旦测试能正常启动后,可以使用nohup方式在后台运行:

nohup python3 -m vllm.entrypoints.openai.api_server --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ --served-model-name QwQ-32B --max-model-len 2048 --gpu_memory_utilization 0.9 --max_num_seqs 32 --enable-auto-tool-choice --tool-call-parser hermes &

  1.  编写OpenAI格式的测试程序:新建一个Python文件,如qwqOpenAI.py,复制如下示例代码:
from openai import OpenAI
 
# 设置 OpenAI 的 API 密钥和 API 基础 URL 使用 vLLM 的 API 服务器。
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
 
client = OpenAI(
    api_key=openai_api_key,
    base_url=openai_api_base,
)
 
# 使用流式输出(stream=True)
chat_response = client.chat.completions.create(
    model="QwQ-32B",      # vLLM启动时,LLM的命名。注意:不是填写模型所在的路径
    messages=[{"role": "user", "content": "请介绍你自己"}],
    stream=True# 启用流式响应
)
 
# 处理流式输出
contents = []
for e in chat_response:
    # print(e.choices[0].delta.content,end="")
    contents.append(e.choices[0].delta.content)
print("".join(contents))
  1.  运行测试程序:python3 qwqOpenAI.py

在vLLM服务的打印日志中,也可以看到服务器已经接收到了测试程序的请求,说明已工作正常。

 

LLM“知识茧房”问题

为探究MCP为什么会爆火,我们需要先理解LLM的“知识茧房”问题。ChatGPT 等LLM,只需要输入提示语句和问题描述(prompt),模型就能给出类人智能的答复,表现惊艳,在智能问答等场景迅速迎来火爆应用。但随着应用的不断深入,大家发现不论是哪家的LLM,给出的答案,时常出现胡说八道的问题。主要原因之一:回到AI的数据本质,LLM也是采用大量数据样本做训练得出的,模型一旦训练完成,对于数据样本外的知识一无所知,我们给这个现象取个非常形象的名字:知识茧房。LLM的知识局限在所训练的数据样本中,类似被包裹在一个茧房里面一样。由于缺乏茧房之外的知识,所以面对需要额外知识的问题,则只能根据模型已有知识盲目的推测一个答案,问答效果自然很难保障,甚至驴唇不对马嘴。

以企业构建智能问答系统为例,企业的私域知识因涉密、商业价值等原因,不对外开放,无论哪家的LLM,通常都缺乏企业私域知识。在涉及私域知识的问答中,通用LLM很难取得很好的效果。比如,咨询通用大模型公司有几个部门,每个部门有多少人,类似这种涉及公司内私密信息的问题,LLM是无法给出正确答案的。

此外,LLM的知识茧房问题还表现在,LLM的知识是有时效性缺陷的,比如在四月份完成了LLM训练,到六月份,这两个月产生了大量新信息、新数据、新知识,无论LLM的模型参数做多大,哪怕有一天训练了一个超级LLM,把已有的知识都纳入了,这个问题依然是存在的。因为随着时间的流逝,总是不断有新知识新信息产生,知识和信息本身是难以穷尽的。

比如,在2025.4.15日咨询QwQ-32B大模型,问深圳2025年4月14日的天气怎么样,此时,4月14日的深圳天气、气温等其实已经可以查到准确的数据。但QwQ大模型是2025.3.6日发布的,其知识也仅限于2025.3.6日之前(从后面的回答可以看到,QwQ相关数据停留在2023年)。2025年4月10日对于QwQ大模型已经是未来的时间,看到QwQ只能靠推断,无法给出准确的信息,类似如下的答复:

好的,用户问的是2025年4月14日深圳的天气。首先,我需要确认当前时间,现在是2023年,用户想知道两年后的天气情况。显然,这个时间跨度有点长,常规的天气预报通常只能提供几天到一周的准确预测,最多几个月前的长期预报也可能有较大误差。

。。。。。。<为节省篇幅,此处略去思考过程>

关于2025年4月14日深圳的天气,目前无法提供准确的预测。天气预报的准确性通常在**7-10天**内较高,而超过两周的预测误差会显著增加,更不用说两年后的具体日期了

不过,可以参考深圳**4月的气候特征**(基于历史数据)为您提供大致的参考信息:

### 深圳4月气候特点:

1. **气温**:

  - 平均气温约在 **22℃-28℃** 之间,白天温暖,早晚可能稍凉。

  - 四月是春季向夏季过渡的月份,气温逐渐上升。

2. **降水**:

  - 4月属于**雨季初期**,可能有阵雨或雷雨,但强度通常不如夏季台风季。

  - 历史数据显示,4月平均降水量约为 **80-100毫米**,但具体分布不均。

3. **天气模式**:

  - 常见多云或晴天交替,偶有短时降雨。

  - 可能受**华南前汛期**影响,但具体天气需结合当年气候模式(如厄尔尼诺/拉尼娜等)。

。。。。。。<为节省篇幅,此处略去建议,读者可自行测试>

从QwQ给出的答复可以看出,QwQ的相关知识停留在2023年,和实际使用时的时间无法对上,对未来只能靠盲猜。实际上,深圳在4.14日全天天晴,气温是19-27℃,盲猜测信息是很难精准的。

针对时效性缺陷,我们可以再举一个更直观的例子,比如2025年4月份美国发起的震荡全球的关税大战。QwQ是2025年3月份发布的,而美国关税政策的重大调整是发生在4月份,故QwQ是没有这块知识内容的。我们可以咨询QwQ看具体答复,如输入问题:请问2025年4月美国的关税政策变化,以及对全球经济的影响?QwQ的答复如下:

嗯,用户问的是2025年4月美国的关税政策变化以及对全球经济的影响。首先,我需要确认自己是否了解当前的美国关税政策,以及未来 的可能变化。不过,2025年现在还没到,所以只能进行预测和分析。用户可能是在做长期规划,比如企业战略或者投资决策,需要了解未来的关税趋势。

首先,美国的关税政策通常受政治、经济因素影响,比如当前的贸易保护主义倾向,或者经济压力下的调整。比如特朗普时期加征的关税,拜登政府可能有调整,但未来可能又有新的变化。需要考虑到2024年的总统选举结果,如果共和党上台,可能会更倾向于保护主义,继续或加强关税;而民主党可能更倾向于多边贸易协定,减少关税。

另外,2025年全球经济形势可能如何?比如通胀情况、供应链稳定性、地缘政治冲突如俄乌战争是否缓解,中美关系是否改善。这些都会影响关税政策。如果美国经济面临衰退,可能通过关税保护国内产业;如果经济向好,可能更开放。

。。。。。。<为节省篇幅,此处略去建议,读者可自行测试>

从QwQ的答复可以看出,面对这种全球都众所周知的公开信息,依然只能靠盲猜,无法准确的作答。核心原因是LLM在训练时这部分知识语料就是缺乏的,所以也就无法给出准确的答案。

LLM“知识茧房”破解方法

目前业界的解题思路可以归结为两种:1、缺知识就补知识,常见做法是采用行业私域知识直接做训练,或微调训练,不断更新模型的知识。2、调用LLM之外的工具,获取相关的知识信息,并将信息以prompt方式输入给LLM做最终的内容加工和整理。比如,将私域知识加工成知识库,结合大模型构建RAG系统。或者工具提供接口和LLM对接,当LLM判断该问题自身知识不足以作答时,则调用对应接口获取LLM之外的知识做补充,并结合获取的外部知识做进一步的加工整理,最终返回给用户,而MCP就是这种方式。

 

MCP简介

简介可查看官网:https://mcpcn.com/docs/introduction/

MCP(Model Context Protocol) 由 Anthropic 于 2024 年底开源,其目标是为大模型与外部工具 / 数据源之间建立起标准化的调用规范。它定义了统一的通信标准,使得大模型能够通过标准化接口连接任意工具,各工具按照MCP规范,编写server程序,就能便捷对接大模型。

 

1、MCP Server简介:

根据MCP协议定义,Server可以提供三种类型的标准能力,Resources、Tools、Prompts,每个Server可同时提供者三种类型能力或其中一种。

- **Resources:**资源,类似于文件数据读取,可以是文件资源或是API返回的内容。

- **Tools:**工具,第三方服务、功能函数,通过此可控制LLM可调用哪些函数。

- **Prompts:**提示词,为用户预先定义好的,完成特定任务的模板。

2、MCP Server通讯机制

MCP支持两种传输方式:标准输入输出(stdio)和基于 HTTP 的服务器推送事件(SSE)。

其中标准输入输出(stdio)模式,是一种用于本地通信的传输方式。在这种模式下,MCP 客户端会将服务器程序作为子进程启动,双方通过标准输入(stdin)和标准输出(stdout)进行数据交换。这种方式适用于客户端和服务器在同一台机器上运行的场景,确保了高效、低延迟的通信。本例DEMO程序,也将采用这种方式。

 

极简Client示例

官方范例参考:https://mcpcn.com/docs/quickstart/client/

Client示例代码参考(以Python为例):https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py

 

  1.  MCP要求借助uv进行虚拟环境创建和依赖管理。uv 是一个Python 依赖管理工具,类似于 pip 和 conda,但它更高效。安装UV:pip3 install uv

 

2、创建 MCP 客户端项目及目录:

uv init mcp-client

cd mcp-client

 

3、创建MCP客户端虚拟环境,并激活

创建虚拟环境:uv venv

激活虚拟环境:source .venv/bin/activate

注意:下次启动时,无需重复创建项目目录和虚拟环境,直接激活已有虚拟环境即可。

 

4、通过add方法在虚拟环境中安装MCP SDK

uv add mcp

 

5、编写 MCP 客户端。在当前项目主目录中创建 client1.py,并写入以下代码:

import asyncio
from mcp import ClientSession
from contextlib import AsyncExitStack
 
class MCPClient:
def __init__(self):
 """初始化 MCP client"""
self.session = None
self.exit_stack = AsyncExitStack()
 
async def connect_to_server(self):
"""用于后续扩展MCP server连接,当前暂不连接真实server"""
print("MCP client已初始化,但未连接server")
 
async def chat_loop(self):
"""交互式聊天循环"""
print("\nMCP client已启动!输入 'quit' 退出")
 
while True:
try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
                print(f"\n[Response] 你说的是:{query}")
except Exception as e:
                print(f"\n发生错误: {str(e)}")
 
async def cleanup(self):
"""清理资源"""
await self.exit_stack.aclose()
 
async def main():
client = MCPClient()
try:
await client.connect_to_server()
await client.chat_loop()
finally:
await client.cleanup()
 
if __name__ == "__main__":
asyncio.run(main())


----------------代码走读----------------

MCP Client代码由MCPClient类和main函数构成,几个关键函数作用如下:

函数

说明

__init__

初始化客户端。

connect_to_ser

用于连接MCP server,下一章节重点讲解。

chat_loop

连上server后,开启对话循环,可以多轮输入。在对话中可联动LLM,调用server做处理,实现智能问答。

cleanup

用于程序退出时,释放资源。

 

此外,值得注意的是采用了异步编程,asyncio:Python 内置的异步编程库,让 MCP 可以非阻塞地执行任务(比如对话、查询等)。因为采用了异步编程,函数定义时需要用 async 关键字。

main() 函数执行的主要步骤:

- client = MCPClient():实例化一个 MCP client类。

- await client.connect_to_server():初始化 MCP client,连接server(本例暂未连接server)。

- await client.chat_loop():启动对话循环。

- finally: 确保程序退出时,释放资源。

 

6、运行 MCP 客户端

uv run client1.py

极简Server端+Client调用Server tool

范例参考:https://mcpcn.com/docs/quickstart/server/

官网以及目前一些开发者社区的资料上,常以查询天气为范例。实现一个调用天气查询的tool。用于示例,我们并不关注天气网站的具体接口规范,其实可以进一步简化,就在server程序中模拟一个天气查询tool即可,模拟一个查询天气的返回。关键是理解client和server端的调用和交互流程。

本示例用于演示client调用server中tool的过程。实现一个模拟查询当前天气的tool工具,也即client在对话中,输入一个城市名称,然后调用server端查询天气,server端的tool函数收到客户端的输入后,返回天气信息给客户端。如果该流程走通了,则说明client准确调用到了server端的tool,实现了server端的联动处理。其他更为复杂丰富的应用场景,只是进一步开发server端相应的tool函数即可。

  1.  编写server程序:
from mcp.server.fastmcp import FastMCP
 
# create an MCP server
mcp = FastMCP("Demo")
 
# add an weather tool
@mcp.tool()
def get_current_weather(city:str) -> str:
    """ get city weather."""
 
    print(f"\n Hello {city}, it is server tool")
    response = "%s:白天气温23℃,夜间18℃。" % city
    return response
 
if __name__ == "__main__":
    mcp.run(transport='stdio')
  1.  修改client程序:
  •  完善连接Server的函数connect_to_server,client通过调用该函数和server建立连接:

self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

初始化连接后,调用list_tools函数列出server支持的tool:self.session.list_tools()

  •  新增process_query函数,在该函数中封装调用server的tool做处理:

self.session.call_tool(tool_name, tool_arguments)

  •  修改chat_loop函数,在client的每轮对话中,调用process_query,实现server中tool的调用:self.process_query(query)
  •  main()函数的处理几乎没变化,只是增加了server连接时需要的参数传入(server程序所在的路径和名称),完整程序:
import asyncio
import os
from typing import Optional
from contextlib import AsyncExitStack
 
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
 
class MCPClient:
    def __init__(self):
        """初始化 MCP client"""
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
 
    async def connect_to_server(self, server_script_path: str):
        """连接到 MCP server并列出可用工具"""
        is_python = server_script_path.endswith('.py')
        is_js = server_script_path.endswith('.js')
        if not (is_python or is_js):
            raise ValueError("server脚本必须是 .py 或 .js 文件")
 
        command = "python" if is_python else "node"
        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )
 
        # 启动 MCP server并建立通信
        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio, self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
 
        await self.session.initialize()
 
        # 列出 MCP server上的工具
        response = await self.session.list_tools()
        tools = response.tools
        print("\n已连接到server,支持以下工具:", [tool.name for tool in tools])
 
    async def process_query(self, query: str) -> str:
        """
        调用MCP server工具处理输入,得到输出
        """
 
        response = await self.session.list_tools()
        tools = response.tools
        #print(f"\n session.list_tools: {response}, \n tools:{tools}")
 
        tool_name = tools[0].name
        tool_arguments = {"city": query}          # 注意:入参名称需要和tool中匹配,否则会调用失败
        #print(f"\n tool_name: {tool_name} ;  tool_arguments: {tool_arguments}.")
 
        # 执行工具
        result = await self.session.call_tool(tool_name, tool_arguments)      #tool name->str,tool arguments-> {\"a\": 3,\"b\": 5}
        #print(f"\n\n[Calling tool {tool_name} with args {query}, result is:{result}]\n\n")
 
        return result.content[0].text
 
    async def chat_loop(self):
        """交互式聊天循环"""
        print("\nMCP client已启动!输入 'quit' 退出")
 
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
 
                response = await self.process_query(query)  # 发送用户输入到server,并得到处理后的结果
                print(f"\n MCP Server response: {response}")
 
            except Exception as e:
                print(f"\n发生错误: {str(e)}")
 
    async def cleanup(self):
        """清理资源"""
        await self.exit_stack.aclose()
 
async def main():
    if len(sys.argv) < 2:
        print("Usage: python client.py <path_of_the_server_script>")
        sys.exit(1)
 
    client = MCPClient()
    try:
        await client.connect_to_server(sys.argv[1])
        await client.chat_loop()
    finally:
        await client.cleanup()
 
if __name__ == "__main__":
    import sys
 
asyncio.run(main())
  1.  运行client和server程序:uv run client2.py server2.py

 

扩展:上述示例仅演示了一个最简示例,实际上,调用链路通了后,可以做更多事情,可以在server中封装各种tool,实现丰富的功能。也可以开发各种不同用途的server,由client实现统一的调用。

问题:如果一个server中有很多tools,client如何知道什么时候需要调用tool,需要调用哪些tool?并且准确传入需要的参数呢?

解法:结合LLM的function call能力,基于用户输入的问题做智能化判断。

Step1:Client先调用LLM,LLM根据用户输入的问题和传入的tools列表信息,做智能化判断是否需要调用server中的tool,以及需要调用哪些tool

Step2:Client基于LLM返回的需要调用的具体tool列表,就可以进一步调用server中的tool做处理,返回结果。

Step3:基于Step2 server返回的结果,可以再一次以prompt方式输入给LLM做内容整理加工,将LLM加工后的最终内容返回给用户。

这就是MCP和LLM相结合的点,这也是MCP诞生的初衷。接下来将进一步探索Client和LLM,以及和Server端tools之间的交互。实现完整应用示例。

 

Client+LLM+Server完整应用示例

  1.  修改vLLM启动QwQ的启动参数(增加enable-auto-tool-choice、tool-call-parser hermes),重新启动QwQ,以支持QwQ函数调用。

python3 -m vllm.entrypoints.openai.api_server --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ --served-model-name QwQ-32B --max-model-len 2048 --gpu_memory_utilization 0.9 --max_num_seqs 32 --enable-auto-tool-choice --tool-call-parser hermes

注意:可以使用nvidia-smi命令查看显存使用情况,如果发现停止vLLM的服务后,显存依然没有释放,就需要重启ECS后再使用vLLM新的启动参数启动QwQ。

  1.  为了支持以OpenAI方式调用模型,且需要在程序中读取API-KEY等信息,需要安装如下依赖:uv add mcp openai python-dotenv
  2.  在MCP client所在项目目录下,创建.env文件,用于保存LLM的相关信息。本例是在本地安装了QwQ,并使用vLLM启动了OpenAI服务,大模型在启动时命名为QwQ-32B,故对应的配置信息如下(读者可以回顾下前文QwQ的测试程序中的对应配置,若是其他LLM则需要相应修改为其对应的配置):

OPENAI_API_KEY = "EMPTY"

BASE_URL = "http://localhost:8000/v1"

MODEL = "QwQ-32B"

  1.  修改client代码,在client中加载LLM配置,联动LLM。Server代码则无需修改。
import asyncio
import os
from typing import Optional
from contextlib import AsyncExitStack
 
 
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
 
import json
from openai import OpenAI
from dotenv import load_dotenv
 
# 加载 .env 文件,API Key等私密信息存放在.env 文件中
load_dotenv()
 
class MCPClient:
    def __init__(self):
        """初始化 MCP client"""
        self.exit_stack = AsyncExitStack()
        self.openai_api_key = os.getenv("OPENAI_API_KEY")  # 读取API Key
        self.base_url = os.getenv("BASE_URL")  # 读取BASE URL
        self.model = os.getenv("MODEL")  # 读取model
        if not self.openai_api_key:
            raise ValueError("缺失OpenAI API Key,请在 .env 文件中设置 OPENAI_API_KEY")
        self.client = OpenAI(api_key=self.openai_api_key, base_url=self.base_url)  # 创建OpenAI client,用于连接LLM
 
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
 
    async def connect_to_server(self, server_script_path: str):
        """连接到 MCP server并列出可用工具"""
        is_python = server_script_path.endswith('.py')
        is_js = server_script_path.endswith('.js')
        if not (is_python or is_js):
            raise ValueError("server脚本必须是 .py 或 .js 文件")
 
        command = "python" if is_python else "node"
        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )
 
        # 启动 MCP server并建立通信
        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio, self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
 
        await self.session.initialize()
 
        # 列出 MCP server上的工具
        response = await self.session.list_tools()
        tools = response.tools
        print("\n已连接到server,支持以下工具:", [tool.name for tool in tools])
 
    async def process_query(self, query: str) -> str:
        """ 调用MCP server,列出全部工具,并加工成OpenAI调用工具的格式 """
        response = await self.session.list_tools()
        available_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "input_schema": tool.inputSchema
            }
        } for tool in response.tools]
        # print(available_tools)
 
        """ 第一次调用LLM:输入用户的问题和server tools列表。由LLM判断该用户问题需要使用哪个或哪些tools """
        messages = [{"role": "user", "content": query}]
 
        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            tools=available_tools
        )
 
        # 解析LLM的返回,确认需要哪个或哪些tools,以及tool需要传入的参数
        content = response.choices[0]
        if content.finish_reason == "tool_calls":
            tool_call = content.message.tool_calls[0]
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)
 
            # 在client调用server,执行tool。
            result = await self.session.call_tool(tool_name, tool_args)
            print(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n")
 
            # 再次调用LLM,将server tool处理后返回的结果及工具信息追加到prompt,传入LLM,进一步做内容整理后作为最终结果
messages.append(content.message.model_dump())
            messages.append({
                "role": "tool",
                "content": result.content[0].text,
                "tool_call_id": tool_call.id,
            })
 
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
            )
            return response.choices[0].message.content
 
        return content.message.content
 
    async def chat_loop(self):
        """交互式聊天循环"""
        print("\nMCP client已启动!输入 'quit' 退出")
 
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
 
                response = await self.process_query(query)  # 处理用户输入的问题,该函数会结合LLM和server tools做信息处理
                print(f"\n MCP Server response: {response}")
 
            except Exception as e:
                print(f"\n发生错误: {str(e)}")
 
    async def cleanup(self):
        """清理资源"""
        await self.exit_stack.aclose()
 
async def main():
    if len(sys.argv) < 2:
        print("Usage: python client.py <path_of_the_server_script>")
        sys.exit(1)
 
    client = MCPClient()
    try:
        await client.connect_to_server(sys.argv[1])
        await client.chat_loop()
    finally:
        await client.cleanup()
 
if __name__ == "__main__":
    import sys
 
asyncio.run(main())


-----------关键代码走读,原理解析----------

在MCPClient类的init函数中完成了LLM的配置信息加载,并创建了OpenAI client,用于连接和调用LLM。

LLM、client和server tools间的主要调用流程:

Step1:在process_query函数中将获取到的server tools信息,按照OpenAI function call的格式,填好了available_tools,并结合用户问题封装好prompt模板。然后进行第一次LLM的问答调用:

response = self.client.chat.completions.create(

           model=self.model,

           messages=messages,

           tools=available_tools

       )

Step2:解析第一次LLM调用的返回信息(判断是否需要调用tools,涉及哪些tools,以及tool需要传入的参数),确认是否需要调用tools才能作答,如果不需要则直接作答,并返回。

Step3:如果该问题,需要调用tools才能作答(content.finish_reason == "tool_calls"表示需要调用tools),则根据LLM返回的server tools信息,调用tools(注意此调用不是LLM发起的,而是在client直接调用server对应的tools),result = await self.session.call_tool(tool_name, tool_args)

Step4:根据server tools的返回信息追加到prompt,再次调用LLM问答,由LLM进一步做内容加工整理后,作为最终结果返回。这种使用LLM做信息加工整理的处理方法在RAG系统中也经常使用到。


  1.  启动client和server:uv run client3.py server3.py

程序启动后,只输入地名,如本例输入:shenzhen,发现QwQ会自动识别当前输入是地名,而且会自动调用天气接口返回该地的接口。

如果用户输入其他问题,则不会调用天气接口:

如果用户的问题,明确说了需要查询某地天气,则QwQ会准确识别该问题,并返回该地天气(本例是模拟了一个天气查询的接口,写死了天气信息,故返回的值是固定的)。

从上述几个对话测试,可以看出当用户问题涉及天气问题时,则QwQ会自动调用server提供的天气相关的tool返回天气信息。若不涉及天气问题,则QwQ不会调用server的天气tool接口。DEMO程序正常运行。基于此,可以进一步丰富和扩展server的各自tools来实现其他功能,网上也有大量的MCP server可供使用,如:https://github.com/punkpeye/awesome-mcp-servers

 

小结:交互流程整理如下(蓝色字体部分,尤其值得关注和理解):

Client+LLM+数据库Server

再回到数据库场景,也可以编写数据库的MCP server tools,并接入到LLM中,当用户的问题涉及需要查询数据库中的信息是,LLM就可以通过数据库的server tools来查询数据库,进而完成问题的准确回答。

在server端将数据库连接和查询的代码扩展进去,就能实现数据库的MCP server tools了。此外,也有一些开源的MCP Server实现了数据库连接和查询,可供参考和使用,如:https://github.com/designcomputer/mysql_mcp_server

准备工作

  1. 以MySQL为例,先安装一个数据库。本例直接选择在ECS所在的可用区,在阿里云上开通了一个RDS MySQL实例。VPC、vswitch网络配置和ECS选成一样(相当于构建了一个内网可达的数据库)。
  2. 将ECS的IP加入到RDS MySQL数据库的白名单(只有加入到白名单的IP才能访问数据库)。说明:因为本例ECS和RDS MySQL选择了相同的az和vpc网络,所以ECS可以直接使用内网访问MySQL数据库,白名单中的IP填写ECS内网IP即可。

  1. 在RDS MySQL中创建用于测试的用户名及库表等基本信息:可以直接使用云上的DMS管理工具进行可视化的开发。

  1. 在数据库表中写入测试数据,以班级、老师做一个精简示例,创建两张表:teacher、class,并分别插入几条数据用于测试。

CREATE TABLE teacher (

   teacher_id INT AUTO_INCREMENT PRIMARY KEY,

   name VARCHAR(100) NOT NULL,

   age INT,

   gender ENUM('Male', 'Female', 'Other'),

   phone_number VARCHAR(20)

);

 

insert into `teacher`(`teacher_id`,`name`,`age`,`gender`,`phone_number`) values

('1001','张三','30','Male','13700008888'),

('1002','李四','25','Male','13700008888'),

('1003','王五','30','Female','13700008888'),

('1004','赵六','29','Male','13700008888'),

('1005','刘七','30','Male','13700008888'),

('1006','林八','26','Female','13700008888');

 

CREATE TABLE class (

   class_id INT AUTO_INCREMENT PRIMARY KEY,

   class_name VARCHAR(100) NOT NULL,

   male_num INT,

   female_num INT,

   teacher_id INT,

   FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id)

);

 

insert into `class`(`class_id`,`class_name`,`male_num`,`female_num`,`teacher_id`) values

('31','三一班','30','25','1001'),

('32','三二班','32','26','1002'),

('33','三三班','29','27','1003'),

('34','三四班','29','28','1004');

  1. 安装MySQL连接器模块:pip3 install mysql-connector-python>=9.1.0
  2. 将连接数据库所需的相关配置变量,填入.env文件中:

MYSQL_HOST = localhost  # Database host

MYSQL_PORT = 3306    # Optional: Database port (defaults to 3306 if not specified)

MYSQL_USER = your_username

MYSQL_PASSWORD = your_password

MYSQL_DATABASE = your_database

MYSQL_TABLE = "class"

  1. 编写数据库测试脚本pySQL.py,见如下示例代码
#!/bin/python3
# -*- coding: utf-8 -*-
import os
from mysql.connector import connect, Error
import logging
 
from dotenv import load_dotenv
# 加载 .env 文件,API Key等私密信息存放在.env 文件中
load_dotenv()
 
# global env:如下配置正式使用时存放在配置文件中,如:.env
# MYSQL_HOST = "rm-xxx.mysql.rds.aliyuncs.com"     # Database host
# MYSQL_PORT = 3306             # Optional: Database port (defaults to 3306 if not specified)
# MYSQL_USER = "xxx"       # User name
# MYSQL_PASSWORD = "xxx"   # User password
# MYSQL_DATABASE = "xxx"   # MySQL database
# MYSQL_TABLE = "xxx"         # MySQL table
 
def get_db_config():
    """Get database configuration from environment variables."""
    config = {
        "host": os.getenv("MYSQL_HOST", "localhost"),
        "port": int(os.getenv("MYSQL_PORT", "3306")),
        "user": os.getenv("MYSQL_USER"),
        "password": os.getenv("MYSQL_PASSWORD"),
        "database": os.getenv("MYSQL_DATABASE")
    }
 
    if not all([config["user"], config["password"], config["database"]]):
        logger.error("Missing required database configuration. Please check environment variables:")
        logger.error("MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DATABASE are required")
        raise ValueError("Missing required database configuration")
 
    return config
 
# 连接数据库,执行SQL查询
config = get_db_config()
try:
    with connect(**config) as conn:
        with conn.cursor() as cursor:
            MYSQL_TABLE = os.getenv("MYSQL_TABLE")  # 读取MYSQL_TABLE变量
            sql = "SELECT male_num FROM %s WHERE class_name='三一班'" % MYSQL_TABLE
            cursor.execute(sql)
            for result in cursor:
                male_num = result[0]
                print("male_num:", male_num)
 
finally:
    cursor.close()
    conn.close()

脚本如果运行成功,则会返回查询结果(示例为查询三一班男生人数):

手写MySQL server DEMO程序

有了前面的准备工作,MySQL数据库已经安装好,并且在ECS上的python测试程序能正常连接并查询数据库了。那么接下来可以把这个查询数据库的功能扩展到server程序中,作为server的tools功能。

  1. 修改server3.py代码,增加数据库查询的tool功能。示例代码:
from mcp.server.fastmcp import FastMCP
import os
from mysql.connector import connect, Error
import logging
 
from dotenv import load_dotenv
# 加载 .env 文件,API Key等私密信息存放在.env 文件中
load_dotenv()
 
# global env:如下配置正式使用时存放在配置文件中,如:.env
# MYSQL_HOST = "rm-xxx.mysql.rds.aliyuncs.com"# Database host
# MYSQL_PORT = 3306             # Optional: Database port (defaults to 3306 if not specified)
# MYSQL_USER = "xxx"       # User name
# MYSQL_PASSWORD = "xxx"   # User password
# MYSQL_DATABASE = "xxx"   # MySQL database
# MYSQL_TABLE = "xxx"         # MySQL table
 
# create an MCP server
mcp = FastMCP("Demo")
 
# add an weather tool
@mcp.tool()
def get_current_weather(city:str) -> str:
    """get city weather."""
 
    print(f"\n Hello {city}, it is server tool")
    response = "你好 %s, 白天气温23℃,夜间18℃。" % city
    return response
 
def get_db_config():
    """Get database configuration from environment variables."""
    config = {
        "host": os.getenv("MYSQL_HOST", "localhost"),
        "port": int(os.getenv("MYSQL_PORT", "3306")),
        "user": os.getenv("MYSQL_USER"),
        "password": os.getenv("MYSQL_PASSWORD"),
        "database": os.getenv("MYSQL_DATABASE")
    }
 
    if not all([config["user"], config["password"], config["database"]]):
        logger.error("Missing required database configuration. Please check environment variables:")
        logger.error("MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DATABASE are required")
        raise ValueError("Missing required database configuration")
 
    return config
 
# add an query database tool
@mcp.tool()
def query_database(class_name:str) -> int:
    """query the number of male students in the class from database."""
    # 连接数据库,执行SQL查询
    config = get_db_config()
    try:
        with connect(**config) as conn:
            with conn.cursor() as cursor:
                MYSQL_TABLE = os.getenv("MYSQL_TABLE")  # 读取MYSQL_TABLE变量
 
                sql = "SELECT male_num FROM %s WHERE class_name='%s'" % (MYSQL_TABLE , class_name)
                cursor.execute(sql)
                for result in cursor:
                    male_num = int(result[0])
print("male_num:", male_num)
 
    finally:
        cursor.close()
        conn.close()
 
    return male_num
 
if __name__ == "__main__":
    mcp.run(transport='stdio')
  1. client代码中已支持传入一个参数的tools调用,本例查询数据库也仅需要传入班级这个入参,所以无需修改。

 

  1. 安装客户端连接器模块:uv add mysql-connector-python

 

  1. 启动client和server:uv run client3.py server3.py

如果涉及的问题需要查询数据库,则LLM会判断出该问题需要查询数据库,并识别出需要调用的tools名称及其需要传入的参数。见下:

扩展:由LLM生成查询SQL

思考:上述示例只是个最简示例,在调用查询数据库的tools时,仅需要传入一个参数:班级。功能上也只能查班级男生人数。实际使用时,数据库内的表是非常多的,一个问题可能需要关联多张表的查询,而且每个表内支持的查询变量也非常多,不同问题对应不同的查询变量。所以查询的SQL语句也必将是五花八门的。

解法:众所周知,LLM其实也具备SQL编写能力,如果把数据库内的库表结构信息输入给LLM,则LLM能自动生成对应的SQL查询语句。所以,可以看到,目前业界的不少MySQL MCP server就是直接提供了SQL查询的tools能力,SQL由LLM根据问题自行生成。下面,我们继续顺着这个思路往下拓展。直接使用开源的MCP Server,分析该程序可以看到其本质就是实现了一个简洁的SQL查询:https://github.com/designcomputer/mysql_mcp_server

方法:

  1.  从前面的原理可以看到,server端程序的关键代码就是server.py这个文件,所以我们可以直接把开源项目中的server.py文件下载下来,拷贝到演示环境中。调测中,会发现报MySQL相关的配置变量无法获取,是因为.env文件没有加载。在server.py中增加如下代码,即可加载:

from dotenv import load_dotenv

# 加载 .env 文件,API Key等私密信息存放在.env 文件中

load_dotenv()

  1.  直接运行client3.py和新加入的MySQL开源server,发现是可以正常启动的。但从调用日志中可以看出,LLM生成的SQL语句中的列名关键字是错误的。原因是,我们并没有把数据库的表结构信息输入给LLM,所以LLM就盲猜了表中的列名称,故通常就很难和实际情况匹配。也可以看到后续的SQL执行时必然会失败。

  1.  从报错中,可以看出,LLM盲猜的表名称和表结构都是错误的。有个笨办法:直接建表时就建成LLM盲猜的表结构,这样就能和LLM生成的SQL语句匹配上,当然这个实际应用时极少用,因为如果用户换了问题,LLM又盲猜了其他的表结构,就又要修改。

比较靠谱的解法是,将表结构这些必要的信息填入prompt提示词中,传入给LLM,这样LLM就能生成正确的SQL语句了。解决方法也很多,比如可以直接修改首次调用LLM的prompt模板,加入表结构信息。也可以等首次调用完成,LLM返回需要调用SQL查询tools的时候,把表结构信息放在prompt模板,调用LLM生成SQL语句等。

  1.  按上述思路,尝试修改修改client.py中的process_query函数中,首次调用LLM的prompt模板,加入表结构信息,看LLM能否一次到位,生成准确的SQL语句:
async def process_query(self, query: str) -> str:
        """ 调用MCP server,列出全部工具,并加工成OpenAI调用工具的格式 """
        response = await self.session.list_tools()
        available_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "input_schema": tool.inputSchema
            }
        } for tool in response.tools]
        print(available_tools)
 
        """ 第一次调用LLM:输入用户的问题和server tools列表。由LLM判断该用户问题需要使用哪个或哪些tools """
        prompt_template = '''任务描述:解答用户问题。如果能直接作答,则返回答案。如果用户问题需要查询数据库,则生成的SQL查询语句需要符合补充信息中的数据库表结构。
 
        用户问题:'%s'
 
        补充信息(数据库表结构):"
        表名: `teacher`
        - `teacher_id` (INT, 主键): 老师的ID
        - `name` (VARCHAR(100)): 老师的名字
        - `age` (INT): 老师的年龄
        - `gender` (ENUM('Male', 'Female', 'Other')): 老师的性别
        - `phone_number` (VARCHAR(20)): 老师的手机号
        表名: `class`
        - `class_id` (INT, 主键): 班级ID
        - `teacher_id` (INT, 外键, 引用 `teacher.teacher_id`): 老师的ID
        - `class_name` (VARCHAR(100)): 班级名称
        - `male_num` (INT)): 男生数量
        - `female_num`(INT)): 女生数量"
        ''' % query
 
        messages = [{"role": "user", "content": prompt_template}]
 
        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            tools=available_tools
        )
 
        # 解析LLM的返回,确认需要哪个或哪些tools,以及tool需要传入的参数
        content = response.choices[0]
        if content.finish_reason == "tool_calls":
            tool_call = content.message.tool_calls[0]
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)
 
            # 在client调用server,执行tool。
            result = await self.session.call_tool(tool_name, tool_args)
            print(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n")
 
            # 再次调用LLM,将server tool处理后返回的结果及工具信息追加到prompt,传入LLM,进一步做内容整理后作为最终结果
messages.append(content.message.model_dump())
            messages.append({
                "role": "tool",
                "content": result.content[0].text,
                "tool_call_id": tool_call.id,
            })
 
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
            )
            return response.choices[0].message.content
 
        return content.message.content
  1.  运行程序,实测看效果:uv run client.py server.py

从测试结果可以看到,LLM一步到位生成了准确的SQL查询语句。再试一个需要两张表关联查询的例子,发现LLM也能精准的给出对应的SQL查询语句,返回准确结果:

当然,本例只是用作DEMO示例,简化了大量功能。实际应用的client和server程序可以设计的非常丰富,比如本例是把表结构人为填入到prompt模板中,实际上表结构也可能会发生修改而变化,在程序中可以直接通过查询数据库而获得实时准确的表结构。再比如,本例是把client和server放在了同一个ECS中,实际应用时,client和server间也可以使用SSE方式实现远程调用等等。

总结:万物可连、万事归一入口到LLM

MCP的特点:

  1.  MCP采用了client-server结构,制定了一套标准接口规范,开发者按照MCP的编程规范,开发设计自身的client和server程序。就能实现client和server间的访问。
  2.  Client可理解为服务调用方、使用方。
  3.  Server可理解为服务提供方,开发者可将自身各项功能以tools函数方式封装到server端。喜欢动脑筋的同学可能会思考,当存在大量server及tools时,LLM是怎么判断用户输入的问题和哪个或哪些tools相关呢,根据传入LLM的tools相关信息可以看出,LLM主要是根据tools函数里面的函数注释以及函数名称来理解和识别的,所以编写tools函数时,需要把函数注释和函数名称写清楚。
  4.  有了这套机制后,相当于所有的功能、对外的服务接口都有了一个标准规范。可方便的供应用开发者使用。
  5.  Client、Server及LLM间的大致交互如下(蓝色字体部分尤其值得理解):

MCP开创性的提出了一套通用的AI agent应用开发规范:各个不同厂商、不同开发者开发的server,都可以用统一的client调用,这就相当于实现了一套统一且通用的功能开发和接口调用规范,可以屏蔽各厂商、各产品的底层接口差异。这也是MCP目前越来越火爆的关键原因,MCP实际上定义了一个全新的通用的LLM和应用工具之间的开发生态

此外,从MCP的程序开发中可以看到,和LLM相关的交互代码大量使用了OpenAI接口方式,并且主要是使用了LLM的function call功能。国内的LLM建议也早日原生支持OpenAI API及function call能力,这样开发者开发起来就更便捷了。

 

MCP带来的无限想象和新的可能性:

LLM并非无所不知,天生具有“知识茧房”局限,注定对“茧房”外的知识是不甚明了的,所以在涉及私域知识、模型训练后新发生的事情等的知识问答上,经常答非所问。而MCP定义了一套LLM和上下游生态工具、系统的对接规范,构建了万物都可与LLM联动的可能性。这样就解决了LLM的“知识茧房”短板,当一个问题LLM发现自身知识难以精确回答时,就可以调用MCP Server内相关的tools,并结合LLM自身所擅长的文案整理、内容提取等能力,实现精确且文案流畅的回答。这使得,万物皆可通过MCP与LLM实现互联,LLM未来可以作为万事的统一入口,万事不知皆可问LLM,LLM自身知识可解则直接解答,如果自身知识不足以解,则联动server tools解答。万物均可通过MCP接入LLM,成为LLM的知识库补充。

回到数据库领域,在私域知识库场景,RAG系统将不再是知识库的唯一方案,通用数据库的结构化和非结构化数据也可成为LLM的外挂知识库,LLM可生成问题对应的SQL,由Client直接查数据库获取信息,而且还能实现写数据库。


常见问题处理

  1. 大模型下载速度慢,经常中断。

解决方法:使用国内的源做加速,推荐使用modelscope 中的 snapshot_download 函数下载模型。并且使用nohup方式在系统后台下载。详见模型下载章节。

  1. vLLM启动大模型服务时,经常遇到CUDA out of memory的报错,尤其是显存不是特别大的时候。而实际上,显存是够用的。

问题描述:启动命令:python3 -m vllm.entrypoints.openai.api_server   --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ--served-model-name QwQ-32B--max-model-len=4096

报错:RuntimeError: CUDA out of memory occurred when warming up sampler with 1024 dummy requests. Please try lowering `max_num_seqs` or `gpu_memory_utilization` when initializing the engine.

原因:通常并不是因为显存不足,比如QwQ-32B-AWQ在运行时,可以看到只需要约18G显存,但24G显存的卡依然会报显存不够的错。此问题,是因为vLLM的启动参数配置不合理,通常报错信息中有对应提示,需要手工根据提示做不同参数配置的尝试。

解决方法:根据每次报错日志中的原因提示,尝试修改启动命令的对应参数值(亲测max-model-len、gpu_memory_utilization、max_num_seqs这三个参数调整有效),此过程,可能得耐心反复多试试各种参数值的不同配置,比如:

python3 -m vllm.entrypoints.openai.api_server --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ --served-model-name QwQ-32B --max-model-len 2048 --gpu_memory_utilization 0.9 --max_num_seqs 32

  1.  使用LLM调用tool时报错

发生错误: Error code: 400 - {'object': 'error', 'message': '"auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set', 'type': 'BadRequestError', 'param': None, 'code': 400}

解决方法:根据错误提示可以看出是LLM启动时,需要设置相关参数。

python3 -m vllm.entrypoints.openai.api_server --model /home/ecs-user/QwQ-32B/Qwen/QwQ-32B-AWQ --served-model-name QwQ-32B --max-model-len 2048 --gpu_memory_utilization 0.9 --max_num_seqs 32 --enable-auto-tool-choice --tool-call-parser hermes



附录:参考资料

https://github.com/modelcontextprotocol/servers

https://mcp.so/?q=mysql

https://github.com/bytebase/dbhub/

https://mp.weixin.qq.com/s/Dcfg32sWnAmQGTEth53OYg

https://mp.weixin.qq.com/s/iHlH7zXKeRGHEPi8pSiUrg

https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models

https://datawhaler.feishu.cn/docx/Wz2NdqSx1oEZsuxB9zHcEQ20nNe

https://deepseek.csdn.net/67e28cf28393e26e265938ce.html

目录
打赏
0
14
14
0
28
分享
相关文章
【AI帮我写代码,上班摸鱼不是梦】手摸手图解CodeWhisperer的安装使用
除了借助ChatGPT通过问答的方式生成代码,也可以通过IDEA插件在写代码是直接帮助我们生成代码。 目前,IDEA插件有CodeGeeX、CodeWhisperer、Copilot。其中,CodeGeeX和CodeWhisperer是完全免费的,Copilot是收费的,每月10美元。 下面我们来了解CodeWhisperer的安装和使用,如果你还想了解其他的可以在评论告诉我。
320 4
【五子棋实战】第1章 项目架构与开发思路
五子棋是一种古老而受欢迎的棋类游戏,本博客将介绍如何使用前端和算法接口来制作一个五子棋游戏。 此次实战项目包含项目的所有代码、教学的系列博客,分别放在公众号、CSDN,以便广大读者能够更好地学习、实践与二开。
390 0
手撕“开源版Manus”
Manus作为一款引发热议的AI智能体产品吸引了大量的关注。OpenManus作为一个开源项目,尝试复现了Manus的部分功能,可以作为一种“平替”来体验类似的技术。
手撸了一个全自动微信清粉小工具(源码详解)
微信清理僵尸粉工具利用Python和`uiautomator2`库,通过模拟转账操作自动检测并标记微信好友列表中被删除、被拉黑或账号存在问题的“僵尸粉”。工具支持开启调试模式、自动打开微信、获取好友信息、判断好友状态、管理标签等功能,最终将检测结果记录到文件中,便于用户管理和清理好友列表。此工具适用于Android设备,已在OPPO Reno4 Pro上测试成功。
286 5
InqureJS:手搓脚手架必备!让你的命令行交互花里胡哨!
【10月更文挑战第5天】InqureJS:手搓脚手架必备!让你的命令行交互花里胡哨!
InqureJS:手搓脚手架必备!让你的命令行交互花里胡哨!
【经验分享】如何手撸一个switch组件。
【经验分享】如何手撸一个switch组件。
109 7
🚀今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗?
🚀今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗?
152 0
【测试平台系列】第一章 手撸压力机(九)- 封装函数
将我们的一些代码封装到函数和方法中,这样我们看来代码可读性更好。如果发现bug,也可以更好的进行追踪。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等