0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「あのAIアイデア、試したい!」を爆速で形に。GraphAI × LangGraphで実験フローを量産・管理する新発想

Posted at

はじめに:AIアプリ開発、アイデアは無限でも「試す」のが大変じゃないですか?

「このプロンプトとあのツールを組み合わせたら、すごいものができるかも!」
「ユーザーごとに最適な情報を提供できるAIエージェントを作りたい!」

個人開発者や数人の小さなチームでAIアプリケーションを開発していると、次から次へと新しいアイデアが浮かんできますよね。特にLLM(大規模言語モデル)の進化は目覚ましく、可能性は無限に広がっているように感じます。

でも、その「試してみたい!」を実際に形にするのは、思った以上に大変だったりしませんか?

  • プロトタイピングの繰り返し: ちょっとしたプロンプトの変更、ツールの組み合わせ変更、処理フローの入れ替え…何度も試行錯誤するうちに、コードがぐちゃぐちゃに。
  • 作ったものの管理: 「あの時のあのバージョンが一番良かった気がするけど、どこにやったっけ…?」「似たような処理、前も書いたような…」実験が増えるほど、管理が追いつかない。
  • 一人(少人数)だと全部やるのがきつい: フロー全体の設計、個々の機能の実装、API連携、そしてもちろんプロンプトエンジニアリングも!

「実験が大事」とはよく言われますが、その「実験しやすい環境」を整えること自体が、個人や小さなチームにとっては大きな負担になりがちです。

この記事では、そんな悩みを抱える開発者の皆さんに、AIアプリケーションの実験フローを「もっと速く、もっと楽に、もっと整理して」量産・管理するための新しいアプローチを提案します。

それは、ワークフローを宣言的に管理する「GraphAI」 と、Pythonで柔軟にAI部品を作れる「LangGraph」 を組み合わせるハイブリッドな方法です。

Pythonだけで頑張る?よくあるAI開発の「成長痛」

多くの場合、AIエージェントやLLMを使ったアプリケーション開発は、PythonとLangChain/LangGraphのようなフレームワークからスタートすると思います。最初はそれで十分かもしれません。しかし、アプリケーションが成長し、試行錯誤を重ねるうちに、こんな「成長痛」を感じ始めるのではないでしょうか。

  • ワークフロー全体の見通しが悪い: エージェントの呼び出し順序、条件分岐、並列処理などが複雑に絡み合い、Pythonコードだけで全体像を把握するのが難しくなってきます。
  • 「ちょっと試したい」が気軽にできない: フローの一部を変更するつもりが、思わぬ副作用で他の部分が動かなくなることを恐れて、新しい実験に踏み出しにくくなります。
  • 過去の実験コードの再利用が難しい: 「あの時のあのプロンプトとツールの組み合わせ、別プロジェクトでも使いたいな」と思っても、コードが密結合していて簡単に切り出せない、なんてことも。

これらの課題は、個人や小規模チームにとって、開発スピードの低下や、新しいアイデアへの挑戦を躊躇させる原因になりかねません。

そこで提案!「設計図」と「強力な部品」を分けて考える新スタイル

この「成長痛」を乗り越えるために、本記事では 「ワークフローの設計図」と「AI機能部品の実装」を分けて考えるアプローチ を提案します。

  1. ワークフローの「設計図」は、宣言的に管理する GraphAI:
    GraphAIは、AI処理のパイプライン(ワークフロー)を、主にJSONやYAMLといった設定ファイル形式で記述するツールです。処理の流れ、データの依存関係、並列実行などをコードではなく「宣言」で定義します。これにより、ワークフロー全体の見通しが格段に良くなります。

  2. AI機能の「強力な部品」は、Pythonで柔軟に実装する LangGraph (+ FastAPI):
    LangGraphは、LLMを使った複雑なエージェントやステートフルなアプリケーションを構築するためのPythonライブラリです。プロンプトエンジニアリング、ツール連携、状態管理など、AIのコアとなるロジックを柔軟に実装できます。そして、このLangGraphで作ったAI部品をFastAPIを使って簡単にAPI化します。

