langchain-MCP与Function Calling
大模型在理解用户自然语言的基础上,自动调用后端预定义的函数或 API
function call
Function Call 的局限性:只支持单步、单函数的调用,难以灵活应对多步骤、多工具协作的复杂业务需求
langchain function call
使用:@tool装饰器,是一个智能的代码生成器
函数分析
当在函数上添加@tool装饰器时,LangChain会自动分析这个Python函数。它会读取函数的签名信息,包括参数名称、参数类型注解、默认值等。同时,它还会提取函数的docstring文档字符串作为工具的描述信息。
自动转换
LangChain将Python类型注解自动转换为OpenAI Function Calling 所需的JSON Schema格式,比如Python的str类型会转换为JSON Schema的"type": "string",int类型转换为"type": "integer",List[str]转换为数组类型等。
工具对象创建
装饰器会创建一个tool对象,这个对象包含了工具的名称、描述、参数模式等信息,这个tool对象既保留了原始python函数的调用,也具备了OpenAI工具定义的所有必要信息。
运行时调用
当AI决定调用某个工具时,LangChain可以直接通过Tool对象的invoke方法调用原始的Python函数,无需额外的映射代码。
案例
from dotenv import load_dotenv
import os
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.runnables import RunnableLambda
# 加载环境变量
load_dotenv()
# 定义@tool工具
# 通过langchain.tools包下的@tool装饰器,将函数注册为工具。这里简单注册两个无输入,只有字符串输出的工具
@tool
def who_are_you() -> str:
"""当被问到身份时,回答身份信息"""
return "我是atguigu工具小助手"
@tool
def what_can_you_do() -> str:
"""当被问到功能时,回答功能信息"""
return "我的功能是进行工具选择"
# 根据用户输入信息,构造系统信息与用户信息,后续传入链中作为提示词。
def format_messages(inputs):
return [
SystemMessage(content="你是一个工具助手。"),
HumanMessage(content=inputs["question"])
]
def execute_tool_calls(result):
tools = [who_are_you,what_can_you_do]
# 如果执行了工具调用,则进入
if result.tool_calls:
# LLM会给工具传入列表,越匹配的工具位置越靠前。对于此类调用单一工具的情况,只需要取出列表中的第一项即为被选中的工具
tool_call = result.tool_calls[0]
print(f"调用工具: {tool_call['name']}")
print(f"参数: {tool_call['args']}")
# 从工具列表中选择与上述被选定工具进行名称匹配,输出名称和参数信息
for tool in tools:
if tool.name == tool_call['name']:
output = tool.invoke(tool_call['args'])
print(f"执行结果: {output}")
return output
else:
print(f"未调用工具: {result.content}")
return result.content
def main():
api_key = os.getenv('DASHSCOPE_API_KEY')
llm_model = os.getenv('QWEN_LLM_MODEL')
base_url = os.getenv('BASE_URL')
llm = ChatOpenAI(
model=llm_model,
api_key=api_key,
base_url=base_url
)
tools = [who_are_you, what_can_you_do]
# 通过bind_tools()将工具使用传入工具列表的方式,绑定到ChatOpenAI注册的llm中
llm_with_tools = llm.bind_tools(tools)
# 将函数转换为节点,接入链中
message_formatter = RunnableLambda(format_messages)
tool_executor = RunnableLambda(execute_tool_calls)
processing_chain = message_formatter | llm_with_tools | tool_executor
test_cases = [
"你是谁?",
"你的功能是什么?"
]
# 通过enumerate获取索引和元素,test_case表示要遍历的列表,1 表示起始索引值
# i对应起始索引值 1
# question对应test_cases中的相应位置元素
for i, question in enumerate(test_cases, 1):
print(f"\n测试 {i}: {question}")
print("-" * 60)
try:
result = processing_chain.invoke({"question": question})
print(f"链式处理完成")
except Exception as e:
print(f"处理出错: {e}")
if __name__ == "__main__":
main()
说明:
@tool装饰器:在程序启动时,将此函数包装成一个标准的工具对象,这个对象包含了自动生成的JSON Schema。
在main函数中,这个工具对象被添加到tools列表中,并通过llm.bind_tools(tools)绑定到LLM
当用户问题被发送给LLM的时候,LLM会分析这个问题和可用的工具列表
LLM通过分析工具的描述和参数说明,决定调用这个工具
LLM根据参数名和上下文自动解析参数表示了什么
生成一个工具调用指令
execute_tool_calls函数接收到这个指令,找到工具对象,并调用其invoke方法。
invoke方法最终执行工具函数的实际代码,并按照工具的要求进行返回
格式化消息处理器
接收一个包含question字段的字典作为参数。
返回一个包含系统消息与人类消息的列表,其中人类消息是input中的question字段对应的值。
作用为进行角色设定,作为Runnable链的一部分,为后续的LLM处理做准备
执行工具调用
首先接收result (一个AIMessage对象),检查其中是否有result.tool_calls 。
如果存在的话则提取工具名,在tools列表中寻找那个被@tool装饰过的Tool对象
它调用工具函数,并将AI生成的参数传进去,从而执行了定义的Python函数
如果result.tool_calls不存在,则直接返回AI的普通聊天回复。
主函数的构建
进行初始化
定义工具列表
构造Runnable链
构造完善的处理链,将消息预处理、调用工具的LLM、工具执行全部加入链中
输出校验
MCP(Model Context Protocol,模型上下文协议)
本质:是一种标准化协议(由 Together AI 提出),定义了 “大模型与外部工具 / 系统交互” 的通用规范,核心是统一模型与工具之间的通信格式、上下文传递方式、错误处理规则等。
定位:属于协议层规范,类似于 HTTP 协议定义了网页通信的规则,MCP 定义了 “模型调用工具” 的通用规则。
作用:解决不同框架(如 LangChain、LlamaIndex)、不同模型、不同工具之间交互的 “兼容性问题”—— 比如按 MCP 规范开发的工具,能无缝对接任何支持 MCP 的大模型框架,无需单独适配。
简单理解:function call模块内部调用能力,mcp系统级,模块与模块之间,系统与系统之间的调用协议
示例:
用 MCPToolWrapper 包装你的 get_weather 函数,再通过 FastAPI 暴露成符合 MCP 协议的 HTTP 接口,让外部系统能访问。
# langchain_mcp_server.py
from fastapi import FastAPI, Request
from langchain_core.tools import tool
from langchain_mcp import MCPToolWrapper
from langchain_openai import ChatOpenAI
import json
# 1. 定义你的工具函数(和之前一样)
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气"""
return f"{city}今天的天气是晴,温度20℃"
# 2. 用 MCPToolWrapper 包装工具(适配 MCP 协议)
mcp_wrapper = MCPToolWrapper(tools=[get_weather])
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 构建 MCP 处理链:接收 MCP 请求 → 调用工具 → 返回 MCP 格式结果
mcp_chain = mcp_wrapper | llm
# 3. 用 FastAPI 搭建 HTTP 服务,暴露 MCP 接口
app = FastAPI()
@app.post("/mcp/invoke")
async def mcp_invoke(request: Request):
"""处理 MCP 协议的调用请求"""
try:
# 接收 MCP 格式的请求体(LlamaIndex 会按 MCP 标准发过来)
request_data = await request.json()
# 提取用户输入(MCP 协议的请求内容)
user_input = request_data.get("input")
# 用 MCP 链处理请求(自动按 MCP 格式调用工具)
result = mcp_chain.invoke(user_input)
# 按 MCP 协议返回结果
return {
"status": "success",
"response": result.content,
"mcp_version": "1.0" # MCP 协议版本标识
}
except Exception as e:
return {"status": "error", "message": str(e)}
# 启动服务(运行后会在 http://localhost:8000 提供 MCP 接口)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
服务会启动在 http://localhost:8000,对外暴露 /mcp/invoke 接口,这个接口完全遵循 MCP 协议。
通过异构服务:LlamaIndex 作为 MCP 客户端,通过 HTTP 调用上面的 MCP 服务,从而间接调用你的 get_weather 函数
# 更规范的 LlamaIndex MCP 客户端
from llama_index.mcp import MCPClient
# 初始化 MCP 客户端(指向 LangChain 的 MCP 服务)
client = MCPClient(
server_url="http://localhost:8000/mcp/invoke",
mcp_version="1.0"
)
# 调用 MCP 工具(LlamaIndex 自动按 MCP 协议发请求)
result = client.invoke("查一下广州的天气")
print("LlamaIndex 原生 MCP 调用结果:", result)
# 输出:LlamaIndex 原生 MCP 调用结果:广州今天的天气是晴,温度20℃
核心原理:MCP 是跨系统的通信协议,需把 LangChain 的 MCP 工具暴露为网络服务(而非进程内函数),LlamaIndex 作为 MCP 客户端通过网络调用;
关键步骤:
LangChain 端:@tool 标记工具 → MCPToolWrapper 适配协议 → FastAPI 暴露 HTTP 接口;
LlamaIndex 端:用 MCP 客户端调用该 HTTP 接口,自动遵循 MCP 协议格式;
核心价值:你的 get_weather 函数本身还是 LangChain 内的方法,但通过 MCP 协议被封装成了 “标准化服务”,任何支持 MCP 的框架(LlamaIndex、甚至自定义系统)都能调用。
查看11道真题和解析