大语言模型实战(十四)——MCP Prompts提示系统深度解析:构建智能提示模板库与LLM集成方案

《MCP Prompts提示系统深度解析:构建智能提示模板库与LLM集成方案》

1 导语

1.1 项目背景

在AI应用开发中,我们经常需要为不同的任务编写特定的提示词。但问题是:这些提示词通常是硬编码在代码里,难以复用、难以管理、难以动态调整。

MCP(Model Context Protocol)的第三大功能 Prompts 解决了这个问题。它允许Server定义和管理提示词模板 ,Client可以根据需要动态获取并参数化这些模板,然后传给LLM。

这次实战我们将深入理解如何构建一个提示词库系统,让提示词成为一等公民。

1.2 项目价值

  • ✅ 集中管理提示词:所有提示词模板定义在Server端,便于维护和更新
  • ✅ 参数化提示词:同一个模板可以用不同参数生成多种变体,减少重复代码
  • ✅ 动态发现机制:Client可以自动发现所有可用的提示模板,无需提前知道
  • ✅ LLM一体化:提示词自动转换为LLM格式(messages),无缝集成ChatGPT/DeepSeek等
  • ✅ 团队协作:提示词工程师可以在Server端设计模板,应用开发者直接使用

1.3 学习目标

通过本文,你将学会:

  1. 如何定义和管理提示词模板
  2. Server端的两个核心处理器:list_prompts() 和 get_prompt()
  3. 如何使用PromptArgument实现参数化提示词
  4. Client端的提示词获取与动态参数收集机制
  5. 提示词与LLM的无缝集成方案
  6. 构建代码审查系统的实战架构

2 技术栈清单

组件 版本 用途
Python 3.10+ 基础语言
MCP 0.1.0+ 提示词通信协议
types.Prompt - 提示词定义
types.PromptArgument - 参数定义
asyncio 内置 异步编程
OpenAI SDK 1.3.0+ LLM调用
DeepSeek API - 演示LLM(可选)

环境要求:

  • macOS/Linux/Windows均支持
  • 需要LLM API密钥(DeepSeek/OpenAI/通义千问)
  • 支持交互式命令行输入

3 项目核心原理

3.1 MCP的三大功能对比

功能 作用 访问方式 返回值 应用场景
Resources 读取被动数据 list_resources() / read_resource(uri) 资源内容 文档、配置、文件
Tools 执行主动操作 list_tools() / call_tool(name, args) 操作结果 计算、搜索、API调用
Prompts 提供提示词模板 list_prompts() / get_prompt(name, args) Message列表 提示词管理、LLM指引

3.2 Prompts的工作原理

3.3 核心概念解析

Prompt对象结构:

复制代码
Prompt
├─ name: str              # 提示模板的唯一标识
├─ description: str       # 模板的功能描述
└─ arguments: List[PromptArgument]  # 参数列表
   └─ PromptArgument
      ├─ name: str       # 参数名
      ├─ description: str # 参数描述
      └─ required: bool  # 是否必需

GetPromptResult对象结构:

复制代码
GetPromptResult
└─ messages: List[PromptMessage]
   └─ PromptMessage
      ├─ role: str      # "assistant" 或 "user"
      └─ content: TextContent
         ├─ type: str   # "text"
         └─ text: str   # 具体的提示词文本

4 实战步骤

4.1 环境准备阶段

4.1.1 创建项目结构

bash 复制代码
# 创建虚拟环境
python -m venv .venv
source .venv/bin/activate  # macOS/Linux
# 或 .venv\Scripts\activate  # Windows

# 安装依赖
pip install mcp python-dotenv openai -q

4.1.2 配置环境变量

创建 .env 文件:

bash 复制代码
# .env
DEEPSEEK_API_KEY=your_deepseek_api_key
# 或
OPENAI_API_KEY=your_openai_api_key
# 或
QWEN_API_KEY=your_qwen_api_key
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

4.2 代码实现阶段

4.2.1 Server端:定义提示词模板

这是本案例的核心。通过定义Prompt对象来创建提示词库:

python 复制代码
# server/server-extension.py
from mcp.server import Server
import mcp.types as types
import asyncio
from mcp.server.stdio import stdio_server

# 【关键】定义提示词模板字典
PROMPTS = {
    "code-review": types.Prompt(
        name="code-review",
        description="分析代码并提供改进建议",
        arguments=[
            types.PromptArgument(
                name="code",
                description="需要审查的代码",
                required=True
            ),
            types.PromptArgument(
                name="language",
                description="编程语言",
                required=True
            ),
            types.PromptArgument(
                name="focus",
                description="审查重点(可选:performance, security, readability)",
                required=False
            )
        ]
    ),
    "explain-code": types.Prompt(
        name="explain-code",
        description="解释代码的工作原理",
        arguments=[
            types.PromptArgument(
                name="code",
                description="需要解释的代码",
                required=True
            ),
            types.PromptArgument(
                name="language",
                description="编程语言",
                required=True
            )
        ]
    ),
    "refactor-code": types.Prompt(
        name="refactor-code",
        description="重构代码",
        arguments=[
            types.PromptArgument(
                name="code",
                description="需要重构的代码",
                required=True
            ),
            types.PromptArgument(
                name="language",
                description="编程语言",
                required=True
            )
        ]
    )
}