なぜこの組み合わせなのか?

  • GraphAIの得意なこと: ワークフロー全体の構造を定義し、視覚化(Mermaid形式など)すること。データフローに基づいた自動並列実行。
  • LangGraphの得意なこと: 複雑なAIロジック、エージェントの振る舞い、状態管理をPythonで柔軟かつ堅牢に実装すること。Pydanticによる型安全性。
  • FastAPIの得意なこと: Pythonで書かれた機能を、型安全なAPIとして簡単に公開すること。

つまり、GraphAIで「何をどの順番でやるか」という全体の流れを設計し、LangGraph + FastAPIで「それぞれの処理をどうやるか」という具体的なAI部品を作り込む、という役割分担です。

GraphAI × LangGraph ハイブリッド構成の全体像とメリット

このハイブリッド構成のイメージは以下のようになります。

GraphAI+LangGraph_arch.png

このアプローチがもたらす具体的なメリットは以下の通りです。

  • メリット1:ワークフローが「見える」!
    GraphAIのYAML定義は、そのままワークフローの設計図です。Mermaid形式で図として出力すれば、チームメンバーや、将来の自分も、処理の流れを一目で理解できます。複雑なPythonコードを追いかける必要はありません。

  • メリット2:AI部品は「再利用」できる!
    LangGraphとFastAPIで作ったAI部品(ユーティリティAgentやエキスパートAgent)は、標準的なAPIとして提供されます。これにより、GraphAIで定義する様々なワークフローから、これらの部品を簡単に呼び出して再利用できます。「あのプロジェクトで作った検索機能を、こっちでも使いたい」が簡単に実現します。

  • メリット3:実験が「爆速で回せる」!
    新しいプロンプトを試したい? 呼び出すAI部品の順番を変えたい? それなら、GraphAIのYAML定義を少し変更するだけです。AI部品の実装コードを直接触る必要がないため、安全かつ迅速に新しい実験フローを試すことができます。YAMLテンプレートを使えば、似たようなフローの「量産」も簡単です。

  • メリット4:「得意」を活かせる!
    ワークフロー全体の構造管理はGraphAIの宣言的な記述に任せ、AIのコアとなる複雑なロジックや状態管理はPythonとLangGraphの得意領域で実装します。これにより、それぞれのツールの強みを最大限に活かせます。

  • メリット5:関心の分離で保守性アップ!
    「全体の流れ」と「個々の処理」が分離されるため、一方の変更が他方に影響を与えにくくなります。部品ごとのテストもしやすくなり、アプリケーション全体の保守性が向上します。

実践!最小構成で作る「ポッドキャストの台本生成」実験フロー

LangGraphについてはweb上に豊富なサンプルが用意されていると思いますので、GraphAIを中心に説明します。GraphAIのコードはGitHubを参照ください。

ここでは下記処理の実現において「★」の処理を並列処理で行う方法を目指します。

  1. キーワードを入力
  2. Plnnerが章に分割しアウトラインを生成
  3. Explorerが章毎に必要な情報を収集 ★
  4. Generatorがアウトラインと情報をもとに台本を生成

1.GraphAIのインストール

チュートリアルを参考にGraphAIをインストールします。

npm i -g  @receptron/graphai_cli

2. YAMLファイルにデータフローを記述

先ほど説明した処理をGraphAIで記述すると下記のようになります。
テキストファイルなのでバージョン管理は容易ですし、複数パターンを用意しての試行錯誤も効率的です。また、ブログ記事の執筆などもファイルをコピーして一部修正するだけで作成可能です。

