LangChain的 Tool(工具) 就像给大语言模型这类“大脑”装上了“手和脚”。大模型本身只能输出文本,但通过工具系统,它能够与外部世界互动,执行诸如搜索、调用API、读写文件等具体操作,从而突破自身能力的局限
核心概念 Tool(工具) 扩展了 智能体(agents) 的能力,允许它们获取实时数据、执行代码、查询外部数据库以及在真实世界中采取行动。
在底层,工具是可调用的函数,具有明确定义的输入和输出,这些会被传递给聊天模型。模型会根据对话上下文决定何时调用工具,以及提供哪些输入参数。
Tool(工具)核心概念:
**工具 (Tool)**:一个封装了特定功能的可调用对象,包含名称、描述、参数和执行逻辑。
工具调用 (Tool Calling):指大模型根据你的请求,自主决定并生成调用哪个工具的 指令 的过程。模型本身不执行工具,只生成指令。
**函数调用 (Function Calling)**:由模型提供商(如OpenAI)提供的原生底层接口,而工具调用是LangChain在此基础上的高级封装,提供了更易用的标准化接口。在LangChain里,可以把“函数调用”理解为“工具调用”的底层技术基础。
简单来说,Tool 是一个封装了特定功能(如查询天气、调用API、搜索数据库)的通用接口。它的核心价值 在于:
拓展能力边界 :让只擅长文本处理的 LLM,获得与现实世界交互的能力。
实现智能决策 :为 Agent(智能体)提供“手脚”,使其能够根据任务目标,自主选择合适的工具并执行,完成复杂任务。
Tool(工具)继承自 Runnable接口,关键属性和方法:
属性/方法
说明
作用说明
name
名称
工具的唯一标识符,方便代理(Agent)快速识别和调用。
description
描述
用自然语言解释工具的功能。 模型会据此判断是否应该调用该工具。
args
参数
工具需要哪些输入参数的JSON结构,帮助模型生成正确的参数。
invoke()
同步调用
同步调用工具
ainvoke()
异步调用
异步调用工具
一个标准的LangChain Tool(工具),主要由以下几部分构成:
组成部分
作用说明
名称 (Name)
工具的唯一标识符,方便代理(Agent)快速识别和调用。
描述 (Description)
用自然语言解释工具的功能。这是最关键的部分 ,模型会据此判断是否应该调用该工具。
参数模式 (Args Schema)
定义工具需要哪些输入参数的JSON结构,帮助模型生成正确的参数。
执行逻辑 (Execution Logic)
工具被调用时实际运行的底层代码或逻辑。
返回控制 (Return Direct)
控制工具执行后的结果是直接返回给用户,还是继续交给模型处理。
典型的工具调用流程通常包含以下几个步骤:
定义工具 :首先定义好一个或多个工具。
绑定工具 :将定义好的工具通过 bind_tools 方法与你的模型绑定。
模型决策 :当用户提问时,模型会根据指令和工具的描述,决定是否调用工具,并生成调用所需的结构化参数。
解析与执行 :你可以解析模型的返回结果,提取出工具调用指令和参数,然后手动调用相应的工具。
返回结果 :将工具执行的结果返回给模型,模型会基于这个结果继续生成最终的应答。
以下是一个Python示例,演示如何通过绑定工具实现模型的自主决策和执行:
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 from langchain_core.tools import toolfrom langchain_openai import ChatOpenAI@tool def get_weather (city: str ) -> str : """根据城市名,获取该城市的实时天气。""" return f"{city} 的天气是晴天,温度25度。" model = ChatOpenAI(model="gpt-3.5-turbo" ) model_with_tools = model.bind_tools([get_weather]) user_input = "北京今天天气如何?" response = model_with_tools.invoke(user_input) tool_calls = response.tool_calls if tool_calls: for tc in tool_calls: selected_tool = {"get_weather" : get_weather}[tc["name" ]] tool_result = selected_tool.invoke(tc["args" ]) print (tool_result)
在 LangChain 1.0+ 中主要有三种创建方式,以满足从简到繁的各种需求。
默认情况下,函数的文档字符串会成为工具的描述 ,帮助模型理解何时使用它:需要类型提示,因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁,以帮助模型理解工具的目的。
@tool 装饰器 (推荐用于绝大多数场景),这是最简单、最推荐的方式,尤其适合将现有的函数快速转换为工具。
1 2 3 4 5 6 7 8 9 10 11 12 from langchain_core.tools import tool@tool def search_database (query: str , limit: int = 10 ) -> str : """在客户数据库中搜索与查询条件匹配的记录。 Args: query: 要查找的搜索词。 limit: 返回结果的最大数量。 """ return f"找到了 {limit} 条关于 '{query} ' 的记录。"
必须添加类型注解 —— 它们定义工具的输入 JSON Schema
必须编写清晰的 docstring —— 这是模型理解工具用途的唯一依据
默认情况下,工具名称来自函数名称。当你需要更具描述性的名称时,可以覆盖它:
1 2 3 4 5 6 @tool("web_search" ) def search (query: str ) -> str : """Search the web for information.""" return f"Results for: {query} " print (search.name)
覆盖自动生成的工具描述,以便为模型提供更清晰的指导:
1 2 3 4 @tool("calculator" , description="Performs arithmetic calculations. Use this for any math problems." ) def calc (expression: str ) -> str : """Evaluate mathematical expressions.""" return str (eval (expression))
自定义参数描述 可以使用 Annotated 工具更简洁地为函数参数添加描述
1 2 3 4 5 6 7 from typing_extensions import Annotated@tool def add (a: Annotated[int , ..., "第一个整数" ], b: Annotated[int , ..., "第二个整数" ] ) -> int : """Add two integers.""" return a + b
高级模式定义 使用 Pydantic 模型或 JSON 模式定义复杂的输入:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 from pydantic import BaseModel, Fieldfrom 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 模型(WeatherInput)来描述函数输入参数的工具(tool),该工具可用于天气查询场景。
**WeatherInput**:一个 Pydantic 模型,定义了工具输入的结构,包括字段类型、默认值和描述。
**@tool(args_schema=WeatherInput)**:装饰器,将函数 get_weather 包装成一个“工具”,并关联输入校验模式。该装饰器通常来自 LLM 工具调用框架(如 LangChain),使得大模型可以依据 Pydantic 模型自动生成正确的函数调用参数。
**get_weather**:实际的业务逻辑函数,接收与模型对应的参数,返回字符串结果。
执行效果 :当大模型需要获取天气时,框架会提供符合 WeatherInput 结构的 JSON 对象,经过校验后转为 Python 参数传递给 get_weather,函数的返回值再返回给大模型。
保留的参数名称 以下参数名称是保留的,不能用作工具参数。使用这些名称会导致运行时错误。要访问运行时信息,请使用 ToolRuntime 参数,而不是将自己的参数命名为 config 或 runtime。
StructuredTool (用于需要更多控制的场景):当需要异步执行、对工具生命周期进行更精细控制时,可以使用StructuredTool.from_function 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from langchain_core.tools import StructuredTooldef multiply (a: int , b: int ) -> int : """将两个整数相乘。""" return a * b async def amultiply (a: int , b: int ) -> int : return a * b structured_tool = StructuredTool.from_function( func=multiply, coroutine=amultiply, name="multiply" , description="将两个整数相乘。" )
继承 BaseTool (用于高级自定义):这是最复杂的方式,但提供了最大的灵活性,允许你完全控制工具的行为,例如集成复杂的验证逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 from langchain_core.tools import BaseToolclass MyCustomTool (BaseTool ): name: str = "custom_tool" description: str = "一个自定义工具。" def _run (self, query: str ) -> str : return f"你查询的内容是:{query} " async def _arun (self, query: str ) -> str : return await some_async_operation(query)
创建 Tool :这是第一步,你需要使用上面介绍的方法,通过函数定义一个 Tool。
bind_tools 绑定 Tool :为了让 LLM “知道” 有哪些工具可用,需要将工具列表绑定到 LLM 上,这就是工具绑定 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from langchain_openai import ChatOpenAIllm = ChatOpenAI(model="gpt-4o" , temperature=0 ) llm_with_tools = llm.bind_tools([search_database, calculate]) response = llm_with_tools.invoke(messages) if response.tool_calls: for tool_call in response.tool_calls: print (f"调用工具: {tool_call['name' ]} , 参数: {tool_call['args' ]} " ) result = weather_tool.invoke(tool_call["args" ]) messages.append({"role" : "tool" , "content" : result, "tool_call_id" : tool_call["id" ]}) final_response = model.invoke(messages)
绑定成功后,当给 LLM 发送用户消息时,它可能会在响应中附带一个请求调用工具的指令,这就是工具调用 。
create_agent(可选) 在 LangChain 1.0+ 中,官方推荐通过 create_agent 来构建智能体,它会内置一个标准的 Agent 循环,自动处理工具调用的解析、执行和结果返回。
1 2 3 4 5 6 7 from langchain.agents import create_agentagent = create_agent( model="your_preferred_model" , tools=[search_database, web_search, multiply], systemPrompt="你是一个有帮助的助手。" , )
**强制执行工具 (tool_choice)**:可以强制 LLM 必须调用某个特定的工具。例如,强制模型调用之前定义的 multiply 工具。这在需要确保特定工作流执行时非常有用(例如先查数据库再回答问题)。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from langchain_core.messages import SystemMessage, HumanMessagefrom langchain_core.tools import toolfrom langchain_openai import ChatOpenAIfrom langchain.agents import create_agentfrom model.deepseek.config import DeepSeekConfig@tool def get_weather (city: str ) -> str : """获取指定城市的天气""" return f"{city} 的天气是晴天,25°C" @tool def get_time (city: str ) -> str : """获取指定城市的当前时间""" return f"{city} 的当前时间是下午 3 点" config = DeepSeekConfig() llm = ChatOpenAI( model=config.model, api_key=config.api_key, base_url=config.base_url, temperature=0 ) system_prompt = """你是一个非常有用的生活助手,可以当地的提供天气和时间信息。""" llm_with_forced_tool = llm.bind_tools( tools=[get_weather, get_time], tool_choice="get_weather" ) messages = [ SystemMessage(content=system_prompt), HumanMessage(content="深圳现在几点了?天气怎么样?" ) ] response = llm_with_forced_tool.invoke(messages) print (response.tool_calls)print (response.content) tool_calls = response.tool_calls if tool_calls: for tc in tool_calls: selected_tool = {"get_weather" : get_weather}[tc["name" ]] tool_result = selected_tool.invoke(tc["args" ]) print (tool_result) agent = create_agent(llm, [get_weather, get_time]) result = agent.invoke({"messages" : messages}) print (result["messages" ][-1 ].content)
代码解释:
当模型被强制调用工具时(tool_choice=”get_weather”),模型的响应主要是工具调用,而 response.content 可能是空的或者很少的内容,这是因为:
tool_choice 也可以设为 "any"(强制调用任意一个工具)或 "auto"(默认,模型自主决定)。在 OpenAI 风格的 API 中,还可以使用 {"type": "function", "function": {"name": "tool_name"}}。
模型的主要输出是 tool_calls,而不是文本内容。
当使用 tool_choice 强制调用工具时,模型会专注于生成工具调用参数,可能不会生成额外的文本内容。
return_direct直接返回结果 **直接返回 (return_direct)**:若将某个工具的 return_direct 属性设为 True,当该工具被调用后,Agent 会将结果直接返回给用户,不再根据结果进行后续的思考和处理。适合那些本身就是最终信息(比如“搜索结束”、“计算完毕”)的工具。
中间件与工具编排 :对于复杂场景,还支持通过中间件实现复杂的工具编排,如强制工具的顺序执行、让 LLM 根据上下文自主规划多步工具调用等。
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 37 38 39 40 41 42 43 44 45 from langchain.agents import create_agentfrom langchain_core.messages import SystemMessage, HumanMessagefrom langchain_core.tools import toolfrom langchain_openai import ChatOpenAIfrom model.deepseek.config import DeepSeekConfig@tool(return_direct=True ) def final_answer (answer: str ) -> str : """当已经得到最终答案时,使用此工具直接回复用户。""" return answer @tool def search (query: str ) -> str : """模拟搜索,返回一个中间结果""" return f"关于 '{query} ' 的搜索结果:……(此处省略内容)" config = DeepSeekConfig() llm = ChatOpenAI( model=config.model, api_key=config.api_key, base_url=config.base_url, temperature=0 ) agent = create_agent( model=llm, tools=[search, final_answer] ) system_prompt = "你是一个助手。如果你已经收集到足够信息,就用 final_answer 工具直接给出最终答案。" messages = [ SystemMessage(content=system_prompt), HumanMessage(content="深圳今天天气怎么样?" ) ] result = agent.invoke({"messages" : messages}) print (result["messages" ][-1 ].content)
代码解释 :
return_direct=True 不会影响模型是否选择该工具,只是在该工具执行完毕后,跳过 Agent 的“再思考”步骤。
必须配合 Agent(如 create_agent)才能体现其作用;如果只是手动调用工具,该属性无效。
最佳实践
工具命名规范 :工具的名称应使用字母、数字、下划线、连字符(即 snake_case 格式,如 web_search)来保证最佳兼容性,避免使用空格或其他特殊字符。
工具描述清晰 :工具的 description 至关重要,模型主要依靠它来判断使用场景。请务必提供清晰、具体的描述,最好在docstring中通过 Args: 和 Returns: 清晰说明参数的用途和返回值的含义。
合理设计参数 :利用类型提示(Type Hints)和字段描述(Field description)来精确地定义参数,这能帮助模型更准确地生成参数。
错误处理 :工具内部应包含完善的错误处理逻辑,防止因 API 调用失败等异常导致整个 Agent 运行崩溃。
从简到繁 :刚上手时,专注于1-2个简单的工具来验证整体工作流,同时建议在 LangSmith 这类可观测性平台上密切监控工具调用的轨迹,以方便调试和优化。
通过以上步骤,就可以在 LangChain 1.0+ 中创建出强大且可靠的 Tool,让 LLM 应用的能力得到极大的扩展。