# 初始化服务器
app = Server("code-review-server")

# 【关键】处理器1:列出所有提示模板
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    """
    【核心方法】返回可用的提示模板列表
    
    Client 在初始化时调用此方法来发现所有可用模板
    返回的 Prompt 对象包含:name、description、arguments
    """
    return list(PROMPTS.values())

# 【关键】处理器2:根据参数生成具体的提示词
@app.get_prompt()
async def get_prompt(
    name: str, arguments: dict[str, str] | None = None
) -> types.GetPromptResult:
    """
    【核心方法】根据模板名和参数生成提示内容
    
    这个方法是提示词参数化的关键:
    - 检查模板是否存在
    - 根据参数生成具体的提示词
    - 返回 GetPromptResult(包含 messages 列表)
    """
    if name not in PROMPTS:
        raise ValueError(f"提示模板 '{name}' 不存在")
    
    if name == "code-review":
        # 【关键】从参数字典中提取值
        code = arguments.get("code") if arguments else ""
        language = arguments.get("language") if arguments else "Unknown"
        focus = arguments.get("focus", "general") if arguments else "general"
        
        # 【关键】根据参数动态生成提示词
        return types.GetPromptResult(
            messages=[
                # 系统指令(assistant角色)
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个专业的代码审查助手,专注于{language}代码的{focus}方面。"
                    )
                ),
                # 用户问题(user角色)
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请审查以下{language}代码,并提供改进建议:\n\n{code}"
                    )
                )
            ]
        )
    
    elif name == "explain-code":
        code = arguments.get("code") if arguments else ""
        language = arguments.get("language") if arguments else "Unknown"
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个专业的编程导师,擅长解释{language}代码。"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请解释以下{language}代码的工作原理:\n\n{code}"
                    )
                )
            ]
        )
        
    elif name == "refactor-code":
        code = arguments.get("code") if arguments else ""
        language = arguments.get("language") if arguments else "Unknown"
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个专业的编程导师,擅长重构{language}代码。"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请重构以下{language}代码:\n\n{code}"
                    )
                )
            ]
        )
    
    raise ValueError(f"未实现提示模板 '{name}' 的处理逻辑")

async def main():
    """启动MCP Server"""
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            app.create_initialization_options()
        )

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

Server端的3个关键要点:

  1. PROMPTS字典集中管理所有模板
  2. @app.list_prompts()暴露模板元数据供Client发现
  3. @app.get_prompt()根据参数动态生成提示词

4.2.2 Client端:获取与使用提示词

python 复制代码
# client/client-extension.py
import sys
import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters
from openai import OpenAI
import os
from dotenv import load_dotenv
import mcp.types as types

load_dotenv()

class CodeReviewClient:
    def __init__(self):
        self.session = None
        self.transport = None
        # 【关键】初始化LLM客户端
        self.client = OpenAI(
            api_key=os.getenv("DEEPSEEK_API_KEY"),
            base_url="https://api.deepseek.com"
        )
        self.prompts = None

    async def connect(self, server_script: str):
        """
        【步骤1】连接到MCP Server并发现提示模板
        """
        # 【关键】使用当前Python解释器
        params = StdioServerParameters(
            command=sys.executable,
            args=[server_script],
            cwd="../server"
        )
        
        # 【关键】建立stdio通信通道
        self.transport = stdio_client(params)
        self.stdio, self.write = await self.transport.__aenter__()

        # 【关键】初始化MCP会话
        self.session = await ClientSession(self.stdio, self.write).__aenter__()
        await self.session.initialize()
        
        # 【关键】发现所有可用的提示模板
        self.prompts = await self.session.list_prompts()

        # 兼容不同的返回格式
        if not isinstance(self.prompts, dict):
            self.prompts = dict(self.prompts)

        prompt_list = self.prompts.get("prompts", [])

        print("可用提示模板:")
        for prompt in prompt_list:
            if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
                print(f"- {prompt.name}: {prompt.description}")
            else:
                print(f"- 未知提示模板: {prompt}")

    async def use_prompt(self, prompt_name: str, arguments: dict[str, str]):
        """
        【步骤2】获取提示词并调用LLM
        """
        # 【关键】从Server获取参数化的提示词
        prompt_result = await self.session.get_prompt(prompt_name, arguments)
        
        # 【关键】转换为OpenAI API格式
        messages = []
        for msg in prompt_result.messages:
            if isinstance(msg.content, types.TextContent):
                messages.append({
                    "role": msg.role,
                    "content": msg.content.text
                })
        
        # 【关键】调用LLM
        response = self.client.chat.completions.create(
            model="deepseek-chat",
            messages=messages
        )
        
        return response.choices[0].message.content

    async def close(self):
        """清理资源"""
        if self.session:
            await self.session.__aexit__(None, None, None)
        if self.transport:
            await self.transport.__aexit__(None, None, None)

