LangChain V1.0 核心解析(三):Tools组件——AI的"瑞士军刀":如何让大模型拥有调用API、操作数据、执行任务的能力

54 阅读7分钟

自定义tool工具使用

@too装饰器

@tool装饰器最简单、最直观的工具创建方式。

概述

  • 自动参数推断:基于函数签名自动生成工具的参数schema
  • 简化配置:只需提供工具名称和描述即可快速创建
  • 同步执行:默认支持同步函数调用,异步需要单独定义
  • 快速验证:适合概念验证和快速迭代开发
from langchain_core.tools import tool
from langchain.agents import create_agent

from base import load_chat_model


# 1. 定义一个简单的 Tool (Runnable)
@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b

# 2.导入模型
model = load_chat_model(
    model="glm-4.7",    # 指定OpenAI的gpt-4o-mini模型
    provider="openai",      # 指定模型提供商为openai
)

# 3.创建Agent
agent = create_agent(model=model,tools=[multiply])

# 4. 调用Agent
response = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "帮我计算12乘以6等于多少?"
    }]
})

result = response["messages"]

print(response)

StructuredTool

StructuredTool.from_function()方法提供了更强大的工具创建能力,支持完整的参数校验和异步执行,适合生产环境使用。

概述

  • 强类型校验:支持Pydantic模型进行参数验证
  • 异步支持:通过coroutine参数支持异步函数
  • 完整元数据:支持name、description、return_direct等完整配置
  • 生产就绪:内置错误处理和参数校验机制
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool

"""
1. 通过 Pydantic BaseModel 定义参数,提供:
- 参数描述(description)
- 必填/可选约束
- 更清晰的 Schema 文档
"""
class DivideInput(BaseModel):
    """除法工具输入参数"""
    dividend: float = Field(description="被除数")
    divisor: float = Field(description="除数,不能为零")

def divide(dividend: float, divisor: float) -> float:
    """执行除法运算,支持浮点数"""
    if divisor == 0:
        raise ValueError("除数不能为零")
    return dividend / divisor

# 2. 创建带参数校验的工具
division_tool = StructuredTool.from_function(
    func=divide,
    name="DivisionTool",
    description="安全执行除法运算,自动处理除零错误",
    args_schema=DivideInput,  # 显式指定参数模式
    return_direct=False,  # 是否直接返回工具结果(不经过 LLM 再次处理)
)

# 3. 测试参数校验(触发 Pydantic 验证)
try:
    division_tool.invoke({"a": 10, "b": 2})  # 错误:参数名不匹配
except Exception as e:
    print(f"参数校验失败:{e}")

# 4. 正确调用
result = division_tool.invoke({"dividend": 10, "divisor": 2})
print(f"除法结果:{result}")

继承structuredTool

继承StructuredTool类创建工具提供了最大的灵活性和控制力,适合复杂业务逻辑和状态管理需求。

import os
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool
from typing import Type

# 1. 定义包含业务逻辑的工具
class OrderQueryInput(BaseModel):          # 此处继承BaseModel
    """订单查询参数"""
    order_id: str = Field(description="订单编号,格式:ORD-2024-XXXX")
    include_details: bool = Field(default=False, description="是否包含商品明细")

class OrderQueryTool(StructuredTool):     # 此处继承StructuredTool
    """订单查询工具"""
    name: str = "query_order"
    description: str = "查询电商平台订单状态和物流信息"
    args_schema: Type[BaseModel] = OrderQueryInput
    return_direct: bool = False

    # 实现父类方法_run()
    def _run(self, order_id: str, include_details: bool = False) -> dict:
        # 模拟数据库查询
        order_db = {
            "ORD-2024-1234": {"status": "已发货", "express": "顺丰", "amount": 299},
            "ORD-2024-5678": {"status": "待付款", "express": "", "amount": 149},
        }

        # 处理查询逻辑
        if order_id not in order_db:
            return {"error": f"订单 {order_id} 不存在"}
        result = order_db[order_id]

        # 处理包含明细的情况
        if include_details:
            result["items"] = ["商品A × 2", "商品B × 1"]

        return result

# 2. 初始化模型
model = init_chat_model(
    model="openai:gpt-4o-mini",
    temperature=0,
    api_key=os.getenv("OPENAI_API_KEY")
)

# 3. 创建 ReAct Agent(自动处理工具调用)
agent = create_agent(
    model=model,
    tools=[OrderQueryTool()],  # 直接传入 StructuredTool 实例
    system_prompt="你是一个电商客服助手,使用工具查询订单信息,回答要友好且准确",
    checkpointer=InMemorySaver()
)

# 4. 执行并观察 ReAct 过程
async def run_agent():
    config = {"configurable": {"thread_id": "customer_001"},"recursion_limit": 15}# 最大 15 次迭代

    query = "请帮我查订单 ORD-2024-1234 的详细状态,包括商品明细"

    async for step in agent.astream(
        {"messages": [{"role": "user", "content": query}]},
        config=config,
        stream_mode="values"  # 流式输出模式,返回每一步的完整状态
    ):
        message = step["messages"][-1]
        message.pretty_print()
        print("-" * 50)

