Open5

Next.jsでのLangGraph

k_zumi_devk_zumi_dev

動くことは確認した
チュートリアルから若干改変

tabilyとOpenAIのAPIキーを.envに書いておく
https://tavily.com/

// 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>
  );
}

k_zumi_devk_zumi_dev

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