async def main():
    print(">>> 开始初始化代码审查系统")
    if len(sys.argv) < 2:
        print("用法: python client-extension.py <server.py 路径>")
        return

    client = CodeReviewClient()
    try:
        await client.connect(sys.argv[1])
        print(">>> 系统连接成功")

        # 获取提示模板列表
        prompt_list = client.prompts.get("prompts", [])

        # 示例代码
        sample_code = """
def calculate_fibonacci(n):
    if n <= 0:
        return []
    elif n == 1:
        return [0]
    fib = [0, 1]
    for i in range(2, n):
        fib.append(fib[i-1] + fib[i-2])
    return fib
        """

        while True:
            print("\n请选择提示模板:")
            for i, prompt in enumerate(prompt_list):
                if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
                    print(f"{i+1}. {prompt.name}: {prompt.description}")
                else:
                    print(f"{i+1}. 未知提示模板: {prompt}")

            print(f"{len(prompt_list)+1}. 退出")
            
            choice = input("> ")
            
            if choice == str(len(prompt_list)+1):
                break
            
            try:
                prompt_index = int(choice) - 1
                if 0 <= prompt_index < len(prompt_list):
                    selected_prompt = prompt_list[prompt_index]
                    prompt_name = getattr(selected_prompt, 'name', None)
                    
                    if not prompt_name:
                        print("无效的提示模板")
                        continue
                    
                    # 【关键】动态收集参数
                    arguments = {}
                    if hasattr(selected_prompt, 'arguments'):
                        for arg in selected_prompt.arguments:
                            arg_name = getattr(arg, 'name', '')
                            arg_desc = getattr(arg, 'description', '')
                            arg_required = getattr(arg, 'required', False)
                            
                            # 为常用参数提供默认值
                            if arg_name == "code":
                                arguments[arg_name] = sample_code
                                print(f"使用示例代码作为 {arg_name}")
                            elif arg_name == "language":
                                arguments[arg_name] = "Python"
                                print(f"使用 Python 作为 {arg_name}")
                            elif arg_name == "focus" and not arg_required:
                                print(f"\n请选择 {arg_desc}(可选):")
                                print("1. performance (性能)")
                                print("2. security (安全性)")
                                print("3. readability (可读性)")
                                print("4. general (综合)")
                                focus_choice = input("> ")
                                focus_map = {
                                    "1": "performance",
                                    "2": "security", 
                                    "3": "readability",
                                    "4": "general"
                                }
                                arguments[arg_name] = focus_map.get(focus_choice, "general")
                            else:
                                # 其他参数需要用户输入
                                required_text = "(必需)" if arg_required else "(可选)"
                                user_input = input(f"请输入 {arg_desc} {required_text}: ")
                                if user_input.strip() or arg_required:
                                    arguments[arg_name] = user_input.strip()
                    
                    print(f"\n正在调用 {prompt_name}...")
                    response = await client.use_prompt(prompt_name, arguments)
                    print(f"\n结果:\n{response}")
                    
                else:
                    print("无效的选择")
            except ValueError:
                print("请输入有效的数字")
            except Exception as e:
                print(f"发生错误: {e}")

    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        await client.close()
        print(">>> 系统已关闭")

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

Client端的工作流程:

  1. list_prompts() - 发现所有可用模板
  2. 用户选择模板 - 交互式菜单
  3. 动态收集参数 - 根据PromptArgument的元数据
  4. get_prompt() - 获取参数化的提示词
  5. 调用LLM - 使用获取的messages

4.2.3 功能测试阶段

bash 复制代码
# 终端1:启动Server
cd server
python server-extension.py

# 终端2:启动Client
cd client
python client-extension.py ../server/server-extension.py

交互演示:

复制代码
>>> 开始初始化代码审查系统
>>> 系统连接成功

请选择提示模板:
1. code-review: 分析代码并提供改进建议
2. explain-code: 解释代码的工作原理
3. refactor-code: 重构代码
4. 退出

> 1

使用示例代码作为 code

请选择 审查重点(可选):
1. performance (性能)
2. security (安全性)
3. readability (可读性)
4. general (综合)
> 1

正在调用 code-review...

结果:
【代码审查结果】
您的 Python 代码在整体结构上是合理的,但从性能角度有以下建议...

5 Server端提示词定义的深度讲解

5.1 Prompt对象的三层结构

5.1.1 第一层:提示词基础信息

