langChain Function Calling/tool/mcp详解

Tool

Tool 是一个核心抽象概念,它代表了一个可以被大型语言模型(LLM)调用的功能或服务。

  • 上层抽象:是 LangChain 框架中的概念

  • 应用层封装:将函数调用、API 调用等各种能力统一封装成标准接口

  • 框架概念:LangChain 提供的组织和管理外部能力的方式

Tools 通常会多次调用大模型进行整合。这正是 Agent 工作模式的核心特点。

LLM 本身是强大的推理引擎,但它们存在固有的局限性:

  • 知识截止性: 它们的知识仅限于训练数据,无法获取最新信息。

  • 缺乏执行能力: 它们无法执行外部操作,如查询数据库、调用 API、操作文件等。

  • 无法进行精确计算: 它们可能不擅长进行复杂的数学运算或代码执行。

将各种功能封装成统一的 Tool 接口,LangChain 使得 LLM 能够根据用户的请求,智能地选择并调用相应的 Tool 来完成任务。

1. Tool 的核心组成部分

一个标准的 Tool 通常包含以下几个关键部分:

  1. 名称(name):

    • 一个简短、描述性的字符串,用于标识这个 Tool。

    • LLM 通过名称来识别和选择要使用的 Tool。因此,名称应该清晰明了,例如 "search""calculator""sql_db_query"

  2. 描述(description):

    • 一段详细的文本,说明这个 Tool 的功能、用途以及最重要的——何时应该使用它。

    • 描述是 Tool 的灵魂。LLM 的决策严重依赖于描述。一个模糊的描述会导致 LLM 错误地选择或忽略该 Tool。例如,一个计算器的描述应该是:“当需要回答数学问题时非常有用。输入应该是一个数学表达式。”

  3. 函数(func)或可调用对象(coroutine):

    • 实际执行任务的代码。这可以是一个普通的 Python 函数,也可以是一个异步函数。

    • 当 LLM 决定调用某个 Tool 时,LangChain 就会执行这个函数,并将函数的返回值作为 Tool 的执行结果返回给 LLM。

2. 如何创建和使用 Tool

LangChain 提供了多种灵活的方式来创建和使用 Tool。

方法一:使用 @tool 装饰器(最简洁)

from langchain.agents import tool
import requests

@tool
def get_weather(city: str) -> str:
    """根据城市名获取当前的天气信息。"""
    # 这里是一个模拟的 API 调用
    # 实际应用中,你会调用真实的天气 API
    if city == "北京":
        return "北京:晴,25°C"
    elif city == "上海":
        return "上海:多云,28°C"
    else:
        return f"无法找到{city}的天气信息"

# 现在 get_weather 就是一个 Tool 对象了
print(get_weather.name)    # 输出:get_weather
print(get_weather.description) # 输出:get_weather(city: str) -> str - 根据城市名获取当前的天气信息。

方法二:继承 BaseTool 类(最灵活)

当需要对 Tool 的行为进行更精细的控制时(例如,自定义输入验证、更复杂的逻辑),可以继承 BaseTool 类。

from langchain.tools import BaseTool
from pydantic import Field

class CustomCalculatorTool(BaseTool):
    name: str = "advanced_calculator"
    description: str = "用于执行复杂数学计算的计算器。输入必须是一个完整的数学表达式。"
    precision: int = Field(default=2, description="结果保留的小数位数")

    def _run(self, expression: str) -> str:
        """同步执行方法"""
        try:
            # 警告:在生产环境中,直接使用 eval 是危险的,这里仅作演示。
            result = eval(expression)
            return f"结果是:{round(result, self.precision)}"
        except Exception as e:
            return f"计算错误:{e}"

    async def _arun(self, expression: str) -> str:
        """异步执行方法(可选)"""
        # 如果不需要异步,可以直接抛出 NotImplementedError
        return self._run(expression)

# 使用自定义 Tool
calc_tool = CustomCalculatorTool(precision=4)

方法三:使用 Tool.from_function 方法

这是一种介于装饰器和继承类之间的方式,可以直接将一个现有的函数包装成 Tool。

def search_web(query: str) -> str:
    # 模拟网络搜索
    return f"关于 '{query}' 的搜索结果:..."

# 创建 Tool
search_tool = Tool.from_function(
    func=search_web,
    name="web_search",
    description="当需要搜索最新或未知信息时使用此工具。"
)

 

示例:

