07-08-周三_10-09-42

This commit is contained in:
AaronXu
2026-07-08 10:09:42 +08:00
commit 825bcf47fa
30 changed files with 16488 additions and 0 deletions

470
09_工具定义.ipynb Normal file
View File

@@ -0,0 +1,470 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 09 工具定义\n",
"\n",
"## 学习目标\n",
"1. 理解工具Tools在智能体开发中的核心作用\n",
"2. 掌握使用 `@tool` 装饰器定义自定义工具\n",
"3. 学会为工具编写清晰的参数说明和文档字符串\n",
"4. 理解工具描述如何影响模型选择工具的能力\n",
"5. 能够定义计算类、查询类和文件处理类工具"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 为什么需要工具\n",
"\n",
"大语言模型本身只能处理文本,它的知识截止于训练数据,也无法直接访问外部世界。\n",
"\n",
"**工具Tools** 让模型能够:\n",
"\n",
"- 执行数学计算(模型不擅长精确计算)\n",
"- 查询数据库、API、搜索引擎\n",
"- 读写文件、操作数据库\n",
"- 获取实时信息(天气、股价、新闻)\n",
"\n",
"智能体Agent的核心工作就是**根据用户问题选择合适的工具,调用工具,再根据结果回答问题**。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. 使用 @tool 装饰器定义第一个工具\n",
"\n",
"LangChain 提供了 `@tool` 装饰器,可以把一个普通 Python 函数变成智能体可调用的工具。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"# 使用 @tool 装饰器定义一个加法工具\n",
"@tool\n",
"def add(a: int, b: int) -> int:\n",
" \"\"\"计算两个整数的和。\"\"\"\n",
" return a + b\n",
"\n",
"# 查看工具信息\n",
"print('工具名称:', add.name)\n",
"print('工具描述:', add.description)\n",
"print('参数Schema', add.args)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- `@tool`:把 `add` 函数注册为 LangChain 工具\n",
"- `a: int, b: int`:参数类型会被自动解析为工具的输入参数格式\n",
"- 函数文档字符串 `\"\"\"...\"\"\"`会被自动提取为工具描述description\n",
"- `add.name`:工具名,默认使用函数名\n",
"- `add.description`:工具描述,来自函数的 docstring\n",
"- `add.args`:参数的 JSON Schema模型根据它决定如何传参"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. 手动调用工具\n",
"\n",
"工具定义好后,可以通过 `invoke` 方法或 `run` 方法调用。\n",
"\n",
"注意:`@tool` 装饰后的对象是一个 `StructuredTool` 对象,**不能直接像普通函数那样加括号调用**(如 `multiply(3, 4)`),需要传入字典参数。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"计算两个整数的乘积。\"\"\"\n",
" return a * b\n",
"\n",
"# 通过 invoke 调用,传入字典参数\n",
"print('invoke 调用:', multiply.invoke({'a': 3, 'b': 4}))\n",
"\n",
"# 通过 run 调用LangChain 工具的传统调用方式)\n",
"print('run 调用:', multiply.run({'a': 5, 'b': 6}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- `@tool` 装饰后的对象是一个 `StructuredTool`,不是普通函数\n",
"- `multiply.invoke({'a': 3, 'b': 4})`LangChain 风格调用,智能体实际使用这种方式\n",
"- `multiply.run({'a': 5, 'b': 6})`:传统工具调用方式,与 `invoke` 效果相同\n",
"- 注意:不能写成 `multiply(3, 4)`,否则会报 `StructuredTool object is not callable`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. 自定义工具名称和描述\n",
"\n",
"默认情况下,工具名就是函数名,描述来自 docstring。你也可以通过 `@tool` 参数自定义。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool('weather-query', description='查询指定城市的当前天气情况')\n",
"def get_weather(city: str) -> str:\n",
" \"\"\"\n",
" 查询天气。\n",
" 参数:\n",
" city: 城市名称,例如 \"北京\"、\"上海\"\n",
" \"\"\"\n",
" # 这里用模拟数据,实际可接入天气 API\n",
" weather_data = {\n",
" '北京': '晴25°C',\n",
" '上海': '多云28°C',\n",
" '广州': '小雨30°C'\n",
" }\n",
" return weather_data.get(city, f'未找到 {city} 的天气信息')\n",
"\n",
"print('工具名称:', get_weather.name)\n",
"print('工具描述:', get_weather.description)\n",
"print('调用结果:', get_weather.invoke({'city': '北京'}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- `@tool('weather-query', description='...')`:自定义工具名和描述\n",
"- 工具描述非常重要,模型主要依赖它判断什么时候使用该工具\n",
"- 描述要清晰、具体,包含「工具能做什么」和「参数含义」\n",
"- 函数 docstring 可以继续补充参数细节"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. 定义带多个参数的工具\n",
"\n",
"工具可以有多个参数LangChain 会自动根据类型注解生成参数 Schema。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def calculate_bmi(height: float, weight: float) -> str:\n",
" \"\"\"\n",
" 根据身高和体重计算 BMI 指数,并返回健康建议。\n",
" 参数:\n",
" height: 身高,单位米\n",
" weight: 体重,单位千克\n",
" \"\"\"\n",
" bmi = weight / (height ** 2)\n",
" if bmi < 18.5:\n",
" suggestion = '体重过轻,建议加强营养'\n",
" elif bmi < 24:\n",
" suggestion = '体重正常,请保持'\n",
" elif bmi < 28:\n",
" suggestion = '超重,建议适当运动'\n",
" else:\n",
" suggestion = '肥胖,建议咨询医生'\n",
" return f'BMI: {bmi:.2f}{suggestion}'\n",
"\n",
"print(calculate_bmi.invoke({'height': 1.75, 'weight': 70}))\n",
"print('\\n参数 Schema')\n",
"print(calculate_bmi.args)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- `height: float, weight: float`:类型注解会被 LangChain 自动识别\n",
"- 生成的 `args` 中会包含参数名、类型、是否必填等信息\n",
"- 模型会根据这个 Schema 构造正确的 JSON 参数调用工具"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. 定义模拟 API 查询工具\n",
"\n",
"实际应用中,工具通常会调用外部 API。下面我们用一个模拟的「产品库存查询」工具来演示。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def query_product_stock(product_name: str) -> str:\n",
" \"\"\"\n",
" 查询指定商品的库存数量。\n",
" 参数:\n",
" product_name: 商品名称\n",
" \"\"\"\n",
" # 模拟数据库查询\n",
" stock_db = {\n",
" '手机': 150,\n",
" '笔记本电脑': 45,\n",
" '耳机': 200,\n",
" '充电宝': 80\n",
" }\n",
" stock = stock_db.get(product_name)\n",
" if stock is None:\n",
" return f'未找到商品 \"{product_name}\" 的库存信息'\n",
" return f'商品 \"{product_name}\" 当前库存:{stock} 件'\n",
"\n",
"# 测试工具\n",
"print(query_product_stock.invoke({'product_name': '手机'}))\n",
"print(query_product_stock.invoke({'product_name': '相机'}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- 这个工具模拟了查询数据库或 API 的场景\n",
"- 返回的字符串会作为观察结果Observation交给模型继续处理\n",
"- 工具内部可以替换为真实的数据库查询、HTTP 请求等"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. 定义文件处理工具\n",
"\n",
"工具也可以读写文件。下面是读取文本文件和写入文本文件的工具示例。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def read_text_file(file_path: str) -> str:\n",
" \"\"\"\n",
" 读取指定文本文件的内容。\n",
" 参数:\n",
" file_path: 文件路径\n",
" \"\"\"\n",
" try:\n",
" with open(file_path, 'r', encoding='utf-8') as f:\n",
" return f.read()\n",
" except FileNotFoundError:\n",
" return f'文件不存在:{file_path}'\n",
" except Exception as e:\n",
" return f'读取失败:{str(e)}'\n",
"\n",
"@tool\n",
"def write_text_file(file_path: str, content: str) -> str:\n",
" \"\"\"\n",
" 将内容写入指定文本文件。\n",
" 参数:\n",
" file_path: 文件路径\n",
" content: 要写入的内容\n",
" \"\"\"\n",
" try:\n",
" with open(file_path, 'w', encoding='utf-8') as f:\n",
" f.write(content)\n",
" return f'已成功写入文件:{file_path}'\n",
" except Exception as e:\n",
" return f'写入失败:{str(e)}'\n",
"\n",
"# 测试写入和读取\n",
"write_text_file.invoke({'file_path': 'test_note.txt', 'content': '这是测试内容'})\n",
"print(read_text_file.invoke({'file_path': 'test_note.txt'}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- `read_text_file`:读取指定文件内容\n",
"- `write_text_file`:把字符串写入指定文件\n",
"- 文件路径使用相对路径时,会以当前工作目录为基准\n",
"- 工具内部要做好异常处理,避免程序崩溃"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 8. 工具列表与工具选择\n",
"\n",
"在构建智能体时,通常会把多个工具放在一个列表里,交给模型选择使用。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def add(a: int, b: int) -> int:\n",
" \"\"\"计算两个整数的和。\"\"\"\n",
" return a + b\n",
"\n",
"@tool\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"计算两个整数的乘积。\"\"\"\n",
" return a * b\n",
"\n",
"@tool\n",
"def power(base: int, exponent: int) -> int:\n",
" \"\"\"计算 base 的 exponent 次方。\"\"\"\n",
" return base ** exponent\n",
"\n",
"# 把工具放入列表\n",
"tools = [add, multiply, power]\n",
"\n",
"# 查看工具列表信息\n",
"for t in tools:\n",
" print(f'名称:{t.name},描述:{t.description}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"- 智能体通过工具列表了解有哪些工具可用\n",
"- 模型会根据用户问题和工具描述,决定调用哪个工具、传什么参数\n",
"- 工具名称和描述的清晰度直接影响模型选择工具的准确率"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 9. 完整示例:多功能计算器工具集\n",
"\n",
"下面把多个数学工具组合成一个工具集,模拟智能体可使用的工具环境。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.tools import tool\n",
"\n",
"@tool\n",
"def add(a: float, b: float) -> float:\n",
" \"\"\"计算两个数的和。\"\"\"\n",
" return a + b\n",
"\n",
"@tool\n",
"def subtract(a: float, b: float) -> float:\n",
" \"\"\"计算两个数的差。\"\"\"\n",
" return a - b\n",
"\n",
"@tool\n",
"def multiply(a: float, b: float) -> float:\n",
" \"\"\"计算两个数的乘积。\"\"\"\n",
" return a * b\n",
"\n",
"@tool\n",
"def divide(a: float, b: float) -> float:\n",
" \"\"\"计算两个数的商如果除数为0则返回错误信息。\"\"\"\n",
" if b == 0:\n",
" return '错误除数不能为0'\n",
" return a / b\n",
"\n",
"math_tools = [add, subtract, multiply, divide]\n",
"\n",
"# 测试每个工具\n",
"for t in math_tools:\n",
" print(f'{t.name}: {t.description}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 10. 本节课练习\n",
"\n",
"1. 使用 `@tool` 定义一个摄氏度转华氏度的工具,参数和返回值都使用 float 类型\n",
"2. 定义一个「查询课程信息」工具,用字典模拟数据库,根据课程名返回上课时间\n",
"3. 定义一个「写入学习笔记」工具,接收文件名和内容,把内容写入指定文件\n",
"4. 把上面三个工具放入一个列表,打印每个工具的名称、描述和参数 Schema\n",
"5. 尝试修改一个工具的描述,观察参数 Schema 是否会变化"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}