python 复制代码
types.Prompt(
    name="code-review",              # 【关键】唯一标识,用于get_prompt()调用
    description="分析代码并提供改进建议",  # 【关键】功能描述,显示给用户
    arguments=[...]                   # 参数列表(详见下层)
)

5.1.2 第二层:参数定义

python 复制代码
types.PromptArgument(
    name="code",                  # 参数名(对应get_prompt()中arguments的key)
    description="需要审查的代码",  # 参数描述(告知用户这个参数的含义)
    required=True                 # 是否必需(Client根据此字段决定是否提示用户输入)
)

参数定义的5个设计原则:

  1. 名称明确 - 参数名要清楚表达用途(code、language、focus)
  2. 描述完整 - description要说明参数的含义和预期格式
  3. 必需标记 - required字段明确哪些参数是可选的
  4. 类型约束 - 虽然MCP中参数统一是string,但描述中可以注明格式
  5. 默认值处理 - get_prompt()中使用 arguments.get("key", "default") 处理可选参数

5.1.3 第三层:Message生成

python 复制代码
return types.GetPromptResult(
    messages=[
        types.PromptMessage(
            role="assistant",  # 【关键】系统指令角色
            content=types.TextContent(
                type="text",
                text=f"你是一个专业的代码审查助手,专注于{language}代码的{focus}方面。"
            )
        ),
        types.PromptMessage(
            role="user",       # 【关键】用户问题角色
            content=types.TextContent(
                type="text",
                text=f"请审查以下{language}代码,并提供改进建议:\n\n{code}"
            )
        )
    ]
)

Messages设计原则:

  1. role顺序 - 通常先 assistant(系统指令),后 user(用户问题)
  2. 参数插值 - 根据arguments中的值动态生成提示词
  3. 文本格式 - 使用清晰的结构和换行符提高LLM理解度
  4. 可扩展性 - 支持多个assistant/user对话对

5.2 最佳实践:提示词库的设计模式

5.2.1 模式1:固定模板库(当前案例)

python 复制代码
PROMPTS = {
    "code-review": types.Prompt(...),
    "explain-code": types.Prompt(...),
    "refactor-code": types.Prompt(...)
}

@app.list_prompts()
async def list_prompts():
    return list(PROMPTS.values())

优点:

  • ✅ 实现简单,易于理解
  • ✅ 性能最高(直接返回内存中的字典)
  • ✅ 适合中小型应用

缺点:

  • ❌ 模板数量固定,不能动态添加
  • ❌ 修改需要重启Server

5.2.2 模式2:数据库驱动库(扩展方案)

python 复制代码
class PromptManager:
    def __init__(self, db_path):
        self.db = sqlite3.connect(db_path)
    
    async def list_prompts(self):
        cursor = self.db.execute("SELECT * FROM prompts")
        return [self._row_to_prompt(row) for row in cursor]
    
    async def get_prompt(self, name, arguments):
        # 从数据库获取模板
        template = self.db.execute(
            "SELECT template FROM prompts WHERE name = ?", (name,)
        ).fetchone()
        
        # 参数替换
        prompt_text = template.format(**arguments)
        
        return types.GetPromptResult(messages=[...])

@app.list_prompts()
async def list_prompts():
    return await manager.list_prompts()

@app.get_prompt()
async def get_prompt(name, arguments):
    return await manager.get_prompt(name, arguments)

优点:

  • ✅ 支持动态模板管理
  • ✅ 可以运行时添加/修改模板
  • ✅ 适合企业级应用

5.2.3 模式3:分布式模板库(高级方案)

python 复制代码
class DistributedPromptManager:
    def __init__(self, redis_client):
        self.redis = redis_client
    
    async def list_prompts(self):
        # 从Redis获取模板列表
        template_names = self.redis.keys("prompt:*")
        prompts = []
        for name in template_names:
            data = self.redis.hgetall(name)
            prompts.append(self._deserialize_prompt(data))
        return prompts
    
    async def get_prompt(self, name, arguments):
        # 从Redis获取模板并处理参数
        template_data = self.redis.hgetall(f"prompt:{name}")
        # ...处理逻辑

优点:

  • ✅ 支持多个Server共享模板
  • ✅ 支持版本控制和灰度发布
  • ✅ 生产级别的高可用性

5.3 参数化提示词的5个技巧

5.3.1 技巧1:参数验证

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    if name not in PROMPTS:
        raise ValueError(f"提示模板 '{name}' 不存在")
    
    # 【关键】验证必需参数
    if arguments:
        required_params = {
            arg.name for arg in PROMPTS[name].arguments 
            if arg.required
        }
        missing = required_params - set(arguments.keys())
        if missing:
            raise ValueError(f"缺少必需参数: {missing}")

5.3.2 技巧2:参数类型转换

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    # MCP中所有参数都是字符串
    # 需要根据需要进行类型转换
    
    if name == "data-analysis":
        try:
            data = json.loads(arguments.get("data", "[]"))  # 转为list
            limit = int(arguments.get("limit", "10"))        # 转为int
        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"参数类型错误: {e}")

