LangChain-07-Tools模块

模块概览

职责与定位

Tools模块是LangChain中Agent与外部世界交互的核心接口层。该模块定义了工具的统一抽象,使Agent能够调用各种功能(搜索、计算、API调用等),并提供了简便的工具创建方式。

核心职责:

  • 定义统一的工具接口(BaseTool)
  • 提供@tool装饰器简化工具创建
  • 自动生成工具参数schema
  • 支持同步和异步工具
  • 提供工具错误处理机制
  • 支持工具输出格式化
  • 与语言模型集成(通过bind_tools)
  • 提供Callback机制追踪工具执行

输入输出

输入:

  • 字符串: 简单工具的输入(str → str)
  • 字典: 结构化工具的参数(dict → Any)
  • ToolCall: 模型生成的工具调用请求(包含name、args、id)

输出:

  • 任意类型: 工具执行结果
  • ToolMessage: 当tool_call_id存在时,自动包装为ToolMessage
  • 可通过response_format控制输出格式:
    • “content”: 返回工具结果内容
    • “content_and_artifact”: 返回(内容, 原始对象)元组

上下游依赖

依赖:

  • langchain_core.runnables: Runnable基类,提供流式、批处理能力
  • pydantic: 参数验证和schema生成
  • typing: 类型推导和schema自动生成
  • langchain_core.callbacks: 工具执行追踪
  • langchain_core.messages: ToolCall和ToolMessage

被依赖:

  • langchain_core.language_models: bind_tools绑定工具,将工具转换为模型特定格式
  • langchain.agents: Agent执行工具,解析模型输出并调用工具
  • langgraph: 图式Agent工作流,在节点中执行工具
  • langchain_core.retrievers: 通过create_retriever_tool包装检索器

整体服务架构图

完整系统架构

flowchart TB
    subgraph UserCode["用户代码层"]
        USER[用户应用<br/>定义工具函数]
    end

    subgraph ToolCreation["工具创建层"]
        DECORATOR[@tool装饰器]
        FROM_FUNC[from_function方法]
        SUBCLASS[继承BaseTool]
        SCHEMA_GEN[create_schema_from_function<br/>Schema生成器]
    end

    subgraph ToolCore["工具核心层"]
        BASE_TOOL[BaseTool<br/>抽象基类]
        TOOL[Tool<br/>简单工具]
        STRUCTURED[StructuredTool<br/>结构化工具]
        RETRIEVER_TOOL[RetrieverTool<br/>检索工具]
    end

    subgraph Integration["模型集成层"]
        BIND_TOOLS[bind_tools<br/>绑定工具到模型]
        TOOL_SCHEMA[tool_call_schema<br/>生成模型schema]
        FORMAT[convert_to_openai_tool<br/>格式转换]
    end

    subgraph LLM["语言模型层"]
        CHAT_MODEL[ChatModel<br/>聊天模型]
        LLM_INVOKE[invoke/ainvoke<br/>模型调用]
    end

    subgraph Execution["工具执行层"]
        INVOKE[invoke/ainvoke<br/>工具调用入口]
        RUN[run/arun<br/>执行管理]
        PARSE_INPUT[_parse_input<br/>输入解析]
        TO_ARGS[_to_args_and_kwargs<br/>参数转换]
        RUN_IMPL[_run/_arun<br/>实际执行]
    end

    subgraph Callback["回调追踪层"]
        CB_START[on_tool_start]
        CB_END[on_tool_end]
        CB_ERROR[on_tool_error]
    end

    subgraph Output["输出处理层"]
        FORMAT_OUTPUT[_format_output<br/>格式化输出]
        TOOL_MSG[ToolMessage<br/>工具消息]
    end

    USER --> DECORATOR
    USER --> FROM_FUNC
    USER --> SUBCLASS

    DECORATOR --> SCHEMA_GEN
    FROM_FUNC --> SCHEMA_GEN
    SCHEMA_GEN --> STRUCTURED
    SCHEMA_GEN --> TOOL
    SUBCLASS --> BASE_TOOL

    BASE_TOOL <|-- TOOL
    BASE_TOOL <|-- STRUCTURED
    BASE_TOOL <|-- RETRIEVER_TOOL

    TOOL --> BIND_TOOLS
    STRUCTURED --> BIND_TOOLS
    RETRIEVER_TOOL --> BIND_TOOLS

    BIND_TOOLS --> TOOL_SCHEMA
    TOOL_SCHEMA --> FORMAT
    FORMAT --> CHAT_MODEL

    CHAT_MODEL --> LLM_INVOKE
    LLM_INVOKE -.返回ToolCall.-> INVOKE

    INVOKE --> RUN
    RUN --> CB_START
    CB_START --> PARSE_INPUT
    PARSE_INPUT --> TO_ARGS
    TO_ARGS --> RUN_IMPL
    RUN_IMPL -.成功.-> CB_END
    RUN_IMPL -.失败.-> CB_ERROR
    CB_END --> FORMAT_OUTPUT
    FORMAT_OUTPUT --> TOOL_MSG

    style BASE_TOOL fill:#e1f5ff,stroke:#0277bd,stroke-width:3px
    style DECORATOR fill:#fff4e1,stroke:#f57c00,stroke-width:2px
    style STRUCTURED fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style BIND_TOOLS fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style RUN_IMPL fill:#ffebee,stroke:#c62828,stroke-width:2px

架构说明

图意概述

该架构图展示了LangChain Tools模块从工具创建到执行的完整生命周期:

  1. 用户代码层: 用户定义Python函数作为工具实现
  2. 工具创建层: 通过装饰器、类方法或继承将函数转换为工具对象
  3. 工具核心层: 三种工具类型(Tool/StructuredTool/RetrieverTool)提供不同能力
  4. 模型集成层: 将工具转换为模型可理解的schema格式
  5. 语言模型层: 模型根据用户输入决定调用哪个工具
  6. 工具执行层: 解析模型输出,执行工具,处理错误
  7. 回调追踪层: 追踪工具执行全过程,支持日志、监控
  8. 输出处理层: 格式化输出为ToolMessage返回给模型

关键接口与边界