generate_podcast_script.yml
version: 0.5
nodes:
  # 1. キーワードを入力
  source:
    value:
      text: 最新のLLM技術トレンド
      
  # 2. Plnnerが章に分割しアウトラインを生成
  plannerPromptBuilder:
    agent: stringTemplateAgent
    inputs:
      keywords: :source.text
    console:
      before: plannerPrompt start 
      after: true
    params:
      template: |-
        あなたは優れたポッドキャストの編集者兼ライターです。
        以下のキーワードをもとに情報を収集し、あなたの知見と合わせてポッドキャストの台本のアウトラインを2部構成で日本語で作成してください。
        また、各章で深掘りすべき具体的なキーワードも考えてください。(一般的な表現は使わず、具体的なキーワードを考えてください。)
        なお、返却は JSON 形式で行い、コメントやマークダウンは含めないでください。

        RESPONSE FORMAT:
        {
          "outline": [
              {
                "title": "...",
                "overview": "...",
                "query_hint": ["...", "..."]
              },
              {
                "title": "...",
                "overview": "...",
                "query_hint": ["...", "..."]
              }
          ]
        }

        User keywords: ${keywords}
  planner:
    agent: fetchAgent
    console:
      before: planner start
    inputs:
      # jsonフォーマットでの出力を行うユーティティエージェント
      url: http://127.0.0.1:8000/aiagent-api/v1/aiagent/utility/jsonoutput
      method: POST
      body:
        user_input: :plannerPromptBuilder
        
  # 3. Explorerが章毎に必要な情報を収集 *並列処理
  explorer_mapper:
    agent: mapAgent
    inputs:
      rows: :planner.result.outline
    console:
      before: explorer_mapper start
    params:
      compositeResult: true
    graph:
      nodes:
        explorerPromptBuilder:
          agent: stringTemplateAgent
          inputs:
            title: :row.title
            overview: :row.overview
            query_hint: :row.query_hint
          console:
            before: explorerPromptBuilder start 
          params:
            template: |-
              あなたは優れた情報収集者です。
              以下のキーワードをもとに広く情報を収集し、あなたの知見と合わせてポッドキャストの台本の作成に必要な情報(用語の意味や概念など)を日本語で整理してください。
              
              Title:${title}
              Overview:${overview}
              Query_hint: ${query_hint}
        explorer:
          agent: fetchAgent
          console:
            before: explorer start
          inputs:
            # google検索を使用して情報収集するユーティティエージェント
            url: http://127.0.0.1:8000/aiagent-api/v1/aiagent/utility/explorer
            method: POST
            body:
              user_input: :explorerPromptBuilder
        rexplorer_result_summary:
          agent: stringTemplateAgent
          inputs:
            title: :row.title
            overview: :row.overview
            query_hint: :row.query_hint
            text: :explorer.result
          console:
            before: rexplorer_result_summary start 
          params:
            template: |-
              # Subject:
              ${title}

              # Overview:
              ${overview}

              # keyword:
              ${query_hint}

              # Reference_Information:
              ${text}
          isResult: true
  explorer_mapper_output:
    agent: arrayJoinAgent
    console:
      before: explorer_mapper_output start
    params:
      separator: \n---\n
    inputs:
      array: :explorer_mapper.rexplorer_result_summary

  # 4. Generatorがアウトラインと情報をもとに台本を生成
  generatorPromptBuilder:
    agent: stringTemplateAgent
    inputs:
      explorer_result: :explorer_mapper_output.text
    console:
      before: generatorPromptBuilder start 
    params:
      template: |-
        あなたは優れたポッドキャストのライターです。
        以下の情報をもとに、1000~2000文字程度でポッドキャストの台本を作成してください。

        # ポッドキャスト生成のインプット情報
        ---
        ${explorer_result}
  generator:
    agent: geminiAgent
    params:
      model: gemini-2.5-flash-preview-04-17
    console:
      before: generator start 
    inputs:
      prompt: :generatorPromptBuilder

  output:
    agent: copyAgent
    console:
      before: output start 
    params:
      namedKey: text
    inputs:
      text: :generator.text
    isResult: true
  • ポイント1:fetchAgentを使用してLangGraphで作成したエージェントを呼び出し
  • ポイント2:mapAgentを使用して並列処理を実現
  • ポイント3:mapAgentにて「compositeResult: true」を指定し、実行結果をarrayJoinAgentで文字列に結合することで、後続の処理での利用を実現