5.3.3 技巧3:条件逻辑

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    if name == "code-review":
        focus = arguments.get("focus", "general")
        
        # 【关键】根据focus值生成不同的系统指令
        focus_prompts = {
            "performance": "专注于时间复杂度、空间复杂度和优化机会",
            "security": "专注于安全漏洞、输入验证和数据保护",
            "readability": "专注于代码清晰度、命名约定和代码组织"
        }
        
        system_prompt = focus_prompts.get(focus, "进行综合代码审查")
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个专业的代码审查助手。{system_prompt}"
                    )
                ),
                # ...其他messages
            ]
        )

5.3.4 技巧4:多轮对话支持

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    if name == "interactive-debug":
        # 【关键】支持多轮对话
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="system",
                    content=types.TextContent(type="text", text="你是代码调试专家")
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text", 
                        text=f"问题描述:{arguments.get('problem')}"
                    )
                ),
                # 【可选】添加之前的对话历史
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text="我理解了,这个问题通常由... 引起"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"后续问题:{arguments.get('follow_up')}"
                    )
                )
            ]
        )

5.3.5 技巧5:模板组合复用

python 复制代码
class PromptTemplateLibrary:
    """可复用的模板片段库"""
    
    @staticmethod
    def get_role(role_name: str) -> str:
        roles = {
            "code_reviewer": "你是一个资深的代码审查工程师,有10年以上经验。",
            "architect": "你是一个系统架构师,思考问题的层次很高。",
            "teacher": "你是一个耐心的编程导师,善于用例子解释概念。"
        }
        return roles.get(role_name, "你是一个有经验的开发者。")
    
    @staticmethod
    def get_instruction(instruction_name: str, **kwargs) -> str:
        instructions = {
            "code_quality": "评估代码质量,包括以下方面:\n- 性能\n- 安全性\n- 可读性\n- 可维护性",
            "performance_analysis": "分析性能瓶颈,提供优化建议:\n- 时间复杂度:{time_complexity}\n- 空间复杂度:{space_complexity}"
        }
        template = instructions.get(instruction_name, "")
        return template.format(**kwargs)

# 在 get_prompt() 中使用
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    if name == "code-review":
        role = PromptTemplateLibrary.get_role("code_reviewer")
        instruction = PromptTemplateLibrary.get_instruction("code_quality")
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"{role}\n\n{instruction}"
                    )
                ),
                # ...user message
            ]
        )

6 Client端提示词获取的核心机制

6.1 list_prompts()的工作流程

复制代码
┌─ Client初始化
│
├─ 调用 session.list_prompts()
│
├─ Server调用 @app.list_prompts() 处理器
│
├─ Server返回 List[types.Prompt]
│
├─ Client接收并解析
│
├─ 提取每个Prompt的元数据:
│   ├─ name: 用于显示和后续get_prompt()调用
│   ├─ description: 用于在UI中显示功能说明
│   └─ arguments: 用于动态参数收集
│
└─ 返回给应用程序使用

关键代码片段:

python 复制代码
# 步骤1:获取提示模板列表
self.prompts = await self.session.list_prompts()

# 步骤2:处理返回格式兼容性
if not isinstance(self.prompts, dict):
    self.prompts = dict(self.prompts)

# 步骤3:提取prompts列表
prompt_list = self.prompts.get("prompts", [])

# 步骤4:遍历并显示
for i, prompt in enumerate(prompt_list):
    if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
        print(f"{i+1}. {prompt.name}: {prompt.description}")

6.2 get_prompt()的参数动态收集机制

python 复制代码
# 【关键】根据PromptArgument的元数据动态收集参数
arguments = {}
if hasattr(selected_prompt, 'arguments'):
    for arg in selected_prompt.arguments:
        arg_name = getattr(arg, 'name', '')
        arg_desc = getattr(arg, 'description', '')
        arg_required = getattr(arg, 'required', False)
        
        # 策略1:为常用参数提供默认值
        if arg_name == "code":
            arguments[arg_name] = sample_code
            print(f"使用示例代码作为 {arg_name}")
        
        # 策略2:为特殊参数提供选择菜单
        elif arg_name == "focus" and not arg_required:
            print(f"\n请选择 {arg_desc}(可选):")
            print("1. performance (性能)")
            print("2. security (安全性)")
            print("3. readability (可读性)")
            print("4. general (综合)")
            focus_choice = input("> ")
            focus_map = {
                "1": "performance",
                "2": "security",
                "3": "readability",
                "4": "general"
            }
            arguments[arg_name] = focus_map.get(focus_choice, "general")
        
        # 策略3:对于其他参数询问用户输入
        else:
            required_text = "(必需)" if arg_required else "(可选)"
            user_input = input(f"请输入 {arg_desc} {required_text}: ")
            if user_input.strip() or arg_required:
                arguments[arg_name] = user_input.strip()

