Open5
Next.jsでのLangGraph

チュートリアルはこれ

動くことは確認した
チュートリアルから若干改変
tabilyとOpenAIのAPIキーを.envに書いておく
// app/page.tsx
import React from "react";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { StateGraph, MessagesAnnotation } from "@langchain/langgraph";
export default async function Page() {
// エージェントが使用するツールを定義
const tools = [new TavilySearchResults({ maxResults: 3 })];
const toolNode = new ToolNode(tools);
// ツールへのアクセス権を与えたモデルの作成
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
}).bindTools(tools);
// 継続するかどうか判定する関数
function shouldContinue({ messages }: typeof MessagesAnnotation.State) {
const lastMessage = messages[messages.length - 1] as AIMessage;
// LLM がツール呼び出しを行った場合は "tools" ノードへ遷移
if (lastMessage.tool_calls?.length) {
return "tools";
}
// それ以外は特別な "__end__" ノードで終了
return "__end__";
}
// モデル呼び出しを行う関数
async function callModel(state: typeof MessagesAnnotation.State) {
const response = await model.invoke(state.messages);
// 返答は配列として返す(既存のメッセージリストに追加されるため)
return { messages: [response] };
}
// ワークフローグラフの定義
const workflow = new StateGraph(MessagesAnnotation)
.addNode("agent", callModel)
.addEdge("__start__", "agent") // __start__ はエントリーポイントの特殊名称
.addNode("tools", toolNode)
.addEdge("tools", "agent")
.addConditionalEdges("agent", shouldContinue);
// グラフをコンパイルして LangChain Runnable を作成
const app = workflow.compile();
// 最初の問い合わせ: サンフランシスコの天気
const finalState = await app.invoke({
messages: [new HumanMessage("sfの天気は?")],
});
const firstResponse =
finalState.messages[finalState.messages.length - 1].content;
// 関係のない質問 (前回のコンテキストを保持)
const secondState = await app.invoke({
messages: [
...finalState.messages,
new HumanMessage("おすすめの夜ご飯は?"),
],
});
const secondResponse =
secondState.messages[secondState.messages.length - 1].content;
// 継続問い合わせ: ニューヨークの天気 (前回のコンテキストを保持)
const thirdState = await app.invoke({
messages: [
...finalState.messages,
new HumanMessage("そういえばNYはどう?"),
],
});
const thirdResponse =
thirdState.messages[thirdState.messages.length - 1].content;
return (
<div style={{ padding: "2rem" }}>
<h1>LLM の返答結果</h1>
<section style={{ marginBottom: "1.5rem" }}>
<h2>SF の天気</h2>
<p>{firstResponse}</p>
</section>
<section style={{ marginBottom: "1.5rem" }}>
<h2>関係のない質問</h2>
<p>{secondResponse}</p>
</section>
<section style={{ marginBottom: "1.5rem" }}>
<h2>NYについて</h2>
<p>{thirdResponse}</p>
</section>
</div>
);
}

条件分岐で複数のLLMに振り分けたい。
条件付きエッジを使う必要がありそう。

Difyを使った方が楽そうだが、諸事情でDockerを使えない縛りがあるため、LangChainが有力

マルチエージェント
結構面倒そうなのでサクッと作れるものではなさそう