这篇适合谁,读完能做到什么
适合已经跑通过第一个 LangChain 智能体、想让它调用自己写的函数的读者。读完你能用 @tool 定义工具,给工具起名和写说明,理解参数声明的规则,控制工具返回结果的方式,并知道出错时怎么处理。
工具是什么
工具是有明确输入和输出的可调用函数,会被传给聊天模型。模型根据对话内容决定要不要调用某个工具,以及传哪些参数[1]。工具调用可以用 LangSmith 追踪,出错时也便于排查[1]。
Python:用 @tool 定义工具
最简单的做法是使用 langchain.tools 里的 @tool 装饰器。默认情况下,函数的文档字符串会成为工具的描述,模型靠它判断什么时候该用这个工具[1]:
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}'"Python 的类型注解是必需的,因为它定义了工具的输入结构。文档字符串应当简洁,说清用途和参数即可[1]。
JavaScript:用 tool 和 zod 定义工具
JavaScript 版本从 langchain 导入 tool,用 zod 描述输入结构。名称、描述和 schema 都写在第二个参数里[1]:
import * as z from "zod"
import { tool } from "langchain"
const searchDatabase = tool(
({ query, limit }) => `Found ${limit} results for '${query}'`,
{
name: "search_database",
description: "Search the customer database for records matching the query.",
schema: z.object({
query: z.string().describe("Search terms to look for"),
limit: z.number().describe("Maximum number of results to return"),
}),
}
);给工具起名字和写说明
Python 中工具名默认取自函数名。需要更有描述性的名字时,可以写成 @tool("web_search") 这样的形式[1]:
@tool("web_search") # Custom name
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
print(search.name) # web_search@tool 还接受 description 参数,可以覆盖自动生成的描述[1]。命名上建议使用 snake_case,例如 web_search,不要用 Web Search。部分模型提供方会拒绝带空格或特殊字符的名字,只用字母、数字、下划线和连字符兼容性最好[1]。
复杂参数怎么声明
参数较多时,可以用 Pydantic 模型定义输入,再通过 args_schema 参数交给 @tool,每个字段用 Field(description=...) 写清说明。也可以直接传入 JSON Schema[1]。
两个保留参数名
config 和 runtime 是保留名字,不能用作工具参数,否则运行时会报错[1]。需要读取对话状态、上下文或长期存储时,给工具加一个 runtime: ToolRuntime 参数。它由 ToolNode 在调用时注入,不会出现在发给模型的参数结构里;读取对话状态用 runtime.state[1]。
工具的返回值
工具可以返回字符串、对象、多模态内容,也可以返回 Command。普通返回值会被包装成 ToolMessage 交回模型处理[1]。
直接返回:跳过模型
把 return_direct 设为 True 后,工具的输出会直接作为智能体的结果,不再经过模型加工[1]。这个开关的规则需要注意:如果模型在同一轮里同时调用了普通工具,智能体不会结束,而是把所有结果交回模型;只有这一轮的每个工具都设置了 return_direct=True,循环才会结束[1]。结果还需要模型继续推理、总结或与其他工具串联的,就不适合设置它[1]。
如果工具返回的是 Command 并且指向当前图,那么 Command.update 里必须带上 tool_call_id 相同的 ToolMessage,否则 ToolNode 会抛出 ValueError[1]。
出错怎么办
工具出错时,可以借助智能体的中间件(middleware)重试失败的调用,或者返回自定义的错误信息[1]。
ToolRuntime 能拿到什么
ToolRuntime 把运行时信息分成几类。state 是短期状态,即当前对话的消息等内容;context 是只读的配置,适合放用户 ID 这类调用方传入的信息;store 是长期记忆,可以跨对话保留数据。此外还有 stream writer、执行信息、服务端信息、config 和工具调用 ID[1]。需要什么就读什么,不必全部用到。
三种返回方式怎么选
- 普通返回字符串或对象:结果会包装成
ToolMessage,模型接着处理,适合结果还需要推理或总结的情况[1]。 return_direct=True:工具输出直接作为结果返回,不再经过模型,适合结果本身就是最终答案的情况[1]。- 返回
Command:可以在返回的同时更新状态,但要带上匹配的ToolMessage[1]。
工具出错时的思路
工具可能因为参数不对、外部服务不可用或其他原因失败。把错误处理放在中间件里,可以统一决定是重试,还是把一条说明返回给模型,让它换一种方式继续[1]。
读完以后可以检查的几件事
- 工具名是否是 snake_case,并且没有使用
config、runtime这两个保留名[1]。 - 需要运行时信息的工具,是否通过
runtime: ToolRuntime参数读取[1]。 - 直接返回的工具,是否是本轮唯一被调用的工具,否则智能体不会结束[1]。
为什么工具名和描述很重要
模型只能通过工具的名字和描述来判断它的用途,并决定要不要调用,因此两者都要写清楚。默认情况下,描述来自函数的文档字符串;需要更精确的描述时,可以在 @tool 上直接指定[1]。
工具调用的基本流程
模型看到工具的名字、描述和参数结构之后,决定要不要调用某个工具,并生成调用所需的参数。工具运行后,它的返回值进入对话,模型再根据结果决定下一步:继续调用别的工具,或者直接回答[1]。理解这个循环,有助于判断一个工具应该返回什么。
常见问题
- 工具名报错:通常是名字里有空格或特殊字符,改成 snake_case 即可,见上文[1]。
- 模型自带的服务端工具:部分模型内置网络搜索、代码解释器等工具,它们在服务端执行,与自己写的工具不是一回事[1]。
- 参数名冲突:
config和runtime不能用,见「两个保留参数名」一节[1]。
下一步
LangChain 提供了大量预置工具和工具包,覆盖网络搜索、代码解释、数据库访问等场景,完整列表在集成页面[1]。另外,LangChain 也支持从 MCP 服务器获取工具[1]。
本文改编自 LangChain 开源文档中的 Tools 页面[1],原文以 MIT 许可证发布。
来源
- 1ToolsLangChain docs (MIT)
引用本页: LangChain 工具调用:@tool 怎么写. TellWorld, https://www.tellworld.net/zh/practice/practice-langchain-tools. 更新于 2026-11-14