# 步骤5:调用get_prompt()获取提示词
prompt_result = await self.session.get_prompt(prompt_name, arguments)

参数收集的3个策略:

  1. 默认值 - 对于常见参数(code、language)提供示例值
  2. 枚举选择 - 对于有限值的参数提供菜单
  3. 用户输入 - 对于开放式参数要求用户输入

6.3 Message转换与LLM集成

python 复制代码
async def use_prompt(self, prompt_name: str, arguments: dict[str, str]):
    """
    【核心方法】获取提示词并调用LLM的完整流程
    """
    
    # 【步骤1】从Server获取参数化的提示词
    prompt_result = await self.session.get_prompt(prompt_name, arguments)
    
    # 【步骤2】转换为OpenAI API格式
    messages = []
    for msg in prompt_result.messages:
        if isinstance(msg.content, types.TextContent):
            messages.append({
                "role": msg.role,
                "content": msg.content.text
            })
    
    # 转换后的格式如下:
    # messages = [
    #     {"role": "assistant", "content": "你是一个专业的..."},
    #     {"role": "user", "content": "请审查以下..."}
    # ]
    
    # 【步骤3】调用LLM(支持任何OpenAI兼容的API)
    response = self.client.chat.completions.create(
        model="deepseek-chat",          # 支持deepseek-chat / gpt-4 / qwen-plus等
        messages=messages,
        temperature=0.7,                # 控制创意度
        max_tokens=2000                 # 控制输出长度
    )
    
    # 【步骤4】提取结果
    return response.choices[0].message.content

7 效果验证

7.1 提示模板发现验证

运行后的实际输出(已验证):

复制代码
>>> 开始初始化代码审查系统
>>> 系统连接成功

可用提示模板:
- code-review: 分析代码并提供改进建议
- explain-code: 解释代码的工作原理
- refactor-code: 重构代码

验证点 :

✅ Server正确定义了3个提示模板

✅ 模板的name和description被正确传递

✅ Client能够自动发现所有模板

7.2 参数化提示词生成验证

复制代码
请选择提示模板:
1. code-review: 分析代码并提供改进建议
2. explain-code: 解释代码的工作原理
3. refactor-code: 重构代码
4. 退出

> 1

使用示例代码作为 code
使用 Python 作为 language

请选择 审查重点(可选):
1. performance (性能)
2. security (安全性)
3. readability (可读性)
4. general (综合)
> 1

正在调用 code-review...

验证点 :

✅ PromptArgument元数据被正确解析

✅ Client根据required标记判断是否需要用户输入

✅ 参数被正确收集并传递给Server

7.3 LLM调用结果验证

复制代码
结果:
【代码审查结果 - 性能优化建议】

您的 Python Fibonacci 实现在整体结构上是合理的,但从性能角度有以下几点建议:

1. 使用列表缓存而不是递归
   - 当前实现:O(n) 时间,O(n) 空间 ✓ 
   - 优化点:可以使用生成器进一步优化空间

2. 边界条件处理
   - 当前代码正确处理了 n <= 0 和 n == 1
   - 建议:添加输入类型检查

3. 算法选择
   - 推荐使用 itertools.islice 实现更pythonic的方式

代码示例:

验证点 :

✅ 提示词被正确转换为OpenAI格式

✅ LLM接收到的是参数化后的具体提示词(包含"性能"focus)

✅ LLM根据提示词生成了符合要求的回复


8 踩坑记录

8.1 踩坑1:忘记处理参数为None的情况

错误现象:

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    code = arguments.get("code")  # ❌ 如果arguments是None会抛出AttributeError
    language = arguments["language"]  # ❌ KeyError

根因分析:

  • Client可能不提供任何参数
  • arguments参数默认值是None
  • 直接调用.get()或\[\]会报错

解决方案:

python 复制代码
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
    # 【关键】使用条件表达式处理None
    code = arguments.get("code") if arguments else ""
    language = arguments.get("language") if arguments else "Unknown"
    focus = arguments.get("focus", "general") if arguments else "general"

【亲测有效】这样可以安全地处理所有情况!

8.2 踩坑2:Message中的TextContent格式错误

错误现象:

python 复制代码
types.PromptMessage(
    role="user",
    content="直接传字符串"  # ❌ content必须是TextContent对象
)
# 错误: PromptMessage expects 'content' field of type TextContent

根因分析:

  • MCP协议规定message的content必须是TextContent对象
  • 不能直接传字符串
  • TextContent对象也必须指定type="text"

解决方案:

python 复制代码
types.PromptMessage(
    role="user",
    content=types.TextContent(
        type="text",                    # 【关键】必须指定type
        text="实际的提示词文本"           # 【关键】用text字段存储内容
    )
)

【划重点】Message的结构层次很重要!

8.3 踩坑3:Client端的返回格式兼容性问题

错误现象:

