在LangChain Agent开发中,工具是调用外部能力、完成实操任务的核心。很多同学初学都会困惑:官方提供了多种工具定义写法,分不清区别、选不对场景,还容易踩版本和语法坑。
我结合实战整理了五种主流、最新无报错的工具定义方式,统一用「美食探店助手」场景演示,覆盖日常开发、原型调试、生产落地全场景。内容通俗易懂、可直接复用,个人总结有限,有疏漏欢迎大家评论指正、交流学习~
统一安装依赖,规避版本报错:
uv add langchain langchain-core langchain-openai pydantic python-dotenv
一、@tool 基础装饰器(最简入门)
LangChain 最轻量化的工具写法,普通函数加装饰器即可转为Agent工具,适合快速开发简单功能、迭代原型。
from langchain_core.tools import tool
@tool
def get_restaurant_avg_price(restaurant_name: str) -> str:
"""查询餐厅人均消费。
Args:
restaurant_name: 餐厅名字
"""
# 模拟数据库查询
mock_data = {
"潮汕牛肉火锅": "人均 95元",
"日料小馆": "人均 180元",
"湘菜馆": "人均 75元"
}
price = mock_data.get(restaurant_name, "暂无该餐厅价格数据")
return f"【{restaurant_name}】{price}"
# 测试
print(get_restaurant_avg_price.invoke({"restaurant_name": "潮汕牛肉火锅"}))
适用场景:单逻辑、无复杂参数的简易工具,快速原型开发。
二、@tool + parse_docstring(用得相对较少)
基础装饰器进阶用法,开启参数可自动解析Google风格注释,无需手动写Schema,大幅精简代码。避坑重点:该模式对注释格式严格,简介与Args需空行、参数冒号后需加空格,格式错误会直接报错。
from typing import Optional
from langchain_core.tools import tool
@tool(parse_docstring=True)
def calc_food_calorie(dish_name: str, weight_g: Optional[int] = 100) -> str:
"""估算菜品卡路里。
Args:
dish_name: 菜品名称,例如红烧肉、白米饭
weight_g: 食物重量,单位克,默认100克
"""
calorie_map = {"红烧肉": 395, "白米饭": 116, "炒青菜": 45}
per_100g = calorie_map.get(dish_name, 100)
total = per_100g * weight_g / 100
return f"{dish_name} {weight_g}g 总热量:{total:.1f} 千卡"
if __name__ == "__main__":
print(calc_food_calorie.invoke({"dish_name": "红烧肉", "weight_g": 200}))
适用场景:多参数工具,追求代码简洁、规范统一的场景。
三、StructuredTool(存量函数复用)
无需修改已有业务函数,零侵入将普通函数封装为Agent工具,兼容同步、异步方法,非常适合老项目改造、存量代码复用。
from langchain_core.tools import StructuredTool
def split_aa_bill(total_money: float, person_count: int, tip: float = 0) -> str:
"""多人AA分摊账单,包含小费。
Args:
total_money: 总账单金额
person_count: 聚餐人数
tip: 小费金额,默认0
"""
total = total_money + tip
per_person = total / person_count
return f"总账单{total}元,{person_count}人AA,每人应付:{per_person:.2f}元"
# 封装成StructuredTool
aa_tool = StructuredTool.from_function(split_aa_bill)
# 测试
print(aa_tool.invoke({"total_money": 500, "person_count": 4, "tip": 50}))
适用场景:复用成熟业务函数、不想侵入原有业务逻辑。
四、继承 BaseTool(生产环境首选)
五种写法中扩展性、稳定性最强,支持自定义参数校验、异常捕获、工具状态,是生产环境复杂工具的最优方案,唯一缺点是代码量稍多。
from typing import Optional
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
# 定义入参Schema
class RecommendDishInput(BaseModel):
restaurant_type: str = Field(description="餐厅类型,例如火锅、湘菜、日料")
spicy_prefer: Optional[str] = Field(default="不辣", description="辣度偏好:不辣/微辣/特辣")
class DishRecommendTool(BaseTool):
name: str = "recommend_dish"
description: str = "根据餐厅类型和辣度推荐招牌菜"
args_schema: type[BaseModel] = RecommendDishInput
def _run(self, restaurant_type: str, spicy_prefer: str = "不辣") -> str:
menu_map = {
"火锅": {"不辣": "骨汤锅底+肥牛", "微辣": "鸳鸯锅+毛肚", "特辣": "红汤牛油锅+鸭肠"},
"湘菜": {"不辣": "小炒黄牛肉(减辣)", "微辣": "剁椒鱼头", "特辣": "爆辣口味虾"}
}
dish = menu_map.get(restaurant_type, {}).get(spicy_prefer, "暂无推荐")
return f"【{restaurant_type}】推荐菜品:{dish}"
# 实例化工具
recommend_tool = DishRecommendTool()
# 测试
print(recommend_tool.invoke({"restaurant_type": "湘菜", "spicy_prefer": "微辣"}))
适用场景:生产环境、复杂业务工具、需要强参数校验和异常处理的场景。
五、Runnable 转 Tool(链路复用)
针对已有LangChain Runnable流水线,可直接封装为Agent工具,避免重复造轮子。版本避坑:新版必须使用 args_schema,不可拼写错误,且需自定义入参模型。
from langchain_core.runnables import RunnableLambda
from langchain_core.tools import convert_runnable_to_tool
from pydantic import BaseModel, Field
# 定义输入参数模型
class ReviewInput(BaseModel):
review_text: str = Field(description="用户的餐厅点评文本")
# 原始Runnable:清洗点评文本,提取一句话摘要
review_runnable = RunnableLambda(lambda x: f"【点评摘要】{x['review_text'].split('。')[0]}")
# Runnable转Tool,修正参数名 args_schema
review_summary_tool = convert_runnable_to_tool(
review_runnable,
name="review_summary",
description="提取用户点评第一句话摘要",
args_schema=ReviewInput, # 这里!!改成 args_schema,并且传入我们定义好的ReviewInput
)
# 测试调用
print(review_summary_tool.invoke({"review_text": "这家店牛肉很嫩,上菜速度快。但是排队太久,周末建议提前预约。"}))
适用场景:已有Runnable业务链路,需要快速接入Agent复用逻辑。
六、五种工具写法选型对照表
极简总结,日常开发直接按需选用:
| 工具定义方式 | 核心优势 | 适用场景 |
|---|---|---|
| @tool 基础装饰器 | 极简零配置、上手快 | 简单工具、快速原型开发 |
| @tool 智能解析 | 自动解析注释、精简代码 | 多参数、规范型工具 |
| StructuredTool | 零侵入、复用存量代码 | 老项目改造、旧函数复用 |
| BaseTool 继承类 | 高自定义、稳定可扩展 | 生产环境、复杂业务工具 |
| Runnable 转 Tool | 复用流水线、避免冗余 | 已有Runnable链路快速接入 |
七、核心实操避坑总结
-
开发选型:简易工具用装饰器,生产复杂工具优先
BaseTool,兼顾稳定性和扩展性。 -
注释报错解决:不想严格遵循Google注释格式,可添加
error_on_invalid_docstring=False关闭强制校验。 -
版本适配:废弃
create_tool_calling_agent、create_react_agent,统一使用官方最新create_agent。 -
新版参数坑:Runnable转工具必须写复数
args_schema,拼写错误直接报错。
以上就是LangChain五大工具定义的方式,都是踩坑后的总结,可直接落地复用。如果有错误或更好的实践,欢迎大家评论交流,一起学习进步!
八、完整实战:全部工具接入新版Agent
前面我们单独演示了五种工具的定义方式,这里将所有工具统一整合,接入LangChain官方最新稳定的 create_agent 实现完整Agent调用流程。
规避版本坑:彻底废弃老旧且已淘汰的 create_tool_calling_agent、create_react_agent,代码适配最新版LangChain,同时兼容阿里通义千问等国内大模型,开箱即用。
环境配置
项目根目录新建 .env 文件,配置模型密钥,避免硬编码泄露:
DASHSCOPE_API_KEY=你的通义千问密钥
DASHSCOPE_MODEL=qwen3.7-flash-2026-07-15
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
完整整合代码
import os
from dotenv import load_dotenv
from typing import Optional
from langchain_core.tools import tool, StructuredTool, BaseTool, convert_runnable_to_tool
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
# 加载本地环境变量
load_dotenv()
# 工具1:@tool 基础装饰器
@tool
def get_restaurant_avg_price(restaurant_name: str) -> str:
"""查询餐厅人均消费。
Args:
restaurant_name: 餐厅名字
"""
mock_data = {"潮汕牛肉火锅": "人均 95元", "日料小馆": "人均 180元", "湘菜馆": "人均 75元"}
price = mock_data.get(restaurant_name, "暂无该餐厅价格数据")
return f"【{restaurant_name}】{price}"
# 工具2:@tool + parse_docstring
@tool(parse_docstring=True)
def calc_food_calorie(dish_name: str, weight_g: Optional[int] = 100) -> str:
"""估算菜品卡路里。
Args:
dish_name: 菜品名称,例如红烧肉、白米饭
weight_g: 食物重量,单位克,默认100克
"""
calorie_map = {"红烧肉": 395, "白米饭": 116, "炒青菜": 45}
per_100g = calorie_map.get(dish_name, 100)
total = per_100g * weight_g / 100
return f"{dish_name} {weight_g}g 总热量:{total:.1f} 千卡"
# 工具3:StructuredTool
def split_aa_bill(total_money: float, person_count: int, tip: float = 0) -> str:
"""多人AA分摊账单,包含小费。
Args:
total_money: 总账单金额
person_count: 聚餐人数
tip: 小费金额,默认0
"""
total = total_money + tip
per_person = total / person_count
return f"总账单{total}元,{person_count}人AA,每人应付:{per_person:.2f}元"
aa_tool = StructuredTool.from_function(split_aa_bill)
# 工具4:继承 BaseTool
class RecommendDishInput(BaseModel):
restaurant_type: str = Field(description="餐厅类型,例如火锅、湘菜、日料")
spicy_prefer: Optional[str] = Field(default="不辣", description="辣度偏好:不辣/微辣/特辣")
class DishRecommendTool(BaseTool):
name: str = "recommend_dish"
description: str = "根据餐厅类型和辣度推荐招牌菜"
args_schema: type[BaseModel] = RecommendDishInput
def _run(self, restaurant_type: str, spicy_prefer: str = "不辣") -> str:
menu_map = {
"火锅": {"不辣": "骨汤锅底+肥牛", "微辣": "鸳鸯锅+毛肚", "特辣": "红汤牛油锅+鸭肠"},
"湘菜": {"不辣": "小炒黄牛肉(减辣)", "微辣": "剁椒鱼头", "特辣": "爆辣口味虾"}
}
dish = menu_map.get(restaurant_type, {}).get(spicy_prefer, "暂无推荐")
return f"【{restaurant_type}】推荐菜品:{dish}"
recommend_tool = DishRecommendTool()
# 工具5:Runnable 转 Tool
class ReviewInput(BaseModel):
review_text: str = Field(description="用户的餐厅点评文本")
review_runnable = RunnableLambda(lambda x: f"【点评摘要】{x['review_text'].split('。')[0]}")
review_summary_tool = convert_runnable_to_tool(
review_runnable,
name="review_summary",
description="提取用户点评核心摘要",
args_schema=ReviewInput,
)
# Agent 组装与运行
if __name__ == "__main__":
# 初始化大模型
llm = ChatOpenAI(
model=os.getenv('DASHSCOPE_MODEL'),
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv('DASHSCOPE_BASE_URL')
)
# 注册全部工具
tools = [get_restaurant_avg_price, calc_food_calorie, aa_tool, recommend_tool, review_summary_tool]
# 创建新版Agent
agent = create_agent(
model=llm,
tools=tools,
system_prompt="你是美食探店助手,可调用对应工具完成用户需求,整合工具结果给出完整、清晰的回答。"
)
# 测试对话
user_query = "周末4个人去湘菜馆,总消费500元,加50元小费,推荐微辣菜品并计算AA账单,同时帮我摘要这条点评:这家店牛肉很嫩,上菜速度快。但是排队太久,周末建议提前预约。"
result = agent.invoke({"messages": [("human", user_query)]})
print("n==== 最终回答 ====")
print(result["messages"][-1].content)
运行说明
-
Agent 会智能解析用户需求,自动匹配、调用对应工具,无需手动指定;
-
全程无过时API、无版本报错,可直接用于学习、项目开发。