LangChain ツールの高度な機能
前のセクションでは @tool の基本的な使い方を学びました。このセクションでは、ツールの高度な機能: return_direct、InjectedToolCallId、ToolException、およびエラー処理について紹介します。
return_direct — 最終結果を直接返す
デフォルトでは、ツール実行後、結果はモデルに返され、モデルはそのツール結果に基づいて最終応答を生成します。しかし、ツール結果自体が求めている最終回答である場合もあります。
設定return_direct=True後、ツールの実行が完了するとすぐに Agent ループが終了し、ツールが返した内容がそのまま最終出力になります。
例
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:
"""サンプル のコースを検索(通常モード)"""
return f"検索結果: Python3 基礎チュートリアル、Python データ分析、Python クローラー入門"
# return_direct ツール: 結果を直接最終出力とする
@tool(return_direct=True)
def search_direct(keyword: str) -> str:
"""サンプル のコースを検索(直接返却モード)。
ユーザーが検索結果だけを必要とし、追加の分析が不要な場合にこのツールを使用します。
"""
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 基础教程 - 适合零基础入门 2. Python 数据分析 - 进阶学习 3. Python 爬虫入门 - 实战项目 === 直接返回模式(工具结果即最终答案)=== 搜索结果:Python3 基础教程、Python 数据分析、Python 爬虫入门
| モード | ツール実行後 | 適用シナリオ |
|---|---|---|
| return_direct=False(デフォルト) | モデルがツール結果を受け取る → モデルが考え続ける → 最終応答を生成 | 分析・要約・さらなる意思決定が必要 |
| return_direct=True | ツール実行後すぐに終了 → ツール結果が最終出力 | クエリ型、データ取得型、フォーマット済みの結果 |
return_direct=True を設定すると、Agent は後続のモデル思考ステップをスキップして、ツール結果を直接返します。これは Token の節約とレイテンシーの低減に非常に価値がありますが、モデルがツール結果に対して二次加工を行わないことを意味します。
注意:Agent に複数のツールが同時にマウントされ、その中に return_direct=True のツールと通常のツールの両方がある場合、モデルがそのラウンドでいずれかの return_direct ツールを呼び出すと、Agent ループは即座に終了します。同じラウンドで他の通常ツールが並行して呼び出されたとしても、その結果はモデルによって加工・要約されません。複数のツールを含む Agent を設計する際は、「要約されるべき内容がスキップされる」ことを避けるために注意してください。
InjectedToolCallId — ツール呼び出し ID を取得する
ツールが「誰がそれを呼び出したか」を知る必要がある場合があります——InjectedToolCallIdツール関数に現在の tool_call_id を注入できます:
例
from langchain.messages import ToolCall
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}"
# モデルが生成した ToolCall を手動でシミュレートする
tool_call: ToolCall = {
"name": "log_user_action",
"args": {
"action": "ユーザーが Python コースを照会しました"
},
"id": "call_log_001",
"type": "tool_call",
}
# InjectedToolCallId は tool_call.id から自動注入されます
result = log_user_action.invoke(tool_call)
print(result)
実行結果:
content='操作已记录 (调用ID: call_log_001): 用户查询了 Python 课程' name='log_user_action' tool_call_id='call_log_001'
マーク付きのInjectedToolArgパラメータは Agent(モデル)が提供する必要はありません。これらのパラメータは、LangChain ランタイムがツール実行時に自動的に注入します。 これらのパラメータはツールのスキーマに表示されないため、モデルはそれらを認識できません。したがって、ユーザーが入力する必要のあるツールパラメータとして説明するべきではありません。
例えばInjectedToolCallIdは特殊な注入パラメータです。LangChain はツール実行時に現在の ToolCall の一意の ID を自動的に渡します。この ID はモデルが生成したパラメータには現れませんが、現在のツール呼び出しに自動的にバインドされます。
直接呼び出して.invoke()手動で構築しToolCall、これは LangChain がパラメータ注入メカニズムをどのように行うかをデモするためだけです。実際のプロジェクトでは、通常この ID を手動で渡すことはありません。
Agent ワークフローでは、InjectedToolCallId現在の呼び出しコンテキストを関連付ける必要があるシナリオでより一般的に使用されます。例えば、Commandオブジェクトを使用してツール内部で Agent 状態を更新し、messagesリストに現在の呼び出しに関連するToolMessageを追加したり、ツール呼び出しの追跡、監査、ログ関連付けなどの機能を実装したりします。
これが、このパラメータが通常のパラメータではなく「注入パラメータ」として設計されている理由です。つまり、LangChain Runtime が現在実行しているこの ToolCall コンテキストを表しており、ユーザー入力の一部ではありません。
ToolException — ツール例外処理
ツール実行中にエラーが発生する可能性があります。ToolException明確なツール例外をスローして、Agent に問題が発生したことを知らせます。
例
@tool
def get_user_info(user_id: int) -> str:
"""ユーザー ID に基づいてユーザー情報を照会します。
Args:
user_id: ユーザー ID。正の整数である必要があります
"""
# データ検証
if user_id <= 0:
# 通常の Exception ではなく ToolException をスローする
# 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 的用户
ここで .invoke() の外側で try/except を使って ToolException を捕捉できるのは、ツールのデフォルトがhandle_tool_errorFalse であるためです。つまり、例外はツール自身に飲み込まれず、通常どおり上位にスローされます。ツールに「自分でエラーを処理させ」、エラーメッセージを文字列に変換してモデルに返したい場合は、次のセクションで説明する handle_tool_error を使用します。
handle_tool_error — ツールにエラーを自分で処理させる
特定のツールでエラーが発生したときにプログラムを中断させず、エラーメッセージをテキストに変換して通常の戻り値としてモデルに渡し、モデル自身に理解・修正させる場合は、ツールを定義するときにhandle_tool_error(単数形であることに注意。s は付きません)。これは BaseTool のプロパティで、最も簡単な設定方法は @tool デコレータに直接書くことです:
例
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
# handle_tool_error=True: エラー時は例外をスローせず、
# ToolException の内容を文字列に変換して、通常どおりモデルに返す
@tool(handle_tool_error=True)
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]}"
# テスト:誤った都市名で呼び出す
# handle_tool_error=True の場合、エラー情報は正常な結果として返され、例外はスローされません。
result = get_weather.invoke({"city": "北境"})
print(f"handle_tool_error=True: {result}")
# すべてのツールのエラーを Agent で一括処理したい場合(ツールごとに設定するのではなく)、
# create_agent 内部で使用される ToolNode レベルで設定できます。
# 新しいバージョンの langchain では、Agent 全体のエラーフォールバックを次のように有効にできます:
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[get_weather],
system_prompt="あなたは天気照会アシスタントです。",
)
result = agent.invoke({
"messages": [HumanMessage(content="北境の天気を調べる")]
})
print("\n=== Agent は handle_tool_error で変換されたエラー情報を受け取り、それに基づいてユーザーに説明します ===)
print(result["messages"][-1].content[:200])
実行結果:
handle_tool_error=True: 未收录城市 '北境'。可使用城市:杭州, 北京, 上海。请使用中文城市全称。 === Agent 收到 handle_tool_error 转换后的错误信息,并据此向用户解释 === 抱歉,我没有查询到"北境"的天气数据。目前支持查询的城市有:杭州、北京、上海。 请问您是想查询其中哪一个城市呢?
| handle_tool_error | 動作 | 適用シナリオ |
|---|---|---|
| False(デフォルト) | ToolException は通常通り上位にスローされ、呼び出し側で自分自身で try/except する必要があります。 | 回復不能なエラーで、フローを中断する必要がある場合 |
| True | ToolException を捕捉し、その内容をツールの正常な戻り値としてモデルに渡します。 | Agent にエラー情報を読ませて修正/再試行させたい場合 |
| str | 例外を捕捉した後、指定された固定文字列でエラー情報を置き換えます。 | 具体的なエラー詳細を公開したくない場合、統一メッセージだけを渡す |
| Callable[[ToolException], str] | 例外を捕捉した後、カスタム関数で例外を処理し、戻り内容を生成します。 | 例外の種類/内容に応じて異なるメッセージをカスタマイズする必要がある場合 |
注意:使わないでくださいtool.with_config(handle_tool_errors=True)この書き方——with_config()これは Runnable 共通の実行時設定メソッドであり、callbacks、tags、metadata などの設定にのみ使用され、ツールのエラー処理動作を実際に変更するものではありません。単一のツールのエラー処理を制御するには、@tool(handle_tool_error=...)(単数)を使用します。Agent/グラフ(Graph)レベルで全てのツールに統一したエラー処理戦略を設定したい場合は、個々のツールオブジェクトではなく、基盤となる ToolNode で handle_tool_errors(複数形)を設定します。両者はフィールド名が似ていますが、作用する階層が異なるため、使用時は区別に注意してください。
完全な例 — エラー処理付き Agent
例
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool(handle_tool_error=True)
def book_course(user_name: str, course_name: str) -> str:
"""ユーザーにサンプル のコースを予約します。
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="あなたはサンプル のコースコンサルタントです。",
)
# 正常な呼び出し
result = agent.invoke({
"messages": [HumanMessage(content="张三のために Python3 基礎チュートリアルを予約する")]
})
print(f"成功: {result['messages'][-1].content[:100]}")
# エラー呼び出し:ユーザーが存在しない
result = agent.invoke({
"messages": [HumanMessage(content="赵六のために Python3 基礎チュートリアルを予約する")]
})
print(f"\nエラー-ユーザーが存在しません:)
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]}")
実行結果:
成功: 已经为您成功预订《Python3 基础教程》,张三同学,祝您学习愉快! 错误-用户不存在: [tool] 用户 '赵六' 不存在。有效用户:张三, 李四, 王五 [ai] 抱歉,系统中没有找到名为"赵六"的用户,暂时无法为您完成预订。 目前可预订的用户有:张三、李四、王五。请确认姓名后重新尝试。
その他の拡張見てわかるように、ツールがhandle_tool_error=Truehandle_tool_error を設定した後、ToolException の内容は ToolMessage の形で通常会話履歴に表示され(上記出力の [tool] の行)、プログラムをクラッシュさせません。モデルはこのエラー情報を見た後、自然言語でユーザーに伝え、利用可能な代替案を提示します。これは return_direct、InjectedToolCallId、ToolException、handle_tool_error を組み合わせて使用したときの典型的な効果です。ツール呼び出しの堅牢性を保証しつつ、ユーザーエクスペリエンスを犠牲にしません。