引言:理解LangChain工具的核心概念
在LangChain框架中,工具(Tools)是扩展智能代理能力的关键组件。它们使AI代理能够超越简单的文本生成,执行实际任务如获取实时数据、执行代码、查询外部数据库以及在环境中执行操作。
本质上,工具是具有明确定义输入和输出的可调用函数,这些函数被传递给聊天模型。模型根据对话上下文决定何时调用工具以及提供什么输入参数。这种机制使代理能够与外部系统交互,将语言模型的能力从纯粹的文本生成扩展到实际的任务执行。
核心设计原则
LangChain工具的设计遵循几个核心原则:
- 声明式定义:工具通过函数签名和文档字符串自我描述
- 运行时集成:工具与代理运行时环境无缝集成
- 上下文感知:工具可以访问对话状态、用户上下文和持久存储
- 类型安全:使用类型提示确保工具输入输出的正确性
第一章:基础工具创建
1.1 使用装饰器创建简单工具
最直接的创建工具方法是使用@tool装饰器。装饰器会自动将函数转换为LangChain工具,并将函数的文档字符串作为工具描述:
fromlangchain.toolsimporttool@tooldefsearch_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 """returnf"Found{limit}results for '{query}'"关键注意事项:
- 类型提示是必需的:它们定义了工具的输入模式
- 文档字符串应信息丰富且简洁:帮助模型理解工具的用途和时机
- 返回类型应为字符串:工具输出必须是字符串格式,以便模型处理
1.2 自定义工具属性
自定义工具名称
默认情况下,工具名称来自函数名称,但可以覆盖以提供更具描述性的名称:
@tool("web_search")# 自定义名称defsearch(query:str)->str:"""Search the web for information."""returnf"Results for:{query}"print(search.name)# 输出: web_search自定义工具描述
覆盖自动生成的工具描述,为模型提供更清晰的指导:
@tool("calculator",description="Performs arithmetic calculations. Use this for any math problems.")defcalc(expression:str)->str:"""Evaluate mathematical expressions."""returnstr(eval(expression))1.3 服务器端工具使用
某些聊天模型(如OpenAI、Anthropic和Gemini)具有内置工具,这些工具在服务器端执行,如网络搜索和代码解释器。要访问这些工具,请参考提供商的特定集成指南。
第二章:高级模式定义
2.1 使用Pydantic定义复杂输入
对于需要复杂输入的工具,可以使用Pydantic模型或JSON模式定义输入结构:
frompydanticimportBaseModel,FieldfromtypingimportLiteralclassWeatherInput(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)defget_weather(location:str,units:str="celsius",include_forecast:bool=False)->str:"""Get current weather and optional forecast."""temp=22ifunits=="celsius"else72result=f"Current weather in{location}:{temp}degrees{units[0].upper()}"ifinclude_forecast:result+="\nNext 5 days: Sunny"returnresult使用Pydantic模型的优势:
- 结构化验证:确保输入数据符合预期格式
- 详细描述:为每个字段提供明确的描述
- 默认值支持:为可选参数提供合理的默认值
- 类型约束:使用Literal等类型限制输入值范围
2.2 保留参数名称
以下参数名称是保留的,不能用作工具参数。使用这些名称将导致运行时错误:
| 参数名称 | 用途 |
|---|---|
config | 保留用于在内部将RunnableConfig传递给工具 |
runtime | 保留用于ToolRuntime参数(访问状态、上下文、存储) |
要访问运行时信息,请使用ToolRuntime参数,而不是使用自己的参数命名为config或runtime。
第三章:访问运行时上下文
3.1 ToolRuntime概述
ToolRuntime是一个统一的参数,为工具提供对状态、上下文、存储、流式传输、配置和工具调用ID的访问。它取代了旧的模式,如使用单独的InjectedState、InjectedStore、get_runtime和InjectedToolCallId注释。
fromlangchain.toolsimporttool,ToolRuntime# 访问当前对话状态@tooldefsummarize_conversation(runtime:ToolRuntime)->str:"""Summarize the conversation so far."""messages=runtime.state["messages"]human_msgs=sum(1forminmessagesifm.__class__.__name__=="HumanMessage")ai_msgs=sum(1forminmessagesifm.__class__.__name__=="AIMessage")tool_msgs=sum(1forminmessagesifm.__class__.__name__=="ToolMessage")returnf"Conversation has{human_msgs}user messages,{ai_msgs}AI responses, and{tool_msgs}tool results"重要特性:runtime参数对模型是隐藏的。在上面的示例中,模型在工具模式中只看到pref_name参数,而runtime不会包含在请求中。
3.2 状态管理
状态是在执行过程中流动的可变数据(例如消息、计数器、自定义字段)。工具可以访问和更新状态:
fromlanggraph.typesimportCommandfromlangchain.messagesimportRemoveMessagefromlanggraph.graph.messageimportREMOVE_ALL_MESSAGESfromlangchain.toolsimporttool,ToolRuntime# 通过删除所有消息来更新对话历史@tooldefclear_conversation()->Command:"""Clear the conversation history."""returnCommand(update={"messages":[RemoveMessage(id=REMOVE_ALL_MESSAGES)],})# 更新代理状态中的用户名称@tooldefupdate_user_name(new_name:str,runtime:ToolRuntime)->Command:"""Update the user's name."""returnCommand(update={"user_name":new_name})状态管理最佳实践:
- 最小化状态更新:只更新必要的状态字段
- 使用不可变操作:尽量使用函数式更新模式
- 状态序列化:确保状态数据可以正确序列化
- 状态隔离:不同工具应操作不同的状态部分
3.3 上下文访问
上下文是不可变的配置,如用户ID、会话详情或应用程序特定配置:
fromdataclassesimportdataclassfromlangchain_openaiimportChatOpenAIfromlangchain.agentsimportcreate_agentfromlangchain.toolsimporttool,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"}}@dataclassclassUserContext:user_id:str@tooldefget_account_info(runtime:ToolRuntime[UserContext])->str:"""Get the current user's account information."""user_id=runtime.context.user_idifuser_idinUSER_DATABASE:user=USER_DATABASE[user_id]returnf"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 持久存储(记忆)
存储允许跨对话保存和检索用户特定或应用程序特定数据:
fromtypingimportAnyfromlanggraph.store.memoryimportInMemoryStorefromlangchain.agentsimportcreate_agentfromlangchain.toolsimporttool,ToolRuntime# 访问记忆@tooldefget_user_info(user_id:str,runtime:ToolRuntime)->str:"""Look up user info."""store=runtime.store user_info=store.get(("users",),user_id)returnstr(user_info.value)ifuser_infoelse"Unknown user"# 更新记忆@tooldefsave_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.dev3.5 流式写入器
流式写入器允许在工具执行时提供实时反馈:
fromlangchain.toolsimporttool,ToolRuntime@tooldefget_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}")returnf"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代理的功能和实用性。