【动手学MCP从0到1】2.4 MCP中的Prompt服务项目搭建步骤详解

1. Prompt

我们可以在服务端提前定义好一套完整的提示词,当客户端需要完成某个任务时,直接从服务器上获取提示词,再把提示词交给大模型,可以使得我们的任务更加流畅的完成。

1.1 代码审查

比如我们可以预先定义好以下提示词。

#代码审查
请审查以下{{language}}语言的代码,集中在{{focusArea}}部分:
{{language}}{{code}}

在使用的时候,可以传入 language=pythonfocusArea=for循环code=...参数:

请审查以下python语言的代码,集中在for循环部分:`
for x in range(100):
	print(x)

1.2 数据分析

请将{{month}}月的销售数据进行分析,分析部分重点为:

  • 1.哪个商品销售量最多
  • 2.哪个商品销售最少
  • 3.哪个商品销售额最多

代码实现:新建一个文件夹Prompt_mcp,然后再其下创建两个py文件,分别为server.py和client.py。然后再创建一个data文件夹,用于存放要进行处理的政策数据

2. 服务端

服务端中通过@app.prompt()装饰器即可添加提示词模板。

比如对于文件内容要点的提取,对政策文件归纳的示例代码如下

from mcp.server.fastmcp import FastMCP

app = FastMCP()

@app.prompt()
def policy_prompt(policy:str):
    '''
    能够对用户提供的政策内容,对其进行总结、提取关键信息的提示词模板
    :param policy: 需要总结的政策内容
    :return: 总结政策的提示词模板
    '''
    # 如果直接返回一个字符串,那么在客户端那边,接收到的是一个PromptMessage对象
    # #这个对象默认的role=user。当然也可以返回其他的role,那么就必须定义成字典的形式
    # #[{"role":"system""content": xxx}]
    return [{
        "role":"user",
        "content":f'''
        这个是政策内容:“{policy}”,请对该政策内容进行总结,总结的规则为:
        1.提取政策要点。
        2.针对每个政策要点按照以下格式进行总结
            *要点标题:政策的标题,要包含具体的政策信息
            *针对人群:政策针对的人群
            *有效时间:政策执行的开始时间和结束时间
            *相关部门:政策是由哪些部门执行总结的内容不要太官方,用通俗易懂的语言。
    '''
    }]

if __name__ == '__main__':
    app.run(transport='sse')

对于提示词设置返回值时,需要注意上面代码注释的部分:

  • 如果直接返回一个字符串,那么在客户端那边,接收到的是一个PromptMessage对象
  • 这个对象默认的role=user。当然也可以返回其他的role,那么就必须定义成字典的形式
  • 比如:[{"role":"system""content": xxx}]

3. 客户端

3.1 客户端调试过程

和之前ToolResource类似,创建session会话之后,可以通过list_prompts()函数获取服务端所有的提示词,然后通过session.get_prompt 即可获取指定的提示词。客户端示例代码如下:


from mcp.client.sse import sse_client
from mcp import ClientSession
from openai import OpenAI
from contextlib import AsyncExitStack
import asyncio



class MCPClient:
    def __init__(self):
        self.deepseek = OpenAI(
            api_key="sk-5d307e0a4xxxxxxe4575ff9",
            base_url="https://api.deepseek.com"
        )
        self.exit_stack = AsyncExitStack()
        self.prompts = {}  #注意这里修改

    async def run(self, query):
        # 1. 创建read_stream、write_stream
        read_stream, write_stream = await self.exit_stack.enter_async_context(sse_client(url="http://127.0.0.1:8000/sse"))
        # 2. 创建session对象
        session: ClientSession = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))

        # 3. 初始化
        await session.initialize()
	    # 4. 获取服务端的所有提示词
        prompts = (await session.list_prompts()).prompts
        print(prompts)

    async def aclose(self):
        await self.exit_stack.aclose()


async def main():
    client = MCPClient()
    try:
        with open("data/policy.txt",mode="r",encoding="utf-8") as f:
            policy = f.read()
            await client.run(f"帮我总结这个政策:{policy}")
    finally:
        await client.aclose()


if __name__ == '__main__':
    asyncio.run(main())

代码执行结果如下:(run函数中,前三步都是一样的,不需要修改,从第四步开始需要根据输出进行调试。最后面的主函数,就是对于一个文件的读写,可以直接使用同步函数,此时异步和同步没有区别了,注意指定的路径和编码形式)

在这里插入图片描述
获得的prompts是一个列表,包含了服务端的所有的提示词,因此进一步进行遍历循环,获取里面的详细信息。代码如下:

functions = []
# 4. 获取所有的提示词
prompts = (await session.list_prompts()).prompts
# print(prompts)
for prompt in prompts:
    name =prompt.name
    description = prompt.description
    prompt_arguments = prompt.arguments

    # 仔细看,可发现这一步后续没有用到
    self.prompts[name] = {
        "name" : name,
        "description" : description,
        "arguments" : prompt_arguments,
    }

    functions.append({
        "type":"function",
        "function":{
            "name":name,
            "description":description,
            "input_schema": None
        }
    })
    # 创建消息发送给大模型
    messages = [{
        "role": "user",
        "content": query
    }]
    deepseek_response = self.deepseek.chat.completions.create(
        messages=messages,
        model="deepseek-chat",
        tools=functions
    )
    print(deepseek_response)

执行代码输出如下:(注意输出结果中的格式,可以按照逐层获取相应的消息。此外,在之前构建resource服务项目中,创建self.resources收集数据,是用来获取uri,而在prompt服务项目中不需要此参数,所有在仿造前面的框架进行设置时,可以发现这里的self.prompts在添加数据后,在之后的代码中并没有使用)

通过对mcp中的Tool、Resource、Resource Template和Prompt服务项目的总结,可以发现: 在进行遍历循环时候,获取信息是针对具体的sesiion会话的读取服务端的需求

  • Tool中是session.call_tool(name=function_name, arguments=function_arguments).即需要获取名称和参数
  • Resource中是session.read_resource(uri),需要获取uri,而uri是在所有资源列表中,可以通过名称访问,所以在类对象初始化时候创建了一个self.resources容器存放资源信息。
  • Resource Templatez中也是session.read_resource(uri),和前面一样
  • Prompt中是session.get_prompt(name=function_name,arguments=function_arguments).即需要获取名称和参数

综上分析,可知在Promp服务项目创建中,类对象初始化设置self.prompts容器是没有必要的。

在这里插入图片描述

比如获取里面的function信息。

逐层获取代码为:deepseek_response.choices[0].message.tool_calls[0].function,执行输出如下

在这里插入图片描述

进一步,就是将大模型选择tool的message(过程)添加到messages中和选择具体的tool(结果)添加到messages中。相当于两个阶段的信息都添加到messages中,让大模型返回的结果更加精准。

chioce = deepseek_response.choices[0]
if chioce.finish_reason == "tool_calls":
	# 将大模型选择tool的message添加到messages中
	# messages中现在已经包含了一个工具选择的message
	messages.append(chioce.message.model_dump())   #第一处添加,应该是在if判断下面,而不是在if上面
	
    tool_call = chioce.message.tool_calls[0]
    function = tool_call.function
    function_name = function.name
    function_arguments = json.loads(function.arguments)  #输出的格式可以已看出是字符串,需要转字典
    result = await session.get_prompt(name=function_name,arguments=function_arguments)
    # print(result)
    content = result.messages[0].content.text
    # 先添加tool的message     第二处添加
    messages.append({
        "role":"tool",
        "content": content,
        "tool_call_id":tool_call.id
    })
    # 再将message发送给大模型返回最终结果
    respose = self.deepseek.chat.completions.create(
        model="deepseek-chat",
        messages=messages,
    )
    print(respose.choices[0].message.content)

执行代码后,输出的结果如下:(受限于截图窗口大小,只截取部分内容。可以看出经过大模型处理的政策文件输出结果基本满足预期构想)

在这里插入图片描述

在执行上述的操作过程中,可以通过打印相应的结果变量进行查看,对于arguments参数的处理,由于刚刚接触MCP服务项目开发,前两次估计很容易遗忘或者感觉到别扭,但是结合着输出结果和敲的次数多了,就知道在把变量传入到session会话之前需要处理一下格式,不能直接传入一个字符串数据类型,比如打印function变量输出结果,如下(注意看下面输出的红框中是字符串数据类型,即引号包含住了字典结构)

在这里插入图片描述

关于第二次使用大模型时,发送messages中的内容问题,经过几个项目实战下来,可以发现,这里的roler就是指tool,然后为啥是tool的思考,就是在于第一次时候我们把大模型选择工具的这个信息(过程)添加到messages中了,因此,再调用时候还需要添加一个对应信息的编号,这个编号就对应着tool_call_id。这个过程的理解可以通过自己改变role的角色和删除 "tool_call_id":tool_call.id这行代码执行,根据数据结果进一步理解和体会。(只有多实践,测试,才会更加理解这个过程)

messages.append({
     "role":"tool",
     "content": content,
     "tool_call_id":tool_call.id
})

3.2 客户端全部代码

"""
-------------------------------------------------------------------------------
@Project : MCP projects
File    : client.py
Time    : 2025-06-05 8:23
author  : musen
Email   : xianl828@163.com
-------------------------------------------------------------------------------
"""
import json
from mcp.client.sse import sse_client
from mcp import ClientSession
from openai import OpenAI
from contextlib import AsyncExitStack
import asyncio



class MCPClient:
    def __init__(self):
        self.deepseek = OpenAI(
            api_key="sk-5d307e0xxxxxxxx575ff9",
            base_url="https://api.deepseek.com"
        )
        self.exit_stack = AsyncExitStack()

    async def run(self, query):
        # 1. 创建read_stream、write_stream
        read_stream, write_stream = await self.exit_stack.enter_async_context(sse_client(url="http://127.0.0.1:8000/sse"))
        # 2. 创建session对象
        session: ClientSession = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))

        # 3. 初始化
        await session.initialize()

        # 4. 获取所有的提示词
        functions = []
        prompts = (await session.list_prompts()).prompts
        # print(prompts)
        for prompt in prompts:
            functions.append({
                "type":"function",
                "function":{
                    "name":prompt.name,
                    "description":prompt.description,
                    "input_schema": None
                }
            })
            # 创建消息发送给大模型
            messages = [{
                "role": "user",
                "content": query
            }]
            deepseek_response = self.deepseek.chat.completions.create(
                messages=messages,
                model="deepseek-chat",
                tools=functions
            )
            chioce = deepseek_response.choices[0]
            # print(chioce)
            if chioce.finish_reason == "tool_calls":
                # 将大模型选择tool的message添加到messages中
                # messages中现在已经包含了一个工具选择的message
                messages.append(chioce.message.model_dump())

                tool_call = chioce.message.tool_calls[0]
                function = tool_call.function
                function_name = function.name
                print(function)
                function_arguments = json.loads(function.arguments)  #输出的格式可以已看出是字符串,需要转字典
                result = await session.get_prompt(name=function_name,arguments=function_arguments)
                # print(result)
                content = result.messages[0].content.text
                # 先添加tool的message
                messages.append({
                    "role":"tool",
                    "content": content,
                    "tool_call_id":tool_call.id
                })
                # 再将message发送给大模型返回最终结果
                respose = self.deepseek.chat.completions.create(
                    model="deepseek-chat",
                    messages=messages,
                )
                print(respose.choices[0].message.content)


    async def aclose(self):
        await self.exit_stack.aclose()


async def main():
    client = MCPClient()
    try:
        with open("data/policy.txt",mode="r",encoding="utf-8") as f:
            policy = f.read()
            await client.run(f"帮我总结这个政策:{policy}")
    finally:
        await client.aclose()


if __name__ == '__main__':
    asyncio.run(main())

至此,关于MCP中的Prompt服务项目搭建步骤详解就梳理完毕,完结撒花✿✿ヽ(°▽°)ノ✿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lys_828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值