2
1

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 簡単なサンプル3つ

Last updated at Posted at 2024-12-29

LangGraph Quick Start (参考:前回のLangGraph Quick Start(日本語訳))のPart1をベースに、普段利用のOpenAI,ChatPromptTemplateを利用した記載に修正しサンプルを残す

  • sample1
    • Quick Start Part1のモデル変更
    • Anthropicのmodel:claude-3-5-sonnet-20240620 =>OpenAI(model:gpt-4o-mini)
  • sample2
    • state add_messagesを利用しない
    • node(chatbot->agent)
    • graphname(graph_builder => workflow)
    • ChatPromptTemplateの利用
  • sample3
    • nodeを2つに(agent_1,agent_2)
    • 結果出力をstreamモードと,invokeの両方を記載
pip list
openai                                   1.58.1
langchain                                0.2.17
langchain-community                      0.2.17
langchain-core                           0.2.43
langchain-experimental                   0.0.64
langchain-openai                         0.1.25
langgraph                                0.2.26
langgraph-checkpoint                     1.0.12
langsmith                                0.1.147
python 3.9.17
langchain-anthropic                      0.1.23 #今回は使ってません
# 訳あって、まだpython 3.9.17、langchain v0.2.17使ってます

0.環境セットアップ

python
import os
from os.path import join, dirname, abspath
from dotenv import load_dotenv

dir_path = dirname(abspath("__file__"))
dotenv_path = join(dir_path, '../[your_dir_name]/.env')
load_dotenv(dotenv_path, verbose=True)

#print(os.environ['ANTHROPIC_API_KEY'])
#print(os.environ['OPENAI_API_KEY'])
#print(os.environ['TAVILY_API_KEY'])

1.Sample1

  • LangGraph Part1 Part 1: Build a Basic Chatbotをベース
  • 利用するStateは、公式そのまま
    • 1つのキー:messages を持つ TypedDict
    • add_messages リデューサー関数
  • 変更点
    • anthropicではなくopenai
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class State(TypedDict):
    # メッセージのタイプは「リスト」です。アノテーション内の `add_messages`
    # 関数は、この状態キーの更新方法を定義します (この場合、メッセージを
    # 上書きするのではなく、リストに追加します)
    messages: Annotated[list, add_messages]

graph_builder = StateGraph(State)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")

def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}

# 最初の引数は一意のノード名
# 2 番目の引数はノードが使用されるたびに呼び出される関数またはオブジェクト
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)

graph = graph_builder.compile()
#可視化
from IPython.display import Image, display

try:
    display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
    # (任意) 追加の依存関係
    pass

点線枠はグラフ名を記載するために加筆してます

# LangGraph Streamによる実行処理を定義
def stream_graph_updates(user_input: str):
    for event in graph.stream({"messages": [("user", user_input)]}):
        for value in event.values():
            print("Assistant:", value["messages"][-1].content)
#実行
stream_graph_updates("こんにちは")
#Assistant: こんにちは!どういったことをお話ししましょうか?

2.Sample2

  • Sample1との変更点
    • Stateに、add_messagesを利用しない
      • 簡易的なのでField(..., description='hogehoge fugafuga')の記載は省略
    • Chatbotではなく、Agentとして役割を与える(今回は日本語から英語への翻訳)
    • 命名変更
      • ノード名・関数名:chatbot => node名:agent_1、関数名:agent1
      • graph_builder => workflow
Stateの作成
#from typing import Annotated #使わない
from typing_extensions import TypedDict
from typing import List
from langgraph.graph import StateGraph, START, END
#from langgraph.graph.message import add_messages #使わない


class State(TypedDict):
    """
    グラフの状態を表す(Dict型)
    
    Attributes:
      query:str 質問
      answer:str 日本語の質問を英語に変換した答え
    """
    query:str
    answer:str
Agentの作成
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")

