虽然模型上下文协议(MCP)目前主要讨论的是工具集成,它的上下文管理能力同样是一个协议的重要方面,甚至更为基础。
MCP中的上下文管理应对了几个关键挑战:
- 有限的上下文窗口限制,大语言模型具有有限的上下文窗口。MCP 提供了一种标准的方法来管理、优先处理和压缩对话历史,以充分利用有限的空间。
- 带状态的对话,通过在模型外部维护对话状态,MCP 实现了跨越单次交互的更长、更连贯的对话。
- 内存管理,MCP 允许选择性地保留重要信息,同时丢弃不太相关的上下文信息,为 AI 系统创建更高效的“工作内存”。
如果实现得当,MCP 可以在不同会话甚至不同模型间保持上下文,为用户提供连续体验。
结构化的知识表示方法,MCP 并不是简单地将所有上下文视为一串扁平的标记,而是能够在上下文中以更结构化的方式表示知识。
增强检索,MCP提供了一个框架来从外部资源动态获取相关信息并将这些信息整合到上下文中。
这一工具连接方面可能更受重视,因为它让大型语言模型能够付诸行动,这种变化更为明显。
但其管理上下文的能力同样具有革命性,因为它们解决了这些问题,这些问题存在于LLM随时间与信息互动时的基本限制。
事实上,有效的管理上下文是有意义地使用工具的前提——模型需要知道之前使用了哪些工具,它们返回了什么信息,以及这些信息与当前对话的情况有何关联。
如果你喜欢这篇文章,可以给点喜爱的心 ❤️ - 拍手五十下,试试看,每次的效果可能比你想象的还要好 👏 - 关注我在 Medium 并免费订阅。 🫶 在 LinkedIn 或 X 上找我哦! 使用 LangChain 和模型上下文协议 (MCP)模型上下文协议 (MCP) 是 Anthropic 开发的开源协议,专注于安全和可解释性等……cobusgreyling.medium.com MCP & 上下文 MCP 服以下是一个MCP服务器和客户端管理上下文的实际例子。您可以在MacBook上本地运行此MCP配置……这里是如何做的……
打开终端窗口,创建一个叫做 mcp_context_server.py
的文件,将下面的代码复制进去,然后保存它。
# mcp_context_server.py
import json
import uuid
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse
# 简单的天气数据库
WEATHER_DATA = {
"New York": {"temperature": 72, "condition": "晴朗", "humidity": 65},
"London": {"temperature": 60, "condition": "雨", "humidity": 80},
"Tokyo": {"temperature": 75, "condition": "多云", "humidity": 70},
"Sydney": {"temperature": 80, "condition": "晴", "humidity": 55},
"Paris": {"temperature": 68, "condition": "阴", "humidity": 75}
}
class SessionContext:
"""表示带上下文数据的用户会话"""
def __init__(self, session_id):
self.session_id = session_id
self.created_at = time.time()
self.last_accessed = time.time()
self.data = {
"recent_searches": [],
"preferred_unit": "celsius",
"visits": 0
}
def update_access(self):
self.last_accessed = time.time()
self.data["visits"] += 1
def add_search(self, city):
# 仅保留最近5次搜索
if city not in self.data["recent_searches"]:
self.data["recent_searches"].insert(0, city)
self.data["recent_searches"] = self.data["recent_searches"][:5]
def set_preference(self, key, value):
self.data[key] = value
def to_dict(self):
return {
"session_id": self.session_id,
"created_at": self.created_at,
"last_accessed": self.last_accessed,
"data": self.data
}
class MCPRequestHandler(BaseHTTPRequestHandler):
def _set_headers(self, content_type='application/json'):
self.send_response(200)
self.send_header('Content-type', content_type)
# 如果需要设置cookie
if hasattr(self, 'should_set_cookie') and self.should_set_cookie:
self.send_header('Set-Cookie', f'session_id={self.new_session_id}; Path=/')
self.end_headers()
def _get_or_create_session(self):
# 检查客户端是否发送了会话cookie
session_id = None
if 'Cookie' in self.headers:
cookies = self.headers['Cookie'].split('; ')
for cookie in cookies:
if cookie.startswith('session_id='):
session_id = cookie.split('=')[1]
break
# 如果会话不存在或已过期,则创建新会话
if not session_id or session_id not in context_store:
session_id = str(uuid.uuid4())
context_store[session_id] = SessionContext(session_id)
# 过早设置cookie标头是不合适的
self.should_set_cookie = True # 设置发送标头时的cookie标志
self.new_session_id = session_id
else:
self.should_set_cookie = False
# 更新最后访问时间
context_store[session_id].update_access()
return context_store[session_id]
def _clean_expired_sessions(self, max_age=3600): # 1小时
"""移除超过 max_age 秒未被访问的会话"""
current_time = time.time()
expired_keys = []
for key, session in context_store.items():
if current_time - session.last_accessed > max_age:
expired_keys.append(key)
for key in expired_keys:
del context_store[key]
def do_GET(self):
self._clean_expired_sessions()
parsed_url = urlparse(self.path)
path = parsed_url.path
query = parse_qs(parsed_url.query)
session = self._get_or_create_session()
# 获取用户的温度偏好
unit = query.get('unit', [session.data.get('preferred_unit')])[0]
# 如果指定了温度单位,则设置偏好
if 'unit' in query:
session.set_preference('preferred_unit', unit)
if path == '/api/weather':
self._set_headers()
# 从查询参数中获取城市
if 'city' in query:
city = query['city'][0]
session.add_search(city)
if city in WEATHER_DATA:
data = WEATHER_DATA[city].copy()
# 根据用户偏好转换温度
if unit == 'fahrenheit' and 'temperature' in data:
# 数据存储为华氏度
pass
elif unit == 'celsius' and 'temperature' in data:
# 华氏度转摄氏度
data['temperature'] = round((data['temperature'] - 32) * 5 / 9, 1)
response = {
"city": city,
"weather": data,
"unit": unit
}
else:
response = {"error": f"城市 '{city}' 未找到"}
else:
response = {"cities": list(WEATHER_DATA.keys())}
# 在响应中包含会话上下文数据
response["context"] = session.to_dict()
self.wfile.write(json.dumps(response).encode())
elif path == '/api/context':
self._set_headers()
response = {"context": session.to_dict()}
self.wfile.write(json.dumps(response).encode())
else:
# 为手动测试提供简单的HTML界面
self._set_headers('text/html')
html = f"""<!DOCTYPE html>
<html>
<head>
<title>MCP Weather Service</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
.card {{ border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin-bottom: 15px; }}
.recent {{ color: #666; font-size: 0.9em; }}
</style>
</head>
<body>
<h1>MCP Weather Service</h1>
<div class="card">
<h2>您的会话</h2>
<p>会话ID:{session.session_id}</p>
<p>访问次数:{session.data["visits"]}</p>
<p>温度单位偏好:{session.data["preferred_unit"]}</p>
<form>
<label>
更改温度单位:
<select name="unit" onchange="this.form.submit()">
<option value="celsius" {"selected" if session.data["preferred_unit"] == "celsius" else ""}>摄氏度</option>
<option value="fahrenheit" {"selected" if session.data["preferred_unit"] == "fahrenheit" else ""}>华氏度</option>
</select>
</label>
</form>
</div>
<div class="card">
<h2>查看天气</h2>
<form action="/" method="get">
<select name="city">
{' '.join([f'<option value="{city}">{city}</option>' for city in WEATHER_DATA.keys()])}
</select>
<button type="submit">获取天气</button>
</form>
</div>
{"<div class='card'><h2>您的最近搜索</h2><ul>" +
''.join([f'<li><a href="/?city={city}">{city}</a></li>' for city in session.data["recent_searches"]]) +
"</ul></div>" if session.data["recent_searches"] else ""}
{"<div class='card'><h2>天气详情</h2>" +
f"<h3>{query['city'][0]} 的天气</h3>" +
f"<p>温度:{WEATHER_DATA[query['city'][0]]['temperature']}°F (" +
f"{round((WEATHER_DATA[query['city'][0]]['temperature'] - 32) * 5/9, 1)}°C)</p>" +
f"<p>天气状况:{WEATHER_DATA[query['city'][0]]['condition']}</p>" +
f"<p>湿度:{WEATHER_DATA[query['city'][0]]['humidity']}%</p></div>"
if 'city' in query and query['city'][0] in WEATHER_DATA else ""}
</body>
</html>
"""
self.wfile.write(html.encode())
def do_POST(self):
self._clean_expired_sessions()
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
request_json = json.loads(post_data.decode('utf-8'))
session = self._get_or_create_session()
if self.path == '/api/preferences':
# 更新用户偏好
for key, value in request_json.items():
session.set_preference(key, value)
self._set_headers()
response = {
"status": "success",
"message": "偏好更新成功",
"context": session.to_dict()
}
self.wfile.write(json.dumps(response).encode())
else:
self.send_response(404)
self.end_headers()
self.wfile.write(json.dumps({"error": "未找到"}).encode())
def run_server(port=8000):
server_address = ('', port)
httpd = HTTPServer(server_address, MCPRequestHandler)
print(f"启动 MCP 上下文感知服务器,端口 {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run_server()
然后在你的终端窗口中运行以下命令来启动服务器:python3 mcp_context_server.py
。
如图所示,以下是服务器运行时终端窗口的截图……命令行没有恢复,如下所示,表明服务器正在运行中。
请注意,这都是在你自己的电脑上本地运行着。
创建一个名为 mcp_context_client.py
的文件,MCP 客户端将通过该文件运行。
将下面的代码复制到文件里,然后保存文件。
# mcp_context_client.py
import json
import requests
import sys
import os
class WeatherClient:
def __init__(self, server_url="http://localhost:8000"):
self.server_url = server_url
self.session = requests.Session() # 使用会话来维持Cookie
self.context = None
# 尝试从文件加载保存的会话ID
self.session_file = "session.json"
if os.path.exists(self.session_file):
try:
with open(self.session_file, "r") as f:
saved_data = json.load(f)
if "session_id" in saved_data:
self.session.cookies.set("session_id", saved_data["session_id"])
if "context" in saved_data:
self.context = saved_data["context"]
print("恢复之前的会话")
except Exception as e:
print(f"加载会话出错: {e}")
def save_session(self):
"""将会话ID和上下文保存到文件中"""
if "session_id" in self.session.cookies:
session_data = {
"session_id": self.session.cookies.get("session_id"),
"context": self.context
}
with open(self.session_file, "w") as f:
json.dump(session_data, f)
def get_cities(self):
"""获取可用城市列表"""
response = self.session.get(f"{self.server_url}/api/weather")
data = response.json()
# 更新上下文(如果有)
if "context" in data:
self.context = data["context"]
self.save_session()
return data["cities"]
def get_weather(self, city, unit=None):
"""获取特定城市的天气信息"""
url = f"{self.server_url}/api/weather?city={city}"
# 如果提供了单位,则将其添加到URL中
if unit:
url += f"&unit={unit}"
elif self.context and "data" in self.context and "preferred_unit" in self.context["data"]:
url += f"&unit={self.context['data']['preferred_unit']}"
response = self.session.get(url)
data = response.json()
# 更新上下文(如果有)
if "context" in data:
self.context = data["context"]
self.save_session()
return data
def update_preferences(self, preferences):
"""更新用户偏好"""
response = self.session.post(
f"{self.server_url}/api/preferences",
json=preferences
)
data = response.json()
# 更新上下文(如果有)
if "context" in data:
self.context = data["context"]
self.save_session()
return data
def get_context(self):
"""获取当前会话上下文"""
if not self.context:
response = self.session.get(f"{self.server_url}/api/context")
data = response.json()
self.context = data["context"]
self.save_session()
return self.context
def display_weather(self, weather_data):
"""漂亮地显示天气信息"""
if "error" in weather_data:
print(f"\n出错信息: {weather_data['error']}")
return
city = weather_data["city"]
weather = weather_data["weather"]
unit = weather_data.get("unit", "celsius")
unit_symbol = "°F" if unit == "fahrenheit" else "°C"
print(f"\n{city}的天气:")
print(f"气温: {weather['temperature']}{unit_symbol}")
print(f"天气状况: {weather['condition']}")
print(f"湿度: {weather['humidity']}%")
def display_context(self):
"""显示当前上下文信息"""
context = self.get_context()
if not context:
print("\n没有可用的上下文")
return
print("\n=== 你的会话上下文 ===")
print(f"会话ID: {context['session_id']}")
print(f"创建时间: {context['created_at']}")
print(f"最后访问时间: {context['last_accessed']}")
print("\n偏好设置:")
print(f"温度单位: {context['data']['preferred_unit']}")
print(f"访问记录数: {context['data']['visits']}")
if context['data']['recent_searches']:
print("\n最近搜索:")
for i, city in enumerate(context['data']['recent_searches'], 1):
print(f"{i}. {city}")
def run_client():
client = WeatherClient()
while True:
print("\n--- MCP 天气客户端带上下文 ---")
print("1. 列出所有城市")
print("2. 获取城市天气")
print("3. 更改温度单位偏好")
print("4. 查看会话上下文")
print("5. 结束")
choice = input("请输入操作 (1-5): ")
if choice == "1":
cities = client.get_cities()
print("\n可用城市:")
for city in cities:
print(f"- {city}")
elif choice == "2":
city = input("请输入城市名称: ")
try:
weather_data = client.get_weather(city)
client.display_weather(weather_data)
except Exception as e:
print(f"获取天气信息出错: {e}")
elif choice == "3":
unit = input("选择温度单位 (℃/℉): ").lower()
if unit in ["celsius", "fahrenheit"]:
try:
result = client.update_preferences({"preferred_unit": unit})
print(f"设置温度单位为 {unit}")
except Exception as e:
print(f"更新偏好设置出错: {e}")
else:
print("无效输入。请输入 'celsius' 或 'fahrenheit'。")
elif choice == "4":
client.display_context()
elif choice == "5":
print("结束天气客户端。")
sys.exit(0)
else:
print("无效选择。请重试。")
if __name__ == "__main__":
run_client()
通过在终端窗口中的命令行输入下面的命令行来运行服务器:
运行以下命令来执行Python脚本: python3 mcp_context_client.py
以下是 UI 的截图,你可以通过命令行与之互动,这同时也展示了上下文管理的方式。
- 服务器为每个客户端创建一个独一无二的会话 ID
- 会话通过 cookies 保持
- 上下文数据会在多个请求之间保持持久
上下文数据元素:
- 用户偏好(温度单位选择:摄氏度/华氏度温度)
- 使用记录(最近搜索)
- 会话统计信息(访问数,创建日期)
这说明了如何在客户端-服务器MCP架构中管理上下文,确保用户偏好和历史记录在多次交互中的一致性。
首席布道师@Kore.ai(科睿公司) | 我热衷于探索人工智能与语言的交汇点。从语言模型、AI代理到代理应用、开发框架,再到以数据为中心的生产力工具,我分享关于这些技术如何影响未来的一些见解和想法。
共同学习,写下你的评论
评论加载中...
作者其他优质文章