tool 本身不会自动被调用,它们需要在一个 Agent 的框架下工作。Agent 是 LLM 的“大脑”,它负责:

  1. 理解用户意图。

  2. 规划行动步骤。

  3. 从可用的 Tools 中选择最合适的一个。

  4. 调用该 Tool 并获取结果。

  5. 根据结果决定下一步:是直接回答用户,还是继续调用其他 Tool。

from langchain.agents import initialize_agent, AgentType
from langchain_openai import ChatOpenAI
from langchain.agents import tool

# 1. 定义 Tools
@tool
def get_weather(city: str) -> str:
    """根据城市名获取当前的天气信息。"""
    # ... (实现同上)
    return f"{city}的天气是..."

@tool
def search_web(query: str) -> str:
    """用于搜索最新信息。"""
    # ... (实现同上)
    return f"搜索结果:..."

# 2. 初始化 LLM 和 Tools 列表
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
tools = [get_weather, search_web]

# 3. 创建 Agent
# ZERO_SHOT_REACT_DESCRIPTION 是一种常用的代理类型,它鼓励 LLM 进行一步步的推理(Reasoning)和行动(Acting)。
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True  # 开启详细日志,可以看到代理的思考过程
)

# 4. 运行!
result = agent.run("北京今天的天气怎么样?然后再帮我搜一下北京有什么室内活动推荐。")
print(result)

Thought: 用户问了两个问题,一个是北京的天气,一个是室内活动推荐。我需要按顺序处理。首先,我需要获取北京的天气。有一个叫 `get_weather` 的工具可以做到这一点。
Action: get_weather
Action Input: {"city": "北京"}
Observation: 北京:晴,25°C
Thought: 好的,我已经得到了北京的天气。现在用户还想要室内活动推荐。这需要最新的信息,我应该使用搜索工具。
Action: search_web
Action Input: {"query": "北京 室内活动 推荐"}
Observation: 搜索结果:... (关于北京室内活动的信息)
Thought: 我现在有了两个问题的答案,可以综合起来回复用户了。
Final Answer: 北京今天天气晴朗,25°C。关于室内活动,根据搜索结果显示,... (综合后的回答)

# LangChain 自动:
# 1. 思考:调用大模型:需要先获取天气信息
# 2. 行动:调用天气工具
# 3. 观察:北京今天下雨
# 4. 思考:调用大模型:既然下雨,应该搜索室内活动
# 5. 行动:调用搜索工具
# 6. 最终回答

 

Function Calling

Function Calling(函数调用):Function Calling 不是让 LLM 直接执行代码,而是让 LLM 理解用户请求,并识别出需要调用哪个外部函数,同时生成符合函数要求的参数。

  • 底层机制:是 LLM 本身具备的一种能力

  • 协议标准:一种让 LLM 识别何时应该调用外部函数、调用哪个函数、以及传递什么参数的标准化方式

  • 模型原生能力:由 OpenAI 等模型提供商在模型层面实现

import openai
import json
import requests

# 1. 定义真实的天气函数
def get_weather(city: str) -> str:
    """实际获取天气数据的函数"""
    # 这里应该是真实的 API 调用,简化为例
    weather_data = {
        "北京": "晴,25°C,湿度40%",
        "上海": "多云,28°C,湿度60%", 
        "广州": "雨,30°C,湿度80%"
    }
    return weather_data.get(city, f"未找到{city}的天气信息")

# 2. 定义函数 schema(给模型看的说明书)
weather_function_schema = {
    "name": "get_weather",
    "description": "根据城市名称获取当前天气信息",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "城市名称,如北京、上海"
            }
        },
        "required": ["city"],  # 必填参数
        "additionalProperties": False
    }
}
def process_user_query(user_query: str):
    """处理用户查询的主函数"""
    
    print("=== 第一次调用:识别意图和参数 ===")
    
    # 第一次调用:让模型判断是否需要调用函数
    response = openai.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": user_query}
        ],
        functions=[weather_function_schema],  # 告诉模型可用的函数
        function_call="auto",  # 让模型自动决定是否调用函数
        temperature=0
    )
    
    message = response.choices[0].message
    print(f"模型原始响应: {message}")
    
    return message
def execute_function_call(message):
    """执行函数调用"""
    
    # 检查模型是否决定调用函数
    if hasattr(message, 'function_call') and message.function_call:
        function_name = message.function_call.name
        arguments = json.loads(message.function_call.arguments)
        
        print(f"\n=== 执行函数调用 ===")
        print(f"函数名: {function_name}")
        print(f"参数: {arguments}")
        
        # 根据函数名调用对应的真实函数
        if function_name == "get_weather":
            city = arguments.get("city")
            function_response = get_weather(city)
            print(f"函数执行结果: {function_response}")
            
            return function_name, function_response
    else:
        print("模型决定不调用函数,直接回答")
        return None, message.content