工具创建边界:

  • 函数必须有类型注解(用于schema推导)
  • 函数必须有docstring(用于工具描述)
  • 参数类型必须可序列化为JSON Schema
  • 不支持*args/**kwargs(除特殊参数run_manager/callbacks)

模型集成边界:

  • bind_tools将工具转换为模型特定格式(OpenAI/Anthropic等)
  • tool_call_schema过滤掉注入参数(如InjectedToolCallId)
  • 模型返回的ToolCall必须包含name、args、id字段

执行边界:

  • 同步工具通过_run执行
  • 异步工具通过_arun执行,如无则用线程池执行_run
  • 错误处理支持三种策略: 抛出异常/返回错误信息/自定义处理函数
  • 并发安全: 工具实例可在多线程/协程中复用

异常与回退

异常类型:

  1. ToolException: 工具执行错误,可通过handle_tool_error捕获
  2. ValidationError: 参数验证失败,可通过handle_validation_error捕获
  3. SchemaAnnotationError: 工具定义错误,创建时抛出
  4. 其他异常: 向上传播,中断Agent执行

回退策略:

# 策略1: 返回错误信息(不中断)
@tool(handle_tool_error=True)
def risky_tool(input: str) -> str:
    if bad_input:
        raise ToolException("Invalid")
    return result

# 策略2: 自定义错误信息
@tool(handle_tool_error="Tool failed, using default value")
def with_default(input: str) -> str:
    ...

# 策略3: 自定义错误处理
@tool(handle_tool_error=lambda e: f"Error: {e}, retrying...")
def with_handler(input: str) -> str:
    ...

性能与容量

性能特征:

  • Schema生成: 装饰时完成,无运行时开销
  • 参数验证: Pydantic验证,典型耗时<1ms
  • 回调开销: 异步执行,不阻塞主流程
  • 序列化: 工具输入/输出使用JSON序列化,复杂对象需自定义

容量限制:

  • 单个工具参数数量: 建议<20个(模型上下文限制)
  • 工具名称长度: 建议<64字符
  • 工具描述长度: 建议<500字符(影响模型选择准确度)
  • 并发执行: 无限制,但需注意外部资源(API限流、数据库连接)

优化建议:

  • 使用批量工具减少调用次数
  • 缓存工具实例避免重复创建
  • 异步工具利用协程并发
  • 长时间工具添加超时控制

版本兼容与演进

Pydantic兼容:

  • 支持Pydantic v1和v2
  • 自动检测函数参数中的Pydantic模型版本
  • 混用v1/v2模型会抛出NotImplementedError

Runnable集成:

  • 工具继承RunnableSerializable
  • 支持所有Runnable方法: invoke/ainvoke/batch/stream
  • 可通过RunnableLambda将工具嵌入链

向后兼容:

  • Tool类保持单参数语义(str → str)
  • args_schema可选,默认推导
  • 旧版Agent API继续支持

模块交互图

模块间调用关系

sequenceDiagram
    autonumber
    participant User as 用户代码
    participant Decorator as @tool装饰器
    participant Schema as create_schema_from_function
    participant StructTool as StructuredTool
    participant Model as ChatModel
    participant BindTools as bind_tools
    participant ToolSchema as tool_call_schema
    participant Converter as convert_to_openai_tool
    participant Invoke as invoke
    participant Run as run
    participant Callback as CallbackManager
    participant RunImpl as _run实现

    Note over User,RunImpl: 阶段1: 工具定义与创建

    User->>Decorator: @tool<br/>def search(query: str): ...
    activate Decorator
    Decorator->>Decorator: 提取函数名称和docstring
    Decorator->>Schema: create_schema_from_function<br/>(name, func, parse_docstring=True)
    activate Schema
    Schema->>Schema: inspect.signature(func)<br/>获取函数签名
    Schema->>Schema: get_type_hints(func)<br/>提取类型注解
    Schema->>Schema: _parse_google_docstring<br/>解析参数描述
    Schema->>Schema: _create_subset_model<br/>构建Pydantic模型
    Schema-->>Decorator: ArgsSchema<br/>(Pydantic模型)
    deactivate Schema

    Decorator->>StructTool: StructuredTool.from_function<br/>(func, name, description, args_schema)
    activate StructTool
    StructTool->>StructTool: 验证参数完整性
    StructTool-->>Decorator: StructuredTool实例
    deactivate StructTool
    Decorator-->>User: search工具对象
    deactivate Decorator

    Note over User,RunImpl: 阶段2: 工具绑定到模型

    User->>Model: model.bind_tools([search, calculator])
    activate Model
    Model->>BindTools: bind_tools(tools)
    activate BindTools

    loop 每个工具
        BindTools->>ToolSchema: tool.tool_call_schema<br/>获取工具schema
        activate ToolSchema
        ToolSchema->>ToolSchema: 过滤注入参数<br/>(InjectedToolCallId等)
        ToolSchema-->>BindTools: 过滤后的schema
        deactivate ToolSchema

        BindTools->>Converter: convert_to_openai_tool(schema)
        activate Converter
        Converter->>Converter: 转换为OpenAI函数格式<br/>{"name":..., "parameters":...}
        Converter-->>BindTools: OpenAI工具定义
        deactivate Converter
    end

    BindTools-->>Model: tools参数<br/>(模型特定格式)
    deactivate BindTools
    Model-->>User: 绑定工具的模型
    deactivate Model

    Note over User,RunImpl: 阶段3: 模型调用与工具选择

    User->>Model: model.invoke("搜索Python教程")
    activate Model
    Model->>Model: 将工具注入prompt<br/>作为函数定义
    Model->>Model: 调用底层LLM API<br/>(OpenAI/Anthropic等)
    Model-->>User: AIMessage(tool_calls=[<br/>  {"name":"search",<br/>   "args":{"query":"Python教程"},<br/>   "id":"call_123"}<br/>])
    deactivate Model

    Note over User,RunImpl: 阶段4: 工具执行

    User->>Invoke: search.invoke(tool_call)
    activate Invoke
    Invoke->>Invoke: _prep_run_args<br/>解析ToolCall
    Invoke->>Run: run(tool_input, tool_call_id)
    activate Run

    Run->>Callback: on_tool_start<br/>(name, input)
    activate Callback
    Callback->>Callback: 记录开始时间
    Callback->>Callback: 触发用户回调
    Callback-->>Run: RunManager
    deactivate Callback

    Run->>Run: _parse_input<br/>验证参数类型
    Run->>Run: _to_args_and_kwargs<br/>转换为函数参数
    Run->>RunImpl: _run(query="Python教程",<br/>     run_manager=...)
    activate RunImpl
    RunImpl->>RunImpl: 执行实际业务逻辑<br/>(API调用/数据库查询等)

    alt 执行成功
        RunImpl-->>Run: 工具结果
        Run->>Run: _format_output<br/>包装为ToolMessage
        Run->>Callback: on_tool_end(output)
        activate Callback
        Callback->>Callback: 记录结束时间
        Callback->>Callback: 计算耗时
        deactivate Callback
        Run-->>Invoke: ToolMessage(content=...,<br/>              tool_call_id="call_123")
    else 工具异常
        RunImpl-->>Run: ToolException
        Run->>Run: _handle_tool_error<br/>根据handle_tool_error策略处理
        alt handle_tool_error=True
            Run->>Callback: on_tool_error(error)
            Run-->>Invoke: ToolMessage(content="Error:...",<br/>              status="error")
        else handle_tool_error=False
            Run->>Callback: on_tool_error(error)
            Run-->>Invoke: 抛出异常
        end
    end
    deactivate RunImpl
    deactivate Run
    Invoke-->>User: ToolMessage
    deactivate Invoke

    Note over User,RunImpl: 阶段5: 结果返回模型

    User->>Model: model.invoke([<br/>  HumanMessage("搜索Python教程"),<br/>  AIMessage(tool_calls=[...]),<br/>  ToolMessage(...)<br/>])
    activate Model
    Model->>Model: 基于工具结果生成最终答案
    Model-->>User: AIMessage(content="根据搜索结果...")
    deactivate Model

模块交互说明

阶段1: 工具定义与创建

图意: 展示用户如何通过@tool装饰器定义工具,以及装饰器内部如何自动生成工具schema。

关键步骤:

  1. 装饰器捕获被装饰的函数
  2. create_schema_from_function解析函数签名:
    • inspect.signature获取参数列表
    • get_type_hints提取类型注解
    • _parse_google_docstring解析参数描述
    • 生成Pydantic模型作为args_schema
  3. StructuredTool.from_function创建工具实例
  4. 返回可调用的工具对象

边界条件:

  • 函数必须有类型注解(否则schema推导失败)
  • 参数类型必须是Pydantic支持的类型
  • 异步函数自动检测(通过inspect.iscoroutinefunction)

性能要点:

  • Schema生成在模块加载时完成(装饰器执行阶段)
  • 运行时无需重新解析函数签名
  • Pydantic模型编译后缓存,验证性能高

阶段2: 工具绑定到模型

图意: 展示如何将工具绑定到语言模型,以及工具schema如何转换为模型特定格式。

关键步骤:

  1. bind_tools接收工具列表
  2. 对每个工具调用tool_call_schema
    • 过滤掉注入参数(InjectedToolCallId、run_manager等)
    • 生成适合模型调用的schema(不包含内部参数)
  3. convert_to_openai_tool转换为OpenAI函数格式:
    {
      "type": "function",
      "function": {
        "name": "search",
        "description": "搜索工具",
        "parameters": {
          "type": "object",
          "properties": {"query": {"type": "string"}},
          "required": ["query"]
        }
      }
    }
    
  4. 返回绑定了工具的新模型实例

边界条件:

  • 工具数量限制: OpenAI支持最多128个工具
  • 工具名称必须是合法标识符(字母数字下划线)
  • schema必须符合JSON Schema规范

兼容性:

  • 不同模型提供商有不同的工具格式(OpenAI/Anthropic/Google)
  • convert_to_openai_tool是通用转换器,各模型实现自己的bind_tools

阶段3: 模型调用与工具选择

图意: 展示模型如何根据用户输入决定调用哪个工具。

关键步骤:

  1. 用户调用绑定工具的模型
  2. 模型将工具定义注入prompt(作为system message或function定义)
  3. 模型分析用户输入,决定是否需要工具以及调用哪个工具
  4. 返回AIMessage,包含tool_calls字段:
    AIMessage(
        content="",  # 可能为空
        tool_calls=[
            {
                "name": "search",
                "args": {"query": "Python教程"},
                "id": "call_123",  # 唯一ID,用于关联响应
                "type": "tool_call"
            }
        ]
    )
    

模型决策因素:

  • 工具描述的清晰度(影响选择准确率)
  • 参数schema的完整性(影响参数提取)
  • 历史对话上下文
  • 用户输入的明确性

并发处理:

  • 模型可能返回多个工具调用(并行执行)
  • Agent需要收集所有工具结果后再调用模型

阶段4: 工具执行

图意: 展示工具执行的完整流程,包括参数解析、回调追踪、错误处理。

关键步骤:

  1. invoke接收ToolCall对象
  2. _prep_run_args解析ToolCall:
    tool_input = {"query": "Python教程"}
    tool_call_id = "call_123"
    
  3. run方法管理执行流程:
    • on_tool_start: 记录开始时间,触发回调
    • _parse_input: Pydantic验证参数类型
    • _to_args_and_kwargs: 转换为函数调用参数
    • _run: 执行用户定义的工具逻辑
    • _format_output: 包装为ToolMessage
    • on_tool_end/on_tool_error: 记录结束,计算耗时

回调机制:

  • CallbackManager管理回调链
  • 支持多个回调处理器(日志/监控/追踪)
  • 异步执行,不阻塞工具执行
  • 传递context(run_id、parent_run_id等)

错误处理策略:

# 策略1: 返回错误信息(不中断Agent)
handle_tool_error=True
# -> ToolMessage(content="Error: ...", status="error")

# 策略2: 自定义错误信息
handle_tool_error="工具执行失败,请重试"
# -> ToolMessage(content="工具执行失败,请重试", status="error")

# 策略3: 自定义处理函数
handle_tool_error=lambda e: f"错误: {e}, 使用默认值"
# -> ToolMessage(content="错误: ..., 使用默认值", status="error")

# 策略4: 抛出异常(中断Agent)
handle_tool_error=False  # 默认
# -> 向上抛出ToolException,Agent停止执行

输出格式:

# response_format="content"
ToolMessage(
    content="搜索结果...",
    tool_call_id="call_123",
    name="search",
    status="success"
)

# response_format="content_and_artifact"
ToolMessage(
    content="搜索结果摘要",
    artifact={"results": [...], "total": 10},  # 原始数据
    tool_call_id="call_123",
    name="search",
    status="success"
)

阶段5: 结果返回模型

图意: 展示工具执行结果如何返回给模型,模型基于结果生成最终答案。

关键步骤:

  1. Agent收集工具执行结果(ToolMessage)
  2. 将消息历史传回模型:
    messages = [
        HumanMessage("搜索Python教程"),
        AIMessage(tool_calls=[{"name":"search", ...}]),
        ToolMessage("搜索结果: ...", tool_call_id="call_123")
    ]
    
  3. 模型分析工具结果:
    • 提取关键信息
    • 判断是否需要调用更多工具
    • 生成最终答案
  4. 返回AIMessage(content=“根据搜索结果…”)

多轮交互:

用户 -> 模型 -> 工具1 -> 模型 -> 工具2 -> 模型 -> 最终答案

终止条件:

  • 模型不再返回tool_calls(生成文本答案)
  • 达到最大轮次限制(防止无限循环)
  • 工具返回return_direct=True(直接返回工具结果)

核心数据结构

classDiagram
    class BaseTool {
        <<abstract>>
        +name: str
        +description: str
        +args_schema: Type[BaseModel]|None
        +return_direct: bool
        +handle_tool_error: bool|str|Callable
        +response_format: str
        +invoke(input) Any
        +_run(...)* Any
        +_arun(...) Any
    }

    class Tool {
        +func: Callable
        +coroutine: Callable|None
        +from_function(...) Tool
    }

    class StructuredTool {
        +func: Callable
        +coroutine: Callable|None
        +from_function(...) StructuredTool
    }

    class ToolCall {
        +name: str
        +args: dict
        +id: str
        +type: str
    }

    class ToolException {
        <<Exception>>
    }

    BaseTool <|-- Tool
    BaseTool <|-- StructuredTool
    BaseTool ..> ToolCall : accepts
    BaseTool ..> ToolException : raises

数据结构说明

BaseTool字段

字段 类型 必填 默认值 说明
name str - 工具唯一名称
description str - 工具功能描述,Agent据此选择工具
args_schema Type[BaseModel]|None None 参数schema,用于验证和生成工具描述
return_direct bool False True时直接返回结果,不继续Agent循环
handle_tool_error bool|str|Callable False 错误处理策略
response_format str “content” 输出格式: “content"或"content_and_artifact”

ToolCall结构

字段 类型 说明
name str 工具名称
args dict 工具参数字典
id str 调用唯一ID
type str 固定为"tool_call"

LangChain-07-Tools模块 - 补充章节

模块内部调用链路详解

路径1: 工具创建路径(@tool装饰器)

完整调用链时序图

sequenceDiagram
    autonumber
    participant UserFunc as 用户函数
    participant Tool as tool装饰器
    participant Factory as _create_tool_factory
    participant CreateSchema as create_schema_from_function
    participant ParseDoc as _parse_google_docstring
    participant StructTool as StructuredTool.from_function

    Note over UserFunc,StructTool: 装饰时执行(模块加载阶段)

    UserFunc->>Tool: @tool<br/>def search(query: str): ...
    activate Tool
    Tool->>Tool: 检测调用模式<br/>(直接装饰/带参数/指定名称)
    Tool->>Factory: _create_tool_factory("search")
    activate Factory
    Factory->>Factory: 返回_tool_factory闭包
    deactivate Factory

    Tool->>Factory: _tool_factory(user_func)
    activate Factory
    Factory->>Factory: 提取函数名和docstring
    Factory->>Factory: inspect.iscoroutinefunction(func)<br/>检测异步函数

    alt parse_docstring=True
        Factory->>CreateSchema: create_schema_from_function<br/>(name, func, parse_docstring=True)
        activate CreateSchema
        CreateSchema->>CreateSchema: inspect.signature(func)
        CreateSchema->>CreateSchema: get_type_hints(func)
        CreateSchema->>ParseDoc: _parse_google_docstring(docstring)
        activate ParseDoc
        ParseDoc->>ParseDoc: 分割summary和Args部分
        ParseDoc->>ParseDoc: 解析每个参数描述
        ParseDoc-->>CreateSchema: (description, arg_descriptions)
        deactivate ParseDoc
        CreateSchema->>CreateSchema: _create_subset_model<br/>构建Pydantic模型
        CreateSchema-->>Factory: args_schema
        deactivate CreateSchema
    end

    Factory->>StructTool: StructuredTool.from_function<br/>(func, name, description, args_schema)
    activate StructTool
    StructTool->>StructTool: 验证参数完整性
    StructTool->>StructTool: 创建工具实例
    StructTool-->>Factory: StructuredTool实例
    deactivate StructTool
    Factory-->>Tool: BaseTool实例
    deactivate Factory
    Tool-->>UserFunc: 返回工具对象
    deactivate Tool

关键代码路径说明

1. 装饰器入口 (langchain_core/tools/convert.py)

装饰器支持三种调用方式:

# 方式1: 直接装饰
@tool
def search(query: str) -> str:
    \"\"\"搜索工具\"\"\"
    return api_search(query)

# 方式2: 指定名称
@tool("custom_search")
def my_search(query: str) -> str:
    return api_search(query)

# 方式3: 带参数
@tool(parse_docstring=True, return_direct=True)
def calculator(expr: str) -> float:
    \"\"\"计算表达式

    Args:
        expr: 数学表达式
    \"\"\"
    return eval(expr)

核心逻辑:

  • 检测name_or_callable类型确定调用模式
  • 创建工具工厂函数_create_tool_factory
  • 工厂函数返回闭包_tool_factory,延迟到应用时执行

2. Schema生成 (langchain_core/tools/base.py:279-376)

def create_schema_from_function(model_name: str, func: Callable, *, parse_docstring: bool = False) -> type[BaseModel]:
    # 1) 获取函数签名
    sig = inspect.signature(func)

    # 2) 使用Pydantic的validate_arguments生成模型
    validated = validate_arguments(func, config=_SchemaConfig)
    inferred_model = validated.model

    # 3) 解析docstring提取参数描述
    if parse_docstring:
        description, arg_descriptions = _parse_python_function_docstring(func, annotations)

    # 4) 过滤特殊参数(run_manager, callbacks等)
    filter_args_ = list(FILTERED_ARGS) + [注入参数]

    # 5) 创建子模型(只包含用户参数)
    return _create_subset_model(model_name, inferred_model, valid_properties, descriptions=arg_descriptions)

关键点:

  • validate_arguments是Pydantic装饰器,自动从函数签名生成模型
  • _parse_google_docstring解析Google Style docstring
  • _create_subset_model创建干净的schema(不包含内部参数)

性能: Schema生成耗时1-5ms,仅在模块加载时执行一次

路径2: 工具执行路径(run方法)

完整调用链时序图

sequenceDiagram
    autonumber
    participant User as 用户/Agent
    participant Invoke as tool.invoke
    participant Run as tool.run
    participant CBStart as on_tool_start
    participant ParseInput as _parse_input
    participant RunImpl as _run实现
    participant FormatOutput as _format_output
    participant CBEnd as on_tool_end

    User->>Invoke: tool.invoke(tool_call, config)
    activate Invoke
    Invoke->>Invoke: _prep_run_args<br/>解析ToolCall
    Invoke->>Run: run(tool_input, tool_call_id)
    activate Run

    Run->>Run: CallbackManager.configure<br/>配置回调
    Run->>CBStart: on_tool_start(name, input)
    activate CBStart
    CBStart->>CBStart: 记录开始时间
    CBStart->>CBStart: 生成run_id
    CBStart-->>Run: run_manager
    deactivate CBStart

    Run->>ParseInput: _parse_input(tool_input, tool_call_id)
    activate ParseInput
    ParseInput->>ParseInput: Pydantic验证参数
    ParseInput->>ParseInput: 注入tool_call_id(如需要)
    ParseInput-->>Run: 验证后的参数
    deactivate ParseInput

    Run->>Run: _to_args_and_kwargs<br/>转换为函数参数
    Run->>Run: 注入run_manager<br/>注入config

    Run->>RunImpl: _run(*args, **kwargs)
    activate RunImpl
    RunImpl->>RunImpl: 执行业务逻辑

    alt 执行成功
        RunImpl-->>Run: result
        Run->>FormatOutput: _format_output(content, artifact, tool_call_id)
        activate FormatOutput
        FormatOutput->>FormatOutput: 创建ToolMessage
        FormatOutput-->>Run: ToolMessage
        deactivate FormatOutput
        Run->>CBEnd: on_tool_end(output)
        activate CBEnd
        CBEnd->>CBEnd: 记录结束时间
        CBEnd->>CBEnd: 计算耗时
        deactivate CBEnd
        Run-->>Invoke: ToolMessage
    else 工具异常
        RunImpl-->>Run: ToolException
        Run->>Run: _handle_tool_error
        alt handle_tool_error=True
            Run->>CBEnd: on_tool_error
            Run-->>Invoke: ToolMessage(status="error")
        else handle_tool_error=False
            Run->>CBEnd: on_tool_error
            Run-->>Invoke: 抛出异常
        end
    end
    deactivate RunImpl
    deactivate Run
    Invoke-->>User: 工具执行结果
    deactivate Invoke

关键代码路径说明

1. run方法核心流程 (langchain_core/tools/base.py:750-860)

def run(self, tool_input, callbacks, tags, metadata, config, tool_call_id, **kwargs):
    # 1) 配置回调管理器
    callback_manager = CallbackManager.configure(callbacks, self.callbacks, self.verbose, tags, self.tags, metadata, self.metadata)

    # 2) 启动追踪
    run_manager = callback_manager.on_tool_start(
        {"name": self.name, "description": self.description},
        tool_input,
        run_name=run_name,
        run_id=run_id,
    )

    # 3) 执行工具
    try:
        # 3.1) 转换参数
        tool_args, tool_kwargs = self._to_args_and_kwargs(tool_input, tool_call_id)

        # 3.2) 注入上下文
        if signature(self._run).parameters.get("run_manager"):
            tool_kwargs["run_manager"] = run_manager
        if config_param := _get_runnable_config_param(self._run):
            tool_kwargs[config_param] = config

        # 3.3) 执行
        response = self._run(*tool_args, **tool_kwargs)

        # 3.4) 处理响应格式
        if self.response_format == "content_and_artifact":
            content, artifact = response
        else:
            content = response

    except ValidationError as e:
        # 参数验证错误
        if self.handle_validation_error:
            content = _handle_validation_error(e, self.handle_validation_error)
            status = "error"
        else:
            run_manager.on_tool_error(e)
            raise

    except ToolException as e:
        # 工具执行错误
        if self.handle_tool_error:
            content = _handle_tool_error(e, self.handle_tool_error)
            status = "error"
        else:
            run_manager.on_tool_error(e)
            raise

    # 4) 格式化输出
    output = _format_output(content, artifact, tool_call_id, self.name, status)

    # 5) 结束追踪
    run_manager.on_tool_end(output)

    return output

关键点:

  • 回调管理器合并多个回调源
  • run_manager追踪整个执行流程
  • 错误分类处理(ValidationError/ToolException/其他)
  • 根据tool_call_id决定是否包装为ToolMessage

2. _parse_input参数验证 (langchain_core/tools/base.py:606-683)

def _parse_input(self, tool_input: str | dict, tool_call_id: str | None) -> str | dict:
    if isinstance(tool_input, str):
        # 字符串输入:单参数工具
        if self.args_schema:
            key_ = next(iter(get_fields(self.args_schema).keys()))
            self.args_schema.model_validate({key_: tool_input})
        return tool_input

    # 字典输入:Pydantic验证
    if self.args_schema:
        # 注入tool_call_id(如果工具需要)
        for k, v in get_all_basemodel_annotations(self.args_schema).items():
            if _is_injected_arg_type(v, injected_type=InjectedToolCallId):
                if tool_call_id is None:
                    raise ValueError("缺少tool_call_id")
                tool_input[k] = tool_call_id

        # 验证
        result = self.args_schema.model_validate(tool_input)

        # 只返回用户提供的参数
        return {k: getattr(result, k) for k in tool_input}

    return tool_input

关键点:

  • 字符串输入适用于单参数简单工具
  • Pydantic自动验证类型、必填、默认值
  • 注入参数(InjectedToolCallId)自动填充
  • 过滤默认值(只返回用户提供的参数)

3. _format_output输出包装 (langchain_core/tools/base.py:1090-1119)

def _format_output(content, artifact, tool_call_id, name, status):
    # 如果已经是ToolMessage或无tool_call_id,直接返回
    if isinstance(content, ToolOutputMixin) or tool_call_id is None:
        return content

    # 确保content是字符串或列表格式
    if not _is_message_content_type(content):
        content = json.dumps(content)

    # 创建ToolMessage
    return ToolMessage(content, artifact=artifact, tool_call_id=tool_call_id, name=name, status=status)

关键点:

  • tool_call_id存在时自动包装为ToolMessage(Agent调用场景)
  • tool_call_id为None时返回原始内容(直接调用场景)
  • 复杂对象自动JSON序列化

性能与并发

性能特征:

  • 参数验证: ~0.1-1ms (Pydantic)
  • 回调开销: ~0.01-0.1ms/回调
  • ToolMessage创建: ~0.01ms
  • 总开销: ~1-3ms (不含工具实际执行)

并发安全:

tool = create_my_tool()

# 线程安全
with ThreadPoolExecutor() as executor:
    results = list(executor.map(tool.invoke, inputs))

# 协程并发
results = await asyncio.gather(*[tool.ainvoke(inp) for inp in inputs])

工具实例是无状态的,可以在多线程/协程中安全复用。

路径3: 工具绑定路径(bind_tools)

完整调用链时序图

sequenceDiagram
    autonumber
    participant User as 用户代码
    participant Model as ChatModel
    participant BindTools as bind_tools
    participant ToolSchema as tool_call_schema
    participant Converter as convert_to_openai_tool

    User->>Model: model.bind_tools([search, calculator])
    activate Model
    Model->>BindTools: bind_tools(tools)
    activate BindTools

    loop 每个工具
        BindTools->>ToolSchema: tool.tool_call_schema
        activate ToolSchema
        ToolSchema->>ToolSchema: 获取args_schema
        ToolSchema->>ToolSchema: 过滤注入参数<br/>(InjectedToolCallId, run_manager等)
        ToolSchema->>ToolSchema: _create_subset_model<br/>创建干净的schema
        ToolSchema-->>BindTools: 过滤后的schema
        deactivate ToolSchema

        BindTools->>Converter: convert_to_openai_tool(schema)
        activate Converter
        Converter->>Converter: 转换为OpenAI函数格式<br/>{"name":..., "parameters":...}
        Converter-->>BindTools: OpenAI工具定义
        deactivate Converter
    end

    BindTools->>BindTools: 生成tools列表
    BindTools-->>Model: 返回tools参数
    deactivate BindTools

    Model->>Model: 创建新模型实例<br/>self.bind(tools=tools)
    Model-->>User: 绑定工具的模型
    deactivate Model

关键代码路径说明

1. tool_call_schema属性 (langchain_core/tools/base.py:542-564)

@property
def tool_call_schema(self) -> ArgsSchema:
    """获取用于模型调用的schema(过滤注入参数)"""
    if isinstance(self.args_schema, dict):
        if self.description:
            return {**self.args_schema, "description": self.description}
        return self.args_schema

    # 获取完整schema
    full_schema = self.get_input_schema()

    # 过滤注入参数
    fields = []
    for name, type_ in get_all_basemodel_annotations(full_schema).items():
        if not _is_injected_arg_type(type_):
            fields.append(name)

    # 创建子模型
    return _create_subset_model(self.name, full_schema, fields, fn_description=self.description)

关键点:

  • 过滤掉InjectedToolCallId等注入参数
  • 过滤掉run_managercallbacks等内部参数
  • 返回的schema只包含模型应该提供的参数

2. convert_to_openai_tool转换 (langchain_core/utils/function_calling.py)

def convert_to_openai_tool(tool: BaseTool | type[BaseModel] | dict) -> dict:
    \"\"\"转换为OpenAI函数格式\"\"\"
    if isinstance(tool, BaseTool):
        schema = tool.tool_call_schema
        name = tool.name
        description = tool.description
    else:
        # 处理Pydantic模型或dict
        ...

    # 转换为OpenAI格式
    return {
        "type": "function",
        "function": {
            "name": name,
            "description": description,
            "parameters": schema.model_json_schema()
        }
    }

输出示例:

{
  "type": "function",
  "function": {
    "name": "search",
    "description": "搜索互联网信息",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "description": "搜索关键词"
        },
        "limit": {
          "type": "integer",
          "description": "结果数量",
          "default": 10
        }
      },
      "required": ["query"]
    }
  }
}

3. ChatModel.bind实现 (langchain_core/language_models/chat_models.py)

def bind_tools(self, tools: Sequence[dict | type | Callable | BaseTool], *, tool_choice: str | None = None, **kwargs) -> Runnable:
    \"\"\"绑定工具到模型\"\"\"
    # 转换所有工具为统一格式
    formatted_tools = [convert_to_openai_tool(tool) for tool in tools]

    # 调用self.bind绑定参数
    return self.bind(tools=formatted_tools, tool_choice=tool_choice, **kwargs)

关键点:

  • bind方法创建新的Runnable,不修改原模型
  • tool_choice控制模型是否必须调用工具
  • 不同模型提供商实现自己的bind_tools(格式转换)

模型特定格式

OpenAI格式:

{
  "type": "function",
  "function": {"name": "...", "description": "...", "parameters": {...}}
}

Anthropic格式:

{
  "name": "...",
  "description": "...",
  "input_schema": {...}
}

Google格式:

{
  "name": "...",
  "description": "...",
  "parameters": {...}
}

convert_to_openai_tool是通用转换器,各模型实现适配器转换为特定格式。

核心API详解

API-1: @tool装饰器

基本信息

  • 名称: tool
  • 函数签名: def tool(name_or_callable: str | Callable | None = None, *, description: str | None = None, return_direct: bool = False, args_schema: ArgsSchema | None = None, infer_schema: bool = True, response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False) -> BaseTool | Callable
  • 幂等性: 幂等

功能说明

将Python函数转换为LangChain工具。自动从函数签名推导参数schema,从docstring提取描述。

请求结构体

name_or_callable: str | Callable | None  # 工具名或函数
description: str | None  # 工具描述
return_direct: bool  # 是否直接返回
args_schema: ArgsSchema | None  # 自定义参数schema
infer_schema: bool  # 是否推导schema
response_format: str  # 响应格式
parse_docstring: bool  # 是否解析docstring
参数 类型 必填 默认值 说明
name_or_callable str|Callable|None None 工具名称或函数
description str|None None 工具描述,优先级高于docstring
return_direct bool False 是否直接返回结果
args_schema ArgsSchema|None None 自定义参数schema
infer_schema bool True 是否从函数签名推导schema
response_format str “content” “content"或"content_and_artifact”
parse_docstring bool False 是否解析Google Style docstring

响应结构体

BaseTool | Callable[[Callable], BaseTool]
# 返回工具实例或装饰器函数

入口函数与关键代码

def tool(
    name_or_callable: str | Callable | None = None,
    *,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: ArgsSchema | None = None,
    infer_schema: bool = True,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
) -> BaseTool | Callable[[Callable], BaseTool]:
    """将函数转换为工具

    Args:
        name_or_callable: 工具名称或函数
        description: 工具描述
        ...其他参数

    Returns:
        工具实例或装饰器函数
    """
    def _create_tool_factory(tool_name: str) -> Callable:
        """创建工具工厂函数"""
        def _tool_factory(dec_func: Callable) -> BaseTool:
            # 1) 确定工具描述
            tool_description = description
            if tool_description is None:
                if dec_func.__doc__:
                    tool_description = dec_func.__doc__.strip()
                elif args_schema:
                    tool_description = args_schema.__doc__

            # 2) 分离同步和异步函数
            if inspect.iscoroutinefunction(dec_func):
                func = None
                coroutine = dec_func
            else:
                func = dec_func
                coroutine = None

            # 3) 处理参数schema
            schema = args_schema
            if parse_docstring:
                # 从docstring解析参数描述
                schema = create_schema_from_function(
                    tool_name,
                    dec_func,
                    parse_docstring=True,
                )

            # 4) 创建StructuredTool或Tool
            if infer_schema or args_schema is not None:
                return StructuredTool.from_function(
                    func,
                    coroutine,
                    name=tool_name,
                    description=tool_description,
                    return_direct=return_direct,
                    args_schema=schema,
                    infer_schema=infer_schema,
                    response_format=response_format,
                )
            else:
                # 简单的字符串工具
                return Tool(
                    name=tool_name,
                    func=func,
                    description=tool_description,
                    return_direct=return_direct,
                    coroutine=coroutine,
                    response_format=response_format,
                )

        return _tool_factory

    # 5) 处理不同的调用方式
    if name_or_callable is not None:
        if callable(name_or_callable) and hasattr(name_or_callable, "__name__"):
            # @tool 直接装饰
            return _create_tool_factory(name_or_callable.__name__)(name_or_callable)
        elif isinstance(name_or_callable, str):
            # @tool("custom_name") 指定名称
            return _create_tool_factory(name_or_callable)

    # @tool(parse_docstring=True) 带参数装饰
    def _partial(func: Callable) -> BaseTool:
        name = func.__name__
        tool_factory = _create_tool_factory(name)
        return tool_factory(func)

    return _partial

代码说明:

  1. _create_tool_factory创建工具工厂函数
  2. 自动从docstring提取描述
  3. 检测函数是否为协程
  4. parse_docstring时解析参数描述
  5. infer_schema时创建StructuredTool,否则创建Tool
  6. 支持三种装饰器使用方式

调用链与上层函数

# 使用方式1: 直接装饰
@tool
def search_web(query: str) -> str:
    \"\"\"在互联网上搜索信息

    Args:
        query: 搜索关键词
    \"\"\"
    # 实现搜索逻辑
    return f"搜索结果: {query}"

# 使用方式2: 指定名称
@tool("web_search")
def my_search_function(query: str) -> str:
    \"\"\"搜索工具\"\"\"
    return search_api(query)

# 使用方式3: 带参数
@tool(return_direct=True, parse_docstring=True)
def calculator(expression: str) -> float:
    \"\"\"计算数学表达式

    Args:
        expression: 数学表达式,"2+2*3"
    \"\"\"
    return eval(expression)

# 使用方式4: 结构化参数
@tool
def send_email(to: str, subject: str, body: str) -> str:
    \"\"\"发送电子邮件

    Args:
        to: 收件人邮箱地址
        subject: 邮件主题
        body: 邮件正文
    \"\"\"
    # 发送逻辑
    return "邮件已发送"

# 使用方式5: 异步工具
@tool
async def async_search(query: str) -> str:
    \"\"\"异步搜索工具\"\"\"
    async with httpx.AsyncClient() as client:
        response = await client.get(f"https://api.example.com/search?q={query}")
        return response.text

# 在Agent中使用
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
model_with_tools = model.bind_tools([search_web, calculator, send_email])

response = model_with_tools.invoke("帮我搜索Python教程")
# AIMessage(tool_calls=[{"name": "search_web", "args": {"query": "Python教程"}, "id": "..."}])

时序图

sequenceDiagram
    autonumber
    participant User as 用户代码
    participant Decorator as @tool装饰器
    participant Factory as _tool_factory
    participant Struct as StructuredTool
    participant Model as ChatModel

    User->>Decorator: @tool<br/>def search(query: str): ...
    Decorator->>Decorator: 提取函数名"search"
    Decorator->>Factory: _create_tool_factory("search")
    Factory->>Factory: docstring提取描述
    Factory->>Factory: 检测函数签名
    Factory->>Struct: from_function(...)<br/>创建StructuredTool
    Struct->>Struct: 生成args_schema<br/>从类型注解
    Struct-->>Decorator: StructuredTool实例
    Decorator-->>User: search工具实例

    User->>Model: bind_tools([search])
    Model->>Model: 转换为模型特定格式
    Model-->>User: 绑定工具的模型

    User->>Model: invoke("搜索Python")
    Model-->>User: AIMessage(tool_calls=[...])

时序图说明:

图意: 展示@tool装饰器如何将函数转换为工具,以及工具如何与模型集成。

边界条件:

  • 函数必须有类型注解(用于schema推导)
  • 函数必须有docstring(用于描述)
  • 异步函数自动检测并支持

性能要点:

  • schema生成在装饰时完成,运行时无额外开销
  • 工具实例可缓存复用
  • 参数验证使用Pydantic,高效

API-2: BaseTool.invoke

基本信息

  • 名称: invoke
  • 方法签名: def invoke(self, input: str | dict | ToolCall, config: RunnableConfig | None = None) -> Any
  • 幂等性: 取决于具体实现

功能说明

执行工具,处理输入并返回结果。支持错误处理和输出格式化。

请求结构体

input: str | dict | ToolCall  # 工具输入
config: RunnableConfig | None  # 运行配置
参数 类型 说明
input str|dict|ToolCall 字符串(简单工具)或字典(结构化工具)或ToolCall对象
config RunnableConfig|None 运行时配置

响应结构体

Any  # 工具返回值,类型取决于工具实现

入口函数与关键代码

class BaseTool(RunnableSerializable):
    def invoke(
        self,
        input: str | dict | ToolCall,
        config: RunnableConfig | None = None,
    ) -> Any:
        """执行工具

        Args:
            input: 工具输入
            config: 运行配置

        Returns:
            工具执行结果
        """
        config = ensure_config(config)
        callback_manager = get_callback_manager_for_config(config)

        # 1) 解析输入
        tool_input, tool_call_id = self._parse_input(input)

        # 2) 开始工具调用回调
        run_manager = callback_manager.on_tool_start(
            {"name": self.name, "description": self.description},
            tool_input if isinstance(tool_input, str) else str(tool_input),
        )

        # 3) 执行工具
        try:
            observation = self._run(
                **tool_input if isinstance(tool_input, dict) else {"input": tool_input},
                run_manager=run_manager,
            )
        except ToolException as e:
            # 4) 处理工具异常
            if self.handle_tool_error:
                observation = self._handle_error(e)
            else:
                run_manager.on_tool_error(e)
                raise
        except Exception as e:
            # 5) 处理其他异常
            run_manager.on_tool_error(e)
            raise
        else:
            # 6) 成功回调
            run_manager.on_tool_end(observation)

        return observation

    def _parse_input(self, input: str | dict | ToolCall) -> tuple[Any, str | None]:
        """解析输入"""
        if isinstance(input, str):
            return input, None
        elif isinstance(input, dict):
            if "type" in input and input["type"] == "tool_call":
                # ToolCall字典
                return input["args"], input.get("id")
            else:
                # 普通参数字典
                return input, None
        else:
            # ToolCall对象
            return input.args, input.id

    @abstractmethod
    def _run(self, *args, run_manager: CallbackManagerForToolRun | None = None, **kwargs) -> Any:
        """子类实现的核心逻辑"""
        ...

    def _handle_error(self, error: ToolException) -> str:
        """处理工具错误"""
        if self.handle_tool_error is True:
            return f"Error: {str(error)}"
        elif isinstance(self.handle_tool_error, str):
            return self.handle_tool_error
        elif callable(self.handle_tool_error):
            return self.handle_tool_error(error)

代码说明:

  1. 解析输入(字符串/字典/ToolCall)
  2. 触发on_tool_start回调
  3. 调用_run执行核心逻辑
  4. 捕获ToolException并根据handle_tool_error处理
  5. 触发on_tool_end或on_tool_error回调
  6. 返回执行结果

调用链与上层函数

# 直接调用工具
@tool
def calculator(expression: str) -> float:
    \"\"\"计算表达式\"\"\"
    return eval(expression)

result = calculator.invoke("2+2*3")
# 8.0

# 传入字典
@tool
def send_email(to: str, subject: str) -> str:
    \"\"\"发送邮件\"\"\"
    return f"邮件已发送到{to}"

result = send_email.invoke({"to": "user@example.com", "subject": "Hello"})
# "邮件已发送到user@example.com"

# Agent执行工具调用
tool_call = ToolCall(
    name="calculator",
    args={"expression": "2+2"},
    id="call_123"
)

result = calculator.invoke(tool_call)
# 4.0

# 错误处理
@tool(handle_tool_error=True)
def risky_tool(input: str) -> str:
    \"\"\"可能失败的工具\"\"\"
    if input == "bad":
        raise ToolException("Invalid input")
    return f"Success: {input}"

result = risky_tool.invoke("bad")
# "Error: Invalid input" (不抛出异常)

result = risky_tool.invoke("good")
# "Success: good"

时序图

sequenceDiagram
    autonumber
    participant Agent
    participant Tool as BaseTool
    participant CB as CallbackManager
    participant Impl as _run实现

    Agent->>Tool: invoke({"query": "Python"})
    Tool->>Tool: _parse_input
    Tool->>CB: on_tool_start

    Tool->>Impl: _run(query="Python")

    alt 执行成功
        Impl-->>Tool: "搜索结果..."
        Tool->>CB: on_tool_end
        Tool-->>Agent: "搜索结果..."
    else 工具异常
        Impl-->>Tool: ToolException
        Tool->>Tool: _handle_error
        Tool->>CB: on_tool_error
        Tool-->>Agent: "Error: ..."
    else 其他异常
        Impl-->>Tool: Exception
        Tool->>CB: on_tool_error
        Tool-->>Agent: 抛出异常
    end

时序图说明:

图意: 展示工具执行的完整流程,包括回调和错误处理。

边界条件:

  • handle_tool_error=True时捕获ToolException
  • handle_tool_error=False时异常向上传播
  • 其他异常总是向上传播

性能要点:

  • 工具执行时间取决于具体实现
  • 回调异步执行,不阻塞主流程
  • 参数验证在_parse_input阶段完成

API-3: StructuredTool.from_function

基本信息

  • 名称: from_function
  • 方法签名: @classmethod def from_function(cls, func: Callable | None, coroutine: Callable | None, name: str, description: str, return_direct: bool = False, args_schema: ArgsSchema | None = None, infer_schema: bool = True, **kwargs) -> StructuredTool
  • 幂等性: 幂等

功能说明

从函数创建结构化工具,自动推导参数schema。

请求结构体

func: Callable | None  # 同步函数
coroutine: Callable | None  # 异步函数
name: str  # 工具名称
description: str  # 工具描述
return_direct: bool  # 是否直接返回
args_schema: ArgsSchema | None  # 参数schema
infer_schema: bool  # 是否推导schema

响应结构体

StructuredTool  # 结构化工具实例

入口函数与关键代码

class StructuredTool(BaseTool):
    @classmethod
    def from_function(
        cls,
        func: Callable | None,
        coroutine: Callable | None,
        name: str,
        description: str,
        return_direct: bool = False,
        args_schema: ArgsSchema | None = None,
        infer_schema: bool = True,
        **kwargs,
    ) -> StructuredTool:
        """从函数创建结构化工具

        Args:
            func: 同步函数
            coroutine: 异步函数
            name: 工具名称
            description: 工具描述
            return_direct: 是否直接返回
            args_schema: 自定义参数schema
            infer_schema: 是否推导schema

        Returns:
            StructuredTool实例
        """
        # 1) 确定使用哪个函数
        fn = func or coroutine

        # 2) 生成或使用args_schema
        if args_schema is None and infer_schema:
            args_schema = create_schema_from_function(
                name,
                fn,
                parse_docstring=kwargs.get("parse_docstring", False),
            )

        # 3) 创建实例
        return cls(
            name=name,
            description=description,
            func=func,
            coroutine=coroutine,
            args_schema=args_schema,
            return_direct=return_direct,
            **kwargs,
        )

def create_schema_from_function(
    name: str,
    func: Callable,
    *,
    parse_docstring: bool = False,
) -> type[BaseModel]:
    """从函数签名创建Pydantic schema

    Args:
        name: schema名称
        func: 函数
        parse_docstring: 是否解析docstring获取参数描述

    Returns:
        Pydantic模型类
    """
    # 1) 获取函数签名
    sig = inspect.signature(func)

    # 2) 构建字段定义
    fields = {}
    for param_name, param in sig.parameters.items():
        if param_name in FILTERED_ARGS:
            # 跳过特殊参数(如run_manager)
            continue

        # 提取类型注解
        annotation = param.annotation if param.annotation != inspect.Parameter.empty else Any

        # 提取默认值
        default = param.default if param.default != inspect.Parameter.empty else ...

        # 从docstring提取描述
        description = None
        if parse_docstring:
            description = _extract_param_description(func, param_name)

        fields[param_name] = (annotation, Field(default, description=description))

    # 3) 创建Pydantic模型
    return create_model(f"{name}Schema", **fields)

代码说明:

  1. 选择同步或异步函数
  2. 从函数签名自动生成args_schema
  3. 支持解析docstring提取参数描述
  4. 创建StructuredTool实例

典型使用场景

场景1: 搜索工具

@tool
def search_database(query: str, limit: int = 10) -> list[dict]:
    \"\"\"在数据库中搜索相关记录

    Args:
        query: 搜索关键词,支持模糊匹配
        limit: 返回结果数量上限,默认10

    Returns:
        匹配记录列表
    \"\"\"
    results = db.query(f"SELECT * FROM records WHERE content LIKE '%{query}%' LIMIT {limit}")
    return [dict(row) for row in results]

# 使用
results = search_database.invoke({"query": "Python", "limit": 5})

场景2: API调用工具

@tool
async def fetch_weather(city: str) -> str:
    \"\"\"获取城市天气信息

    Args:
        city: 城市名称,"北京"
    \"\"\"
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.weather.com/v1/current?city={city}",
            headers={"Authorization": f"Bearer {API_KEY}"}
        )
        data = response.json()
        return f"{city}天气: {data['weather']}, 温度: {data['temp']}°C"

# 异步使用
weather = await fetch_weather.ainvoke("北京")

场景3: 工具组合

from langchain_core.tools import create_retriever_tool

# 检索工具
retriever = vectorstore.as_retriever()
retriever_tool = create_retriever_tool(
    retriever,
    name="search_docs",
    description="搜索文档知识库,用于回答产品相关问题"
)

# 计算工具
@tool
def calculator(expression: str) -> float:
    \"\"\"计算数学表达式\"\"\"
    return eval(expression)

# 组合使用
tools = [retriever_tool, calculator, search_database]
model_with_tools = ChatOpenAI(model="gpt-4o").bind_tools(tools)

最佳实践

1. 工具设计原则

# 推荐: 清晰的工具描述
@tool
def send_notification(
    user_id: str,
    message: str,
    priority: Literal["low", "normal", "high"] = "normal"
) -> str:
    \"\"\"向用户发送通知消息

    Args:
        user_id: 用户唯一标识符
        message: 通知消息内容,最多500字
        priority: 优先级,高优先级会立即推送

    Returns:
        发送状态信息
    \"\"\"
    # 实现...
    return f"通知已发送给用户{user_id}"

# 不推荐: 描述不清晰
@tool
def do_something(data: str) -> str:
    \"\"\"处理数据\"\"\"  # 太模糊
    pass

2. 错误处理

# 推荐: 使用ToolException
@tool
def validate_email(email: str) -> str:
    \"\"\"验证邮箱地址是否有效\"\"\"
    import re
    pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
    if not re.match(pattern, email):
        raise ToolException(f"无效的邮箱地址: {email}")
    return f"{email}是有效的邮箱地址"

# 自定义错误处理
@tool(handle_tool_error=lambda e: f"邮箱验证失败: {str(e)}")
def validate_email_safe(email: str) -> str:
    \"\"\"验证邮箱(安全版本)\"\"\"
    if "@" not in email:
        raise ToolException("邮箱格式错误")
    return "有效"

3. 性能优化

# 推荐: 缓存工具实例
TOOLS_CACHE = {}

def get_tool(name: str) -> BaseTool:
    if name not in TOOLS_CACHE:
        TOOLS_CACHE[name] = create_tool(name)
    return TOOLS_CACHE[name]

# 推荐: 批量操作
@tool
async def batch_search(queries: list[str]) -> list[str]:
    \"\"\"批量搜索,提高效率

    Args:
        queries: 搜索查询列表
    \"\"\"
    tasks = [search_api(q) for q in queries]
    results = await asyncio.gather(*tasks)
    return results