def agent1(state:State): #英訳する関数(後でノードとして定義し利用する)
    query = state['query']
    
    # Prompt template(書き方1)
    #message = """与えられた以下質問を英語に翻訳してください。\n query:{query}"""
    #prompt1 = ChatPromptTemplate.from_messages(("human",message))

    # Prompt template(書き方2)
    prompt1 = ChatPromptTemplate([
      ('system', 'あなたは優秀な翻訳家です。'),
      ('user', """以下を英語に翻訳してください。\n query:{query}""")
    ])

    agent1 = (
        prompt1
        | llm
        | StrOutputParser()
    )
    
    answer = agent1.invoke(query)
    #print(answer)
    return {"query":query,"answer":answer}
LangGraphの構築
# グラフの初期化
workflow = StateGraph(State)

# 最初の引数は一意のノード名
# 2 番目の引数はノードが使用されるたびに呼び出される関数またはオブジェクト
workflow.add_node("agent_1", agent1)
workflow.add_edge(START, "agent_1")
workflow.add_edge("agent_1", END)

graph_app = workflow.compile()
グラフの可視化
#可視化
from IPython.display import Image, display
try:
    display(Image(graph_app.get_graph().draw_mermaid_png()))
except Exception:
    # (任意)追加の依存関係
    pass

点線枠はグラフ名を記載するために加筆してます

# Stateの初期値
initial_state = State(query='こんにちは') #Agentに翻訳してほしい言葉を入れる

実行結果の確認

# streamを利用する場合
for chunk in graph_app.stream(initial_state):
    print(chunk)

# {'agent1': {'query': 'こんにちは', 'answer': 'Hello.'}}
# streamを利用する場合
for chunk in graph_app.stream(initial_state):
    print(chunk) #--- print(1)
    # さらにvaluesをprintする場合
    # Agent名を変数に格納
    ai_name = list(chunk.keys())[0] 
    for value in chunk.values():
        print("Assistant:", value) #---print(2)
        print(ai_name, value) #----print(3)

# print(1) --- {'agent_1': {'query': 'こんにちは', 'answer': 'Hello.'}}
# print(2) --- Assistant: {'query': 'こんにちは', 'answer': 'Hello.'}
# print(3) --- agent_1 {'query': 'こんにちは', 'answer': 'Hello.'}

3.Sample3

  • Sample2との変更点
    • Agentを追加して、2つのAgentに働いてもらう
      • 英語をフランス語に翻訳するAgent
from typing_extensions import TypedDict
from typing import List
import pandas as pd

from langgraph.graph import StateGraph, START, END


class State(TypedDict):
    """
    グラフの状態を表す(Dict型)
    
    Attributes:
      query:str 質問
      answer:str 日本語の質問を英語に変換した返事
      answer2:str 英語をフランス語に翻訳した返事
    """
    query:str
    answer:str
    answer2:str
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
agent_1(英語に翻訳)
def agent1(state:State): #英訳する関数(後でノードとして定義し利用する)
    query = state['query']
    
    # Prompt template(書き方1)
    #message = """与えられた以下質問を英語に翻訳してください。\n query:{query}"""
    #prompt1 = ChatPromptTemplate.from_messages(("human",message))

    # Prompt template(書き方2)
    prompt1 = ChatPromptTemplate([
      ('system', 'あなたは優秀な翻訳家です。'),
      ('user', """以下を英語に翻訳してください。\n query:{query}""")
    ])

    agent1 = (
        prompt1
        | llm
        | StrOutputParser()
    )
    
    answer = agent1.invoke(query)
    #print(answer)
    return {"query":query,"answer":answer}
agent_2(フランス語に翻訳)
def agent2(state:State): #フランス語訳する関数(後でノードとして定義し利用する)
    answer = state['answer']
    
    # Prompt template(書き方1)
    #message = """与えられた以下質問をフランス語に翻訳してください。\n answer:{answer}"""
    #prompt1 = ChatPromptTemplate.from_messages(("human",message))

    # Prompt template(書き方2)
    prompt2 = ChatPromptTemplate([
      ('system', 'あなたは優秀な翻訳家です。'),
      ('user', """以下をフランス語に翻訳してください。\n answer:{answer}""")
    ])

    agent2 = (
        prompt2
        | llm
        | StrOutputParser()
    )
    
    answer2 = agent2.invoke(answer)
    #print(answer)
    return {"answer":answer,"answer2":answer2}
