LangChain 工具高级特性

本节介绍工具的高级特性:return_direct、InjectedToolCallId、ToolException 和错误处理。

return_direct——直接返回最终结果

默认情况下,工具执行后结果会返回给模型,模型再基于工具结果生成最终回复。但有时工具结果本身就是你想要的最终答案。

设置 return_direct=True 后,工具执行完就立即结束 Agent 循环,工具返回内容直接作为最终输出。

from dotenv import load_dotenv
load_dotenv()

from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage


# 普通工具:结果返回给模型,模型再做总结
@tool
def search_normal(keyword: str) -> str:
    """搜索菜鸟教程 RUNOOB 的课程(普通模式)"""
    return f"搜索结果:Python3 基础教程、Python 数据分析、Python 爬虫入门"


# return_direct 工具:结果直接作为最终输出
@tool(return_direct=True)
def search_direct(keyword: str) -> str:
    """搜索菜鸟教程 RUNOOB 的课程(直接返回模式)。

    当用户只需要搜索结果,不需要额外分析时使用此工具。
    """
    return f"搜索结果:Python3 基础教程、Python 数据分析、Python 爬虫入门"


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)

# 对比:普通模式 vs 直接返回模式
agent_normal = create_agent(
    model=model,
    tools=[search_normal],
    system_prompt="你是菜鸟教程的学习顾问。",
)

agent_direct = create_agent(
    model=model,
    tools=[search_direct],
    system_prompt="你是菜鸟教程的学习顾问。",
)

# 普通模式:模型会基于搜索结果再生成一段总结
result = agent_normal.invoke({
    "messages": [HumanMessage(content="搜索 Python 课程")]
})
print("=== 普通模式(模型会再加工)===")
print(result["messages"][-1].content[:150])

# 直接返回模式:工具结果就是最终答案
result = agent_direct.invoke({
    "messages": [HumanMessage(content="搜索 Python 课程")]
})
print("\n=== 直接返回模式(工具结果即最终答案)===")
print(result["messages"][-1].content[:150])

运行结果:

=== 普通模式(模型会再加工)===
菜鸟教程为您提供了以下 **Python 相关课程**,让我们来逐一了解:

---

## 🐍 1. Python3 基础教程
- **适合人群**:零基础入门,想系统学习 Python 的初学者
- **内容涵盖**:
  - Python 环境搭建
  - 基本语法、数据类型、运算符
  -

=== 直接返回模式(工具结果即最终答案)===
搜索结果:Python3 基础教程、Python 数据分析、Python 爬虫入门
模式 工具执行后 适用场景
return_direct=False(默认) 模型收到工具结果 → 模型继续思考 → 生成最终回复 需要分析/总结/进一步决策
return_direct=True 工具执行后立即结束 → 工具结果就是最终输出 查询类、数据获取类、已格式化好的结果

当你设置 return_direct=True 时,Agent 会跳过后续的模型思考步骤,直接返回工具结果。这在节省 Token 和降低时延方面非常有价值,但也意味着模型不会对工具结果做任何二次加工。

InjectedToolCallId——获取工具调用 ID

有时工具需要知道”是谁调用了它”——InjectedToolCallId 可以在工具函数中注入当前的 tool_call_id:

from dotenv import load_dotenv
load_dotenv()
from typing import Annotated
from langchain.tools import tool, InjectedToolCallId


@tool
def log_user_action(
    action: str,
    tool_call_id: Annotated[str, InjectedToolCallId],
) -> str:
    """记录用户操作到日志系统。

    Args:
        action: 用户操作描述
        tool_call_id: 系统自动注入的工具调用 ID
    """
    # 实际项目中这里会写入数据库或发送到日志服务
    return f"操作已记录 (调用ID: {tool_call_id}): {action}"


# InjectedToolCallId 参数会被自动注入,不需要手动传入
result = log_user_action.invoke({
    "name": "log_user_action",
    "type": "tool_call",
    "args": {"action": "用户查询了 Python 课程"},
    "id": "manual_call_1"
})
print(result)

执行结果:

content='操作已记录 (调用ID: manual_call_1): 用户查询了 Python 课程' name='log_user_action' tool_call_id='manual_call_1'

带有 InjectedToolArg 标记的参数不需要 Agent(模型)来提供,它们由 LangChain 运行框架自动注入。在工具函数的文档字符串中不需要描述这些注入参数,因为 Agent 不会看到它们。

ToolException——工具异常处理

工具执行过程中可能会出错。使用 ToolException 抛出明确的工具异常,让 Agent 知道出了问题。

from langchain.tools import tool, ToolException
from dotenv import load_dotenv
load_dotenv()


@tool
def get_user_info(user_id: int) -> str:
    """根据用户 ID 查询用户信息。

    Args:
        user_id: 用户 ID,必须是正整数
    """
    # 数据校验
    if user_id <= 0:
        # 抛出 ToolException,而不是普通 Exception
        # ToolException 会被 Agent 捕获并告知模型
        raise ToolException(f"用户 ID 必须为正整数,收到了: {user_id}")

    # 模拟数据库查询
    users = {
        1: "张三(VIP 会员,注册于 2024-01-15)",
        2: "李四(普通用户,注册于 2024-03-20)",
    }

    if user_id not in users:
        raise ToolException(f"未找到 ID 为 {user_id} 的用户")

    return users[user_id]


