LangChain自定义工具的方式有三种,我发现网上的人都是copy官网的教学,很难以理解,所以自己整理了一份简单带注释的python代码,相信你自己运行一遍之后,就明白各自的使用规则了。
- @tool装饰器 - 最简单的方式
- Tool类 - 适合包装现有函数
- StructuredTool - 最强大和灵活的方式
完整示例代码如下,注释解释得非常详细了,就不再文字赘述。完整代码是可以直接执行的哦,建议自己copy下来调试执行一遍哦
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Junius
@Email: see619055@gmail.com
@time: 2025/5/26 16:13
@file: 06 定义工具和工具调用
@project: OpenAppAI
@describe: LangChain工具定义和工具调用完整入门示例
"""
from langchain_openai import ChatOpenAI
from langchain.agents import tool
from langchain.tools import Tool, StructuredTool
from langchain_core.utils.function_calling import convert_to_openai_function
from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain.agents import AgentExecutor
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
from typing import Optional, Type
import json
import requests
from datetime import datetime
# 初始化LLM
llm = ChatOpenAI(
openai_api_base="https://open.bigmodel.cn/api/paas/v4/",
# openai_api_key="your_api_key_here", # 请替换为您的API密钥.示例使用的是智谱的免费模型,你也可以进行自我注册
model="glm-4-flash-250414"
)
print("=" * 60)
print("LangChain工具定义和工具调用完整入门示例")
print("=" * 60)
# ============================================================================
# 方式一:使用 @tool 装饰器创建工具(最简单的方式)
# ============================================================================
print("\n【方式一:使用 @tool 装饰器创建工具】")
@tool
def get_current_time(timezone: str = "Asia/Shanghai") -> str:
"""获取当前时间。
Args:
timezone: 时区,默认为"Asia/Shanghai"
Returns:
当前时间的字符串表示
"""
from datetime import datetime
import pytz
try:
tz = pytz.timezone(timezone)
current_time = datetime.now(tz)
return f"当前时间({timezone}):{current_time.strftime('%Y-%m-%d %H:%M:%S')}"
except:
# 如果时区不存在,返回系统时间
current_time = datetime.now()
return f"当前系统时间:{current_time.strftime('%Y-%m-%d %H:%M:%S')}"
@tool
def calculate_math(expression: str) -> str:
"""计算数学表达式。
Args:
expression: 要计算的数学表达式,如"2 + 3 * 4"
Returns:
计算结果
"""
try:
# 为了安全,只允许基本的数学运算
allowed_chars = set('0123456789+-*/()., ')
if not all(c in allowed_chars for c in expression):
return "错误:表达式包含不允许的字符"
result = eval(expression)
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算错误:{str(e)}"
print("✓ 使用@tool装饰器创建的工具:")
print(f" - {get_current_time.name}: {get_current_time.description}")
print(f" - {calculate_math.name}: {calculate_math.description}")
# ============================================================================
# 方式二:使用 Tool 类创建工具(适合简单函数)
# ============================================================================
print("\n【方式二:使用 Tool 类创建工具】")
def search_weather(location: str) -> str:
"""搜索指定位置的天气信息"""
# 模拟天气数据
weather_data = {
"北京": {"temperature": 25, "condition": "晴朗", "humidity": "45%"},
"上海": {"temperature": 28, "condition": "多云", "humidity": "60%"},
"广州": {"temperature": 32, "condition": "小雨", "humidity": "75%"},
"深圳": {"temperature": 30, "condition": "阴天", "humidity": "70%"},
"杭州": {"temperature": 26, "condition": "晴朗", "humidity": "50%"}
}
if location in weather_data:
data = weather_data[location]
return f"{location}天气:{data['condition']},温度{data['temperature']}°C,湿度{data['humidity']}"
else:
return f"抱歉,暂时无法获取{location}的天气信息"
def translate_text(text: str, target_language: str = "英文") -> str:
"""翻译文本到指定语言"""
# 模拟翻译功能
translations = {
"你好": {"英文": "Hello", "日文": "こんにちは", "韩文": "안녕하세요"},
"谢谢": {"英文": "Thank you", "日文": "ありがとう", "韩文": "감사합니다"},
"再见": {"英文": "Goodbye", "日文": "さようなら", "韩文": "안녕히 가세요"}
}
if text in translations and target_language in translations[text]:
return f"'{text}' 翻译为{target_language}:{translations[text][target_language]}"
else:
return f"抱歉,无法将'{text}'翻译为{target_language}"
# 使用Tool类创建工具
weather_tool = Tool(
name="search_weather",
description="搜索指定城市的天气信息。输入参数:location(城市名称)",
func=search_weather
)
translate_tool = Tool(
name="translate_text",
description="将文本翻译为指定语言。输入参数:text(要翻译的文本),target_language(目标语言,如'英文'、'日文'、'韩文')",
func=lambda text, target_language="英文": translate_text(text, target_language)
)
print("✓ 使用Tool类创建的工具:")
print(f" - {weather_tool.name}: {weather_tool.description}")
print(f" - {translate_tool.name}: {translate_tool.description}")
# ============================================================================
# 方式三:使用 StructuredTool 创建工具(最灵活,支持复杂参数)
# ============================================================================
print("\n【方式三:使用 StructuredTool 创建工具】")
# 定义输入参数的数据模型
class FileOperationInput(BaseModel):
"""文件操作的输入参数"""
operation: str = Field(description="操作类型:'create'(创建)、'read'(读取)、'list'(列出文件)")
filename: Optional[str] = Field(default=None, description="文件名(创建和读取时需要)")
content: Optional[str] = Field(default=None, description="文件内容(创建时需要)")
class DataAnalysisInput(BaseModel):
"""数据分析的输入参数"""
data: str = Field(description="要分析的数据,JSON格式的字符串")
analysis_type: str = Field(description="分析类型:'sum'(求和)、'average'(平均值)、'max'(最大值)、'min'(最小值)")
def file_operation_func(operation: str, filename: Optional[str] = None, content: Optional[str] = None) -> str:
"""执行文件操作"""
if operation == "create":
if not filename or not content:
return "错误:创建文件需要提供文件名和内容"
# 模拟创建文件
return f"成功创建文件 '{filename}',内容:{content[:50]}{'...' if len(content) > 50 else ''}"
elif operation == "read":
if not filename:
return "错误:读取文件需要提供文件名"
# 模拟读取文件
mock_files = {
"config.txt": "这是配置文件的内容",
"data.json": '{"name": "张三", "age": 25, "city": "北京"}',
"readme.md": "# 项目说明\n这是一个示例项目"
}
if filename in mock_files:
return f"文件 '{filename}' 的内容:\n{mock_files[filename]}"
else:
return f"错误:文件 '{filename}' 不存在"
elif operation == "list":
# 模拟列出文件
mock_files = ["config.txt", "data.json", "readme.md", "log.txt"]
return f"当前目录文件列表:{', '.join(mock_files)}"
else:
return f"错误:不支持的操作类型 '{operation}'"
def data_analysis_func(data: str, analysis_type: str) -> str:
"""执行数据分析"""
try:
# 解析JSON数据
parsed_data = json.loads(data)
# 提取数值
if isinstance(parsed_data, list):
numbers = [x for x in parsed_data if isinstance(x, (int, float))]
elif isinstance(parsed_data, dict):
numbers = [v for v in parsed_data.values() if isinstance(v, (int, float))]
else:
return "错误:数据格式不正确,需要JSON数组或对象"
if not numbers:
return "错误:数据中没有找到数值"
# 执行分析
if analysis_type == "sum":
result = sum(numbers)
return f"数据求和结果:{result}"
elif analysis_type == "average":
result = sum(numbers) / len(numbers)
return f"数据平均值:{result:.2f}"
elif analysis_type == "max":
result = max(numbers)
return f"数据最大值:{result}"
elif analysis_type == "min":
result = min(numbers)
return f"数据最小值:{result}"
else:
return f"错误:不支持的分析类型 '{analysis_type}'"
except json.JSONDecodeError:
return "错误:无法解析JSON数据"
except Exception as e:
return f"分析错误:{str(e)}"
# 使用StructuredTool创建工具
file_tool = StructuredTool.from_function(
func=file_operation_func,
name="file_operation",
description="执行文件操作,包括创建、读取和列出文件",
args_schema=FileOperationInput
)
data_analysis_tool = StructuredTool.from_function(
func=data_analysis_func,
name="data_analysis",
description="对JSON格式的数据进行分析,支持求和、平均值、最大值、最小值等操作",
args_schema=DataAnalysisInput
)
print("✓ 使用StructuredTool创建的工具:")
print(f" - {file_tool.name}: {file_tool.description}")
print(f" - {data_analysis_tool.name}: {data_analysis_tool.description}")
# ============================================================================
# 组合所有工具并创建智能代理
# ============================================================================
print("\n【创建智能代理并测试工具调用】")
# 收集所有工具
all_tools = [
# 方式一:@tool装饰器创建的工具
get_current_time,
calculate_math,
# 方式二:Tool类创建的工具
weather_tool,
translate_tool,
# 方式三:StructuredTool创建的工具
file_tool,
data_analysis_tool
]
print(f"✓ 总共创建了 {len(all_tools)} 个工具")
# 将工具转换为OpenAI函数格式
functions = [convert_to_openai_function(t) for t in all_tools]
# 创建代理提示模板
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个智能助手,拥有多种工具来帮助用户解决问题。
可用工具包括:
1. get_current_time - 获取当前时间
2. calculate_math - 计算数学表达式
3. search_weather - 查询天气信息
4. translate_text - 翻译文本
5. file_operation - 文件操作(创建、读取、列出)
6. data_analysis - 数据分析
请根据用户的问题选择合适的工具来提供帮助。如果需要使用多个工具,请按顺序调用。"""),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# 绑定LLM与函数
llm_with_tools = llm.bind(functions=functions)
# 构建代理
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_function_messages(x["intermediate_steps"]),
}
| prompt
| llm_with_tools
| OpenAIFunctionsAgentOutputParser()
)
# 创建代理执行器
agent_executor = AgentExecutor(agent=agent, tools=all_tools, verbose=True)
# ============================================================================
# 测试示例
# ============================================================================
def run_examples():
"""运行测试示例"""
print("\n" + "=" * 60)
print("开始测试工具调用")
print("=" * 60)
test_cases = [
{
"description": "测试时间工具",
"input": "现在几点了?"
},
{
"description": "测试数学计算工具",
"input": "帮我计算一下 15 * 8 + 32 的结果"
},
{
"description": "测试天气查询工具",
"input": "北京今天天气怎么样?"
},
{
"description": "测试翻译工具",
"input": "请把'你好'翻译成英文"
},
{
"description": "测试文件操作工具",
"input": "请列出当前目录的所有文件"
},
{
"description": "测试数据分析工具",
"input": "请分析这组数据的平均值:[10, 20, 30, 40, 50]"
},
{
"description": "测试多工具组合使用",
"input": "请告诉我现在的时间,然后计算一下北京和上海的平均温度是多少"
}
]
for i, test_case in enumerate(test_cases, 1):
print(f"\n【测试 {i}】{test_case['description']}")
print(f"用户问题:{test_case['input']}")
print("-" * 40)
try:
result = agent_executor.invoke({"input": test_case["input"]})
print(f"✓ 回答:{result['output']}")
except Exception as e:
print(f"✗ 错误:{str(e)}")
print("-" * 40)
# ============================================================================
# 工具使用说明和最佳实践
# ============================================================================
def print_best_practices():
"""打印最佳实践说明"""
print("\n" + "=" * 60)
print("LangChain工具创建方式对比和最佳实践")
print("=" * 60)
print("""
【三种工具创建方式对比】
1. @tool 装饰器
✓ 优点:最简单,代码最少
✓ 适用:简单函数,参数类型简单
✗ 缺点:参数验证有限,文档生成简单
2. Tool 类
✓ 优点:灵活性好,可以包装现有函数
✓ 适用:已有函数需要快速转换为工具
✗ 缺点:参数验证有限,复杂参数支持不好
3. StructuredTool
✓ 优点:最强大,支持复杂参数验证和文档
✓ 适用:复杂工具,需要严格参数验证
✗ 缺点:代码较多,学习成本高
【最佳实践建议】
1. 工具设计原则:
- 单一职责:每个工具只做一件事
- 清晰命名:工具名称要直观易懂
- 详细文档:提供清晰的参数说明
- 错误处理:优雅处理异常情况
2. 参数设计:
- 使用类型提示
- 提供默认值
- 验证输入参数
- 返回结构化结果
3. 性能优化:
- 避免长时间运行的操作
- 实现缓存机制
- 提供超时控制
- 异步操作支持
4. 安全考虑:
- 输入验证和清理
- 权限控制
- 敏感信息保护
- 操作日志记录
""")
if __name__ == "__main__":
# 打印最佳实践
print_best_practices()
# 运行测试示例
run_examples()
print("\n" + "=" * 60)
print("示例运行完成!")
print("=" * 60)
print("""
本示例展示了LangChain中三种创建工具的方式:
1. @tool装饰器 - 最简单的方式
2. Tool类 - 适合包装现有函数
3. StructuredTool - 最强大和灵活的方式
每种方式都有其适用场景,可以根据具体需求选择合适的方式。
代理会自动选择合适的工具来回答用户问题,实现智能的工具调用。
""")