def generate_final_answer(user_query, message, function_name, function_response):
    """生成最终回答"""
    
    print(f"\n=== 第二次调用:生成最终回答 ===")
    
    # 准备对话历史
    messages = [
        {"role": "user", "content": user_query}
    ]
    
    if function_name:
        # 如果调用了函数,包含函数调用和结果
        messages.append(message)  # 包含 function_call 的助理消息
        messages.append({
            "role": "function",
            "name": function_name, 
            "content": function_response
        })
    else:
        # 如果没有调用函数,直接使用第一次的回复
        return function_response
    
    # 第二次调用:让模型基于函数结果生成友好回答
    second_response = openai.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=messages,
        temperature=0
    )
    
    final_answer = second_response.choices[0].message.content
    print(f"最终回答: {final_answer}")
    return final_answer
def main():
    # 用户查询
    user_query = "北京今天天气怎么样?"
    print(f"用户查询: {user_query}")
    
    # 1. 第一次调用:识别意图
    message = process_user_query(user_query)
    
    # 2. 执行函数调用
    function_name, function_response = execute_function_call(message)
    
    # 3. 生成最终回答
    final_answer = generate_final_answer(user_query, message, function_name, function_response)
    
    return final_answer

# 运行示例
if __name__ == "__main__":
    result = main()

 

MCP(Model Context Protocol)

MCP(Model Context Protocol) 是一个开放协议,用于在 AI 应用程序(如 Claude)和外部数据源、工具之间建立标准化的连接方式。

  • 标准化接口:为 AI 模型访问外部资源和工具定义统一标准

  • 解耦设计:将工具/数据源开发与具体 AI 应用分离

  • 跨平台兼容:一次开发,多处使用

解决问题:

  问题1:供应商锁定

  问题2:重复开发

  问题3:维护困难

  • 每个平台版本更新可能导致工具失效

  • 安全更新需要在多个地方重复进行

  • 文档和测试需要维护多份

# 传统方式:直接绑定到特定平台
# LangChain 工具
from langchain.tools import Tool

@tool
def my_tool():
    # 这个工具只能在 LangChain 中使用
    pass

# 如果想把工具移到其他框架,需要重写


# 同一个工具需要为不同平台重复实现
# 对于 LangChain
class LangChainWeatherTool(BaseTool):
    # LangChain 特定的实现
    pass

# 对于 LlamaIndex  
class LlamaIndexWeatherTool(BaseTool):
    # LlamaIndex 特定的实现
    pass

# 对于自定义应用
class CustomWeatherTool:
    # 自定义实现
    pass

image

 MCP 使用 JSON-RPC 2.0 进行通信,支持两种模式:

1. STDIO 模式(本地)

// AI 应用 → MCP 服务器
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "weather",
    "arguments": {"city": "Beijing"}
  }
}

// MCP 服务器 → AI 应用
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "Beijing: Sunny, 25°C"}]
  }
}

2. HTTP 模式(远程)

# MCP 服务器作为 HTTP 服务运行
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

 

三者区别

 

特性维度Function CallingTools (LangChain)MCP (Model Context Protocol)
本质 底层协议/机制 应用层框架抽象 跨应用层协议标准
抽象层级 低层级 中层级 高层级/跨平台
核心目标 让LLM可靠调用外部函数 在LangChain中构建AI代理 标准化AI与外部服务的连接
供应商绑定 中等(主要OpenAI) 高(绑定LangChain) 低(开放协议)
开发模式 手动流程控制 声明式框架集成 协议驱动标准化
技术实现Function CallingTools (LangChain)MCP (Model Context Protocol)
通信协议 OpenAI API规范 Python类和方法调用 JSON-RPC 2.0 (STDIO/HTTP)
集成方式 直接API调用 装饰器/类继承 MCP服务器客户端模式
工具定义 JSON Schema Python函数/类 标准化接口定义
执行流程 手动多步调用 Agent自动管理 协议驱动的请求响应

tools相比function calling,是对function calling进行了封装,作为基础类,增加了异步调用。

mcp是将工具和模型进行了解耦,可以跨平台部署。

 

posted @ 2025-10-24 11:25  wangssd  阅读(260)  评论(0)    收藏  举报