学习第17天:Agent Memory 与 MCP 协议
贯穿项目:Enterprise AI Agent Platform --- 本章为 Agent 赋予长期记忆,并实现 MCP 协议集成外部工具
1. 学习目标
完成本章学习后,你将能够:
- 理解 Agent Memory 的三层架构:短期/长期/工作记忆
- 实现对话摘要记忆 、向量记忆 、结构化记忆
- 理解 MCP(Model Context Protocol) 协议的设计理念
- 用 MCP Python SDK 构建 MCP Server 和 Client
- 将 MCP 工具集成到 LangChain/LangGraph Agent 中
- 设计多 Agent 间的记忆共享机制
| 概念 | 含义 | 类比 |
|---|---|---|
| Working Memory | 当前对话上下文 | CPU 寄存器 |
| Short-term Memory | 本次会话历史 | RAM |
| Long-term Memory | 跨会话持久化记忆 | 硬盘/数据库 |
| MCP | 模型上下文协议 | USB-C 统一接口标准 |
| MCP Server | 提供工具/资源的服务 | REST API Server |
| MCP Client | 调用 MCP Server 的客户端 | HTTP Client |
2. 知识体系图
#mermaid-svg-mUT4prrmWLP2ScPn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mUT4prrmWLP2ScPn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mUT4prrmWLP2ScPn .error-icon{fill:#552222;}#mermaid-svg-mUT4prrmWLP2ScPn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mUT4prrmWLP2ScPn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mUT4prrmWLP2ScPn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mUT4prrmWLP2ScPn .marker.cross{stroke:#333333;}#mermaid-svg-mUT4prrmWLP2ScPn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mUT4prrmWLP2ScPn p{margin:0;}#mermaid-svg-mUT4prrmWLP2ScPn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mUT4prrmWLP2ScPn .cluster-label text{fill:#333;}#mermaid-svg-mUT4prrmWLP2ScPn .cluster-label span{color:#333;}#mermaid-svg-mUT4prrmWLP2ScPn .cluster-label span p{background-color:transparent;}#mermaid-svg-mUT4prrmWLP2ScPn .label text,#mermaid-svg-mUT4prrmWLP2ScPn span{fill:#333;color:#333;}#mermaid-svg-mUT4prrmWLP2ScPn .node rect,#mermaid-svg-mUT4prrmWLP2ScPn .node circle,#mermaid-svg-mUT4prrmWLP2ScPn .node ellipse,#mermaid-svg-mUT4prrmWLP2ScPn .node polygon,#mermaid-svg-mUT4prrmWLP2ScPn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mUT4prrmWLP2ScPn .rough-node .label text,#mermaid-svg-mUT4prrmWLP2ScPn .node .label text,#mermaid-svg-mUT4prrmWLP2ScPn .image-shape .label,#mermaid-svg-mUT4prrmWLP2ScPn .icon-shape .label{text-anchor:middle;}#mermaid-svg-mUT4prrmWLP2ScPn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mUT4prrmWLP2ScPn .rough-node .label,#mermaid-svg-mUT4prrmWLP2ScPn .node .label,#mermaid-svg-mUT4prrmWLP2ScPn .image-shape .label,#mermaid-svg-mUT4prrmWLP2ScPn .icon-shape .label{text-align:center;}#mermaid-svg-mUT4prrmWLP2ScPn .node.clickable{cursor:pointer;}#mermaid-svg-mUT4prrmWLP2ScPn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mUT4prrmWLP2ScPn .arrowheadPath{fill:#333333;}#mermaid-svg-mUT4prrmWLP2ScPn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mUT4prrmWLP2ScPn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mUT4prrmWLP2ScPn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mUT4prrmWLP2ScPn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mUT4prrmWLP2ScPn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mUT4prrmWLP2ScPn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mUT4prrmWLP2ScPn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mUT4prrmWLP2ScPn .cluster text{fill:#333;}#mermaid-svg-mUT4prrmWLP2ScPn .cluster span{color:#333;}#mermaid-svg-mUT4prrmWLP2ScPn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mUT4prrmWLP2ScPn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mUT4prrmWLP2ScPn rect.text{fill:none;stroke-width:0;}#mermaid-svg-mUT4prrmWLP2ScPn .icon-shape,#mermaid-svg-mUT4prrmWLP2ScPn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mUT4prrmWLP2ScPn .icon-shape p,#mermaid-svg-mUT4prrmWLP2ScPn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mUT4prrmWLP2ScPn .icon-shape .label rect,#mermaid-svg-mUT4prrmWLP2ScPn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mUT4prrmWLP2ScPn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mUT4prrmWLP2ScPn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mUT4prrmWLP2ScPn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 记忆管理策略
MCP 协议
Agent Memory 架构
三层记忆体系
Working: 当前窗口内消息
Short-term: 会话内全部历史
Long-term: 摘要+向量+结构化
MCP 统一接口
Resources: 数据资源暴露
Tools: 工具调用
Prompts: 预定义 Prompt
Sampling: LLM 采样
记忆操作
写入: 对话结束自动保存
检索: 向量相似度+时间权重
遗忘: TTL + 重要性过滤
共享: 多 Agent 记忆总线
3. 核心知识
3.1 Agent Memory 的三层模型
┌──────────────────────────────────────────┐
│ Working Memory(工作记忆) │
│ - 当前上下文窗口内的所有消息 │
│ - 最多 128K tokens(取决于模型) │
│ - 每次 LLM 调用都会发送 │
│ - 受 token 预算限制,需裁剪 │
├──────────────────────────────────────────┤
│ Short-term Memory(短期记忆) │
│ - 当前会话的全部对话历史 │
│ - 存储在 Redis/内存中 │
│ - 会话结束后可选择性地保存为长期记忆 │
├──────────────────────────────────────────┤
│ Long-term Memory(长期记忆) │
│ - 跨会话的持久化记忆 │
│ - 三种形式: │
│ 1. 摘要记忆:对话的压缩总结 │
│ 2. 向量记忆:Embedding + 向量检索 │
│ 3. 结构化记忆:用户偏好、知识图谱 │
└──────────────────────────────────────────┘
3.2 MCP 协议概览
MCP(Model Context Protocol)是 Anthropic 提出的开放标准,用于 LLM 与外部工具/数据源之间的通信。
类比理解:
- MCP 之于 LLM 工具 = USB-C 之于外设
- 以前:每个 LLM 平台有自己的一套 Tool 定义格式
- MCP:统一标准,一次开发,所有支持 MCP 的 LLM 都能用
MCP 的四大能力:
| 能力 | 说明 | 类比 |
|---|---|---|
| Resources | 暴露数据资源(文件、数据库等) | GET /api/resource |
| Tools | 暴露可执行的工具 | POST /api/tool |
| Prompts | 预定义的 Prompt 模板 | 模板库 |
| Sampling | Server 请求 Client 调用 LLM | 反向调用 |
4. 详细讲解
4.1 短期记忆:对话摘要
python
from langchain_core.messages import SystemMessage
from langchain_openai import ChatOpenAI
class ConversationSummarizer:
"""对话摘要器------将长对话压缩为摘要"""
def __init__(self, llm: ChatOpenAI):
self.llm = llm
async def summarize(
self, messages: list, max_summary_tokens: int = 500
) -> str:
"""对对话历史生成摘要"""
# 将消息列表转为文本
conversation_text = "\n".join([
f"{m.role}: {m.content[:200]}" for m in messages
])
prompt = f"""请用不超过 {max_summary_tokens} 字总结以下对话的关键信息:
{conversation_text}
总结应包括:
1. 用户的核心问题和需求
2. AI 的回答要点
3. 重要的决策和承诺
4. 待跟进的事项"""
response = await self.llm.ainvoke(prompt)
return response.content
4.2 长期记忆:向量记忆
python
# app/memory/vector_memory.py
import chromadb
from datetime import datetime
from typing import List, Optional
class VectorMemory:
"""基于向量的长期记忆
原理:将"记忆"Embedding 后存入向量库,查询时语义检索最相关的记忆
"""
def __init__(self, collection_name: str = "agent_memory"):
from chromadb.utils import embedding_functions
self.client = chromadb.PersistentClient(path="./memory_db")
self.ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=settings.OPENAI_API_KEY,
model_name="text-embedding-3-small",
)
self.collection = self.client.get_or_create_collection(
name=collection_name,
embedding_function=self.ef,
)
async def save(
self,
content: str,
memory_type: str = "conversation",
user_id: str = "default",
importance: float = 0.5,
metadata: dict = None,
):
"""保存一条记忆"""
import uuid
meta = {
"memory_type": memory_type,
"user_id": user_id,
"importance": importance,
"timestamp": datetime.now().isoformat(),
**(metadata or {}),
}
self.collection.add(
documents=[content],
metadatas=[meta],
ids=[str(uuid.uuid4())],
)
async def search(
self,
query: str,
n_results: int = 5,
user_id: str = "default",
min_importance: float = 0.0,
) -> List[dict]:
"""搜索相关记忆"""
results = self.collection.query(
query_texts=[query],
n_results=n_results,
where={
"user_id": user_id,
},
)
memories = []
for doc, meta, distance in zip(
results["documents"][0],
results["metadatas"][0],
results.get("distances", [[1.0]])[0],
):
if meta.get("importance", 0) >= min_importance:
memories.append({
"content": doc,
"metadata": meta,
"relevance": 1 - distance, # 距离 → 相似度
})
return memories
async def forget_old(self, days: int = 30):
"""遗忘过旧的记忆(按时间)"""
# Chroma 不直接支持按时间过滤,需要维护一个元数据索引
# 生产环境建议用 Milvus 的时间分区功能
pass
4.3 结构化记忆:用户画像
python
# app/memory/profile_memory.py
from dataclasses import dataclass, field
from typing import Optional
import json
@dataclass
class UserProfile:
"""用户画像------结构化长期记忆"""
user_id: str
name: Optional[str] = None
preferences: dict = field(default_factory=dict)
skills: list[str] = field(default_factory=list)
known_facts: dict = field(default_factory=dict) # key: fact_type, value: fact
interaction_count: int = 0
last_interaction: str = ""
class ProfileMemory:
"""用户画像记忆管理"""
def __init__(self, redis_client):
self.redis = redis_client
async def get_profile(self, user_id: str) -> UserProfile:
"""获取用户画像"""
key = f"profile:{user_id}"
data = await self.redis.get(key)
if data:
return UserProfile(**json.loads(data))
return UserProfile(user_id=user_id)
async def update_profile(
self, user_id: str, updates: dict
) -> UserProfile:
"""更新用户画像"""
profile = await self.get_profile(user_id)
if "name" in updates:
profile.name = updates["name"]
if "preferences" in updates:
profile.preferences.update(updates["preferences"])
if "fact" in updates:
fact_type = updates["fact"]["type"]
profile.known_facts[fact_type] = updates["fact"]["value"]
profile.interaction_count += 1
profile.last_interaction = datetime.now().isoformat()
key = f"profile:{user_id}"
await self.redis.set(key, json.dumps(profile.__dict__))
return profile
async def extract_and_save(
self, user_id: str, conversation_text: str, llm
):
"""从对话中提取结构化信息并保存"""
prompt = f"""从以下对话中提取用户信息,返回 JSON:
{conversation_text}
提取字段:
- name: 用户姓名
- preferences: 偏好(如喜欢的编程语言、技术栈)
- facts: 用户陈述的事实(如所在公司、职位)
JSON:"""
response = await llm.ainvoke(prompt)
extracted = json.loads(response.content)
await self.update_profile(user_id, extracted)
4.4 MCP 协议实现
python
# mcp_server.py --- MCP Server 端
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
# 创建 MCP Server
server = Server("agent-platform-mcp")
# 注册工具
@server.list_tools()
async def list_tools() -> list[Tool]:
"""返回可用工具列表"""
return [
Tool(
name="search_knowledge_base",
description="搜索企业知识库",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询"},
"category": {
"type": "string",
"enum": ["tech", "product", "policy"],
"default": "all",
},
},
"required": ["query"],
},
),
Tool(
name="get_user_info",
description="获取用户信息",
inputSchema={
"type": "object",
"properties": {
"user_id": {"type": "string"},
},
"required": ["user_id"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""执行工具调用"""
if name == "search_knowledge_base":
query = arguments["query"]
category = arguments.get("category", "all")
# 实际调用知识库搜索
result = f"搜索'{query}'(类别:{category})的结果..."
return [TextContent(type="text", text=result)]
elif name == "get_user_info":
user_id = arguments["user_id"]
# 实际查询用户信息
return [TextContent(type="text", text=f"用户{user_id}的信息...")]
return [TextContent(type="text", text=f"未知工具: {name}")]
# 注册资源
@server.list_resources()
async def list_resources() -> list:
"""返回可用资源列表"""
return [
{
"uri": "knowledge://company/policies",
"name": "公司政策",
"description": "公司内部政策文档",
"mimeType": "text/markdown",
},
]
@server.read_resource()
async def read_resource(uri: str) -> str:
"""读取资源内容"""
if uri == "knowledge://company/policies":
return "# 公司政策\n\n## 年假政策\n..."
return f"未找到资源: {uri}"
async def main():
"""启动 MCP Server"""
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationCapabilities(
sampling={},
experimental={},
),
)
if __name__ == "__main__":
asyncio.run(main())
python
# mcp_client.py --- MCP Client 端(集成到 Agent)
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
class MCPIntegration:
"""MCP 协议集成------将 MCP Server 的工具接入 Agent"""
def __init__(self, server_command: str, server_args: list[str]):
self.server_params = StdioServerParameters(
command=server_command,
args=server_args,
)
async def get_tools(self):
"""获取 MCP Server 提供的所有工具"""
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 加载 MCP 工具为 LangChain 工具
tools = await load_mcp_tools(session)
return tools
async def execute_tool(self, tool_name: str, **kwargs):
"""执行 MCP 工具"""
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(tool_name, kwargs)
return result
# 在 Agent 中使用 MCP 工具
async def integrate_mcp_to_agent(agent_service):
"""将 MCP 工具集成到 Agent"""
mcp = MCPIntegration(
server_command="python",
server_args=["mcp_server.py"],
)
mcp_tools = await mcp.get_tools()
# 注册到 Agent 的 Tool Registry
for tool in mcp_tools:
agent_service.register_tool(
name=tool.name,
description=tool.description,
func=tool.arun if hasattr(tool, "arun") else tool.run,
)
5. 代码示例
5.1 贯穿项目:统一记忆管理器
python
# app/memory/manager.py
from app.memory.vector_memory import VectorMemory
from app.memory.profile_memory import ProfileMemory
import structlog
logger = structlog.get_logger(__name__)
class MemoryManager:
"""统一记忆管理器
协调三层记忆的读写,为 Agent 提供"记得"的能力
"""
def __init__(
self,
vector_memory: VectorMemory,
profile_memory: ProfileMemory,
short_term_store: dict = None,
):
self.vector = vector_memory
self.profile = profile_memory
self.short_term: dict[str, list] = short_term_store or {}
def get_conversation(self, session_id: str) -> list:
"""获取短期记忆(当前会话)"""
return self.short_term.get(session_id, [])
def add_message(self, session_id: str, message: dict):
"""添加消息到短期记忆"""
if session_id not in self.short_term:
self.short_term[session_id] = []
self.short_term[session_id].append(message)
# 超过 50 轮自动触发摘要
if len(self.short_term[session_id]) > 100:
self._trigger_summary(session_id)
async def search_memory(
self, query: str, user_id: str = "default"
) -> list[dict]:
"""搜索长期记忆"""
return await self.vector.search(query, user_id=user_id)
async def save_to_long_term(
self, session_id: str, user_id: str,
summarizer, llm,
):
"""将短期记忆存档为长期记忆"""
messages = self.short_term.pop(session_id, [])
if not messages:
return
# 生成摘要
summary = await summarizer.summarize(messages)
# 保存为向量记忆
await self.vector.save(
content=summary,
memory_type="conversation_summary",
user_id=user_id,
importance=0.7,
metadata={"session_id": session_id, "message_count": len(messages)},
)
# 提取并更新用户画像
conversation_text = "\n".join([
f"{m['role']}: {m['content']}" for m in messages
])
await self.profile.extract_and_save(user_id, conversation_text, llm)
logger.info(
"memory_archived",
session_id=session_id,
messages=len(messages),
)
async def build_enriched_context(
self, user_input: str, session_id: str, user_id: str
) -> str:
"""构建增强上下文------将记忆注入 Prompt"""
context_parts = []
# 1. 用户画像
profile = await self.profile.get_profile(user_id)
if profile.name:
context_parts.append(f"用户姓名:{profile.name}")
if profile.preferences:
pref_str = ", ".join(f"{k}={v}" for k, v in profile.preferences.items())
context_parts.append(f"用户偏好:{pref_str}")
# 2. 相关长期记忆
memories = await self.vector.search(user_input, user_id=user_id, n_results=3)
if memories:
context_parts.append("相关历史对话:")
for m in memories[:3]:
context_parts.append(f"- {m['content'][:200]}")
return "\n".join(context_parts)
5.2 带记忆的 Agent Prompt
python
MEMORY_AWARE_PROMPT = """你是 {agent_name},一个智能 AI Agent。
## 用户画像
{user_profile}
## 相关历史记忆
{relevant_memories}
## 当前对话历史
{conversation_history}
## 用户最新消息
{user_input}
请基于以上所有信息回应用户。如果历史记忆中有相关的上下文,请自然地引用它。
"""
6. 实战案例
案例1:个人助手 --- 完整的记忆系统
python
# 构建一个"记得一切"的个人助手
class PersonalAssistant:
def __init__(self, memory_manager, agent_service, llm):
self.memory = memory_manager
self.agent = agent_service
self.llm = llm
async def chat(self, user_id: str, session_id: str, message: str) -> str:
# 1. 获取用户画像 + 相关记忆
profile = await self.memory.profile.get_profile(user_id)
memories = await self.memory.search_memory(message, user_id)
# 2. 获取当前对话历史
history = self.memory.get_conversation(session_id)
# 3. 构建增强 Prompt
enriched_prompt = MEMORY_AWARE_PROMPT.format(
agent_name="小助手",
user_profile=f"姓名:{profile.name or '未知'}\n偏好:{profile.preferences}",
relevant_memories="\n".join([m["content"][:200] for m in memories]),
conversation_history="\n".join([
f"{m['role']}: {m['content'][:100]}" for m in history[-10:]
]),
user_input=message,
)
# 4. 调用 Agent
response = await self.agent.run(enriched_prompt)
# 5. 更新记忆
self.memory.add_message(session_id, {"role": "user", "content": message})
self.memory.add_message(session_id, {"role": "assistant", "content": response})
# 6. 提取并更新用户画像
await self.memory.profile.extract_and_save(
user_id,
f"User: {message}\nAssistant: {response}",
self.llm,
)
return response
案例2:MCP 多工具集成------数据库 + 天气 + 知识库
这个案例演示如何配置多个 MCP Server,让 Agent 同时具备数据库查询、天气查询和知识库搜索能力:
python
"""
MCP 多 Server 配置示例。
安装:
uv add mcp
启动方式:
方式一:通过 IDE(如 WorkBuddy/Cursor)的 MCP 配置文件管理
方式二:通过代码手动启动 MCP Server 进程
场景:Agent 需要同时访问以下三个 MCP Server:
1. database-mcp:查询业务数据库
2. weather-mcp:查询天气信息
3. knowledge-mcp:搜索企业知识库
"""
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from contextlib import AsyncExitStack
class MultiMCPManager:
"""
多 MCP Server 管理器。
同时管理多个 MCP Server 的连接,获取所有可用工具列表,
并支持工具调用路由------根据工具名自动路由到正确的 Server。
"""
def __init__(self):
self.sessions: dict[str, ClientSession] = {}
self._exit_stack = AsyncExitStack()
self._tools_cache: list[dict] = []
async def connect_all(self):
"""连接所有 MCP Server"""
servers = {
"database": StdioServerParameters(
command="uv",
args=["run", "database-mcp-server"],
),
"weather": StdioServerParameters(
command="uv",
args=["run", "weather-mcp-server"],
),
"knowledge": StdioServerParameters(
command="uv",
args=["run", "knowledge-mcp-server"],
),
}
for name, params in servers.items():
try:
transport = await self._exit_stack.enter_async_context(
stdio_client(params)
)
read_stream, write_stream = transport
session = await self._exit_stack.enter_async_context(
ClientSession(read_stream, write_stream)
)
await session.initialize()
self.sessions[name] = session
print(f"✅ MCP Server [{name}] 已连接")
except Exception as e:
print(f"❌ MCP Server [{name}] 连接失败: {e}")
async def list_all_tools(self) -> list[dict]:
"""列出所有 MCP Server 提供的工具"""
all_tools = []
for name, session in self.sessions.items():
try:
result = await session.list_tools()
for tool in result.tools:
# 给工具名加前缀以区分来源
all_tools.append({
"name": f"{name}__{tool.name}",
"description": tool.description,
"source": name,
"original_name": tool.name,
})
except Exception as e:
print(f"列出 {name} 工具失败: {e}")
self._tools_cache = all_tools
return all_tools
async def call_tool(self, tool_name_with_prefix: str, arguments: dict):
"""根据带前缀的工具名路由到正确的 MCP Server"""
source, original_name = tool_name_with_prefix.split("__", 1)
session = self.sessions.get(source)
if not session:
raise ValueError(f"MCP Server [{source}] 未连接")
return await session.call_tool(original_name, arguments)
async def close_all(self):
"""关闭所有 MCP Server 连接"""
await self._exit_stack.aclose()
self.sessions.clear()
print("👋 所有 MCP Server 已断开")
# ===== 在 Agent 中使用多 MCP =====
async def agent_with_multi_mcp(prompt: str):
"""Agent 使用多个 MCP Server 处理用户请求"""
manager = MultiMCPManager()
await manager.connect_all()
# 获取所有可用工具
tools = await manager.list_all_tools()
print(f"可用工具数: {len(tools)}")
for t in tools:
print(f" - {t['name']}: {t['description'][:50]}...")
# 构建工具描述给 LLM
tools_desc = "\n".join([
f"- {t['name']}: {t['description']}" for t in tools
])
# LLM 决定调用哪个工具
llm_prompt = f"""你是一个智能 Agent,可以使用以下工具:
{tools_desc}
用户请求:{prompt}
分析需要使用哪些工具,以 JSON 格式返回工具调用列表:
[{{"tool": "database__query", "args": {{"sql": "SELECT ..."}}}}, ...]"""
# 这里简化了 LLM 调用流程
# 实际项目中用 LangChain/LangGraph 的 Tool Calling
await manager.close_all()
案例3:多 Agent 共享记忆
当多个 Agent 协作时,需要共享记忆以实现上下文传递。以下是基于 Redis Pub/Sub 的跨 Agent 记忆总线:
python
import json
import asyncio
from datetime import datetime
from typing import Optional
class SharedMemoryBus:
"""
多 Agent 共享记忆总线。
允许多个 Agent 实例之间共享短期记忆------当一个 Agent 学到新信息,
其他 Agent 可以实时感知。
使用 Redis Pub/Sub 作为通信通道(生产环境)或内存队列(开发环境)。
"""
def __init__(self, use_redis: bool = False, redis_url: str = None):
self._subscribers: dict[str, list[callable]] = {}
self._memory_log: list[dict] = [] # 共享记忆日志
self.use_redis = use_redis
async def publish(self, event_type: str, agent_id: str, payload: dict):
"""
发布事件到共享记忆总线。
例如:一个 Agent 发现用户偏好后发布,其他 Agent 接收并更新。
"""
event = {
"type": event_type,
"agent_id": agent_id,
"timestamp": datetime.now().isoformat(),
"payload": payload,
}
# 存入共享日志
self._memory_log.append(event)
# 通知所有订阅者
subscribers = self._subscribers.get(event_type, [])
for callback in subscribers:
try:
if asyncio.iscoroutinefunction(callback):
await callback(event)
else:
callback(event)
except Exception as e:
print(f"[MemoryBus] 回调失败: {e}")
def subscribe(self, event_type: str, callback: callable):
"""订阅特定类型的记忆事件"""
if event_type not in self._subscribers:
self._subscribers[event_type] = []
self._subscribers[event_type].append(callback)
async def query_shared_context(
self, agent_id: str, context_key: str
) -> Optional[str]:
"""查询其他 Agent 发布的相关信息"""
relevant = [
e for e in self._memory_log
if e["agent_id"] != agent_id # 不查自己发布的
and e["type"] == context_key
]
if relevant:
latest = relevant[-1]
return json.dumps(latest["payload"], ensure_ascii=False)
return None
# ===== 使用示例 =====
async def demo_shared_memory():
bus = SharedMemoryBus()
# Agent A:处理用户查询,发现用户偏好
async def on_user_preference_learned(event):
print(f" [Agent B 收到] Agent {event['agent_id']} 学到:{event['payload']}")
# Agent B 订阅偏好更新事件
bus.subscribe("user_preference", on_user_preference_learned)
# Agent A 发布新发现的用户偏好
await bus.publish("user_preference", "agent-sales", {
"user_id": "user_123",
"preference": "喜欢简洁的回复,不要太多术语",
"confidence": 0.9,
})
# Agent B 查询共享上下文
context = await bus.query_shared_context("agent-sales", "user_preference")
print(f"Agent B 查询到共享上下文: {context}")
await asyncio.sleep(0.1) # 等待异步回调
# 实际项目中的使用模式:
# 1. Customer Agent 发现用户偏好 → 发布到总线
# 2. Sales Agent 订阅 → 自动调整推销策略
# 3. Support Agent 订阅 → 知道用户喜欢简洁回答
# 4. 所有 Agent 共享同一份"用户理解"
Java 对照:SharedMemoryBus ≈ Spring Cloud Bus(事件总线) + Spring Event(应用事件),但更专注于 Agent 间的上下文共享而非系统级事件。
7. Java 对比
| 维度 | Java 做法 | Python 做法 |
|---|---|---|
| 短期记忆 | ChatMemory (Spring AI) |
内存 list / Redis |
| 长期记忆 | 需自建 | VectorMemory + Chroma |
| 记忆检索 | 数据库查询 | Embedding 语义搜索 |
| 用户画像 | UserProfile Service | ProfileMemory + Redis |
| MCP | Spring AI MCP Boot Starter | mcp Python SDK |
| 工具协议 | 无统一标准 | MCP 标准协议 |
8. 企业最佳实践
8.1 记忆系统设计原则
- 分层存储:短期 → 摘要 → 长期,逐层降级
- 重要性评分:不是所有对话都需要记住(LLM 评估重要性)
- 遗忘机制:TTL + 重要性衰减 + 存储上限
- 隐私优先:用户可以删除、导出记忆
8.2 MCP 服务设计
单个 MCP Server = 一个领域的所有工具
例如:
- knowledge-mcp:知识库搜索、文档读取
- database-mcp:数据库查询
- filesystem-mcp:文件操作
- email-mcp:邮件发送
9. 常见错误与解决方案
| 错误 | 原因 | 解决 |
|---|---|---|
| 记忆膨胀 | 无限保存所有对话 | 实施 TTL + 重要性过滤 |
| 检索不准确 | Embedding 质量不佳 | 使用更好的 Embedding 模型或 Hybrid Search |
| MCP 连接断开 | stdio 进程退出 | 加入重连机制 + 健康检查 |
| 画像信息冲突 | LLM 提取了矛盾的信息 | 带时间戳的版本化画像 |
10. 本章总结
本章完成了 Enterprise AI Agent Platform 的 记忆系统和 MCP 集成:
- 三层记忆:Working/Short-term/Long-term 三层架构
- 记忆检索:向量语义搜索 + 用户画像
- MCP 协议:统一工具接口标准
- 记忆增强 Prompt:自动注入用户画像和历史记忆
贯穿项目进度:Agent 拥有了长期记忆。最后一章将实现 Multi-Agent 和企业级部署。
11. 面试题
- 解释 Agent Memory 的三层架构。为什么需要分层?
- MCP 协议解决了什么问题?为什么需要统一标准?
- 向量记忆和结构化记忆各自的适用场景是什么?
- 如何防止 Agent 记忆隐私泄露?
12. 练习
练习1:实现带记忆的客服 Agent(难度:★★★)
构建一个"记得用户上次问了什么"的客服 Agent。
练习2:构建 MCP Server(难度:★★★★)
用 MCP SDK 构建一个提供天气查询和新闻搜索的 MCP Server。
练习3:记忆管理 CLI(难度:★★★)
实现 memory search <query>、memory forget <id>、memory export 命令。
13. 作业
- 实现完整的 MemoryManager
- 构建 MCP Server(知识库搜索)
- 将记忆和 MCP 集成到 Agent 服务
14. 预习
最后一章:Multi-Agent 与企业级 AI Agent 平台 --- 多 Agent 协作、部署、监控和成本管理。
文档版本 :v1.0 | 创建日期 :2026-07-23 | 适用 Python 版本:3.12+