# 运行
await run_agent()

image.png

MCP

远程连接MCP服务器

魔搭社区高德地图mcp服务器:www.modelscope.cn/mcp/servers…

申请高德地图的api地址:console.amap.com/dev/key/app

  • Stdio
{
  "mcpServers": {
    "amap-maps": {
      "args": [
        "-y",
        "@amap/amap-maps-mcp-server"
      ],
      "command": "npx",
      "env": {
        "AMAP_MAPS_API_KEY": ""
      }
    }
  }
}

image.png

from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.agents import create_agent

# 1. 正确的 MCP 配置格式(适用于 langchain_mcp_adapters)
# MultiServerMCPClient 需要的是扁平的字典结构,每个服务器是一个键值对
mcp_config = {
    # 本地 Python MCP 服务器
    "math": {
        "transport": "stdio",
        "command": "python",
        "args": ["mcp_server.py"]
    },
    # 高德地图 MCP 服务器
    "amap-maps": {
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@amap/amap-maps-mcp-server"],
        "env": {
            "AMAP_MAPS_API_KEY": os.getenv("AMAP_MAPS_API_KEY"),
        }
    }
}

# 2. 创建 MCP 客户端
client = MultiServerMCPClient(mcp_config)
print("正在连接 MCP 服务器...")

# 3. client.get_tools() 会自动:
#   1. 调用所有服务器的 list_tools 接口
#   2. 将 MCP Tool Schema 转换为 LangChain StructuredTool
tools = await client.get_tools()
print(f"成功加载 {len(tools)} 个工具: {[t.name for t in tools]}")

# 4. 创建 Agent
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 直接将转换好的 tools 传给 create_agent
agent = create_agent(llm, tools,system_prompt="你是会调用工具进行天气查询、地图查询、网页部署的智能助手")

# 5. 运行 Agent
print("\n--- 开始测试 Agent ---")

# 6. 这里我们模拟一个请求(具体 prompt 取决于你的工具功能)
query = "请帮我搜索查询一下北京市今天的天气,并计算一下最大温差是多少度?"

inputs = {"messages": [HumanMessage(content=query)]}

async for chunk in agent.astream(inputs, stream_mode="values"):
    last_msg = chunk["messages"][-1]
    print(f"\n[{type(last_msg).__name__}]:")
    print(last_msg.content)

    if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
        print(f">>> 调用工具详情: {last_msg.tool_calls}")

工具调用错误或者乱调情况

Tool Router (最有效)

Tool Router是解决工具调用混乱的最有效方法,它通过专门的工具路由机制来精确匹配用户意图与可用工具。

实现

  • 意图识别:使用专门的分类器识别用户意图
  • 工具匹配:基于意图选择最合适的工具
  • 参数验证:在调用前验证参数有效性
  • 错误处理:提供优雅的降级策略
# 定义意图分类系统提示
INTENT_SYSTEM_PROMPT = """
你是一个专业的意图分类器,请只返回以下类别之一:
- search
- pdf
- database
- math
- none

并严格只返回类别名,不要输出其它内容。
"""

引入“意图分类模型” (工程最佳方案)

意图分类模型通过机器学习方法识别用户请求的真实意图,从根本上解决工具误用问题。

# 创建一个意图识别模型
intent_llm = load_chat_model(
    model="gpt-4o-mini",    # 指定OpenAI的gpt-4o-mini模型
    provider="openai",      # 指定模型提供商为openai
)

动态加载工具(避免上下过长)

动态工具加载机制根据当前对话上下文和用户意图,按需加载相关工具,避免一次性加载所有工具导致的上下文过长问题。

模型根据"意图"动态读取特定工具,不把所有工具一次性喂给模型。

# 通过Tool 工具分组
TOOL_GROUPS = {
    "search": [search_web],
    "pdf": [extract_pdf_text],
    "database": [query_database],
    "math": [calculate],
}

统一工具规范(提高准确率)

通过强制化Schema和规范化提示词,建立统一的工具使用规范。

@tool
def query_database(sql: str) -> str:
    """
        执行 SQL 查询,仅限内部业务数据库。
        参数:sql Sql语句。
        示例:如 select * from users limit 5
    """
    return f"模拟 SQL 执行:{sql}"

采用“工具过滤Prompt”修饰模型行为(成本最低)

通过系统Prompt显式指导模型行为,设置工具使用边界。

Prompt示例

你必须严格根据工具描述选择工具。 不能猜测工具功能。 如果没有合适的工具,请回答"无合适工具"。

agent = create_agent(
        model=model,
        tools=tools,
        system_prompt="你是一个 helpful assistant,可以使用工具回答问题。你必须严格根据工具描述选择工具!如果没有合适的工具,请回答“无合适工具”"
    )