引言:理解LangChain工具的核心概念

在LangChain框架中,工具(Tools) 是扩展智能代理能力的关键组件。它们使AI代理能够超越简单的文本生成,执行实际任务如获取实时数据执行代码查询外部数据库以及在环境中执行操作。

本质上,工具是具有明确定义输入和输出的可调用函数,这些函数被传递给聊天模型。模型根据对话上下文决定何时调用工具以及提供什么输入参数。这种机制使代理能够与外部系统交互,将语言模型的能力从纯粹的文本生成扩展到实际的任务执行。

核心设计原则

LangChain工具的设计遵循几个核心原则:

  1. 声明式定义:工具通过函数签名和文档字符串自我描述
  2. 运行时集成:工具与代理运行时环境无缝集成
  3. 上下文感知:工具可以访问对话状态、用户上下文和持久存储
  4. 类型安全:使用类型提示确保工具输入输出的正确性

第一章:基础工具创建

1.1 使用装饰器创建简单工具

最直接的创建工具方法是使用@tool装饰器。装饰器会自动将函数转换为LangChain工具,并将函数的文档字符串作为工具描述:

from langchain.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """Search the customer database for records matching the query.

    Args:
        query: Search terms to look for
        limit: Maximum number of results to return
    """
    return f"Found {limit} results for '{query}'"

关键注意事项

  • 类型提示是必需的:它们定义了工具的输入模式
  • 文档字符串应信息丰富且简洁:帮助模型理解工具的用途和时机
  • 返回类型应为字符串:工具输出必须是字符串格式,以便模型处理

1.2 自定义工具属性

自定义工具名称

默认情况下,工具名称来自函数名称,但可以覆盖以提供更具描述性的名称:

@tool("web_search")  # 自定义名称
def search(query: str) -> str:
    """Search the web for information."""
    return f"Results for: {query}"

print(search.name)  # 输出: web_search
自定义工具描述

覆盖自动生成的工具描述,为模型提供更清晰的指导:

@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
    """Evaluate mathematical expressions."""
    return str(eval(expression))

1.3 服务器端工具使用

某些聊天模型(如OpenAI、Anthropic和Gemini)具有内置工具,这些工具在服务器端执行,如网络搜索和代码解释器。要访问这些工具,请参考提供商的特定集成指南。

第二章:高级模式定义

2.1 使用Pydantic定义复杂输入

对于需要复杂输入的工具,可以使用Pydantic模型或JSON模式定义输入结构:

from pydantic import BaseModel, Field
from typing import Literal

class WeatherInput(BaseModel):
    """Input for weather queries."""
    location: str = Field(description="City name or coordinates")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit preference"
    )
    include_forecast: bool = Field(
        default=False,
        description="Include 5-day forecast"
    )

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast."""
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result

使用Pydantic模型的优势

  1. 结构化验证:确保输入数据符合预期格式
  2. 详细描述:为每个字段提供明确的描述
  3. 默认值支持:为可选参数提供合理的默认值
  4. 类型约束:使用Literal等类型限制输入值范围

2.2 保留参数名称

以下参数名称是保留的,不能用作工具参数。使用这些名称将导致运行时错误:

参数名称 用途
config 保留用于在内部将RunnableConfig传递给工具
runtime 保留用于ToolRuntime参数(访问状态、上下文、存储)

要访问运行时信息,请使用ToolRuntime参数,而不是使用自己的参数命名为configruntime

第三章:访问运行时上下文

3.1 ToolRuntime概述

ToolRuntime是一个统一的参数,为工具提供对状态、上下文、存储、流式传输、配置和工具调用ID的访问。它取代了旧的模式,如使用单独的InjectedStateInjectedStoreget_runtimeInjectedToolCallId注释。

from langchain.tools import tool, ToolRuntime

# 访问当前对话状态
@tool
def summarize_conversation(
    runtime: ToolRuntime
) -> str:
    """Summarize the conversation so far."""
    messages = runtime.state["messages"]
    
    human_msgs = sum(1 for m in messages if m.__class__.__name__ == "HumanMessage")
    ai_msgs = sum(1 for m in messages if m.__class__.__name__ == "AIMessage")
    tool_msgs = sum(1 for m in messages if m.__class__.__name__ == "ToolMessage")
    
    return f"Conversation has {human_msgs} user messages, {ai_msgs} AI responses, and {tool_msgs} tool results"

重要特性runtime参数对模型是隐藏的。在上面的示例中,模型在工具模式中只看到pref_name参数,而runtime不会包含在请求中。

3.2 状态管理

状态是在执行过程中流动的可变数据(例如消息、计数器、自定义字段)。工具可以访问和更新状态:

from langgraph.types import Command
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain.tools import tool, ToolRuntime

# 通过删除所有消息来更新对话历史
@tool
def clear_conversation() -> Command:
    """Clear the conversation history."""
    
    return Command(
        update={
            "messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)],
        }
    )

# 更新代理状态中的用户名称
@tool
def update_user_name(
    new_name: str,
    runtime: ToolRuntime
) -> Command:
    """Update the user's name."""
    return Command(update={"user_name": new_name})

