🦔

GraphAIをRaspberry pi 5で動かしてみた

2024/05/18に公開

GraphAIとは

GraphAIは、複雑なエージェントアプリケーションを簡単に構築するための非同期データフロー実行エンジンです。エージェントワークフローをYAMLまたはJSON形式で宣言的なデータフローグラフとして記述することができます。

Raspberry pi で動作確認

早速、Raspberry piでGraphAIを動かしてみましょう。
確認した機種は最近買った、Raspberry pi 5 です。

インストール

npm, yarnをインストールします。

  • npm
sudo apt-get update
sudo apt-get install -y nodejs npm
sudo npm cache clean
sudo npm install npm n -g
sudo n stable
  • yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install yarn
  • GraphAIをインストールします。
mkdir ~/ai
cd ~/ai
git clone https://github.com/receptron/graphai.git
cd glaphai
yarn install

必要に応じて .env に OpenAI、Groq のキーを設定します。

sample.tsx
OPENAI_API_KEY=sk- ...
GROQ_API_KEY=gsk_ ...

サンプルのインタビューアプリを動かしてみましょう。

sampleを実行したい場合は graphaiディレクトリ配下のsamplesディレクトリに移動してください。

pi@sspi5:~/ai/graphai $ cd agents/llm_agents

以下のようにコマンドを実行します。

pi@sspi5:~/ai/graphai/packages/samples $ yarn sample ./samples/llm/interview_jp.ts
yarn run v1.22.19
$ npx ts-node  -r tsconfig-paths/register ./src/llm/interview_jp.ts
? インタビューしたい人の名前を入力してください: サティア・ナデラ
  • 以下のような会話が続きます。

サティア・ナデラ: こんにちは、サティア・ナデラと申します。

司会: こんにちは、ナデラさん。お越しいただき光栄です。あなたのキャリアは非常に素晴らしく、Microsoftをいくつもの驚異的な変革へと導いてこられました。まずは個人的なことから始めたいと思います。インドでの幼少期に、リーダーシップに対するあなたの視点を形作った決定的な瞬間があれば教えていただけますか?

サティア・ナデラ: 温かい言葉とお招きいただき、ありがとうございます。インドで育った私にとって、リーダーシップの視点を形成する決定的な瞬間は、クリケットをしていた時期でした。インドにおいてクリケットは単なるスポーツではなく、生活の一部です。ボウラーとして、私は戦略、忍耐、そしてチームワークの重要性を学びました。成功は個々の優れた才能だけでなく、他者との協力や状況の変化に適応する能力にかかっていることを教えてくれました。この経験から得た協力とレジリエンスの価値は、私のキャリアやマイクロソフトでの役割において非常に重要なものとなっています。

司会: それは興味深いですね。その経験があなたのプロフェッショナルな人生にうまく反映されていることがよくわかります。協力とレジリエンス(回復力)と言えば、あなたがマイクロソフトのCEOに就任されたとき、会社は岐路に立たされていました。クラウドコンピューティングへのシフトにおいて最も困難だった点は何でしたか?そして、どのようにしてチームをあなたのビジョンに引き込んだのですか?

...

本当のインタビュー記事のようです。面白いですね。
このサンプルアプリの内容を見てみましょう。
このデータフローグラフでは、各ステップで非同期的に処理が進行し、次のステップが前のステップの結果を待って実行される形になります。これにより、ユーザーとのインタラクティブなチャット形式の対話が実現されています。

interview_jp.ts
import "dotenv/config";
import { graphDataTestRunner } from "@/utils/test_runner";
import * as agents from "@graphai/agents";

const system_interviewer =
  "You are a professional interviewer. It is your job to dig into the personality of the person, making some tough questions. In order to engage the audience, ask questions one by one, and respond to the answer before moving to the next topic.";

