0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangGraph入門 単純なチャット実装

Last updated at Posted at 2025-11-29

はじめに

LangGraphは、LLM(大規模言語モデル)を使ったアプリケーションを「グラフ構造」で構築するためのフレームワークです。本記事では、最もシンプルなチャットボットをLangGraphで実装します。

対象読者

  • Pythonの基本を理解している方
  • LangChain・LangGraphを初めて学ぶ方
  • LLMを使ったアプリケーション開発に興味がある方

今回作るもの

START → chatbot → END

この一直線のグラフで、シンプルなチャットボットを構築します。


1. State(状態)

Stateとは

LangGraphの公式ドキュメントによると:

"The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state."

Stateはグラフ全体で共有されるデータ構造であり、ノードが読み書きする対象です。

基本的なState定義

from typing import Annotated, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

TypedDictを使う理由

  • 型でステートの構造を明示できる
  • エディタの補完が効く
  • どんなデータがグラフ内を流れるか一目瞭然

リデューサー(Reducer)とは

ノードが返した値をどのようにStateに適用するかを決める関数です。

リデューサーがない場合(デフォルト)

class State(TypedDict):
    bar: list[str]

# 現在の状態: {"bar": ["hi"]}
# ノードが返す: {"bar": ["bye"]}
# 結果: {"bar": ["bye"]}  ← 完全に上書き!

会話履歴が毎回消えてしまいます。

リデューサーがある場合

from operator import add

class State(TypedDict):
    bar: Annotated[list[str], add]

# 現在の状態: {"bar": ["hi"]}
# ノードが返す: {"bar": ["bye"]}
# 結果: {"bar": ["hi", "bye"]}  ← 追加される!

リデューサーの種類

リデューサー 動作 用途
なし(デフォルト) 上書き 単一値、フラグ
operator.add リスト連結 ログ、検索結果
add_messages メッセージ追加・マージ 会話履歴

リデューサーの実行タイミング

ノードが値を返すたびに、そのキーに対応するリデューサーが実行されます。複数のノードを通過すれば、その回数だけ実行されます。

Stateの拡張

公式ドキュメントより:

"Typically, there is more state to track than just messages"

メッセージ以外にも追跡したいデータがあるのが普通です。

拡張例1: ドキュメント保存(RAG用)

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    documents: list[str]  # 検索で取得したドキュメント

拡張例2: ユーザー情報

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    user_id: str
    user_preferences: dict

拡張例3: 中間結果の保存

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    search_results: Annotated[list[str], add]
    current_step: str

2. Messageオブジェクト

主要なMessageタイプ

HumanMessage - ユーザーからの入力

from langchain_core.messages import HumanMessage

messages.append(HumanMessage(content=user_input))

AIMessage - LLMからの応答

response = llm.invoke(state["messages"])  # AIMessageが返される

主な属性:

  • content: テキスト内容
  • tool_calls: ツール呼び出し情報
  • usage_metadata: トークン使用量

SystemMessage - システム指示

from langchain_core.messages import SystemMessage

messages = [
    SystemMessage(content="あなたは親切で丁寧な日本語アシスタントです。")
]

公式ドキュメントより:

"Establishes initial instructions that prime the model's behavior. Used to set the tone, define the model's role, and establish guidelines for responses."

使用タイミング:

  • AIの役割を定義する時
  • 応答のトーンや形式を指定する時
  • 制約やルールを設定する時

SystemMessageはmessagesリストの先頭に置くのが一般的です。

実際に送られるデータ形式

LangChainのMessageオブジェクト(コード内)

[
    SystemMessage(content="あなたは親切なアシスタントです"),
    HumanMessage(content="こんにちは"),
    AIMessage(content="こんにちは!どうされましたか?")
]

OpenAI APIに送られる形式(内部で変換)

[
    {"role": "system", "content": "あなたは親切なアシスタントです"},
    {"role": "user", "content": "こんにちは"},
    {"role": "assistant", "content": "こんにちは!どうされましたか?"}
]

プロバイダー間の違い

LangChain OpenAI API Claude API
SystemMessage messages配列内 別パラメータ
HumanMessage "user" "user"
AIMessage "assistant" "assistant"

LangChainを使う利点:各プロバイダーの形式の違いを吸収してくれるため、統一的なMessageオブジェクトだけ意識すれば良い。


