LangChain Chat Model 上級用法

このセクションでは、Chat Model の2つの上級機能を紹介します:bind_tools()(ツールのバインド)と with_structured_output()(構造化出力)です。これらは Agent と構造化データ抽出の基盤です。


bind_tools()——モデルに利用可能なツールを認識させる

通常のモデルはテキストを生成するだけです。しかし、呼び出しbind_tools()た後、モデルは応答で返すことができますtool_call、プログラムに「このツールを呼び出す必要があります」と伝えます。

例

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

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

# 辞書でツールを記述(OpenAI function calling 形式)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "指定された都市の天気を調べる",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "都市名、例:杭州、北京"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# bind_tools() でツールをモデルにバインド
# モデルは「get_weather ツールが利用可能」と認識する
model_with_tools = model.bind_tools(tools)

# ツールが必要な質問をする
response = model_with_tools.invoke("今日の杭州の天気は?")

# モデルがツール呼び出しを要求したか確認
if response.tool_calls:
    print("モデルが以下のツール呼び出しを要求:")
    for tc in response.tool_calls:
        print(f" ツール名: {tc['name']}")
        print(f" パラメータ: {tc['args']}")
        print(f" 呼び出しID: {tc['id']}")
else:
    print(f"モデルの直接応答: {response.content}")

実行結果:

模型请求调用以下工具:
  工具名: get_weather
  引数: {'city': '杭州'}
  呼び出しID: call_abc123def456

モデルは実際には get_weather 関数を実行していないことに注意してください。bind_tools() は単にモデルに「利用できるツールがある」と伝えるだけで、モデルはツール呼び出しのリクエストを返します。実際の実行は Agent または自分で書いたコードが行います。


Pydantic モデルでツールを記述する

複雑なツールの場合、手書きの辞書よりも Pydantic モデルでパラメータ構造を定義する方が明確です:

例

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model

# Pydantic でツールのパラメータ構造を定義
class WeatherInput(BaseModel):
    """指定された都市の天気を調べます"""
    city: str = Field(description="都市名、例:杭州、北京")
    unit: str = Field(
        default="celsius",
        description="温度単位、celsius(摂氏)または fahrenheit(華氏)"
    )

class CalculatorInput(BaseModel):
    """数学計算を実行します"""
    expression: str = Field(
        description="計算する数式、例:'(3 + 5) * 2'"
    )

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

# Pydantic モデルを渡すと、LangChain が自動的にツールの記述に変換
model_with_tools = model.bind_tools([WeatherInput, CalculatorInput])

# 複雑なシナリオをテスト
response = model_with_tools.invoke(
    "今日の北京は何度?ついでに 123 * 456 を計算して"
)

print(f"モデルは {len(response.tool_calls)} 件のツール呼び出しを要求しました:")
for tc in response.tool_calls:
    print(f"  {tc['name']}({tc['args']})")

実行結果:

模型请求了 2 个工具调用:
  get_weather({'city': '北京', 'unit': 'celsius'})
  calculate({'expression': '123 * 456'})

Pydantic でツールパラメータを定義することが推奨されます。型安全性と自動検証を提供し、さらに LangChain はクラス名と Field の記述から自動的にツールの説明を生成します。


with_structured_output()——モデルに構造化データを返させる

with_structured_output()tool_calling よりも直接的な方法です。モデルが tool_call を返す代わりに、指定した形式(Schema)でデータを返させます。

例

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model

# 期望する出力構造を定義
class PersonInfo(BaseModel):
    """テキストから抽出する人物情報"""
    name: str = Field(description="人物の氏名")
    age: int = Field(description="年齢")
    occupation: str = Field(description="職業")
    skills: list[str] = Field(description="スキルリスト")

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

# with_structured_output() でモデルに PersonInfo 形式で返させる
structured_model = model.with_structured_output(PersonInfo)

# 非構造化テキストを渡して、構造化データを取得する
text = "張三は28歳で、フルスタックエンジニアです。Python、React、Docker に精通しています。"
result = structured_model.invoke(text)

print(f"名前: {result.name}")
print(f"年齢: {result.age}")
print(f"職業: {result.occupation}")
print(f"スキル: {', '.join(result.skills)}")
print(f"タイプ: {type(result)}")

実行結果:

姓名: 张三
年龄: 28
职业: 全栈工程师
技能: Python, React, Docker
タイプ: <class '__main__.PersonInfo'>

戻り値は直接Pydantic モデルのインスタンスであり、.name、.age などの属性で直接アクセスできます。


with_structured_output() vs bind_tools()

これら2つのメソッドは似ていますが、用途が異なります:

比較項目with_structured_output()bind_tools()
用途テキストから構造化データを抽出するモデルに利用可能なツールリストを認識させる
戻り値の形式Pydantic オブジェクトを直接返すtool_calls を含む AIMessage を返す
適用シナリオ情報抽出、データ解析Agent ツール呼び出し、外部実行が必要なシナリオ
モデルの対応モデルがネイティブ structured output をサポートしている必要があるfunction calling をサポートするすべてのモデル

