【基礎確認】langglaphについて

# はじめに
[LangChainとLangGraphによるRAG・AIエージェント［実践］入門](https://www.amazon.co.jp/dp/B0DK4YGYBL?tag=xpg-22&linkCode=ogi&th=1&psc=1)
を読んでいて、10章で躓いたので、初心に戻って、一つずつ紐解いて学習することにしました。
今回のテーマは「langgraph」モジュール「graph」関数

# 前回までの投稿
1. 必要なライブラリとモジュールをインポートする
[【基礎確認】pythonのoperatorモジュール](https://qiita.com/o_chan_t/items/4ad3cd5fc832f9be9d86)
[【基礎確認】pythonのtypingモジュール](https://qiita.com/o_chan_t/items/1f54806ad90c79904af2)
[【基礎確認】pythonのdotenvモジュール](https://qiita.com/o_chan_t/items/641eada727fa5a27aaf3)
[【基礎確認】pythonのlangchain_core.output_parsersモジュールStrOutputParser関数](https://qiita.com/o_chan_t/items/9a3d23537c7dc0327ddb)
[【基礎確認】pythonのlangchain_core.promptsモジュールChatPromptTemplate関数](https://qiita.com/o_chan_t/items/e64733a7d5d97c0cc126)
[【基礎確認】pythonのlangchain_community.llmsモジュールFakeListLLM関数](https://qiita.com/o_chan_t/items/e261a9b4d2458876e99b)

# 参考ページ
hakotensan様作成の[LangGraphの基本的な使い方](https://zenn.dev/pharmax/articles/8796b892eed183)

# 独り言

langgraphについて、本を読んで分かった気になっていたのだけど、何が便利なのかが全く理解出来なかった。
だけど、10章で躓き、色々調べて実験することでlanggraphの便利さがようやくわかった。
これは確かに便利そうだなー。

可能であればlanggraphについて基本レベルはマスターできるようにしておきたい。
遅くとも、年始の業務開始までには・・・！

# langgraph(ノードが2つのケース)
```langgraph01.py
from typing_extensions import TypedDict  # TypedDictを使用して型付きの辞書を定義
from langchain_core.runnables import RunnableConfig  # LangChainの実行可能な設定を扱うためのクラス
from langgraph.graph import StateGraph  # 状態遷移グラフを作成するためのクラス
from langchain_community.llms import FakeListLLM  # テスト用のダミーLLMクラス
from langchain_core.prompts import PromptTemplate  # プロンプトテンプレートを作成するためのクラス

# グラフの状態を表すStateクラスの定義
# TypedDictを使用して、questionとanswerフィールドを持つ辞書型を定義
class State(TypedDict):
    question: str  # 質問を格納するフィールド
    answer: str    # 回答を格納するフィールド

# FakeListLLMの設定
# テスト用に、事前に定義された応答のリストを使用
responses = [
    "FakerListLLM: 1つ目のダミーレスポンス",
    "FakerListLLM: 2つ目のダミーレスポンス",
    "FakerListLLM: 3つ目のダミーレスポンス"
]
fake_llm = FakeListLLM(responses=responses)  # ダミーLLMのインスタンスを作成

# プロンプトテンプレートの定義
# 質問を入力として受け取り、それを組み込んだプロンプトを生成する
prompt_template = PromptTemplate(
    input_variables=["question"],
    template="Answer the following question: {question}"
)

# 質問応答を行うノード関数の定義
def question_answering(state: State, config: RunnableConfig):
    question = state["question"]  # 状態から質問を取得
    prompt = prompt_template.format(question=question)  # プロンプトを生成
    answer = fake_llm.invoke(prompt)  # LLMを使用して回答を生成
    return {"answer": answer}  # 生成された回答を辞書形式で返す

# 回答の後処理を行うノード関数の定義
def post_processing(state: State, config: RunnableConfig):
    answer = state["answer"]  # 状態から回答を取得
    return {"answer": f"回答: {answer}"}  # 回答に接頭辞を追加

# グラフの構築
graph_builder = StateGraph(State)  # StateGraphのインスタンスを作成
graph_builder.add_node("question_answering", question_answering)  # 質問応答ノードを追加
graph_builder.add_node("post_processing", post_processing)  # 後処理ノードを追加
graph_builder.add_edge("question_answering", "post_processing")  # ノード間のエッジを追加
graph_builder.set_entry_point("question_answering")  # エントリーポイントを設定
graph_builder.set_finish_point("post_processing")  # 終了ポイントを設定

# グラフのコンパイルと実行
graph = graph_builder.compile()  # グラフをコンパイル
result = graph.invoke({"question": "ダミーの質問", "answer": ""})  # グラフを実行
print(result)  # 結果を出力

```
```
{'question': 'ダミーの質問', 'answer': '回答: FakerListLLM: 1つ目のダミーレスポンス'}
```

# langgraph(ノードが3つのケース)

```langgraph02.py
from typing_extensions import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph
from langchain_community.llms import FakeListLLM
from langchain_core.prompts import PromptTemplate

class State(TypedDict):
    question: str
    answer: str
    evaluation: str  # 新しいフィールドを追加

responses = [
    "FakerListLLM: 1つ目のダミーレスポンス",
    "FakerListLLM: 2つ目のダミーレスポンス",
    "FakerListLLM: 3つ目のダミーレスポンス"
]
fake_llm = FakeListLLM(responses=responses)

prompt_template = PromptTemplate(
    input_variables=["question"],
    template="Answer the following question: {question}"
)

def question_answering(state: State, config: RunnableConfig):
    question = state["question"]
    prompt = prompt_template.format(question=question)
    answer = fake_llm.invoke(prompt)
    return {"answer": answer}

def answer_evaluation(state: State, config: RunnableConfig):
    answer = state["answer"]
    if len(answer) < 25:
        evaluation = "短い回答です。もう少し詳細な情報が必要かもしれません。"
    else:
        evaluation = "十分な長さの回答です。"
    return {"evaluation": evaluation}

def post_processing(state: State, config: RunnableConfig):
    answer = state["answer"]
    evaluation = state["evaluation"]
    return {"answer": f"回答: {answer}, 評価: {evaluation}"}

graph_builder = StateGraph(State)
graph_builder.add_node("question_answering", question_answering)
graph_builder.add_node("answer_evaluation", answer_evaluation)  # 新しいノードを追加
graph_builder.add_node("post_processing", post_processing)
graph_builder.add_edge("question_answering", "answer_evaluation")  # 新しいエッジを追加
graph_builder.add_edge("answer_evaluation", "post_processing")
graph_builder.set_entry_point("question_answering")
graph_builder.set_finish_point("post_processing")

graph = graph_builder.compile()
result = graph.invoke({"question": "ダミーの質問", "answer": "", "evaluation": ""})
print(result)
```
```
{'question': 'ダミーの質問', 'answer': '回答: FakerListLLM: 1つ目のダミーレスポンス, 評価: 十分な長さの回答です。', 'evaluation': '十分な長さの回答です。'}
```

# 独り言2

langgraphを使うと問い合わせや評価を行うオブジェクトをフロートして管理出来て、便利だよ！ってことね。

自分なりに理解した内容を、細かく書こうとすればするほど本や参考資料に書いてある通りだった。

まぁ、理解していないと正しくてわかりやすく書かれた内容も腑に落ちないもので、ここらへんは試行錯誤して手を動かしながらでないと分からない、よくあるパターンに陥っていました。

[LangChainとLangGraphによるRAG・AIエージェント［実践］入門](https://www.amazon.co.jp/dp/B0DK4YGYBL?tag=xpg-22&linkCode=ogi&th=1&psc=1)の11章で紹介されている様々なデザインパターンに対し、langgraphを使わないと、管理しきれないですね。

次回以降はlanggraph使いながらlangchainを触っていきますか。