MCP (Model Context Protocol)
MCP (Model Context Protocol) 是一个开放标准,允许 AI 模型安全地连接到本地或远程的数据和工具。LangChain 通过 langchain-mcp-adapters 包提供了对 MCP 的支持。
基础用法
使用 MultiServerMCPClient 可以连接到一个或多个 MCP 服务器,并将其工具转换为 LangChain 可用的工具。
1. 连接 MCP 服务器
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
async def main():
# 1. 初始化 MCP 客户端
mcp_client = MultiServerMCPClient(
{
"drawio": {
"command": "npx",
"args": ["@next-ai-drawio/mcp-server@latest"],
"transport": "stdio"
}
}
)
# 2. 获取工具
# get_tools() 会自动将 MCP 工具转换为 LangChain Tool 对象
mcp_tools = await mcp_client.get_tools()
# 3. 创建 Agent
model = ChatOpenAI(model="gpt-4o")
agent = create_agent(model, mcp_tools)
# 4. 调用
query = "使用drawio绘制一个点击删除按钮,页面进行乐观更新的流程图"
async for chunk in agent.astream({"messages": [HumanMessage(query)]}, stream_mode="values"):
last_msg = chunk["messages"][-1]
if last_msg.content:
print(last_msg.content)
if __name__ == "__main__":
asyncio.run(main())
工具分类与优化
当 MCP 服务器提供了大量工具时,直接将所有工具放入上下文可能会超出模型的 Token 限制或降低推理准确性。使用“小模型 + 结构化输出”进行预处理是最佳实践。
1. 基于意图分类的工具筛选
如果工具可以明确归类,可以使用小模型(如 gpt-4o-mini)判断用户意图,返回严格定义的类别 Enum,从而加载对应的工具集。
from typing import Literal, List
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 1. 定义输出结构:限制模型只能返回预定义的类别
class IntentCategory(BaseModel):
"""用户意图分类结果"""
category: Literal["search", "pdf", "database", "none"] = Field(
description="根据用户输入判断的最合适的工具类别"
)
async def get_tools_by_category(query: str, model: ChatOpenAI, tool_registry: dict):
# 2. 绑定结构化输出 (利用底层 Function Calling 保证稳定性)
structured_llm = model.with_structured_output(IntentCategory)
# 3. 定义提示词
system_prompt = "你是一个意图分类器。请根据用户输入,选择最合适的一个工具类别。"
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{query}")
])
# 4. 执行链
chain = prompt | structured_llm
# 结果是一个 IntentCategory 对象,无需正则解析
result: IntentCategory = await chain.ainvoke({"query": query})
# 5. 返回对应工具
print(f"检测到意图分类: {result.category}")
return tool_registry.get(result.category, [])
# --- 使用示例 ---
# async def usage_example():
# # 预定义的工具注册表
# tool_registry = {
# "search": [search_tool, bing_tool],
# "pdf": [pdf_reader, pdf_writer],
# "database": [sql_query_tool],
# }
# small_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
#
# selected_tools = await get_tools_by_category("帮我查一下最新的AI新闻", small_model, tool_registry)
# # 接下来将 selected_tools 传给主 Agent
2. 基于语义的动态工具选择
如果无法简单分类,可以将所有工具的元数据(名称和描述)提供给小模型,让其从列表中筛选出最相关的工具名称列表。
from typing import List
from pydantic import BaseModel, Field
# 1. 定义输出结构:包含工具名称列表
class ToolSelection(BaseModel):
"""筛选出的相关工具列表"""
selected_tool_names: List[str] = Field(
description="从可用工具列表中选出的、与用户查询最相关的工具名称列表。如果没有相关工具,返回空列表。"
)
reasoning: str = Field(description="选择这些工具的简短理由")
async def select_tools_dynamically(query: str, all_tools: list, model: ChatOpenAI):
# 2. 构建工具描述上下文
# 格式:- 工具名: 工具描述
tools_context = "\n".join([
f"- {t.name}: {t.description}"
for t in all_tools
])
# 3. 绑定结构化输出
structured_llm = model.with_structured_output(ToolSelection)
# 4. 定义提示词
prompt = ChatPromptTemplate.from_template("""
你是一个工具筛选助手。
用户输入: {query}
以下是可用工具列表及其描述:
{tools_desc}
请分析用户需求,严格从上述列表中筛选出解决问题所需的工具名称。
""")
# 5. 执行筛选
chain = prompt | structured_llm
result: ToolSelection = await chain.ainvoke({
"query": query,
"tools_desc": tools_context
})
print(f"筛选理由: {result.reasoning}")
print(f"选中工具: {result.selected_tool_names}")
# 6. 映射回真实的 Tool 对象
# 增加一层过滤防止模型幻觉生成不存在的工具名
valid_tools = [
t for t in all_tools
if t.name in result.selected_tool_names
]
return valid_tools
# --- 使用示例 ---
# async def usage_example():
# # 假设 all_mcp_tools 是从 mcp_client.get_tools() 获取的大量工具
# small_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
#
# relevant_tools = await select_tools_dynamically(
# "帮我把这份数据保存到数据库",
# all_mcp_tools,
# small_model
# )
# # relevant_tools 现在只包含数据库相关的工具