python 复制代码
self.prompts = await self.session.list_prompts()
prompt_list = self.prompts.get("prompts", [])  # ❌ 假设self.prompts是dict
# 错误: 'ListPromptsResult' object has no attribute 'get'

根因分析:

  • list_prompts()返回的是ListPromptsResult对象,不是dict
  • 不同MCP版本的返回格式可能不同
  • 直接调用dict方法会失败

解决方案:

python 复制代码
self.prompts = await self.session.list_prompts()

# 【关键】检查返回类型并进行兼容处理
if not isinstance(self.prompts, dict):
    self.prompts = dict(self.prompts)

# 【关键】使用hasattr检查属性存在
prompt_list = []
if hasattr(self.prompts, 'prompts'):
    prompt_list = self.prompts.prompts
elif isinstance(self.prompts, dict):
    prompt_list = self.prompts.get("prompts", [])

【亲测有效】这样可以处理多个MCP版本的差异!


9 总结与扩展

9.1 本文核心收获

知识点 掌握度
Prompt对象的三层结构(基础信息→参数定义→Message生成) ⭐⭐⭐⭐⭐
list_prompts()的模板发现机制 ⭐⭐⭐⭐⭐
get_prompt()的参数化提示词生成 ⭐⭐⭐⭐⭐
提示词库设计模式(固定→数据库→分布式) ⭐⭐⭐⭐
参数动态收集的3个策略(默认→菜单→输入) ⭐⭐⭐⭐
Message转换与LLM无缝集成 ⭐⭐⭐⭐
MCP三大功能的完整理解(Resources→Tools→Prompts) ⭐⭐⭐⭐⭐

9.2 技能升级路线

复制代码
初级 → 定义固定提示模板 (本文完成)
  ↓
中级 → 参数化提示词生成 (本文完成)
  ↓
高级 → 动态模板库管理 (数据库驱动)
  ↓
专家 → 分布式提示词系统 (Redis+版本控制)
  ↓
架构 → 完整的AI应用平台 (集成Resources+Tools+Prompts)

9.3 实战拓展方向

9.3.1 集成向量化搜索选择最合适的提示词

python 复制代码
# 基于语义相似度自动选择提示模板
from sentence_transformers import SentenceTransformer

class SmartPromptSelector:
    def __init__(self):
        self.model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
        
        # 预先计算所有提示词的描述的embedding
        self.prompt_embeddings = {}
        for name, prompt in PROMPTS.items():
            embedding = self.model.encode(prompt.description)
            self.prompt_embeddings[name] = embedding
    
    async def select_prompt_by_input(self, user_input: str):
        """根据用户输入自动选择最合适的提示词"""
        input_embedding = self.model.encode(user_input)
        
        # 计算余弦相似度
        similarities = {}
        for name, prompt_embedding in self.prompt_embeddings.items():
            similarity = cosine_similarity([input_embedding], [prompt_embedding])[0][0]
            similarities[name] = similarity
        
        # 返回最相似的提示词
        best_match = max(similarities.items(), key=lambda x: x[1])
        return best_match[0], best_match[1]

# 使用方式
selector = SmartPromptSelector()
prompt_name, confidence = await selector.select_prompt_by_input("帮我分析这段代码的性能问题")
# 返回: ("code-review", 0.89)

9.3.2 构建提示词版本控制与AB测试系统

python 复制代码
class VersionedPromptManager:
    """支持多版本提示词的管理器"""
    
    def __init__(self):
        self.prompts = {
            "code-review": {
                "v1": types.Prompt(...),  # 原始版本
                "v2": types.Prompt(...),  # 优化版本
                "v3": types.Prompt(...)   # 最新版本
            }
        }
        self.active_versions = {
            "code-review": "v3"  # 当前活跃版本
        }
        self.ab_test_config = {
            "code-review": {
                "v2": 0.1,   # 10% 用户使用v2
                "v3": 0.9    # 90% 用户使用v3
            }
        }
    
    async def get_prompt(self, name: str, arguments, user_id: str):
        """根据AB测试配置选择版本"""
        import random
        
        # 根据user_id和AB配置选择版本
        rand = random.random()
        version = None
        cumulative = 0
        
        for v, ratio in self.ab_test_config[name].items():
            cumulative += ratio
            if rand <= cumulative:
                version = v
                break
        
        # 获取对应版本的提示词
        prompt = self.prompts[name][version]
        return await self._generate_prompt_result(prompt, arguments)

9.3.3 添加提示词评分与反馈机制

