一文掌握Langchain自定义工具调用的所有方式

在这里插入图片描述

LangChain自定义工具的方式有三种,我发现网上的人都是copy官网的教学,很难以理解,所以自己整理了一份简单带注释的python代码,相信你自己运行一遍之后,就明白各自的使用规则了。

  1. @tool装饰器 - 最简单的方式
  2. Tool类 - 适合包装现有函数
  3. StructuredTool - 最强大和灵活的方式

内容参考来自于Langchain官网

完整示例代码如下,注释解释得非常详细了,就不再文字赘述。完整代码是可以直接执行的哦,建议自己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 - 最强大和灵活的方式

每种方式都有其适用场景,可以根据具体需求选择合适的方式。
代理会自动选择合适的工具来回答用户问题,实现智能的工具调用。
""")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值