無料でLangGraphに入門してみよう!【Quick Start】
はじめに
こんにちは、iharuです。
LangGraphのQuick Startが面白かったので、日本語&無料APIのみで記事として書き直してみました!
今回はツールを組み込んだグラフまで作成したいと思います。
事前準備
適当なディレクトリを作成します。(今回はlanggraph
とします)
.env
というファイルとquickstart
というフォルダを作成します。
langgraph
├── quickstart
└── .env
お好きなPython環境と次のライブラリをインストールしておきます。
パッケージ名 | バージョン |
---|---|
langgraph | ^0.2.0 |
langchain | ^0.3.0 |
langchain-community | ^0.3.0 |
langchain-groq | ^0.2.0 |
今回はLLMのAPIとして、Groqという無料で利用できるサービスを使いたいと思います。
(OpenAIやBedrockのAPIを使用している方はlangchain-groqの代わりにlangchain-openai, langchain-awsを使用してください)
またツールのAPIとして、TavilySearchというこちらも無料で利用できるサービスを使いたいと思います。
Groq APIキーの取得
こちらからGroqのアカウントを作成します。
続いて、こちらからCreate API Keyを押してAPIキーを取得します。
APIキーが取得出来たら、先ほど作成した.env
ファイルに貼り付けます。
GROQ_API_KEY = ***
TavilySearch APIキーの取得
こちらからTavilySearchのアカウントを作成します。
続いて、API Keys
にあるAPIキーを取得します。
APIキーが取得出来たら、先ほど作成した.env
ファイルに貼り付けます。
TAVILY_API_KEY = ***
実践
LLMインスタンスの作成
llm.py
にGroqのLLMインスタンスを作成する関数を作成します。
from dotenv import load_dotenv
from langchain_groq import ChatGroq
load_dotenv()
def get_llm():
return ChatGroq()
if __name__ == "__main__":
llm = get_llm()
print(llm.invoke("What is LangGraph?"))
このファイルを実行すると次のような回答が返ってきます。
content='I'm not aware of a specific technology or tool called "LangGraph." The term "langgraph" could potentially refer to a linguistic graph, which is a type of data structure used in natural language processing and computational linguistics. Linguistic graphs are used to represent various aspects of language, such as syntax trees, semantic networks, or word embeddings.\n\nSyntax trees are hierarchical structures that represent the grammatical structure of a sentence. Semantic networks are used to represent the meaning of words and their relationships to other words in a conceptual space. Word embeddings are vector representations of words that capture their semantic and syntactic properties.\n\nIt's also possible that "LangGraph" could refer to a proprietary technology or tool developed by a specific organization. To provide a more accurate answer, I would need more context or information about where you encountered the term "LangGraph."' additional_kwargs={} response_metadata={'token_usage': {'completion_tokens': 189, 'prompt_tokens': 13, 'total_tokens': 202, 'completion_time': 0.300618712, 'prompt_time': 0.002309784, 'queue_time': 0.012407025, 'total_time': 0.302928496}, 'model_name': 'mixtral-8x7b-32768', 'system_fingerprint': 'fp_c5f20b5bb1', 'finish_reason': 'stop', 'logprobs': None} id='run-329f90e3-1e13-452f-bfcc-4d9887dc98be-0' usage_metadata={'input_tokens': 13, 'output_tokens': 189, 'total_tokens': 202}
とりあえずLLMが動くことがわかりましたが、GroqはLangGraphについては知らないようです。
続いて、LangGraphの形式にLLMを組み込んでいきます。
シンプルなグラフの作成
llm
を使ってチャット回答をするchatbot
ノードをつくることを目標にします。
LangGraphの形式で動かすためには以下のコンポーネントが必要となります。
コンポーネント | 説明 |
---|---|
State | アプリケーションの現在の状態を表すデータ構造 |
Nodes | 現在のStateを入力として受け取り、ロジックに基づき更新されたStateを返す関数 |
Edges | 現在のStateに基づいて次に実行するNodeを決定する関数 |
LangGraphの一連の流れは以下のようになります。
- 最初のノード
START
からState
が出発 - 最後のノード
END
にState
が到着するまで以下を繰り返す- 各ノードで
State
を更新 -
State
に基づきエッジを通り別のノードへ遷移
- 各ノードで
State
State
はグラフのコンポーネントで共通のデータ構造となります。
今回はmessages
という会話履歴のリストを持たせることにします。
add_messages
はLangGraphの組み込み関数でmessages
に色々な情報を持たせて会話履歴を追加するものになります。
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
Node
続いて、llm
からchatbot
ノードを返す関数を作成します。
このchatbot
ノードは、後ほどtool
ノードを追加した際にエージェントとしての役割を持ちます。
ノードに入れるものはState -> State
の関数だったので、get_chatbot_node
の戻り値は関数chatbot
になっています。
from .state import State
def get_chatbot_node(llm):
def chatbot(state: State):
return {"messages": [llm.invoke(state["messages"])]}
return chatbot
Graph
最後に今まで作ったコンポーネントを組み合わせてグラフを作っていきます。
まずはStateGraph(State)
でState
をもつグラフインスタンスを作成します。
このグラフインスタンスにadd_node
とadd_edge
でノードとエッジを追加していきます。
最後にcompile
でグラフをコンパイルします。(ここのオプション変数は後ほどmemory
を追加するときに使います)
from langgraph.graph import StateGraph, START, END
from components.chatbot import get_chatbot_node
from components.llm import get_llm
from components.state import State
graph_builder = StateGraph(State)
llm = get_llm()
chatbot = get_chatbot_node(llm)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
graph = graph_builder.compile()
グラフを実行する前に、グラフ図を確認するためのdraw_graph
という関数とグラフの実行結果を確認するためのstream_graph
という関数を定義しておきます。
from langgraph.graph.state import CompiledStateGraph
def draw_graph(graph: CompiledStateGraph):
try:
graph_image = graph.get_graph().draw_mermaid_png()
file_path = f"output.png"
with open(file_path, "wb") as f:
f.write(graph_image)
print(f"画像が '{file_path}' に保存されました.")
except Exception as e:
print("画像の保存中にエラーが発生しました:", e)
pass
これを先ほどのgraph.pyで実行してみましょう。
if __name__ == "__main__":
from utils.draw import draw_graph
draw_graph(graph)
以下の画像が出力されます。
draw_mermaid_png()
の代わりにdraw_mermaid()
を使うとmermaidのコードを出力することもできます。
mermaidの出力結果
%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
__start__([<p>__start__</p>]):::first
chatbot(chatbot)
__end__([<p>__end__</p>]):::last
__start__ --> chatbot;
chatbot --> __end__;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
次に対話式でグラフを実行する関数を作成します。
from langgraph.graph.state import CompiledStateGraph
def stream_graph(graph: CompiledStateGraph):
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)
while True:
user_input = input("User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
stream_graph_updates(user_input)
"User: "
でユーザーインプットを受け付け、stream_graph_updates
でユーザーインプットを実行し、途中結果を"Assistant:"
に続けて返すようなスクリプトになっています。
ユーザーインプットが"quit", "exit", "q"
なら終了します。
これを先ほどのgraph.pyで実行してみましょう。
if __name__ == "__main__":
from utils.stream import stream_graph
stream_graph(graph)
User: Hello
Assistant: Hello! It's nice to meet you. Is there something you would like to talk about or ask me? I'm here to help with any questions you might have about writing, grammar, or language-related topics. Just let me know how I can assist you.
対話形式で会話ができることが確認できました!
Toolを組み込む
tools
ノードを追加してツールを利用して回答するグラフをつくることを目標にします。
Tool
今回はTavilySearchというLLM用の検索ツールを使用します。
まずはTavilySearchツールを返す関数を作成します。ToolNodeでtoolのリストをラップするとノードとして利用できるようになります。
from dotenv import load_dotenv
from langgraph.prebuilt import ToolNode
from langchain_community.tools.tavily_search import TavilySearchResults
load_dotenv()
tavily_tool = TavilySearchResults(max_results=2)
tools = [tavily_tool]
def get_tool_node():
return ToolNode(tools=tools)
tavily_toolの部分だけ実行してみます。
if __name__ == "__main__":
print(tavily_tool.invoke("What's a 'node' in LangGraph?"))
LangGraphについて、webで検索して最新の情報を教えてくれます。
[{'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141', 'content': 'Nodes: Nodes are the building blocks of your LangGraph. Each node represents a function or a computation step. You define nodes to perform specific tasks, such as processing input, making'}, {'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial', 'content': "In LangGraph, each node represents an LLM agent, and the edges are the communication channels between these agents. This structure allows for clear and manageable workflows, where each agent performs specific tasks and passes information to other agents as needed. State management. One of LangGraph's standout features is its automatic state"}]
conditional edge
続いて、chatbotからToolやENDへ遷移するための条件付きエッジを作成します。
条件付きエッジは複数のノードへ条件により遷移するエッジとなり、条件分岐のロジックを書くroute_tool関数を作成します。
from components.state import State
from langgraph.graph import END
def route_tools(
state: State,
):
if isinstance(state, list):
ai_message = state[-1]
elif messages := state.get("messages", []):
ai_message = messages[-1]
else:
raise ValueError(f"No messages found in input state to tool_edge: {state}")
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return END
このコードでは、State
を受け取りリストなら一番最後のもの、そうでなければmessages
にアクセスしてそれをAIの最後のメッセージとします。AIの最後のメッセージにtool_calls
が含まれており、tool_calls
の要素が1つ以上あるならツールノードに遷移するため"tools"
を返します。
それ以外なら終了するためにEND
を返します。
これも加えてグラフを作っていきます。まず、llm
にtool
の情報を渡すためにllm.bind_tools(tools)
とします。(情報しか渡していないのでこれだけではツールの実行はできません)
次に、graph_builder.add_node("tools", tool_node)
でツールノードを追加します。
最後にadd_conditional_edges
に先ほどのroute_tool
を渡すことで条件付きエッジが追加されます。
from langgraph.graph import StateGraph, START, END
from components.chatbot import get_chatbot_node
from components.llm import get_llm
from components.state import State
+ from components.tools import get_tool_node, tools
graph_builder = StateGraph(State)
llm = get_llm()
+ llm_with_tools = llm.bind_tools(tools)
+ chatbot = get_chatbot_node(llm_with_tools)
+ tool_node = get_tool_node()
graph_builder.add_node("chatbot", chatbot)
+ graph_builder.add_node("tools", tool_node)
graph_builder.add_edge(START, "chatbot")
+ graph_builder.add_conditional_edges(
+ "chatbot",
+ route_tools,
+ {"tools": "tools", END: END},
+ )
+ graph_builder.add_edge("tools", "chatbot")
graph = graph_builder.compile()
グラフを実行する前に、グラフ図を確認します。
if __name__ == "__main__":
from utils.draw import draw_graph
draw_graph(graph)
以下の画像が出力されます。
chatbotからtoolとENDに点線の矢印が伸びていますが、これが条件付きエッジです。
グラフを実行しましょう。
if __name__ == "__main__":
from utils.stream import stream_graph
stream_graph(graph)
User: What is LangGraph?
Assistant:
Assistant: [{"url": "https://www.langchain.com/langgraph", "content": "LangGraph is a framework for building stateful, multi-actor agents with LLMs that can handle complex scenarios and collaborate with humans. Learn how to use LangGraph with Python or JavaScript, deploy it with LangGraph Cloud, and see examples from real-world use cases."}, {"url": "https://www.datacamp.com/tutorial/langgraph-tutorial", "content": "LangGraph is a library within the LangChain ecosystem that simplifies the development of complex, multi-agent large language model (LLM) applications. Learn how to use LangGraph to create stateful, flexible, and scalable systems with nodes, edges, and state management."}]
Assistant: LangGraph is a framework for building stateful, multi-actor agents with large language models (LLMs) that can handle complex scenarios and collaborate with humans. It can be used with Python or JavaScript and can be deployed with LangGraph Cloud. LangGraph simplifies the development of complex, multi-agent LLM applications, allowing for the creation of stateful, flexible, and scalable systems with nodes, edges, and state management. You can learn more about LangGraph at https://www.langchain.com/langgraph and https://www.datacamp.com/tutorial/langgraph-tutorial.
1回目のAssistantでは何も出力されていません。これはchatbotがツールを選択したときには空文字を返すようになっているためです。
2回目のAssistantでは、先ほどのTavilySearchツールと同じような内容が返ってきており、ツールを使用したことがわかります。
最後のAssistantでは、ツールの結果をもとに回答が生成されていることが確認できました!
なお、ツールを使うかはLLMが判断をするのですが、Groqでは通常の会話でもツールを使ってしまうことがありました。
内部的なモデルにも依存しますが、ツールの説明を追加したりプロンプトを追加することで適切な判断をしてもらう必要がありそうです。
通常の会話でもツールを使ってしまう例
User: Hello
Assistant:
Assistant: [{"url": "https://www.openphone.com/blog/how-to-professionally-answer-the-phone/", "content": "2. Say the greeting with a smile (because warmth comes through the voice) Smile. Now say something. It might not sound to you like there's a big difference, but your body language can have a genuine impact on your tone of voice. "A smile is more than an expression," writes Kaan Turnali for Forbes."}, {"url": "https://smith.ai/blog/receptionist-greeting-scripts-15-professional-ways-to-answer-the-phone", "content": "14. Be an active listener. What you hear is more important than what you say—make sure that you know how to listen to what the callers need so that you can assist them properly. 15. Avoid slang and filler words. Don't just answer the phone with "yeah.". That sounds short and rude."}]
Assistant: Hello! It's nice to speak with you. I'm here to help answer your questions. Based on the information provided, it's important to remember to smile while answering the phone as it can genuinely impact the tone of voice, making it sound warm and welcoming. Additionally, it's recommended to avoid using slang and filler words, ensuring a professional and respectful greeting.
おわりに
今回はLangGraphのチュートリアルを実践してみました。
エッジの付け方の自由度が高いので、より発展的なグラフを作ることもできます。
LangGraphのチュートリアルでは色々なパターンが解説されているので、これらもキャッチアップして自分なりのグラフを作っていきたいですね!
Discussion