LangGraph構築
# グラフの初期化
workflow2 = StateGraph(State)

# 最初の引数は一意のノード名
# 2 番目の引数はノードが使用されるたびに呼び出される関数またはオブジェクト
workflow2.add_node("agent_1", agent1)
workflow2.add_node("agent_2", agent2)  # <==追加(2つ目のAgent)
workflow2.add_edge(START, "agent_1")
workflow2.add_edge("agent_1","agent_2") #<==追加(agent1からagent2へのedge)
workflow2.add_edge("agent_2", END)     #<==修正(ENDに向かうagentを1から2に修正)

graph_app2 = workflow2.compile()
可視化
from IPython.display import Image, display

try:
    display(Image(graph_app2.get_graph().draw_mermaid_png()))
except Exception:
    # This requires some extra dependencies and is optional
    pass

点線枠はグラフ名を記載するために加筆してます

Stateの初期値
# Stateの初期値
initial_state = State(query='こんにちは')

実行確認

# stream出力
for chunk in graph_app2.stream(initial_state):
    print(chunk)

# {'agent_1': {'query': 'こんにちは', 'answer': 'Hello.'}}
# {'agent_2': {'answer': 'Hello.', 'answer2': 'Bonjour.'}}
  • stream_modeには2種類ある
    • stream_mode = "values" : 各ノードが呼び出された後の全てのState
    • stream_mode = "updates" : 各ノードが呼び出された後の更新されたState
      • keyにnode名を取って更新されたstateのみを返す
# stream出力
# stream_mode="values"の場合
for chunk in graph_app2.stream(initial_state, stream_mode="values"): 
    print(chunk)
    final_chunk = chunk
    
print("final_chunk:",final_chunk)

# {'query': 'こんにちは'}
# {'query': 'こんにちは', 'answer': 'Hello.'}
# {'query': 'こんにちは', 'answer': 'Hello.', 'answer2': 'Bonjour.'}
# final_chunk: {'query': 'こんにちは', 'answer': 'Hello.', 'answer2': 'Bonjour.'}

# stream出力
# stream_mode="updates"の場合
for chunk in graph_app2.stream(initial_state, stream_mode="updates"): 
    print(chunk)
    final_chunk = chunk
    
print("final_chunk:",final_chunk)

# {'agent_1': {'query': 'こんにちは', 'answer': 'Hello.'}}
# {'agent_2': {'answer': 'Hello.', 'answer2': 'Bonjour.'}}
# final_chunk: {'agent_2': {'answer': 'Hello.', 'answer2': 'Bonjour.'}}

Tip:辞書型なのでDataFrameにして表示も可能

import pandas as pd
df=pd.DataFrame([final_chunk['agent_2']])
display(df)
answer answer2
0 Hello. Bonjour.

invokeによる出力

#invoke出力
graph_app2.invoke(initial_state)
#{'query': 'こんにちは', 'answer': 'Hello.', 'answer2': 'Bonjour.'}
# ivoke出力
# 最終結果の値だけほしい場合
dict_data = graph_app2.invoke(initial_state)
dict_data['answer2']

# 'Bonjour.'
# inovke出力
# 最終のstatusだけ、pandas DataFrame に変換し、HTMLファイル出力する場合
# DF変換
dict_data = graph_app2.invoke(initial_state)
df=pd.DataFrame([dict_data])
display(df)

# HTMLファイル出力
df.to_html("sample_df.html",index=False, encoding='utf-8')
print("sample_df.htmlに出力しました")

# HTML出力
HTML=df.to_html(index=False)
# Flaskでtemplateに入れテーブル表示したい場合などに利用
# ファイル出力しない場合はencoding='utf-8'をつけるとエラーになるので注意

出力ファイル

sample_df.html
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th>query</th>
      <th>answer</th>
      <th>answer2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>こんにちは</td>
      <td>Hello.</td>
      <td>Bonjour.</td>
    </tr>
  </tbody>
</table>
query answer answer2
0 こんにちは Hello. Bonjour.
# sample_df.htmlに出力しました

参考

  • 参考にさせていただきました

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?