3. CLIで実行

.envファイルを用意し書きコマンドを実行することでgraphAIを実行

  • .envファイル
    OPENAI_API_KEY=<APIKEY>
    GOOGLE_GENAI_API_KEY=<APIKEY>
    CLAUDE_API_KEY=<APIKEY>
    
  • GraphAI CLIによる実行
    graphai generate_podcast_script.yml
    # -m をつけて実行することでmermaidで出力できます。
    # graphai -m generate_podcast_script.yml
    # flowchart TD
    #  source(source) -- text --> plannerPromptBuilder
    #  plannerPromptBuilder(plannerPromptBuilder) --> planner
    #  planner(planner) -- result.outline --> explorer_mapper
    #  explorer_mapper(explorer_mapper) -- rexplorer_result_summary --> explorer_mapper_output
    #  explorer_mapper_output(explorer_mapper_output) -- text --> generatorPromptBuilder
    #  generatorPromptBuilder(generatorPromptBuilder) --> generator
    #  generator(generator) -- text --> output
    

4. APIで公開

yamlファイルで記述したデータフローをほとんどそのまま利用してAPIで公開する方法を説明します。

  1. 下記ファイルを用意

    package.json
    {
      "name": "graphaiserver",
      "version": "1.0.0",
      "type": "module",
      "description": "```bash npm init -y yarn add typescript --dev yarn add @types/node --dev npx tsc --init ```",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@types/node": "^22.15.17",
        "typescript": "^5.8.3"
      },
      "dependencies": {
        "@graphai/agent_filters": "^1.0.1",
        "@graphai/agents": "^2.0.0",
        "@graphai/token_bound_string_agent": "^1.0.1",
        "@graphai/vanilla": "^2.0.0",
        "@graphai/vanilla_node_agents": "^1.0.1",
        "@receptron/test_utils": "^2.0.0",
        "dotenv": "^16.5.0",
        "express": "^5.1.0",
        "graphai": "^1.0.12",
        "ts-node": "^10.9.2"
      }
    }
    
    tsconfig.json
    {
      "compilerOptions": {
        "module": "ESNext",
        "target": "ESNext",
        "esModuleInterop": true,
        "moduleResolution": "node",
        "strict": true
      },
      "ts-node": {
        "esm": true
      }
    }
    
    src/app.js
    // app.js
    import express from 'express';
    import main from './services/sample.js';
    import dotenv from 'dotenv';
    dotenv.config();
    
    const app = express();
    const port = process.env.PORT || 3000;
    
    app.use(express.json());
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    app.post('/multiagentflow', async (req, res) => {
      const { user_input,  model_name} = req.body;
      if (!user_input) {
        return res.status(400).json({ error: 'user_input は必須です' });
      }
    
      try {
        const result = await main(user_input, model_name); // await で完了を待つ
        res.json(result);
      } catch (error) {
        console.error("Error executing GraphAI sample:", error);
        res.status(500).json({ error: 'An error occurred while executing the GraphAI sample.' });
      }
    });
    
    
    app.listen(port, () => {
      console.log(`サーバーがポート ${port} で起動しました: http://localhost:${port}`);
    });
    
    src/services/sample.js
    // src/services/sample.ts (修正版)
    import { GraphAI } from "graphai";
    import { readGraphaiData } from "@receptron/test_utils";
    import * as packages from "@graphai/agents";
    import { tokenBoundStringsAgent } from "@graphai/token_bound_string_agent";
    import { fileReadAgent, fileWriteAgent, pathUtilsAgent } from "@graphai/vanilla_node_agents";
    import dotenv from 'dotenv';
    dotenv.config();
    const MODEL_BASE_PATH = process.env.MODEL_BASE_PATH || "";
    const agents = {
        ...packages,
        tokenBoundStringsAgent,
        fileReadAgent,
        fileWriteAgent,
        pathUtilsAgent,
    };
    // Remove unwanted properties like `__esModule` and `module.exports`
    const agents_2 = Object.fromEntries(Object.entries(agents).filter(([key]) => key !== "__esModule" && key !== "module.exports"));
    const main = async (user_input, model_name) => {
        // console.log("Available agents:", Object.keys(agents_2));
        try {
            const modelpath = MODEL_BASE_PATH + model_name + ".yml";
            const graph_data = readGraphaiData(modelpath);
            // console.log("Graph data:", graph_data);
            // console.log(JSON.stringify(graph_data, null, 2));
            const graph = new GraphAI(graph_data, agents_2);
            graph.injectValue("source", user_input);
            const result = await graph.run();
            console.log("GraphAI Result:", result);
            return result;
        }
        catch (error) {
            console.error("Error during GraphAI instantiation or run:", error);
            throw error;
        }
    };
    export default main;
    
  2. install & yamlファイル格納ディレクトリ作成

    yarn install
    mkdir src/graphAIyml
    
  3. .envファイルを作成

    .env
    PORT=3030
    MODEL_BASE_PATH=XXX/src/graphAIyml/
    OPENAI_API_KEY=<APIKEY>
    GOOGLE_GENAI_API_KEY=<APIKEY>
    CLAUDE_API_KEY=<APIKEY>
    
  4. yamlファイルを一部修正し「src/graphAIyml」ディレクトリに格納

    version: 0.5
    nodes:
      source:
        value:
          text: 最新のLLM技術トレンド
      
      plannerPromptBuilder:
        agent: stringTemplateAgent
        inputs:
          keywords: :source.text
    ・・・以下は変更なし
    

    version: 0.5
    nodes:
      source: {}
      plannerPromptBuilder:
        agent: stringTemplateAgent
        inputs:
          keywords: :source
    ・・・以下は変更なし
    
  5. 実行

    • Expressサーバーの起動
    # サービスの起動
    node src/app.js
    
    • curlコマンドの実行
    curl -X POST "http://localhost:3030/multiagentflow" \
        -H "Content-Type: application/json" \
        -d '{
          "user_input": "量子コンピューティング, LLM, AI, ChatGPT",
          "model_name": "generate_podcast_script"
        }'
    {"output":"・・・・}
    

まとめ:小さなチームでもAI開発の「実験ループ」を爆速で回そう!

AIアプリケーション開発の面白さは、新しいアイデアを次々と試し、ユーザーに価値を届けられる点にあります。しかし、その「試す」プロセスが煩雑で時間がかかってしまうと、開発の勢いが失われてしまいます。

今回提案した GraphAI × LangGraph (+ FastAPI) のハイブリッドアプローチは、

  1. GraphAI でワークフロー全体の 「見える化」と「宣言的な管理」 を実現し、
  2. LangGraph でPythonの柔軟性を活かした 「強力なAI部品」 を作り込み、
  3. これらを FastAPI を介して疎結合に連携させる

ことで、個人開発者や小規模チームが抱えがちな「実験・管理の複雑さ」という課題を解決します。

このアプローチが提供する価値はシンプルです。

  • コードとワークフロー構造が分離されることで、保守しやすくなる
  • YAML定義やAPI部品の組み合わせで、実験フローを短時間で複製・試行できる
  • 全体像を視覚的に把握しながら改善できるので、学習コストを抑えつつ、より良いものを作れる

これからのAI開発は、「いかに多くのアイデアを、いかに速く試し、改善し続けられるか」がますます重要になります。
このハイブリッドアプローチが、皆さんのAIアイデアを形にし、"実験ループ"を高速に回し続けるための一助となれば幸いです。

ぜひ、あなたの次のプロジェクトで試してみてください!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?