LangChain 入门系列⑤:核心组件之 Tools 快速入门指南

Tools概述
介绍

Tools 用于扩展大语言模型(LLM)的能力,使其能够与外部系统、API 或自定义函数交互,从而完成仅靠文本生成无法实现的任务(如搜索、计算、数据库查询等)。
LangChain 拥有大量第三方工具。请访问工具集成查看可用工具列表。
python.langchain.com/v0.2/docs/integrations/tools/
Tool 的要素
Tools 本质上是封装了特定功能的可调用模块,是Agent、Chain或LLM可以用来与世界互动的接口。
Tool 通常包含如下几个要素:
- name:工具的名称
- description:工具的功能描述
- 该工具输入的 JSON 模式
- 要调用的函数
- return_direct:是否应将工具结果直接返回给用户(仅对 Agent 相关)
实操步骤:
-
将name、description 和 JSON模式作为上下文提供给LLM
-
LLM会根据提示词推断出需要调用哪些工具,并提供具体的调用参数信息
-
用户需要根据返回的工具调用信息,自行触发相关工具的回
自定义工具
两种自定义方式
第1种:使用@tool装饰器(自定义工具的最简单方式)
装饰器默认使用函数名称作为工具名称,但可以通过参数name_or_callable来覆盖此设置。
同时,装饰器将使用函数的文档字符串作为工具的描述,因此函数必须提供文档字符串。
第2种:使用StructuredTool.from_function类方法
这类似于@tool装饰器,但允许更多配置和同步/异步实现的规范。
几个常用属性
Tool由几个常用属性组成:
| 属性 | 类型 | 描述 |
|---|---|---|
name | str | 必选,在提供给 LLM 或 Agent 的工具集中必须是唯一的。 |
description | str | 可选但建议,描述工具的功能。LLM 或 Agent 将使用此描述作为上下文,使用它确定工具的使用 |
args_schema | Pydantic BaseModel | 可选但建议,可用于提供更多信息(例如,few-shot 示例)或验证预期参数。 |
return_direct | boolean | 仅对 Agent 相关。当为 True 时,在调用给定工具后,Agent 将停止并将结果直接返回给用户。 |
具体实现
方式1:@tool 装饰器
举例1:
@tool
def add_number(a: int, b: int) -> int:
"""两个整数相加"""
return a + b
def main():
"""主函数入口(你的核心代码写在这里)"""
print(f"name={add_number.name}")
print(f"name={add_number.args}")
print(f"name={add_number.description}")
print(f"name={add_number.return_direct}")
res = add_number.invoke({"a": 10, "b": 20})
print(res)
main()
输出如下:
name=add_number
name={'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
name=两个整数相加
name=False
30
举例2:通过@tool的参数设置进行重置
@tool(name_or_callable="add_two_number", description="two number add", return_direct=True)
def add_number(a: int, b: int) -> int:
return a + b
def main():
"""主函数入口(你的核心代码写在这里)"""
print(f"name={add_number.name}")
print(f"name={add_number.args}")
print(f"name={add_number.description}")
print(f"name={add_number.return_direct}")
res = add_number.invoke({"a": 10, "b": 20})
print(res)
输出如下:
name=add_two_number
name={'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
name=two number add
name=True
30
补充:还可以修改参数的说明
class FieldInfo(BaseModel):
a: int = Field(description="第1个参数")
b: int = Field(description="第2个参数")
@tool(name_or_callable="add_two_number", description="two number add", return_direct=True, args_schema=FieldInfo)
def add_number(a: int, b: int) -> int:
return a + b
def main():
"""主函数入口(你的核心代码写在这里)"""
print(f"name={add_number.name}")
print(f"name={add_number.args}")
print(f"name={add_number.description}")
print(f"name={add_number.return_direct}")
res = add_number.invoke({"a": 10, "b": 20})
print(res)
main()
输出如下:
name=add_two_number
name={'a': {'description': '第1个参数', 'title': 'A', 'type': 'integer'}, 'b': {'description': '第2个参数', 'title': 'B', 'type': 'integer'}}
name=two number add
name=True
30
方式2:StructuredTool的from_function()
StructuredTool.from_function 类方法提供了比@tool装饰器更多的可配置性,而无需太多额外的代码。
举例1:
def search_function(query: str):
return "LangChain"
search1 = StructuredTool.from_function(
func=search_function,
name="Search",
description="useful for when you need to answer questions about current events"
)
def main():
print(f"name={search1.name}")
print(f"name={search1.args}")
print(f"name={search1.description}")
search1.invoke("hello")
main()
输出如下:
name=Search
name={'query': {'title': 'Query', 'type': 'string'}}
name=useful for when you need to answer questions about current events
举例2:
class FieldInfo(BaseModel):
query: str = Field(description="要检索的关键词")
def search_function(query: str):
return "LangChain"
search1 = StructuredTool.from_function(
func=search_function,
name="Search",
description="useful for when you need to answer questions about current events. You should ask targeted questions",
args_schema=FieldInfo,
return_direct=True
)
def main():
print(f"name={search1.name}")
print(f"name={search1.args}")
print(f"name={search1.description}")
print(f"return_direct={search1.return_direct}")
search1.invoke("hello")
main()
输出如下:
name=Search
name={'query': {'description': '要检索的关键词', 'title': 'Query', 'type': 'string'}}
name=useful for when you need to answer questions about current events. You should ask targeted questions
return_direct=True
工具调用举例
我们通过大模型分析用户需求,判断是否需要调用指定工具。
举例1:大模型分析调用工具
load_dotenv()
# 定义LLM
chat_model = ChatOpenAI(model=os.getenv("LLM_MODEL"), temperature=0)
# 定义工具
tools = [MoveFileTool()]
# 4.需要将工具转换为openai函数,后续再将函数传入模型调用
functions = [convert_to_openai_function(t) for t in tools]
# 5.提供大模型调用的消息列表
messages = [HumanMessage(content="将文件a.txt移到桌面")]
# 6.调用大模型
response = chat_model.invoke(
input=messages,
functions=functions
)
print(response)
输出如下:
content='' additional_kwargs={'function_call': {'arguments': '{"source_path":"a.txt","destination_path":"/Users/YourUsername/Desktop/a.txt"}', 'name': 'move_file'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 77, 'total_tokens': 106, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'finish_reason': 'function_call', 'logprobs': None} id='run--c6bb46e7-6739-4927-83f3-04cbb29dbd8d-0' usage_metadata={'input_tokens': 77, 'output_tokens': 29, 'total_tokens': 106, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
可以看到其中有additional_kwargs的详细信息,如果是换成没有的工具,如下:
response = chat_model.invoke(
[HumanMessage(content="今天的天气怎么样?")],
functions=functions
)
print(response)
输出如下:
content='\n\n我目前无法直接获取实时天气信息。不过,您可以通过以下方式查询:\n1. 打开手机天气预报应用\n2. 在电脑浏览器搜索"天气+您所在城市"\n3. 使用智能音箱或语音助手询问\n\n如果您告诉我您所在的城市,我可以为您提供一些天气相关的建议或常识性信息。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 243, 'prompt_tokens': 12, 'total_tokens': 255, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 175, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-14B', 'system_fingerprint': '', 'finish_reason': 'stop', 'logprobs': None} id='run--564e4a18-ce41-4d9e-afaf-7f54764bc4bd-0' usage_metadata={'input_tokens': 12, 'output_tokens': 243, 'total_tokens': 255, 'input_token_details': {}, 'output_token_details': {'reasoning': 175}}
可以看到其中有additional_kwargs的详细信息只有refusal: None
调用工具说明
有两种情况,如下
情况1:大模型决定调用工具
如果模型认为需要调用工具(如MoveFileTool),返回的message会包含:
content: 通常为空(因为模型选择调用工具,而非生成自然语言回复)。
additional_kwargs: 包含工具调用的详细信息
AIMessage(
content="", # 无自然语言回复
additional_kwargs={
'function_call': {
'name': 'move_file', # 工具名称
'arguments':
'{"source_path":"a","destination_path":"/Users/YourUsername/Desktop/a"}' # 工具参数
}
}
)
情况2:大模型不调用工具
如果模型认为无需调用工具(例如用户输入与工具无关),返回的 message 会是普通文本回复:
AIMessage(
content='我没有找到需要移动的文件。', # 自然语言回复
additional_kwargs={'refusal': None} # 无工具调用
)
举例2:确定工具并调用
tip:
该操作目前可能只用OpenAI原生支持,其他模型估计得使用MCP1
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Project :LangChain-tutorial
@File :demo01.py
@Author :zxb
@Date :2025/12/9 17:06
"""
import json
import os
from dotenv import load_dotenv
from langchain_community.tools import MoveFileTool
from langchain_core.messages import HumanMessage
from langchain_core.utils.function_calling import convert_to_openai_function
from langchain_openai import ChatOpenAI
load_dotenv()
# 定义LLM
chat_model = ChatOpenAI(model=os.getenv("LLM_MODEL"), temperature=0)
# 定义工具
tools = [MoveFileTool()]
# 4.需要将工具转换为openai函数,后续再将函数传入模型调用
functions = [convert_to_openai_function(t) for t in tools]
# 5.提供大模型调用的消息列表
messages = [HumanMessage(content="将本目录下的a.txt移动到F盘")]
# 6.调用大模型
response = chat_model.invoke(
input=messages,
functions=functions
)
# print(response)
if "function_call" in response.additional_kwargs:
tool_name = response.additional_kwargs["function_call"]["name"]
tool_args = json.loads(response.additional_kwargs["function_call"]["arguments"])
print(f"调用工具: {tool_name}, 参数: {tool_args}")
if "move_file" in response.additional_kwargs["function_call"]["name"]:
tool = MoveFileTool()
result = tool.run(tool_args) # 执行工具
print("工具执行结果:", result)
else:
print("模型回复:", response.content)
输出如下:
调用工具: move_file, 参数: {'source_path': './a.txt', 'destination_path': 'F:/a.txt'}
工具执行结果: File moved successfully from ./a.txt to F:/a.txt.
欢迎关注我的公众号【zxb的博客】!


Footnotes
-
MCP
↩
-
1.MCP介绍
如果说大模型(ChatGPT、Deepseek)被称为人工智能的大脑,那么MCP就可以被称为人工智能的手臂
MCP提供统一且可靠的方式来访问所需数据,克服了以往集成方法的局限性。

特征 MCP TCP/IP、HTTPS 作用 协议(Protocol) 协议(Protocol) 作用 标准化AI模型与上下文来源/工具 标准化设备之间的网络通信方式
之间的数据交互方式目标 让不同模型应用可以用统一方式访问资源/工具 让不同设备、系统可以互通数据 好处 消除碎片化集成、形成生态闭环 解决设备互联、实现互联网基础 MCP资源推荐
MCP应用场景
↩应用领域 经典场景 MCP价值 代表实现 智能编程助手 代码生成、Bug修复、API集成 安全访问本地代码库、CI/CD系统 Cursor、VSCode插件 数据分析工具 自然语言查询数据库、可视化生成 安全查询内部数据库、连接BI工具 XiYanSQL-MCP、数据库MCP服务器 企业知识管理 知识库查询、文档生成、邮件撰写 安全访问内部文档、保护隐私数据 文件系统MCP、Email-MCP 创意设计工具 3D建模、图形生成、UI设计 与专业软件无缝集成 BlenderMCP、浏览器自动化 工作流自动化 多系统协调、事件驱动流程 跨系统安全协作 CloudflareMCP、AWS自动化套件 -
2.MCP实战
MCP通信机制
MCP的通信机制(传输方式)如下:
- stdio(标准输入输出) :主要作用在 本地服务 上,操作你本地的软件或本地的文件。比如 Blender 这类软件只能使用 Stdio。
- SSE(Server-Sent Events) :主要用在 远程通信服务 上,这个服务本身就有在线的API,比如访问你的邮箱,查看天气情况等。
stdio方式优缺点
优点:
- 这种方式适用于客户端和服务器在同一台机器上运行的场景,简单
- stdio模式无需外部网络依赖,通信速度快,适合快速响应的本地应用。
- 可靠性高,且易于调试
缺点: - Stdio 的配置比较复杂,我们需要做些准备工作,你需要提前安装需要的命令行工具。
- stdio模式为单进程通信,无法并行处理多个客户端请求,同时由于进程资源开销较大,不适合在本地运行大量服务。(限制了其在更复杂分布式场景中的使用)
SSE方式优缺点
场景
- SSE方式适用于客户端和服务器位于不同物理位置的场景。
- 适用于实时数据更新、消息推送、轻量级监控和实时日志流等场景
- 对于分布式或远程部署的场景,基于 HTTP 和 SSE 的传输方式则更为合适。
优点
- 配置方式非常简单,基本上就一个链接就行,直接复制他的链接填上就行

关于在IDE中使用mcp:
MCP的工作原理
MCP的C/S架构
MCP遵循客户端-服务端架构(client-server),其中包含以下核心概念:
- MCP主机(MCP Hosts)
- MCP客户端(MCP Clients)
- MCP服务端(MCP Servers)
- 本地资源(Local Resources)
- 远程资源(Remote Resources)

MCP Host
作为运行MCP的主应用程序,例如 Claude Desktop、Cursor、Cline或AI小工具。
为用户提供LLM交互的接口,同时继承 MCP client 以连接MCP Server。
MCP Client
MCP Client充当LLM和MCP Server之间的桥梁,嵌入在主机程序中,主要负责:
- 接受来自LLM的请求
- 将请求转发到相应的MCP Server
- 将 MCP Server 的结构返回给LLM

官网提出的常用的MCP Client: https://modelcontextprotocol.io/clients
分为两类:
- AI编程IDE:Cursor、Cline、Continue......
- 聊天客户端:Cherry Studio、Claude、Librechat......
MCP Server
每个MCP服务器都提供了一组特定的工具,负责从本地数据或远程服务中检索信息,是MCP架构中的关键组件。
- 感谢你赐予我前进的力量