状态管理最佳实践

  1. 最小化状态更新:只更新必要的状态字段
  2. 使用不可变操作:尽量使用函数式更新模式
  3. 状态序列化:确保状态数据可以正确序列化
  4. 状态隔离:不同工具应操作不同的状态部分

3.3 上下文访问

上下文是不可变的配置,如用户ID、会话详情或应用程序特定配置:

from dataclasses import dataclass
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime

USER_DATABASE = {
    "user123": {
        "name": "Alice Johnson",
        "account_type": "Premium",
        "balance": 5000,
        "email": "alice@example.com"
    },
    "user456": {
        "name": "Bob Smith",
        "account_type": "Standard",
        "balance": 1200,
        "email": "bob@example.com"
    }
}

@dataclass
class UserContext:
    user_id: str

@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id
    
    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return f"Account holder: {user['name']}\nType: {user['account_type']}\nBalance: ${user['balance']}"
    return "User not found"

model = ChatOpenAI(model="gpt-4o")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant."
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    context=UserContext(user_id="user123")
)

3.4 持久存储(记忆)

存储允许跨对话保存和检索用户特定或应用程序特定数据:

from typing import Any
from langgraph.store.memory import InMemoryStore
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime

# 访问记忆
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
    """Look up user info."""
    store = runtime.store
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "Unknown user"

# 更新记忆
@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
    """Save user info."""
    store = runtime.store
    store.put(("users",), user_id, user_info)
    return "Successfully saved user info."

store = InMemoryStore()
agent = create_agent(
    model,
    tools=[get_user_info, save_user_info],
    store=store
)

# 第一个会话:保存用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev"}]
})

# 第二个会话:获取用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "Get user info for user with id 'abc123'"}]
})
# 输出:用户"abc123"的信息:
# - 名称: Foo
# - 年龄: 25
# - 邮箱: foo@langchain.dev

3.5 流式写入器

流式写入器允许在工具执行时提供实时反馈:

from langchain.tools import tool, ToolRuntime

@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
    """Get weather for a given city."""
    writer = runtime.stream_writer
    
    # 工具执行时流式传输自定义更新
    writer(f"Looking up data for city: {city}")
    writer(f"Acquired data for city: {city}")
    
    return f"It's always sunny in {city}!"

重要提示:如果在工具内部使用runtime.stream_writer,则必须在LangGraph执行上下文中调用该工具。

第四章:工具设计最佳实践

4.1 工具设计原则

  1. 单一职责:每个工具应只做一件事
  2. 明确接口:输入输出应清晰定义
  3. 错误处理:工具应优雅处理异常情况
  4. 性能考虑:避免长时间运行的操作阻塞对话

4.2 工具描述编写指南

工具描述对模型选择正确工具至关重要:

  1. 简明扼要:用一两句话描述工具功能
  2. 包含使用场景:说明何时应使用此工具
  3. 参数说明:简要描述每个参数的作用
  4. 输出格式:说明工具返回什么类型的信息

4.3 安全性考虑

  1. 输入验证:始终验证和清理输入
  2. 权限控制:基于上下文限制工具访问
  3. 资源限制:限制工具的资源使用
  4. 审计日志:记录工具调用以供分析

第五章:常见问题与故障排除

5.1 工具调用失败

常见原因和解决方案:

问题 可能原因 解决方案
工具未被调用 描述不清晰 改进工具描述
参数错误 模式定义不正确 检查类型提示和Pydantic模型
运行时错误 访问无效状态 验证状态字段存在性
权限问题 上下文限制 检查上下文配置

5.2 调试工具

使用以下技术调试工具问题:

# 打印工具模式以查看模型看到的内容
print(tool.args_schema.schema())

# 检查工具是否在代理中正确注册
print(agent.tools)

# 手动测试工具调用
result = tool.invoke({"query": "test", "limit": 5})
print(result)

结论

LangChain工具系统提供了一个强大而灵活的框架,用于扩展AI代理的能力。通过理解工具创建、上下文访问和运行时集成的基本原理,您可以构建能够与现实世界系统交互的智能应用程序。

核心要点总结

  1. 工具是函数:具有明确定义输入输出的可调用函数
  2. 上下文是关键:使用ToolRuntime访问状态、上下文和存储
  3. 设计很重要:清晰描述和良好结构化的工具更有效
  4. 测试是必须的:始终手动测试工具并验证集成

通过遵循本指南中的原则和实践,您将能够创建强大的工具,显著增强LangChain代理的功能和实用性。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