ネストされた構造化出力

with_structured_output() は複雑なネスト構造をサポートします:

例

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model


class Ingredient(BaseModel):
    """食材情報"""
    name: str = Field(description="食材名")
    amount: str = Field(description="使用量、例:'200g'、'2個'")


class CookingStep(BaseModel):
    """調理手順"""
    step_number: int = Field(description="手順番号")
    description: str = Field(description="手順の説明")
    duration_minutes: int = Field(description="この手順に必要な時間(分)")


class Recipe(BaseModel):
    """レシピ"""
    dish_name: str = Field(description="料理名")
    difficulty: str = Field(description="難易度:簡単、普通、難しい")
    ingredients: list[Ingredient] = Field(description="食材リスト")
    steps: list[CookingStep] = Field(description="調理手順")


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
structured_model = model.with_structured_output(Recipe)

# レシピの説明を入力
recipe_text = """
今日は定番のトマトと卵の炒め物の作り方を教えます。この料理はとても簡単です。
準備が必要なもの:トマト 2個、卵 3個、ネギのみじん切り少々、塩適量、砂糖少々。
手順:
1. まずトマトを一口大に切り、卵を溶きます。だいたい5分かかります。
2. 鍋を熱して油を入れ、まず卵を炒めて取り出します。だいたい3分です。
3. 鍋に再度油を入れ、トマトを炒めて汁が出るまで加熱し、塩と砂糖を加えます。だいたい5分です。
4. 炒めた卵を加え、均一に炒め合わせ、ネギを散らします。だいたい2分です。
"""


result = structured_model.invoke(recipe_text)

print(f"料理名: {result.dish_name}")
print(f"難易度: {result.difficulty}")
print(f"食材({len(result.ingredients)} 種類):")
for ing in result.ingredients:
    print(f"  - {ing.name}: {ing.amount}")
print(f"手順({len(result.steps)} ステップ):")
for step in result.steps:
    print(f" {step.step_number}. {step.description}({step.duration_minutes}分)")

実行結果:

菜名: 番茄炒蛋
難易度: 简单
食材 (5 种):
  - 番茄: 2个
  - 鸡蛋: 3个
  - 葱花: 少许
  - 盐: 适量
  - 糖: 少许
步骤 (4 步):
  1. 番茄切块,鸡蛋打散 (5分钟)
  2. 热锅放油,鸡蛋炒熟盛出 (3分钟)
  3. 炒番茄至出汁,加盐和糖 (5分钟)
  4. 倒入鸡蛋翻炒均匀,撒上葱花 (2分钟)

JSON Schema モード

Pydantic モデルだけでなく、JSON Schema を直接渡すこともできます:

例

from langchain.chat_models import init_chat_model

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

# JSON Schema を直接渡す
json_schema = {
    "title": "SentimentAnalysis",
    "description": "感情分析結果",
    "type": "object",
    "properties": {
        "sentiment": {
            "type": "string",
            "enum": ["positive", "negative", "neutral"],
            "description": "感情の傾向"
        },
        "confidence": {
            "type": "number",
            "description": "信頼度、0〜1"
        },
        "keywords": {
            "type": "array",
            "items": {"type": "string"},
            "description": "主要な感情語"
        }
    },
    "required": ["sentiment", "confidence"]
}

structured_model = model.with_structured_output(json_schema)
result = structured_model.invoke("サンプル は本当に素晴らしいので、すべてのプログラミング初心者に強くおすすめします!")

print(f"感情: {result['sentiment']}")
print(f"信頼度: {result['confidence']}")
print(f"キーワード: {result['keywords']}")

実行結果:

感情: positive
信頼度: 0.95
キーワード: ['太棒了', '强烈推荐']

ConfigurableModel での bind_tools と with_structured_output

設定可能モデルもこれらの2つのメソッドをサポートしており、使用方法は完全に同じです:

例

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model

# 設定可能モデルを作成
configurable_model = init_chat_model(
    "deepseek-v4-flash",
    configurable_fields=("model", "model_provider"),
    temperature=0,
)

# チェーン呼び出し:まずツールをバインドしてから呼び出す
configurable_with_tools = configurable_model.bind_tools([...])

# 実行時には異なるモデルを使用して実行できる
result = configurable_with_tools.invoke(
    "天気を調べる",
    config={"configurable": {"model": "claude-sonnet-4-5"}}
)

ConfigurableModel 上で bind_tools または with_structured_output をチェーン呼び出しすると、実際の操作は遅延実行されます——モデルがインスタンス化されるまで実際にはバインドされないため、実行時にモデルを動的に切り替える機能には影響しません。

その他の拡張