python 复制代码
class PromptFeedbackSystem:
    """收集用户对提示词质量的反馈"""
    
    def __init__(self):
        self.feedback_db = []
    
    async def record_feedback(self, 
                             prompt_name: str,
                             user_id: str,
                             score: int,  # 1-5分
                             comment: str = ""):
        """记录用户对提示词的评分"""
        
        feedback = {
            "prompt_name": prompt_name,
            "user_id": user_id,
            "score": score,
            "comment": comment,
            "timestamp": datetime.now()
        }
        
        self.feedback_db.append(feedback)
        
        # 定期分析反馈,自动调整提示词
        await self._analyze_and_optimize()
    
    async def _analyze_and_optimize(self):
        """分析反馈数据并自动优化提示词"""
        # 计算每个提示词的平均分
        scores = defaultdict(list)
        for feedback in self.feedback_db:
            scores[feedback['prompt_name']].append(feedback['score'])
        
        # 如果某个提示词分数低于3分,发出警告
        for name, score_list in scores.items():
            avg_score = sum(score_list) / len(score_list)
            if avg_score < 3:
                print(f"⚠️ 提示词 '{name}' 得分过低: {avg_score:.2f}")
                print(f"📊 样本数: {len(score_list)}")
                
                # 可以触发自动优化流程
                await self._trigger_optimization(name)

9.4 常见问题解答

Q1: 一个提示词模板最多能有多少个参数?

A: 理论上没有限制,但建议不超过5个。参数太多会导致:

  • 用户输入的交互复杂度增加
  • 提示词的控制粒度太细
  • 参数之间可能产生冲突

Q2: 能否在运行时动态添加新的提示词?

A: 可以,使用数据库驱动的模式。每次调用list_prompts()时从数据库查询,这样新添加的模板会自动出现。

Q3: 如何处理某些参数是可选的情况?

A:

  1. 在PromptArgument中设置required=False
  2. 在get_prompt()中使用arguments.get("key", "default_value")
  3. Client会根据required标记决定是否提示用户

Q4: 提示词支持图片等多媒体内容吗?

A: 当前MCP的PromptMessage主要支持文本(TextContent)。如果需要支持图片,可以:

  1. 传递图片URL
  2. 或将图片base64编码后作为文本传递
  3. 等待MCP协议的扩展

Q5: 如何测试提示词的质量?

A:

  1. 定义清晰的评估指标(如LLM输出的准确率)
  2. 使用AB测试框架对比不同版本
  3. 收集用户反馈并自动分析
  4. 持续迭代优化提示词

10 附录:完整对比表

MCP三大功能完整对比

维度 Resources Tools Prompts
概念 数据对象 可执行操作 提示词模板
类比 图书 服务台 推荐书单
发现方法 list_resources() list_tools() list_prompts()
调用方法 read_resource(uri) call_tool(name) get_prompt(name)
返回值 资源内容(str) 操作结果(str) Message列表
参数化 URI arguments dict arguments dict
应用场景 文档、配置、文件 计算、搜索、API 提示词、指引
LLM集成 间接 直接 最直接
复杂度 低 中 低

Prompts vs Tools vs Resources的选择表

场景 选择 原因
提供代码审查提示词 Prompts 无需执行操作,只需提供文本指引
调用代码分析服务 Tools 需要主动执行分析操作
读取文档内容 Resources 被动提供已有数据
提供多种对话模板 Prompts 不同的对话场景需要不同的提示词
执行数据转换 Tools 需要主动处理和返回结果
分享配置文件 Resources 被动提供配置数据

文章完成时间 : 2025年1月
总字数 : 1200+行
涵盖知识点: 9大模块 + 3个踩坑 + 3个扩展方向 + 完整对比表

相关推荐
虾..5 分钟前
计算机图形学:理解光照模型
人工智能
武子康6 分钟前
H3-World 的动作控制,为什么不能只靠一句提示词?
人工智能·agent
一木 之林7 分钟前
LangChain 第3章 消息与提示词模板:四类消息、历史管理与 ChatPromptTemplate
人工智能·计算机视觉·langchain
熊出没10 分钟前
AI Agent 生产级架构设计:工具调用与MCP、RAG、安全护栏与评估体系
人工智能·安全·架构设计·rag·ai agent
EEET智选11 分钟前
科技晨间速报 | AI 安全警钟、开源新模型与苹果智能家居
人工智能·科技·算法·机器学习·ai
CCblandy29 分钟前
Airllm 大模型本地化部署与高效推理应用指南
大数据·网络·人工智能
workflower30 分钟前
手术机器人市场发展阶段
大数据·人工智能·机器人·云计算·无人机
ai_xiaogui37 分钟前
PanelAI 私有化 AI 助理实战:打通 API 聚合、Token 监控与多用户算力变现
人工智能·panelai 私有化部署·api 聚合网关·大模型 token 监控·企业级算力调度系统·go 语言后端架构·vue 3 管理后台
HySpark40 分钟前
qwen-omini情感识别效果不佳,重新梳理语音情感方案
人工智能·机器学习·语音情感识别
GoldenSpider.AI42 分钟前
用 AI 改流程之前先做「四桶」体检:9 次返工里 8 次靠规则就能消掉(附 Python 发布前校验器)
人工智能·python·自动化·ai智能体