3. LLMの初期化

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0.7,
)

Temperatureとは

応答のランダム性・創造性を制御するパラメータです。

仕組み

LLMは次の単語を確率に基づいて選択します。

  • Temperature = 0: 常に最も確率の高い単語を選ぶ → 決定論的
  • Temperature 高い: 確率が少し低い単語も選ぶ → ランダム・創造的

推奨値

特徴 用途
0 〜 0.3 決定論的、一貫性高い 翻訳、分類、コード生成
0.5 〜 0.7 バランス良い 一般的な会話、Q&A
0.7 〜 1.0+ 創造的、多様性あり マーケティング文、創作

注意: Temperatureが高すぎるとハルシネーション(嘘)が増える可能性があります。


4. ノード関数

def chatbot(state: State) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

ノードの基本ルール

  1. 引数: 現在のState全体を受け取る
  2. 戻り値: 更新したいキーだけを含む辞書を返す

重要なポイント

return {"messages": [response]}  # リストで返す
  • responseは単一のAIMessage
  • リストで包んで返すadd_messagesが期待する形式)
  • State全体を返す必要はなく、更新したい部分だけでOK

5. グラフの構築

from langgraph.graph import StateGraph, START, END

def create_graph():
    # 1. グラフビルダーの作成
    graph_builder = StateGraph(State)

    # 2. ノードの追加
    graph_builder.add_node("chatbot", chatbot)

    # 3. エッジの追加
    graph_builder.add_edge(START, "chatbot")
    graph_builder.add_edge("chatbot", END)

    # 4. コンパイル
    return graph_builder.compile()

各ステップの説明

  1. StateGraph(State): Stateの型を渡して型安全なグラフを作成
  2. add_node: ノード名と関数を登録
  3. add_edge: ノード間の接続を定義
  4. compile: 実行可能なグラフオブジェクトに変換

6. invoke() - グラフの実行

result = graph.invoke({"messages": messages})

実行フロー

  1. 初期Stateを渡す

    {"messages": [SystemMessage, HumanMessage]}
    
  2. chatbotノード実行

    • llm.invoke(state["messages"])を実行
    • AIMessageを返す
  3. リデューサー適用

    # ノードの戻り値
    {"messages": [AIMessage]}
    # ↓ add_messagesにより追加
    {"messages": [SystemMessage, HumanMessage, AIMessage]}
    
  4. 最終Stateを返す

戻り値の使用

# 最新のAIメッセージを取得
ai_message = result["messages"][-1]
print(f"AI: {ai_message.content}")

# 次のループのためにmessagesを更新
messages = result["messages"]

完全なコード例

動作環境

  • Python 3.12.12

ディレクトリ構成

langgraph-chatbot/
├── .env              # APIキーを記載
├── main.py           # メインコード
└── requirements.txt  # 依存パッケージ

requirements.txt

langchain-openai==1.0.3
langgraph==1.0.3
python-dotenv==1.2.1

main.py

from dotenv import load_dotenv
from typing import Annotated, TypedDict

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, BaseMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

load_dotenv()

# State定義
class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

# LLM初期化
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)

# ノード関数
def chatbot(state: State) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

# グラフ構築
def create_graph():
    graph_builder = StateGraph(State)
    graph_builder.add_node("chatbot", chatbot)
    graph_builder.add_edge(START, "chatbot")
    graph_builder.add_edge("chatbot", END)
    return graph_builder.compile()

# メイン
def main():
    graph = create_graph()

    messages = [
        SystemMessage(content="あなたは親切で丁寧な日本語アシスタントです。")
    ]

    while True:
        user_input = input("あなた: ")
        if user_input.lower() == "quit":
            break

        messages.append(HumanMessage(content=user_input))
        result = graph.invoke({"messages": messages})

        ai_message = result["messages"][-1]
        messages = result["messages"]

        print(f"AI: {ai_message.content}\n")

if __name__ == "__main__":
    main()

まとめ

本記事で学んだLangGraphのチャットボット実装:

概念 説明
State グラフ全体で共有されるデータ構造
リデューサー ノードの戻り値をどうStateに適用するか
add_messages メッセージを追加・マージするリデューサー
ノード 処理単位(Stateを受け取り、更新を返す)
エッジ ノード間の接続
invoke() グラフを実行し、最終Stateを返

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?