
前言
欢迎来到AI Agent零基础入门教程的第五课!在之前的学习中,我们已经掌握了LangChain框架的基本使用方法,并成功构建了多种类型的Agent。本节课我们将深入学习Agent最核心的能力之一——工具调用。
工具是Agent与外部世界交互的桥梁。一个功能强大的Agent,必然拥有丰富且精准的工具。本节课将从工具的基本概念讲起,手把手教大家如何自定义工具、如何注册工具、以及如何让Agent正确选择和使用工具。
通过本节课的学习,你将能够为自己的Agent添加任何需要的功能,实现与各种外部系统的交互。
工具的基本概念
什么是Tool
在LangChain中,Tool(工具)是一个可以被Agent调用的函数。每个Tool都有明确的输入输出定义,Agent可以根据任务需求选择合适的工具来执行操作。
┌─────────────────────────────────────────────────────────────┐│ Tool 的结构 │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────┐ ││ │ Tool 定义 │ ││ ├─────────────────────────────────────────────────────┤ ││ │ │ ││ │ name │ 工具名称(唯一标识) │ ││ │ description │ 工具描述(Agent据此选择工具) │ ││ │ args_schema │ 输入参数定义(Pydantic模型) │ ││ │ func │ 实际执行的函数 │ ││ │ return_direct│ 是否直接返回结果 │ ││ │ │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘
Tool的核心属性
属性名 | 类型 | 说明 | 示例 |
name | str | 工具的唯一名称 | “calculator” |
description | str | 描述工具功能和用途 | “用于执行数学计算” |
args_schema | BaseModel | 输入参数的定义 | Pydantic模型 |
func | Callable | 实际执行的函数 | Python函数 |
Tool的工作原理
当Agent需要完成某个任务时,它会经历以下决策过程:
用户提问:计算 25 * 17 + 100 的结果Agent思考:1. 用户需要计算数学表达式2. 我需要使用计算器工具3. 输入参数是数学表达式Agent决策:Action: calculatorInput: "25 * 17 + 100"Tool执行:┌────────────────────────────┐│ def calculator(expr): ││ return eval(expr) ││ ││ calculator("25*17+100") ││ → 525 │└────────────────────────────┘Agent获取观察结果:Observation: 525Agent生成回答:"25 * 17 + 100 = 525"
使用LangChain内置工具
LangChain提供了丰富的内置工具,可以直接加载使用。
加载内置工具
from langchain.agents import load_tools# 加载多个工具tools = load_tools( ["serpapi", "llm-math", "python_repl"], llm=llm)
常用内置工具一览
工具名称 | 功能 | 输入参数 | 输出 |
serpapi | 搜索互联网 | 搜索关键词 | 搜索结果摘要 |
llm-math | 数学计算 | 数学表达式 | 计算结果 |
python_repl | 执行Python代码 | Python代码 | 执行结果 |
search | 通用搜索 | 搜索查询 | 搜索结果 |
wolfram-alpha | 高级数学计算 | 数学查询 | 计算结果 |
实战:使用内置工具
创建01_builtin_tools.py:
"""LangChain 工具实战:使用内置工具本程序展示如何使用LangChain提供的内置工具"""import osfrom dotenv import load_dotenvfrom langchain.agents import AgentType, initialize_agent, load_toolsfrom langchain_openai import OpenAI# 加载环境变量load_dotenv()def demo_builtin_tools():"""演示内置工具的使用 """print("="*60)print("�� 内置工具演示")print("="*60)# 1. 初始化语言模型 llm = OpenAI(temperature=0)# 2. 加载工具print("\n正在加载工具...") tools = load_tools( ["serpapi", "llm-math", "python_repl"], llm=llm, handle_parsing_errors=True )print(f"已加载 {len(tools)}个工具:")for tool in tools:print(f" - {tool.name}: {tool.description[:50]}...")# 3. 创建Agent agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True )# 4. 运行测试 test_tasks = ["请计算:(125 + 67) * 3 - 89 = ?","2024年奥运会中国获得多少枚金牌?","使用Python计算1到100的和" ]for i, task inenumerate(test_tasks, 1):print(f"\n\n{'#'*60}")print(f"# 测试任务 {i}")print(f"# {task}")print('#'*60) result = agent.run(task)print(f"\n✅ 结果: {result}")def main():"""主函数"""print("""╔════════════════════════════════════════════════════════════╗║ ║║ �� LangChain 工具实战:内置工具使用 ║║ ║║ 学习使用LangChain提供的内置工具 ║║ ║╚════════════════════════════════════════════════════════════╝ """) demo_builtin_tools()if__name__=="__main__": main()
运行程序:
python 01_builtin_tools.py
自定义工具
内置工具虽然丰富,但在实际开发中,我们经常需要创建自定义工具来满足特定需求。
使用@tool装饰器定义工具
这是最简单的自定义工具方式,使用Python的装饰器语法:
from langchain.tools import tool@tooldef get_weather(location: str) ->str:"""获取指定地点的天气信息 Args: location: 地点名称,如"北京"、"上海" Returns:天气信息字符串 """# 这里可以调用真实的天气APIreturnf"{location}今天晴天,气温20-28摄氏度"
完整示例:自定义天气工具
创建02_custom_tools.py:
"""LangChain 工具实战:自定义工具本程序展示如何创建自定义工具"""import osfrom dotenv import load_dotenvfrom langchain.agents import AgentType, initialize_agentfrom langchain_openai import OpenAIfrom langchain.tools import toolfrom datetime import datetime, timedelta# 加载环境变量load_dotenv()# ========================================# 自定义工具定义# ========================================@tooldef get_current_time(format: str="%Y年%m月%d日 %H:%M:%S") ->str:"""获取当前时间 Args: format: 时间格式,默认格式为中文格式 Returns:格式化的时间字符串 """ now = datetime.now()return now.strftime(format)@tooldef calculate_days_until(date: str) ->str:"""计算距离指定日期还有多少天 Args: date: 目标日期,格式为YYYY-MM-DD Returns:距离天数和日期信息 """try: target = datetime.strptime(date, "%Y-%m-%d") today = datetime.now() delta = target - todayif delta.days ==0:returnf"今天就是{date}!"elif delta.days >0:returnf"距离{date}还有{delta.days}天"else:returnf"{date}已经过去{abs(delta.days)}天了"exceptValueError:return"日期格式错误,请使用YYYY-MM-DD格式"@tooldef get_day_of_week(date: str="today") ->str:"""获取指定日期是星期几 Args: date: 日期,默认为"today"表示今天,也可以是YYYY-MM-DD格式 Returns:星期几的信息 """if date =="today": target = datetime.now()else:try: target = datetime.strptime(date, "%Y-%m-%d")exceptValueError:return"日期格式错误,请使用YYYY-MM-DD格式" week_days = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]return week_days[target.weekday()]@tooldef text_transform(text: str, operation: str) ->str:"""对文本进行转换操作 Args: text: 要转换的原始文本 operation: 操作类型,可选值: - uppercase: 转换为大写 - lowercase: 转换为小写 - titlecase: 转换为标题格式 - reverse: 反转文本 Returns:转换后的文本 """ operations = {"uppercase": lambda x: x.upper(),"lowercase": lambda x: x.lower(),"titlecase": lambda x: x.title(),"reverse": lambda x: x[::-1] }if operation notin operations:returnf"未知的操作:{operation}。可用操作:{', '.join(operations.keys())}"return operations[operation](text)# ========================================# 创建Agent# ========================================def create_custom_tools_agent():"""创建使用自定义工具的Agent Returns: AgentExecutor: 配置好的Agent """print("�� 正在创建自定义工具Agent...")# 1. 初始化语言模型 llm = OpenAI(temperature=0)# 2. 收集所有自定义工具 tools = [ get_current_time, calculate_days_until, get_day_of_week, text_transform ]print(f"已注册 {len(tools)}个自定义工具:")for t in tools:print(f" - {t.name}: {t.description[:60]}...")# 3. 创建Agent agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, handle_parsing_errors=True )print("✅ 自定义工具Agent创建成功!\n")return agentdef demo_custom_tools(agent):"""演示自定义工具的使用 """ test_queries = ["现在的时间是几点?","2024年12月25日是星期几?","距离2025年春节(2025年1月29日)还有多少天?","把'Hello World'转换为大写","今天和2025年元旦之间相差多少天?" ]for i, query inenumerate(test_queries, 1):print(f"\n{'='*60}")print(f"�� 查询 {i}: {query}")print('='*60) result = agent.run(query)print(f"\n✅ 回答: {result}")def interactive_custom_agent(agent):"""交互式测试 """print("""╔════════════════════════════════════════════════════════════╗║ ║║ �� 自定义工具Agent - 交互模式 ║║ ║║ 可用工具:时间查询、日期计算、文本转换 ║║ 输入 'exit' 退出 ║║ ║╚════════════════════════════════════════════════════════════╝ """)whileTrue:try: query =input("\n❓ 请输入你的问题:\n> ").strip()if query.lower() in ['exit', 'quit']:breakifnot query:continue result = agent.run(query)print(f"\n✅ 回答: {result}")exceptKeyboardInterrupt:print("\n\n再见!")breakdef main():"""主函数"""print("""╔════════════════════════════════════════════════════════════╗║ ║║ �� LangChain 工具实战:自定义工具 ║║ ║║ 学习如何创建和使用自定义工具 ║║ ║╚════════════════════════════════════════════════════════════╝ """) agent = create_custom_tools_agent() demo_custom_tools(agent) interactive_custom_agent(agent)if__name__=="__main__": main()
运行结果解析
运行程序后,你会看到类似以下的输出:
# 查询 1: 现在的时间是几点?> Entering new AgentExecutor chain... Thought: 用户想知道当前时间,应该使用get_current_time工具。 Action: get_current_time Input: "%Y年%m月%d日 %H:%M:%S" Observation: 2024年01月15日 14:30:25 Thought: 工具返回了当前时间,可以直接回答用户。 Answer: 现在是2024年01月15日 14:30:25。
使用Pydantic定义复杂工具
当工具需要多个参数或需要参数验证时,可以使用Pydantic模型定义输入模式。
Pydantic输入模型
创建03_pydantic_tools.py:
"""LangChain 工具实战:Pydantic工具定义本程序展示如何使用Pydantic定义复杂的工具输入"""import osfrom dotenv import load_dotenvfrom langchain.agents import AgentType, initialize_agentfrom langchain_openai import OpenAIfrom langchain_core.tools import toolfrom pydantic import BaseModel, Field# 加载环境变量load_dotenv()# ========================================# Pydantic输入模型定义# ========================================class WeatherInput(BaseModel):"""天气查询的输入参数""" location: str= Field(description="城市名称", min_length=1) include_forecast: bool= Field( description="是否包含预报", default=False )class CalculatorInput(BaseModel):"""计算器的输入参数""" expression: str= Field( description="数学表达式,如 '2+2' 或 'sqrt(16)'" )class DateRangeInput(BaseModel):"""日期范围查询的输入参数""" start_date: str= Field(description="开始日期 YYYY-MM-DD") end_date: str= Field(description="结束日期 YYYY-MM-DD") include_weekends: bool= Field( description="是否包含周末", default=True )# ========================================# 使用Pydantic模型创建工具# ========================================@tool(args_schema=WeatherInput)def get_weather(location: str, include_forecast: bool=False) ->str:"""查询指定城市的天气信息这个工具可以帮你查询任意城市的当前天气,也可以查询未来几天的天气预报。适用场景: - 出行前查看目的地天气 - 安排户外活动 - 了解目的地的气候特点 """# 模拟天气数据(实际项目中应调用真实API) weather_data = {"北京": {"temp": "15-25°C", "condition": "晴", "humidity": "45%"},"上海": {"temp": "18-28°C", "condition": "多云", "humidity": "60%"},"广州": {"temp": "22-32°C", "condition": "阵雨", "humidity": "75%"} }if location notin weather_data:returnf"抱歉,暂不支持查询{location}的天气信息" data = weather_data[location] result =f"{location}今天天气{data['condition']},气温{data['temp']},湿度{data['humidity']}"if include_forecast: result +=f"\n未来三天:以{data['condition']}为主,气温变化不大"return result@tool(args_schema=CalculatorInput)def advanced_calculator(expression: str) ->str:"""执行高级数学计算支持的运算: - 基本运算:+, -, *, / - 幂运算:** - 数学函数:sqrt, sin, cos, tan, log, exp - 圆周率:pi - 自然常数:e示例: - "2**10" 计算2的10次方 - "sqrt(144)" 计算144的平方根 - "sin(pi/2)" 计算sin(π/2) """try:# 安全计算(实际项目中应使用更安全的计算方式)import math# 替换数学函数 expression = expression.replace("sqrt", "math.sqrt") expression = expression.replace("sin", "math.sin") expression = expression.replace("cos", "math.cos") expression = expression.replace("tan", "math.tan") expression = expression.replace("log", "math.log") expression = expression.replace("exp", "math.exp") expression = expression.replace("pi", "math.pi") expression = expression.replace("e)", "math.e)") result =eval(expression)returnf"计算结果:{expression} = {result}"exceptExceptionas e:returnf"计算错误:{str(e)}"@tool(args_schema=DateRangeInput)def calculate_date_range( start_date: str, end_date: str, include_weekends: bool=True) ->str:"""计算两个日期之间的详细信息用途: - 计算项目工期 - 规划旅行时间 - 统计工作日 """from datetime import datetime, timedeltatry: start = datetime.strptime(start_date, "%Y-%m-%d") end = datetime.strptime(end_date, "%Y-%m-%d")if end < start:return"结束日期不能早于开始日期" days = (end - start).days weeks = days //7 remaining_days = days %7 result =f"""��日期范围计算结果:━━━━━━━━━━━━━━━━━━━━━━━━━开始日期:{start_date}结束日期:{end_date}总天数:{days}天约{weeks}周零{remaining_days}天 """if include_weekends: weekend_count =sum(1for i inrange(days)if (start + timedelta(days=i)).weekday() >=5) weekday_count = days - weekend_count result +=f"工作日:{weekday_count}天\n周末:{weekend_count}天"return result.strip()exceptValueErroras e:returnf"日期格式错误:{str(e)}"# ========================================# 创建Agent# ========================================def create_pydantic_agent():"""创建使用Pydantic工具定义的Agent """print("�� 正在创建Pydantic工具Agent...") llm = OpenAI(temperature=0) tools = [ get_weather, advanced_calculator, calculate_date_range ]print(f"已注册 {len(tools)}个Pydantic工具")for t in tools:print(f" - {t.name}") agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, handle_parsing_errors=True )print("✅ Pydantic工具Agent创建成功!\n")return agentdef demo_pydantic_tools(agent):"""演示Pydantic工具 """ test_cases = [ ("天气查询", "北京今天天气怎么样?需要带伞吗?"), ("天气预报", "上海未来三天天气如何?"), ("基础计算", "计算 256 * 256"), ("高级计算", "计算 2 的 20 次方结果"), ("数学函数", "计算根号2的平方是多少"), ("日期计算", "2024-01-01到2024-01-31有多少工作日?") ]for title, query in test_cases:print(f"\n{'='*60}")print(f"�� {title}: {query}")print('='*60) result = agent.run(query)print(f"\n✅ 结果: {result}")def main():"""主函数"""print("""╔════════════════════════════════════════════════════════════╗║ ║║ �� LangChain 工具实战:Pydantic工具定义 ║║ ║║ 学习使用Pydantic模型定义复杂工具输入 ║║ ║╚════════════════════════════════════════════════════════════╝ """) agent = create_pydantic_agent() demo_pydantic_tools(agent)if__name__=="__main__": main()
集成外部API工具
在实际应用中,我们经常需要调用外部API来获取数据。下面展示如何创建调用外部API的工具。
调用天气API示例
创建04_api_tools.py:
"""LangChain 工具实战:外部API工具本程序展示如何创建调用外部API的工具"""import osimport requestsfrom dotenv import load_dotenvfrom langchain.agents import AgentType, initialize_agentfrom langchain_openai import OpenAIfrom langchain_core.tools import tool# 加载环境变量load_dotenv()# ========================================# 外部API工具# ========================================@tooldef get_exchange_rate(from_currency: str, to_currency: str) ->str:"""获取货币汇率 Args: from_currency: 源货币代码,如 USD, EUR, GBP to_currency: 目标货币代码,如 CNY, JPY Returns:汇率转换结果 """try:# 使用免费的汇率API# 实际项目中应使用自己的API密钥 response = requests.get(f"https://api.exchangerate-api.com/v4/latest/{from_currency}", timeout=10 )if response.status_code ==200: data = response.json() rate = data["rates"].get(to_currency)if rate:returnf"1 {from_currency} = {rate:.4f}{to_currency}"else:returnf"不支持的货币代码:{to_currency}"else:returnf"API请求失败:{response.status_code}"except requests.RequestException as e:returnf"网络请求错误:{str(e)}"@tooldef get_news_headlines(category: str="general", country: str="cn") ->str:"""获取新闻头条 Args: category: 新闻类别,可选 general, business, technology, sports 等 country: 国家代码,cn=中国,us=美国 Returns:最新新闻标题列表 """try:# 这里使用模拟数据,实际项目中应调用真实新闻API news_data = {"cn": {"general": ["① 国内要闻:各地经济持续复苏","② 社会关注:养老服务体系建设加速","③ 国际关系:外交活动频繁" ],"technology": ["① 科技创新:人工智能应用取得新突破","② 产业发展:新能源行业快速发展","③ 数字经济:数字化转型深入推进" ] } } headlines = news_data.get(country, {}).get(category, [])if headlines:return"�� 最新"+ category +"新闻:\n"+"\n".join(headlines)else:return"暂无可用的新闻数据"exceptExceptionas e:returnf"获取新闻失败:{str(e)}"@tooldef search_products(keyword: str, max_results: int=5) ->str:"""搜索商品信息 Args: keyword: 搜索关键词 max_results: 最大返回结果数,默认5条 Returns:商品搜索结果 """# 模拟商品数据 products = {"手机": [ {"name": "iPhone 15", "price": "5999元", "rating": "4.8"}, {"name": "华为Mate 60", "price": "5499元", "rating": "4.7"}, {"name": "小米14", "price": "3999元", "rating": "4.6"} ],"电脑": [ {"name": "MacBook Pro", "price": "12999元", "rating": "4.9"}, {"name": "ThinkPad X1", "price": "9999元", "rating": "4.7"}, {"name": "戴尔XPS", "price": "8999元", "rating": "4.6"} ] } results = products.get(keyword, [])if results: formatted = []for i, p inenumerate(results[:max_results], 1): formatted.append(f"{i}. {p['name']} - 价格:{p['price']} - 评分:{p['rating']}" )return"�� "+ keyword +"搜索结果:\n"+"\n".join(formatted)else:returnf"未找到'{keyword}'相关的商品"# ========================================# 创建Agent# ========================================def create_api_agent():"""创建API工具Agent """print("�� 正在创建API工具Agent...") llm = OpenAI(temperature=0.3) tools = [ get_exchange_rate, get_news_headlines, search_products ]print(f"已注册 {len(tools)}个API工具") agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, handle_parsing_errors=True )print("✅ API工具Agent创建成功!\n")return agentdef demo_api_tools(agent):"""演示API工具 """ test_cases = [ ("货币汇率", "1美元兑换多少人民币?"), ("货币汇率", "100欧元等于多少英镑?"), ("新闻查询", "给我看看今天的科技新闻"), ("商品搜索", "帮我搜索手机商品"), ("综合查询", "iPhone 15和华为Mate 60哪个更好?") ]for title, query in test_cases:print(f"\n{'='*60}")print(f"�� {title}: {query}")print('='*60) result = agent.run(query)print(f"\n✅ 结果: {result}")def main():"""主函数"""print("""╔════════════════════════════════════════════════════════════╗║ ║║ �� LangChain 工具实战:外部API工具 ║║ ║║ 学习如何创建调用外部API的工具 ║║ ║╚════════════════════════════════════════════════════════════╝ """) agent = create_api_agent() demo_api_tools(agent)if__name__=="__main__": main()
工具定义最佳实践
1. 清晰准确的描述
工具的描述(description)是Agent选择工具的关键依据,描述应该: - 明确说明工具的功能 - 列举适用的场景 - 说明输入输出的格式
@tooldef search_movie(title: str) ->str:"""搜索电影信息用途: - 查询电影的基本信息(导演、演员、年份) - 获取电影评分和评价 - 了解电影的剧情简介输入:电影名称(支持中文和英文)输出:电影详细信息示例: - 输入:"流浪地球" - 输出:导演、演员、上映时间、评分等信息 """pass
2. 完善的参数验证
使用Pydantic模型进行参数验证:
from pydantic import BaseModel, Field, validatorclass SearchInput(BaseModel): query: str= Field(description="搜索关键词", min_length=1) limit: int= Field(description="返回结果数量", ge=1, le=50)@validator('query')def query_not_empty(cls, v):ifnot v.strip():raiseValueError("搜索关键词不能为空")return v.strip()
3. 详细的错误处理
每个工具都应该有完善的错误处理:
@tooldef divide_numbers(a: float, b: float) ->str:"""执行除法运算"""try:if b ==0:return"错误:除数不能为0" result = a / breturnf"{a}÷ {b} = {result}"exceptTypeError:return"错误:参数必须是数字类型"exceptExceptionas e:returnf"未知错误:{str(e)}"
4. 适当的工具粒度
每个工具应该专注于单一功能: - ✅ 好的设计:get_weather, search_movies, send_email - ❌ 不好的设计:manage_everything, do_tasks
常见问题与解决方案
问题1:Agent不使用工具
问题描述:Agent直接回答问题,不调用工具。
解决方案: 1. 检查工具描述是否清晰 2. 在用户问题中明确提示需要使用工具 3. 使用few-shot示例教Agent何时使用工具
prompt ="""你是一个智能助手。当用户询问实时信息时,你应该使用工具获取最新数据。示例:用户:北京今天天气怎么样?你:get_weather(location="北京")
问题2:工具调用参数错误
问题描述:Agent传递了错误的参数给工具。
解决方案: 1. 使用Pydantic模型明确定义参数 2. 设置handle_parsing_errors=True 3. 在工具描述中提供参数示例
问题3:工具执行超时
问题描述:外部API调用时间过长。
解决方案:
@tooldef slow_api_call() ->str:"""工具描述"""try: response = requests.get(url, timeout=30) # 设置超时return response.textexcept requests.Timeout:return"请求超时,请稍后重试"
总结与下节预告
本节课,我们深入学习了工具的调用与定义:
1.工具基本概念:理解了Tool的结构和属性
2.内置工具:学习了如何使用LangChain提供的内置工具
3.自定义工具:掌握了使用@tool装饰器定义工具的方法
4.Pydantic工具:学会了使用Pydantic模型定义复杂工具输入
5.API工具:了解了如何创建调用外部API的工具
6.最佳实践:学习了一些工具定义的最佳实践
下一节课,我们将学习Agent的记忆系统,这是实现真正智能对话的关键。我们将学习: - 记忆的类型和特点 - 如何实现短期记忆和长期记忆 - 如何使用向量存储实现语义记忆 - 构建具有持续记忆能力的Agent
敬请期待!
练习题
1.实践题:创建一个能够查询火车票信息的工具
2.思考题:分析自定义工具和内置工具各自的优势
3.拓展题:研究如何让多个工具协同工作
4.挑战题:构建一个完整的”个人助手”工具集
夜雨聆风