27、LangChain开发框架(四)-- LangChain接入工具基本流程
在上一篇针对agent做了简单的介绍,本篇重点介绍下工具在LangChain中的使用方法。
1、LangChain内部工具
我们首先介绍一个最基本的LangChain接入工具流程。在MCP爆火之前,LangChian生态中就已经内置集成了非常多的实用工具,开发者可以快速调用这些工具完成更加复杂工作流的开发。
LangChain内置工具列表:https://python.langchain.com/docs/integrations/tools/

这里以其中代码解释器为例,来介绍如何将内置工具接入LangChain的工作流中。
1.1 数据集介绍
数据集使用常见的开源数据集:titanic数据集
Titanic数据集是机器学习领域经典的入门数据集,记录了1912年泰坦尼克号沉船事件中部分乘客的生存信息。该数据集常用于分类任务(预测乘客是否幸存)和数据探索分析。
数据字段说明
数据集通常包含以下字段(具体字段可能因版本不同略有差异):
- PassengerId:乘客的唯一标识符。
- Survived:是否幸存(0=否,1=是)。
- Pclass:船舱等级(1/2/3等舱,反映社会经济地位)。
- Name:乘客姓名。
- Sex:性别(male/female)。
- Age:年龄(可能存在缺失值)。
- SibSp:同船兄弟姐妹/配偶数量。
- Parch:同船父母/子女数量。
- Ticket:船票编号。
- Fare:船票价格。
- Cabin:船舱编号(缺失值较多)。
- Embarked:登船港口(C=瑟堡,Q=皇后镇,S=南安普顿)。
数据集特点
- 包含数值型(如Age、Fare)、类别型(如Sex、Pclass)和文本型(如Name)数据。
- 存在缺失值(如Age、Cabin),需进行数据清洗或插补。
- 可通过特征工程衍生新特征(如家庭规模=SibSp+Parch)。
典型应用场景
- 分类任务:预测乘客是否幸存(目标变量为
Survived)。 - 探索性分析:研究性别、船舱等级等因素与生存率的关系。
- 数据清洗与特征工程:处理缺失值、编码类别变量等。
数据获取方式
可通过以下途径获取:
- Kaggle竞赛平台(Titanic: Machine Learning from Disaster)。
- Python库
seaborn内置数据集:import seaborn as sns titanic = sns.load_dataset('titanic')
数据描述
dataset = pd.read_csv('titanic.csv')
dataset.head()
dataset.info()
<class ‘pandas.core.frame.DataFrame’>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
-|Column - |Non-Null Count - |Dtype
|0 | PassengerId |891 |non-null | int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
1.2 内置代码解释器工具功能测试
测试LangChain内置代码解释器工具功能
在langchain_experimental.tools中导入PythonAstREPLTool
import pandas as pd
from langchain_core.prompts import ChatPromptTemplate
from langchain_experimental.tools import PythonAstREPLTooltool = PythonAstREPLTool(locals={"df": dataset})
tool.invoke("df['Age'].mean()")
PythonAstREPLTool 的初始化参数 locals 用于定义工具执行代码时可访问的局部变量。这里将变量 df 绑定到 dataset(通常是一个 pandas DataFrame),即工具内部可通过 df 访问 dataset 的数据。
tool也可以调用工具的 invoke 方法,传入字符串形式的 Python 代码片段,工具会在其局部变量环境中执行该代码。
np.float64(29.69911764705882)
直接使用dataframe计算,来验证下
dataset['Age'].mean()np.float64(29.69911764705882)
创建LangChain工作流并绑定内置工具
import os
from dotenv import load_dotenv
load_dotenv(override=True)from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv(override=True)
- dotenv 是一个用于管理环境变量的 Python 库,核心功能是从 .env 文件中读取键值对,并将其设置为系统环境变量。
这里导入了 dotenv 库中的 load_dotenv 函数,该函数是加载 .env 文件的核心工具。 - 参数 override=True 表示:如果系统中已经存在同名的环境变量(例如在终端中预先设置过),则用 .env 文件中的值覆盖已有的环境变量(即 .env 文件的配置优先)。
若不设置此参数(默认 override=False),则不会覆盖已有环境变量,仅添加 .env 中新增的变量。 - 项目中通常将 API 密钥、数据库密码等敏感信息写入 .env 文件(而非硬编码到代码中),再通过 load_dotenv 加载,避免敏感信息泄露(.env 一般会被添加到 .gitignore 中,不纳入版本控制)
- 执行代码后,程序中可通过 os.getenv(“OPENAI_API_KEY”) 获取到 sk-xxxxxx,方便后续调用相关 API。
model = init_chat_model(model="deepseek-chat", model_provider="deepseek")
llm_with_tools = model.bind_tools([tool])
response = llm_with_tools.invoke("我有一张表,名为'dataset',请帮我计算Fare字段的均值。"
)
responseAIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_00_eYMWf0694x3Rxg4JcCjHRH6J', 'function': {'arguments': '{"query": "import pandas as pd\\n\\n# 读取数据集\\ndataset = pd.read_csv(\'dataset.csv\') # 假设数据文件名为dataset.csv\\n\\n# 计算Fare字段的均值\\nfare_mean = dataset[\'Fare\'].mean()\\nprint(f\\"Fare字段的均值为: {fare_mean}\\")"}', 'name': 'python_repl_ast'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 83, 'prompt_tokens': 212, 'total_tokens': 295, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 212}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_ffc7281d48_prod0820_fp8_kvcache', 'id': 'b787abe3-b729-4def-a691-b95593c01e7e', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--096d3ff8-b060-4ea3-8aea-253d13f4dcea-0', tool_calls=[{'name': 'python_repl_ast', 'args': {'query': 'import pandas as pd\n\n# 读取数据集\ndataset = pd.read_csv(\'dataset.csv\') # 假设数据文件名为dataset.csv\n\n# 计算Fare字段的均值\nfare_mean = dataset[\'Fare\'].mean()\nprint(f"Fare字段的均值为: {fare_mean}")'}, 'id': 'call_00_eYMWf0694x3Rxg4JcCjHRH6J', 'type': 'tool_call'}], usage_metadata={'input_tokens': 212, 'output_tokens': 83, 'total_tokens': 295, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})
使用output_parsers 对结果进行解析,结构化输出
from langchain_core.output_parsers.openai_tools import JsonOutputKeyToolsParserparser = JsonOutputKeyToolsParser(key_name=tool.name, first_tool_only=True)llm_chain = llm_with_tools | parserllm_chain.invoke("我有一张表,名为'df',请帮我计算Age字段的均值。"){'query': "df['Age'].mean()"}
这样便可得到我们需要的内容,然后进入前面定义好的tool
重新定义链
system = f"""
你可以访问一个名为 `df` 的 pandas 数据框,请根据用户提出的问题,编写 Python 代码来回答。只返回代码,不返回其他内容。只允许使用 pandas 和内置库。
"""
prompt = ChatPromptTemplate([("system", system),("user", "{question}")
])chain = prompt | llm_with_tools | parser | tool
chain.invoke({"question": "请计算Age字段的均值。"})np.float64(29.69911764705882)
chain.invoke({"question": "请计算Age字段和Fare字段的相关系数。"})
np.float64(0.09606669176903912)chain.invoke({"question": "请按照Pclass 字段计算Age字段的均值。"})Pclass
1 38.233441
2 29.877630
3 25.140620
Name: Age, dtype: float64
有的时候最终的输出结果会报错,我们希望查看中间的结果,可以加上一个打印的操作
from langchain_core.runnables import RunnableLambdadef code_print(res):print("即将运行Python代码:", res['query'])return resprint_node = RunnableLambda(code_print)
print_code_chain = prompt | llm_with_tools | parser | print_node | tool
print_code_chain.invoke({"question": "请计算Age字段和Fare字段的相关系数。"})即将运行Python代码: import pandas as pd# 计算Age字段和Fare字段的相关系数
correlation = df['Age'].corr(df['Fare'])
print(f"Age和Fare的相关系数为: {correlation}")'Age和Fare的相关系数为: 0.09606669176903888\n'
至此,一个简单的包含官方内置工具的代码解释器工作流就搭建完成了。
2、 LangChain接入自定义外部工作流程
2.1 自定义一个外部工具
这里自定义一个查询天气的外部工具,
需要去openweathermap官网注册一个api-key,https://home.openweathermap.org/api_keys
将注册的key写在.env 文件中
import os
from dotenv import load_dotenv
load_dotenv(override=True)OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
先测试一下是否注册成功
import requests,jsondef get_weather(loc):"""查询即时天气函数:param loc: 必要参数,字符串类型,用于表示查询天气的具体城市名称,\注意,中国的城市需要用对应城市的英文名称代替,例如如果需要查询北京市天气,则loc参数需要输入'Beijing';:return:OpenWeather API查询即时天气的结果,具体URL请求地址为:https://api.openweathermap.org/data/2.5/weather\返回结果对象类型为解析之后的JSON格式对象,并用字符串形式进行表示,其中包含了全部重要的天气信息"""# Step 1.构建请求url = "https://api.openweathermap.org/data/2.5/weather"# Step 2.设置查询参数params = {"q": loc, "appid": os.getenv("OPENWEATHER_API_KEY"), # 输入API key"units": "metric", # 使用摄氏度而不是华氏度"lang":"zh_cn" # 输出语言为简体中文}# Step 3.发送GET请求response = requests.get(url, params=params)# Step 4.解析响应data = response.json()return json.dumps(data)
自定义一个查询的函数,函数本身比较简单,主要是需要按照规范,注释清楚函数的作用、输入、输出信息,因为需要让大语言模型也能明白,这样后面大模型才知道如何进行调用。
测试一下,查看请输出
get_weather("hangzhou")'{"coord": {"lon": 120.1614, "lat": 30.2937}, "weather": [{"id": 800, "main": "Clear", "description": "\\u6674", "icon": "01n"}], "base": "stations", "main": {"temp": 17.95, "feels_like": 17.76, "temp_min": 17.95, "temp_max": 17.95, "pressure": 1025, "humidity": 75, "sea_level": 1025, "grnd_level": 1022}, "visibility": 10000, "wind": {"speed": 1.94, "deg": 4, "gust": 4.35}, "clouds": {"all": 2}, "dt": 1761317352, "sys": {"type": 1, "id": 9651, "country": "CN", "sunrise": 1761257236, "sunset": 1761297576}, "timezone": 28800, "id": 1808926, "name": "Hangzhou", "cod": 200}'
2.2 封装成工具
上面能够正常运行后,可以将其封装成LangChain可识别的工具,这里需要用到装饰函数。在 LangChain 中,@tool 装饰器用于将 Python 函数转换为可供语言模型(LLM)调用的工具(Tool)。这使得 LLM 能够在生成响应时,调用外部函数或 API,执行特定任务。
@tool 装饰器的作用@tool 装饰器将一个普通的 Python 函数转换为 LangChain 的工具对象,具备以下功能:自动推断工具的名称、描述和参数模式:通过解析函数的名称和文档字符串,自动生成工具的元数据。支持自定义配置:可以通过装饰器参数自定义工具的名称、描述、参数模式等。与 LLM 兼容:生成的工具对象可以传递给支持工具调用的聊天模型,使模型能够执行特定的函数。
from langchain_core.tools import tool@tool
def get_weather(loc):"""查询即时天气函数:param loc: 必要参数,字符串类型,用于表示查询天气的具体城市名称,\注意,中国的城市需要用对应城市的英文名称代替,例如如果需要查询北京市天气,则loc参数需要输入'Beijing';:return:OpenWeather API查询即时天气的结果,具体URL请求地址为:https://api.openweathermap.org/data/2.5/weather\返回结果对象类型为解析之后的JSON格式对象,并用字符串形式进行表示,其中包含了全部重要的天气信息"""# Step 1.构建请求url = "https://api.openweathermap.org/data/2.5/weather"# Step 2.设置查询参数params = {"q": loc, "appid": os.getenv("OPENWEATHER_API_KEY"), # 输入API key"units": "metric", # 使用摄氏度而不是华氏度"lang":"zh_cn" # 输出语言为简体中文}# Step 3.发送GET请求response = requests.get(url, params=params)# Step 4.解析响应data = response.json()return json.dumps(data)
查看工具函数的属性,如果能正常查看说明封装成功
print(get_weather.name)
print(get_weather.description)
print(get_weather.args)get_weather
查询即时天气函数
:param loc: 必要参数,字符串类型,用于表示查询天气的具体城市名称, 注意,中国的城市需要用对应城市的英文名称代替,例如如果需要查询北京市天气,则loc参数需要输入'Beijing';
:return:OpenWeather API查询即时天气的结果,具体URL请求地址为:https://api.openweathermap.org/data/2.5/weather 返回结果对象类型为解析之后的JSON格式对象,并用字符串形式进行表示,其中包含了全部重要的天气信息
{'loc': {'title': 'Loc'}}
接下来,便可以通过新的llm_with_tools模型通过invoke方法来调用模型。代码如下:
from langchain.chat_models import init_chat_model# 初始化模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")# 定义 天气查询 工具函数
tools = [get_weather]# 将工具绑定到模型
llm_with_tools = model.bind_tools(tools)response = llm_with_tools.invoke("你好, 请问杭州的天气怎么样?")print(response)content='我来帮您查询杭州的天气情况。' additional_kwargs={'tool_calls': [{'id': 'call_00_fAeFff70ejt1xNCAiZvasSaG', 'function': {'arguments': '{"loc": "Hangzhou"}', 'name': 'get_weather'}, 'type': 'function', 'index': 0}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 251, 'total_tokens': 276, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 251}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_ffc7281d48_prod0820_fp8_kvcache', 'id': 'a5d535bf-d141-4efa-a277-c26d2c0b5e22', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None} id='run--317f2959-3cf0-42f0-95d6-2bf050199e27-0' tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Hangzhou'}, 'id': 'call_00_fAeFff70ejt1xNCAiZvasSaG', 'type': 'tool_call'}] usage_metadata={'input_tokens': 251, 'output_tokens': 25, 'total_tokens': 276, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}
可以看到已经正常在调用并返回了
response.additional_kwargs{'tool_calls': [{'id': 'call_00_fAeFff70ejt1xNCAiZvasSaG','function': {'arguments': '{"loc": "Hangzhou"}', 'name': 'get_weather'},'type': 'function','index': 0}],'refusal': None}
在打印的结果中可以看到:
-
tool_calls:这是一个列表,包含了模型生成的工具调用信息。每个工具调用信息是一个字典,包含以下字段:
id:工具调用的唯一标识符。
function:一个字典,描述了被调用的函数信息:
name:函数的名称。
arguments:传递给函数的参数,以 JSON 字符串的形式表示。
type:工具调用的类型,通常为 ‘function’。 -
说明,tool_calls 列表包含一个工具调用,表示模型调用了名为 get_weather 的函数,并传递了参数 {“loc”: “Hangzhou”}。
-
refusal:如果模型拒绝执行某个操作或回答某个问题,refusal 字段将包含相关信息。在您的示例中,refusal 为 None,表示模型没有拒绝任何操作。
下面我们继续调用JsonOutputKeyToolsParser输出解析器来处理模型响应。
from langchain_core.output_parsers.openai_tools import JsonOutputKeyToolsParserparser = JsonOutputKeyToolsParser(key_name=get_weather.name, first_tool_only=True)llm_chain = llm_with_tools | parser
llm_chain.invoke("请问杭州今天天气如何?"){'loc': 'Hangzhou'}
组合起来看一下是否能够输出调用结果
get_weather_chain = llm_with_tools | parser | get_weather'{"coord": {"lon": 120.1614, "lat": 30.2937}, "weather": [{"id": 800, "main": "Clear", "description": "\\u6674", "icon": "01n"}], "base": "stations", "main": {"temp": 17.95, "feels_like": 17.76, "temp_min": 17.95, "temp_max": 17.95, "pressure": 1025, "humidity": 75, "sea_level": 1025, "grnd_level": 1022}, "visibility": 10000, "wind": {"speed": 1.94, "deg": 4, "gust": 4.35}, "clouds": {"all": 2}, "dt": 1761317352, "sys": {"type": 1, "id": 9651, "country": "CN", "sunrise": 1761257236, "sunset": 1761297576}, "timezone": 28800, "id": 1808926, "name": "Hangzhou", "cod": 200}'
2.3 整合成链
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser# Prompt 模板
output_prompt = PromptTemplate.from_template("""你将收到一段 JSON 格式的天气数据,请用简洁自然的方式将其转述给用户。
以下是天气 JSON 数据:```json{weather_json}```请将其转换为中文天气描述,例如:
“北京当前天气晴,气温为 23°C,湿度 58%,风速 2.1 米/秒。”
只返回一句话描述,不要其他说明或解释。"""
)
output_chain = output_prompt | model | StrOutputParser()full_chain = get_weather_chain | output_chain
response = full_chain.invoke("请问杭州今天的天气如何?")
print(response)杭州当前天气晴,气温为18°C,湿度75%,风速1.94米/秒。
