# 创建自定义工具的 3 种技巧与使用场景

在使用 函数调用 或者创建 智能体 时,我们需要提供 工具列表 ,以便大语言模型可以使用这些工具,虽然 LangChain 内部集成了大量的工具和工具包,但并不一定适合我们的业务场景,更多场合下我们会使用自定义工具,在 LangChain 中提供了 3 种构建自定义工具的技巧: @tool 装饰器 、 StructuredTool.from_function()类方法 、 BaseTool子类 ,不同的方式有不同的优缺点与应用场景。

# 01. @tool 装饰器

@tool 装饰器是定义自定义工具的最简单方式,可以快速将当前的 函数 改造成 大语言模型工具 ,该装饰器默认使用函数名称作为工具名称,但可以通过传递字符串作为第一个参数来覆盖,此外,该装饰器将使用函数的 文档字符串 作为工具的描述,所以被装饰的函数必须要提供 文档字符串 。

使用示例如下:

from langchain_core.tools import tool


@tool
def multiply(a: int, b: int) -> int:
    """将传递的两个数字相乘"""
    return a * b



print("名称: ", multiply.name)
print("描述: ", multiply.description)
print("参数: ", multiply.args)
print("直接返回: ", multiply.return_direct)


print(multiply.invoke({"a": 2, "b": 8}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

输出内容:

名称:  multiply
描述:  将传递的两个数字相乘
参数:  {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
直接返回:  False
16
1
2
3
4
5

除了使用默认的配置外, @tool 装饰器还可以传递多个参数来执行相应的配置,例如传递 工具名字 、 参数描述 、 是否直接返回 等。

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import tool


class CalculatorInput(BaseModel):
    a: int = Field(description="第一个数字")
    b: int = Field(description="第二个数字")


@tool("multiply_tool", args_schema=CalculatorInput, return_direct=True)
def multiply(a: int, b: int) -> int:
    """将传递的两个数字相乘"""
    return a * b



print("名称: ", multiply.name)
print("描述: ", multiply.description)
print("参数: ", multiply.args)
print("直接返回: ", multiply.return_direct)


print(multiply.invoke({"a": 2, "b": 8}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

输出内容:

名称:  multiply_tool
描述:  将传递的两个数字相乘
参数:  {'a': {'title': 'A', 'description': '第一个数字', 'type': 'integer'}, 'b': {'title': 'B', 'description': '第二个数字', 'type': 'integer'}}
直接返回:  True
16
1
2
3
4
5

当使用 Google-style文档字符串 风格进行函数注释时,还可以设置 parse_docstring 为 True,这样 @tool 装饰器还可以获取到每个参数的相关解释,例如:

@tool(parse_docstring=True)
def foo(bar: str, baz: int) -> str:
    """The foo.

    Args:
        bar: The bar.
        baz: The baz.
    """
    return bar

print(foo.args)
1
2
3
4
5
6
7
8
9
10
11

输出内容:

{'bar': {'title': 'Bar', 'description': 'The bar.', 'type': 'string'}, 'baz': {'title': 'Baz', 'description': 'The baz.', 'type': 'integer'}}
1

对于一个原有的函数,并且该函数的参数相对来说比较简单,我们可以考虑使用 @tool 装饰器来转换该函数,可以极大减少重复性的工作,但是 @tool 装饰器装饰的工具并不能同时拥有 同步 和 异步 方法,只可以单独装饰,例如:

from langchain_core.tools import tool
@tool
async def amultiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b
1
2
3
4
5

所以对于一些需要同时考虑 同步 和 异步 的工具来说, @tool 装饰器就没法使用了。

# 02. StructuredTool 类方法

第 2 种快速创建 工具 的技巧是使用 StructuredTool.from_function() 类方法,该方法提供了比 @tool 装饰器更多的配置项,例如同时支持同步和异步等,修改的示例如下:

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import StructuredTool


class CalculatorInput(BaseModel):
    a: int = Field(description="第一个数字")
    b: int = Field(description="第二个数字")


def multiply(a: int, b: int) -> int:
    """将传递的两个数字相乘"""
    return a * b


async def amultiply(a: int, b: int) -> int:
    """将传递的两个数字相乘"""
    return a * b


calculator = StructuredTool.from_function(
    func=multiply,
    coroutine=amultiply,
    name="multiply_tool",
    description="用于将传递的两个整型相乘",
    return_direct=True,
    args_schema=CalculatorInput,
)


print("名称: ", calculator.name)
print("描述: ", calculator.description)
print("参数: ", calculator.args)
print("直接返回: ", calculator.return_direct)


print(calculator.invoke({"a": 2, "b": 8}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

输出内容:

名称:  multiply_tool
描述:  用于将传递的两个整型相乘
参数:  {'a': {'title': 'A', 'description': '第一个数字', 'type': 'integer'}, 'b': {'title': 'B', 'description': '第二个数字', 'type': 'integer'}}
直接返回:  True
16
1
2
3
4
5

对于已有的函数,并且想同时支持 同步 和 异步 ,可以考虑使用 StructuredTool.from_function() 类方法来实现,该方法的配置项很灵活,而且无需太多额外代码。

# 03. BaseTool 子类

在 LangChain 中,所有的工具都是 BaseTool 子类,并且使用 @tool 或者 StructuredTool.from_function() 创建的工具,底层都是包装成了 StructuredTool ,本质上也是 BaseTool 的子类。

所以如果对一个自定义工具来说,如果目前并没有任何一段已经实现的代码,则可以考虑继承 BaseTool 基类,并实现 _run() 方法来实现自定义工具(和使用 StructuredTool.from_function() 代码量差异并不是特别大)。

代码示例:

from typing import Any, Type

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import BaseTool


class CalculatorInput(BaseModel):
    a: int = Field(description="第一个数字")
    b: int = Field(description="第二个数字")


class MultiplyTool(BaseTool):
    """乘法计算工具"""
    name = "multiply_tool"
    description = "将传递的两个数字相乘"
    args_schema: Type[BaseModel] = CalculatorInput

    def _run(self, a: int, b: int) -> Any:
        return a * b


calculator = MultiplyTool()


print("名称: ", calculator.name)
print("描述: ", calculator.description)
print("参数: ", calculator.args)
print("直接返回: ", calculator.return_direct)


print(calculator.invoke({"a": 2, "b": 8}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

输出示例:

名称:  multiply_tool
描述:  将传递的两个数字相乘
参数:  {'a': {'title': 'A', 'description': '第一个数字', 'type': 'integer'}, 'b': {'title': 'B', 'description': '第二个数字', 'type': 'integer'}}
直接返回:  False
16
1
2
3
4
5

# 最新版 LangChain 用法提示

  • 新版 LangChain 更推荐用 LCEL、Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式 Chain、部分 AgentExecutor 写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。

  • 工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。

  • 如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从 langchain 拆到 langchain-core、langchain-community、langchain-openai、langgraph 等包。

# 拓展

  • 工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。

  • Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。

# 常见问题

  • 为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。

  • 为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。

# 面试题

  • 解释函数调用、工具调用和 Agent 的区别。

  • LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?

  • ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?

  • LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?

# 生产问题排查

问题 常见原因 处理方式
工具没有被调用 工具描述弱、绑定失败、模型不支持 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型
参数格式错误 schema 设计不清晰,模型生成字段不稳定 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试
联网结果不可用 搜索为空、接口超时、命中低质量页面 增加超时、重试、结果过滤、来源白名单和降级回答
Agent 循环不停止 缺少终止条件或工具返回被误判 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则
线上难以复现 缺少输入、工具请求和模型响应日志 给每次调用加 trace id,记录工具入参、出参、耗时和异常