# 正常调用
print(get_user_info.invoke({"user_id": 1}))

# 异常调用 1:无效 ID
try:
    get_user_info.invoke({"user_id": -1})
except ToolException as e:
    print(f"工具异常: {e}")

# 异常调用 2:用户不存在
try:
    get_user_info.invoke({"user_id": 999})
except ToolException as e:
    print(f"工具异常: {e}")

运行结果:

张三(VIP 会员,注册于 2024-01-15)
工具异常: 用户 ID 必须为正整数,收到了: -1
工具异常: 未找到 ID 为 999 的用户

handle_tool_errors——让 Agent 自动处理工具错误

当允许模型处理工具错误时(通过 ToolNode 配置),模型可以尝试修正参数后重新调用:

from langchain.tools import tool, ToolException
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage


@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气。

    Args:
        city: 城市名称,必须是中文全称,如 "杭州"、"北京"
    """
    weather_data = {
        "杭州": "晴,25°C",
        "北京": "多云,18°C",
        "上海": "小雨,22°C",
    }
    if city not in weather_data:
        # 城市不在数据中时抛出 ToolException
        raise ToolException(
            f"未收录城市 '{city}'。"
            f"可使用城市:{', '.join(weather_data.keys())}。"
            f"请使用中文城市全称。"
        )
    return f"{city}天气:{weather_data[city]}"


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)

# 在不使用 Agent 时,也可以直接用 handle_tool_errors 控制行为
# 方式 1:返回错误信息字符串(模型能看到并修正)
tool_with_feedback = get_weather.with_config(
    handle_tool_errors=True  # 捕获异常并返回错误信息给模型
)

# 方式 2:直接抛出异常(Agent 终止)
tool_without_feedback = get_weather.with_config(
    handle_tool_errors=False  # 异常直接上抛
)

# 测试:用错误城市名调用
# handle_tool_errors=True 时,错误信息会返回给模型
result = tool_with_feedback.invoke({"city": "北境"})
print(f"handle_tool_errors=True: {result}")

运行结果:

handle_tool_errors=True: 未收录城市 '北境'。可使用城市:杭州, 北京, 上海。请使用中文城市全称。
handle_tool_errors 行为 适用场景
True 捕获所有异常,将错误信息作为 ToolMessage 返回给模型 希望 Agent 自行修正错误
False 异常直接上抛,Agent 执行中断 不可恢复的错误
str 捕获异常后用指定字符串替换错误信息 自定义错误提示
tuple[type] 只捕获指定类型的异常 只处理特定异常

完整示例——带错误处理的 Agent

from langchain.tools import tool, ToolException
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv()

@tool
def book_course(user_name: str, course_name: str) -> str:
    """为用户预订菜鸟教程 RUNOOB 的课程。

    Args:
        user_name: 用户姓名
        course_name: 课程名称
    """
    # 校验用户是否存在
    valid_users = {"张三", "李四", "王五"}
    if user_name not in valid_users:
        raise ToolException(
            f"用户 '{user_name}' 不存在。"
            f"有效用户:{', '.join(sorted(valid_users))}"
        )

    # 校验课程是否存在
    valid_courses = {"Python3 基础教程", "HTML 基础教程", "Java 面向对象"}
    if course_name not in valid_courses:
        raise ToolException(
            f"课程 '{course_name}' 不存在。"
            f"有效课程:{', '.join(sorted(valid_courses))}"
        )

    return f"已为 {user_name} 成功预订《{course_name}》"


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    tools=[book_course],
    system_prompt="你是菜鸟教程 RUNOOB 的课程顾问。",
)

# 正常调用
result = agent.invoke({
    "messages": [HumanMessage(content="帮张三预订 Python3 基础教程")]
})
print(f"成功: {result['messages'][-1].content[:100]}")

# 错误调用:用户不存在
print(f"\n错误-用户不存在:")
try:
    result = agent.invoke({
        "messages": [HumanMessage(content="帮赵六预订 Python3 基础教程")]
    })
    for msg in result["messages"]:
        if msg.type == "tool":
            print(f"  [{msg.type}] {msg.content[:80]}")
        elif msg.type == "ai" and msg.content:
            print(f"  [{msg.type}] {msg.content[:100]}")
except ToolException as e:
    print(f"  [tool_error] {e}")

执行结果:

成功: ✅ **预订成功!** 以下是详细信息:

- **学员姓名:** 张三
- **课程名称:** 🐍《Python3 基础教程》
- **预订状态:** ✅ 已成功预订

恭喜张三同学!请按时参加课程

错误-用户不存在:
  [tool_error] 用户 '赵六' 不存在。有效用户:张三, 李四, 王五
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