export const graph_data = {
  version: 0.3,
  nodes: {
    name: {
      // Asks the user to enter the name of the person to interview.
      agent: "textInputAgent",
      params: {
        message: "インタビューしたい人の名前を入力してください:",
      },
    },
    context: {
      // prepares the context for this interview.
      agent: "stringTemplateAgent",
      params: {
        template: {
          person0: {
            name: "Interviewer",
            system: system_interviewer,
          },
          person1: {
            name: "${0}",
            system: "You are ${0}.",
            greeting: "Hi, I'm ${0}",
          },
        },
      },
      inputs: [":name"],
    },
    messages: {
      // Prepares the conversation with one system message and one user message
      agent: "propertyFilterAgent",
      params: {
        inject: [
          {
            index: 0,
            propId: "content",
            from: 1,
          },
          {
            index: 1,
            propId: "content",
            from: 2,
          },
        ],
      },
      inputs: [[{ role: "system" }, { role: "user" }], ":context.person0.system", ":context.person1.greeting"],
    },

    chat: {
      // performs the conversation using nested graph
      agent: "nestedAgent",
      inputs: [":messages", ":context"],
      params: {
        namedInputs: ["messages", "context"],
      },
      isResult: true,
      graph: {
        loop: {
          count: 6,
        },
        nodes: {
          messages: {
            // Holds the conversation, array of messages.
            value: [], // to be filled with inputs[2]
            update: ":swappedMessages",
            isResult: true,
          },
          context: {
            // Holds the context, which is swapped for each iteration.
            value: {}, // te be mfilled with inputs[1]
            update: ":swappedContext",
          },
          groq: {
            // Sends those messages to the LLM to get a response.
            agent: "openAIAgent",
            params: {
              model: "gpt-4o",
            },
            inputs: [undefined, ":messages"],
          },
          translate: {
            // Asks the LLM to translate it into Japanese.
            agent: "openAIAgent",
            params: {
              system: "この文章を日本語に訳して。意訳でも良いので、出来るだけ自然に相手に敬意を払う言葉遣いで。余計なことは書かずに、翻訳の結果だけ返して。",
              model: "gpt-4o",
            },
            inputs: [":messages.$last.content"],
          },
          output: {
            // Displays the response to the user.
            agent: "stringTemplateAgent",
            params: {
              template: "\x1b[32m${1}:\x1b[0m ${0}\n",
            },
            console: {
              after: true,
            },
            inputs: [":translate.choices.$0.message.content", ":context.person1.name"],
          },
          reducer: {
            // Append the responce to the messages.
            agent: "pushAgent",
            inputs: [":messages", ":groq.choices.$0.message"],
          },
          swappedContext: {
            // Swaps the context
            agent: "propertyFilterAgent",
            params: {
              swap: {
                person0: "person1",
              },
            },
            inputs: [":context"],
            isResult: true,
          },
          swappedMessages: {
            // Swaps the user and assistant of messages
            agent: "propertyFilterAgent",
            params: {
              inject: [
                {
                  propId: "content",
                  index: 0,
                  from: 1,
                },
              ],
              alter: {
                role: {
                  assistant: "user",
                  user: "assistant",
                },
              },
            },
            inputs: [":reducer", ":swappedContext.person0.system"],
            isResult: true,
          },
        },
      },
    },
    translate: {
      // This node sends those messages to Llama3 on groq to get the answer.
      agent: "openAIAgent",
      params: {
        system: "この文章を日本語に訳して。出来るだけ自然な口語に。余計なことは書かずに、翻訳の結果だけ返して。",
      },
      inputs: [":chat.swappedMessages.$last.content"],
    },
    output: {
      // This node displays the responce to the user.
      agent: "stringTemplateAgent",
      params: {
        template: "\x1b[32m${1}:\x1b[0m ${0}\n",
      },
      console: {
        after: true,
      },
      inputs: [":translate.choices.$0.message.content", ":chat.swappedContext.person1.name"],
    },
  },
};

export const main = async () => {
  const result = await graphDataTestRunner<{ messages: { role: string; content: string }[] }>(
    __dirname + "/../",
    __filename,
    graph_data,
    agents,
    () => {},
    false,
  );
  if (result?.chat) {
    console.log("Complete", result.chat["messages"].length);
  }
};

if (process.argv[1] === __filename) {
  main();
}

system_interviewer にプロのインタビュアーとして応えるようプロンプトが設定されています。
graph_dataに非同期処理の構造を定義したノード(データグラフ)が設定されています。

メインで graphDataTestRunner を各種エージェントを引数にしてコールしています。

nodes 配下がデータフローグラフで、
なんとなく各ノードにはエージェントが定義されていて、入出力内容にノード間の関連が見られます。また、ループや条件によって、同期、非同期が制御されていることがわかります。

サンプルではgroqノードをOpenAIAgentに置き換えていますが、以下のようにすることで、groqとOpenAIとを行き来するようなフローも簡単にできますね。用途にあわせてチューニングやいいとこ取りができそうです。

interview_jp.ts
          groq: {
-           // Sends those messages to the LLM to get a response.
-           agent: "openAIAgent",
+           // This node sends those messages to Llama3 on groq to get the answer.
+           //agent: "openAIAgent",
+           agent: "groqAgent",
            params: {
-             model: "gpt-4o",
+             model: "Llama3-8b-8192",
+             //model: "gpt-4o",
            },
            inputs: [undefined, ":messages"],
          },

ログを見るとGraphAIがものすごい勢いで働いていることがわかります。
tests/logs/interview_jp.log に実行時のログが出ています。

データフローグラフのnodeやagentについて知りたい方はこちらの記事がお勧めです。
https://zenn.dev/singularity/articles/async-await-graphai

概要や詳しい仕様はREADMEや公式ドキュメントを参照しましょう。
https://github.com/receptron/graphai/blob/main/README.md
https://github.com/receptron/graphai/blob/main/docs/Paper.md

シンギュラリティ・ソサエティ

Discussion