LangChain Tools 工具使用
本文介绍了LangChain框架中工具(Tools)的概念与实现方法。工具作为扩展AI代理能力的核心组件,通过声明式定义和运行时集成,使模型能够执行实际任务。文章详细讲解了基础工具创建方法(如装饰器使用、自定义属性)、高级模式定义(如Pydantic模型定义复杂输入),以及运行时上下文访问(包括ToolRuntime、状态管理和上下文访问)。这些技术使语言模型能够超越文本生成,实现与外部系统的交互
引言:理解LangChain工具的核心概念
在LangChain框架中,工具(Tools) 是扩展智能代理能力的关键组件。它们使AI代理能够超越简单的文本生成,执行实际任务如获取实时数据、执行代码、查询外部数据库以及在环境中执行操作。
本质上,工具是具有明确定义输入和输出的可调用函数,这些函数被传递给聊天模型。模型根据对话上下文决定何时调用工具以及提供什么输入参数。这种机制使代理能够与外部系统交互,将语言模型的能力从纯粹的文本生成扩展到实际的任务执行。
核心设计原则
LangChain工具的设计遵循几个核心原则:
- 声明式定义:工具通过函数签名和文档字符串自我描述
- 运行时集成:工具与代理运行时环境无缝集成
- 上下文感知:工具可以访问对话状态、用户上下文和持久存储
- 类型安全:使用类型提示确保工具输入输出的正确性
第一章:基础工具创建
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模型的优势:
- 结构化验证:确保输入数据符合预期格式
- 详细描述:为每个字段提供明确的描述
- 默认值支持:为可选参数提供合理的默认值
- 类型约束:使用Literal等类型限制输入值范围
2.2 保留参数名称
以下参数名称是保留的,不能用作工具参数。使用这些名称将导致运行时错误:
| 参数名称 | 用途 |
|---|---|
config |
保留用于在内部将RunnableConfig传递给工具 |
runtime |
保留用于ToolRuntime参数(访问状态、上下文、存储) |
要访问运行时信息,请使用ToolRuntime参数,而不是使用自己的参数命名为config或runtime。
第三章:访问运行时上下文
3.1 ToolRuntime概述
ToolRuntime是一个统一的参数,为工具提供对状态、上下文、存储、流式传输、配置和工具调用ID的访问。它取代了旧的模式,如使用单独的InjectedState、InjectedStore、get_runtime和InjectedToolCallId注释。
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})
状态管理最佳实践:
- 最小化状态更新:只更新必要的状态字段
- 使用不可变操作:尽量使用函数式更新模式
- 状态序列化:确保状态数据可以正确序列化
- 状态隔离:不同工具应操作不同的状态部分
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 工具设计原则
- 单一职责:每个工具应只做一件事
- 明确接口:输入输出应清晰定义
- 错误处理:工具应优雅处理异常情况
- 性能考虑:避免长时间运行的操作阻塞对话
4.2 工具描述编写指南
工具描述对模型选择正确工具至关重要:
- 简明扼要:用一两句话描述工具功能
- 包含使用场景:说明何时应使用此工具
- 参数说明:简要描述每个参数的作用
- 输出格式:说明工具返回什么类型的信息
4.3 安全性考虑
- 输入验证:始终验证和清理输入
- 权限控制:基于上下文限制工具访问
- 资源限制:限制工具的资源使用
- 审计日志:记录工具调用以供分析
第五章:常见问题与故障排除
5.1 工具调用失败
常见原因和解决方案:
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 工具未被调用 | 描述不清晰 | 改进工具描述 |
| 参数错误 | 模式定义不正确 | 检查类型提示和Pydantic模型 |
| 运行时错误 | 访问无效状态 | 验证状态字段存在性 |
| 权限问题 | 上下文限制 | 检查上下文配置 |
5.2 调试工具
使用以下技术调试工具问题:
# 打印工具模式以查看模型看到的内容
print(tool.args_schema.schema())
# 检查工具是否在代理中正确注册
print(agent.tools)
# 手动测试工具调用
result = tool.invoke({"query": "test", "limit": 5})
print(result)
结论
LangChain工具系统提供了一个强大而灵活的框架,用于扩展AI代理的能力。通过理解工具创建、上下文访问和运行时集成的基本原理,您可以构建能够与现实世界系统交互的智能应用程序。
核心要点总结:
- 工具是函数:具有明确定义输入输出的可调用函数
- 上下文是关键:使用ToolRuntime访问状态、上下文和存储
- 设计很重要:清晰描述和良好结构化的工具更有效
- 测试是必须的:始终手动测试工具并验证集成
通过遵循本指南中的原则和实践,您将能够创建强大的工具,显著增强LangChain代理的功能和实用性。
更多推荐



所有评论(0)