複合AIシステムのデザインパターン(対話型AI、CoPilots、RAG)

複合AIシステムのデザインパターン(対話型AI、CoPilots、RAG)

Design Patterns for Compound AI Systems (Conversational AI, CoPilots & RAG)

https://medium.com/@raunak-jain/design-patterns-for-compound-ai-systems-copilot-rag-fa911c7a62e0

オープンソースツールを使用して設定可能なフローと複合AIシステムを構築する方法。

複合AIシステムの理解

最近の記事である モデルから複合AIシステムへのシフト において、バークレーの研究者たちはAIが単一のモデルの使用から複数のコンポーネントの複雑なシステムへと移行していることについて話しました。基本モデルが同じ(例えばGPT4)であっても、その使用方法により、それがより大きなシステムの異なる部分のように見せる事ができます。

以下は、これらのシステムが一般的にどのように使用されているかのいくつかの方法です:

  1. RAG(理解が鍵) - これらのシステムは、思考生成、推論、コンテキストを使用してユーザーの質問を理解し、それに対応します。これらはエージェントアシスト設定に最適です。ダイアログモデルのようなユーザー向けモデルと一緒に使用すると、RAGシステムは会話型AIまたはCoPilotシステムの一部になることができます。
  2. マルチエージェント問題解決者(チームワークが鍵) - これらのシステムは、異なるエージェントが協力して働く結果に基づいたソリューションを構築します。各エージェントは独自のツールを持ち、推論と行動計画において特定の役割を果たします。
  3. 会話型AI(チャットが鍵) - これらは、カスタマーサービスエージェントのように、人間とやり取りし、人間の入力に基づいて繰り返し行うタスクを自動化します。ここでの主な特徴は、過去の会話を記憶し、新しい会話を生成する能力で、あなたが人間と話しているかのように感じさせます。会話型AIは、基礎となるRAGシステムやマルチエージェント問題解決者を使用することができます。
  4. CoPilots(人間の関与が鍵) - これらのシステムは、ツール、データ、推論、計画を使用して人間と対話しながら問題を解決できます。システムがCoPilotである主な特徴は、人間の作業環境を理解していることです。例としては、MetaGPT Researcher: Webを検索しレポートを作成A measured take on DevinLet’s build something with CrewAIAutogen Studioがあります。
  5. 注:LLMを使用して多くのシステムを設定した後、それらが皆が望む完璧な解決策ではないことがわかりました。すべてのAIと同様に、LLMでさえ単純なタスクを実行するためには多くの作業が必要で、パフォーマンス保証があっても、信頼性や安定性が保証されるわけではありません。
  6. 潜在的な利点:LLMの最良の使用法は、製品やサービスがどこで改善できるかを理解し、その情報を使って時間とともに改善することで、機械学習を支援することです。これは人間のレビュアーの作業を減らすことを意味するかもしれません。LLMをテストし評価できる閉じたシステムでは、それらは多くのことを達成できますが、それは高価です。しかし、それはまた、より良いLLMを作るためのデータを生成します。この改善のサイクルこそが、LLMが本当に輝く場所です。

これらのシステムのコンポーネントとそれらがどのように相互作用して複雑なシステムを構築するか

複合AIシステムは通常、互いに依存して特定のタスクを行い、デザインパターンを実行するために連鎖する「モジュール」を使用してデプロイされます。

ここで、モジュールは、システム内の個々のコンポーネントを指し、それは検索エンジン、LLMなどの基礎となるシステムの助けを借りても借りなくても、よく定義されたタスクを行います。一般的なモジュールには、ジェネレーター、リトリーバー、ランカー、クラス分類器などがあり、これらは伝統的にNLPのタスクと呼ばれています。これらは、ドメイン固有の概念的な抽象化であり(例えば、NLPのモジュールはコンピュータビジョンや推奨システムとは異なる抽象化モジュールを持つかもしれませんが、それらはすべて同じ基本モデルの提供や検索プロバイダーに依存するかもしれません)。

https://miro.medium.com/v2/resize:fit:750/1*uNJw3fHBZID_1sYLpoJV2w.png

Key components of a module

https://miro.medium.com/v2/resize:fit:750/1*JgKXJhmui92kk6_JcZl5xQ.png

追加の標準モジュール

AIの領域では、ツールエージェントコンポーネントなどの用語によく出会います。これらはすべて実質的にモジュールです。

LLMベースの自律エージェント - 複合AIシステムの重要なモジュール

モジュールの一種に、LLMによって可能となる、独立した推論と計画を立てる能力を持つ自律エージェントがあります。自律エージェントは、環境との相互作用時に推論アプローチと行動計画を決定するための一連のサブモジュールに依存する可能性があります。

https://miro.medium.com/v2/resize:fit:525/1*_NZt68XlQNHWBv407MRNsg.png

source

Key capabilities or skills of a Autonomous Agent module:

https://miro.medium.com/v2/resize:fit:525/1*36lfyC7SBGfHVROLHZ4vQQ.png

推論能力は思考の連鎖を引き起こし、実際の行動に結びつきます。

  1. 推論 — 観察を行い、仮説を生成し、データに基づいて検証するなど、問題を解決するための論理的な方法。
  2. 思考 — 推論の適用と因果関係の一貫した生成。
  3. 思考の連鎖 — 解決策を論理的につながった推論の連鎖に分解する一連の思考。
  4. 計画 — サブゴールの形成を行い、現状から未来の状態への道筋を作るための決定力。これは通常、行動を起こし、学習するための基本的な環境にアクセスすることで利益を得る。LLM計画についてはここで詳しく読むことができます。
  5. ツール — エージェントがエージェントからの指示に基づいて外部世界と対話することを可能にするサブモジュール。
  6. アクション — 名前が示す通り、これはエージェントが計画を通じて目標を追求するための決定的なステップです。ツールがアクションをトリガーするために呼び出されることがあります。アクションは環境で行われます。
  7. 環境 — エージェントのアクションと報酬が発生する外部世界、例えばカスタムアプリ(Microsoft Word、コーディング環境など)やゲーム、シミュレーションなど。

「確率的なオウム」との作業

注:大規模言語モデル(LLM)の推論と計画における限界についての広範な理論的研究にもかかわらず、ここここで示されているように、LLMが解決方法を"オウム返し"できることは明らかです。問題と過去の解決例が提供されれば、LLMは論理的な思考過程を効果的に複製できます。それが一般化するかどうかは、ここでは焦点ではありません。ヤン・ル・クンが言うように、自己回帰モデルは失敗する運命にある

ビジネス環境では、信頼性の高いタスクの繰り返しと過去の経験からの学習を、過剰な創造性なしで目指すのが目標です。

これらの”思考過程の複製”は実際にどのように計画を立てるのでしょうか?

この調査に基づくと、LLMは以下のようにして自律的なエージェントを強化できるようです:

  1. タスクの分解 - 実生活でのタスクは通常複雑で多段階的であり、計画を立てるのが非常に難しくなります。このような方法は分割統治の考え方を採用し、複雑なタスクをいくつかのサブタスクに分解し、それぞれのサブタスクについて順序立てて計画を立てます。例えば、TDAG
  2. 複数の計画選択 - このような方法は、LLMに「もっと考える」ことに焦点を当て、タスクに対するさまざまな代替計画を生成します。その後、タスク関連の検索アルゴリズムが用いられて、実行する計画を一つ選びます。例えば、ReWoo
  3. 外部モジュールを利用した計画 - また、計画の取り出しとも言えます。
  4. 反省と改善 - この方法論は、反省と改善を通じて計画能力を向上させることを強調します。LLMに失敗について反省し、計画を改善することを奨励します。例えば、自己批評と改善ループ。
  5. メモリ強化型計画 - このようなアプローチは、付加的なメモリモジュールを用いて計画を強化します。このモジュールには、一般的な知識、過去の経験、ドメイン固有の知識など、価値のある情報が保存されます。この情報は計画を立てる際に取り出され、補助信号として機能します。

https://miro.medium.com/v2/resize:fit:525/1*Vmw2T2mjhFFZuhZNC2TZ8w.png

LLMエージェントプランニングの理解:ガイド

これらの特徴の訓練方法、特にRAGのために知流ためには、企業向けRAGのためのLLM Fine-tuningの簡素化をチェックしてみてください。

これらのモジュールを理解したところで、会話型AI、RAG、およびCoPilotシステムの複雑な問題を解決するためにどのように異なる設計を作ることができるかを見てみましょう。

複合AIシステムデザインパターン

役立つ定義

現在のAIの話題性を考えると、特定の用語が誤解されたり、誤用されたりすることも多いので、まずは正確な定義を設定しましょう:

  1. "Agentic"パターンとは何ですか? 自律エージェントの主な利点は、自身の行動コースを決定する能力です。もし私たちが手動でフローや決定を定義しているなら、それは単にスマートなワークフローです。しかし、フローが事前に定義されていない場合や、決定過程が上記の能力とツール、行動を利用している場合、私たちは"Agentic"パターンを扱っています。例えば、Agentic RAGは、モジュールが検索ツールにアクセスを許可され、事前に定義されたステップなしで複雑な検索フローを自動的に生成できるパターンです。
  2. "ワークフロー"とは何ですか? 簡単に言うと、ワークフローとは、問題を一貫性のある、繰り返し可能な方法で解決するための事前エンコード、手動で宣言された計画です。
  3. "マルチエージェント"とは何ですか? これは、各々が自身の役割と責任を持つ異なるモジュールが、お互いの出力を共同で処理し、問題を協力して解決するシステムを指します。

エージェントを作成する際には、以下に注目してください:

  1. エージェントのプロファイリング — エージェントの役割に密接に関連したプロンプトに基づいて、エージェントの行動を定義します。
  2. エージェントのコミュニケーション — これらの部分がお互いにどのように交流するかを特定し、下の画像で示されているように描き出します。
  3. エージェント環境のインターフェース — 環境からのフィードバックを収集して、エージェントが学習、適応、そしてより具体的な反応を作り出すのを助けます。
  4. エージェントの学習と開発 — エージェントは、環境との交流(特にコーディングアシスタントの場合)、他のエージェント、自己評価、人間からのフィードバック、ツールから学びます。各設計がリアルタイムで問題解決にどのように取り組むかを見ていきましょう。

パターンを選ぶ前に考えるべきこと

RAG、会話型AI、CoPilots、複雑な問題解決者などのデザインパターンを作成する際には、以下のことを考慮する必要があります:

  1. モジュール間の相互作用の方法は固定されているのか、それともそれらは独立して動作することができるのか? エンジニアリングフロー対エージェントシステム。
  2. 情報は一方向に流れるのか、それともメッセージを渡すようなものなのか? 彼らは協力して働くのか、それとも反対に働くのか? エージェントモジュロ.*
  3. システムは自己学習できるのか? 自己修正が重要なのか?
  4. 推論と行動をループさせることができるのか?
  5. モジュールは互いに学習することができるのか?
  6. 各モジュールの結果は実世界でテストすることができるのか?
  7. システムの動作方法はユーザーの入力に基づいて変化するのか?

パターン1 - RAG / 会話型RAG

以下の図は、RAG / 会話型RAGシステムのモジュールの主なタスクを示しています。これは情報検索(IR)分野から始まり、ニューラルサーチと知識グラフによって改善され、言語モデルを用いたループベースの形に進化しました。IRと対話システムが結合し、会話中にクエリが変わると、これを会話型IRと呼びます。

RAGシステムを機能させるためには、ユーザーが何を尋ねているかを理解し、関連する知識(組織化されているかどうか)を見つけて、それをGenerator / Dialogue Managerに適切な指示で提供することが重要です。これは、よく計画されたワークフローを使用するか、次に何をすべきかを選択するAgentモジュールを使用して実行できます(次の部分で詳しく説明します)。

https://miro.medium.com/v2/resize:fit:525/1*T-DF9Gbw_g-K4nIZ9v9Weg.png

RAGシステムはダイアログマネージャーとペアにすることができます。ダイアログマネージャーがエージェントである場合、RAGはツールとして使用できます。

以下は、エージェントが複雑なRAGシステムを操作するのを助ける中間モジュール/ツールの一部です。

クエリの理解と変更

クエリの拡張 / 複数のクエリ

https://miro.medium.com/v2/resize:fit:767/1*TGcKK_GtL4f6XzAPbEHDUA.png

https://miro.medium.com/v2/resize:fit:1521/1*hy94U4CiUSQZQOWOJ6W_2A.png

LLMを使用して検索クエリをより詳細にすると、特定のタイプのリトリーバーを使用した場合の検索結果が向上します。

自己問い合わせリトリーバー

自己問い合わせリトリーバーは自己に質問をすることができます。自然言語での任意のクエリを取り、LLMチェーンを使用して構造化クエリを作成し、これを自身のVectorStoreに適用します。このようにして、初期のユーザークエリを保存されたドキュメントと比較し、ユーザークエリからの任意のフィルターをドキュメントのメタデータに適用することができます。

エンティティの識別

クエリの強化

情報の検索または意図の理解

複数のドキュメントの検索

会話の管理

レスポンスの作成

エージェント型RAG

エージェント型RAGは、LLMによって駆動されるモジュールが、使用可能なツールのセットに基づいて、質問にどのように答えるかを推理し計画するデザインパターンです。高度なシナリオでは、複数のエージェントを接続して、エージェントが情報を取得するだけでなく、確認、要約などを行うような創造的な方法でRAGを解決することもあります。これについては、マルチエージェントセクションを参照してください。

改善が必要な主要な手順とコンポーネントは次の通りです:

  1. 推理、サブタスクの形成、および体系的な配置に基づいた計画。
  2. 複数のパスの生成と推理、計画に基づくRAGアプローチ(ReWooとPlan+)による自己整合性に基づく自己修正は、単なる推理に基づくもの(ReAct)よりも優れています。
  3. 実行に基づいて適応する能力、より多エージェント型のパラダイム。

通常、これらは以下のパターンを使用して実行されます:

推理に基づいたエージェント型RAG

ReActhttps://blog.langchain.dev/planning-agents/

ReAct:言語モデルにおける推論と行動のシナジー

While large language models (LLMs) have demonstrated impressive capabilities across tasks in language understanding and…

https://miro.medium.com/v2/resize:fit:525/1*SkTt9vPLwzYII3ig3u8bGw.png

arxiv.org

検索ツールを使用して理由(Reason)と行動(Act) ⇒ ReAct

計画に基づくAgentic RAG

Untitled%2077bf2364b2f445d0b3e70ff2fc6d969a/Untitled.png)

https://blog.langchain.dev/planning-agents/

ReWOO:効率的な拡張言語モデルのための観察からの推論の分離

Untitled%2077bf2364b2f445d0b3e70ff2fc6d969a/Untitled%201.png)

https://arxiv.org/abs/2305.18323

Augmented Language Models (ALMs) blend the reasoning capabilities of Large Language Models (LLMs) with tools that allow…

https://miro.medium.com/v2/resize:fit:668/1*9IRQkDf-YcxCn7DXv8OmoA.png

https://miro.medium.com/v2/resize:fit:1362/1*eEz3IN5lpO0psZECdvd63A.png

ReWoo - ReActよりもはるかに少ないトークン生成につながります。

ReActがReWooよりもずっと劣っている理由についての詳細は、こちらをご覧ください。

PlanRAG

それは二つのコンポーネントで構成されています:まず、全体のタスクをより小さなサブタスクに分割する計画を立て、次にその計画に従ってサブタスクを実行する。

Untitled%2077bf2364b2f445d0b3e70ff2fc6d969a/Untitled%202.png)

https://openreview.net/forum?id=4sajV6NEnWE

Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models

デプロイメントパターン2 - 会話型AI

過去、チャットボットの会話は単純なパターンに従っていました:ボットが話し、ユーザーが反応し、その後ボットが返答します。このパターンは、Rasa開発者の「ストーリー」としても知られており、異なる現実世界の状況を表しています。各ユーザーの意図は、ユーザーの状態と相互作用に基づいて数百の「ストーリー」で示すことができます。その後、ボットはアクションを起こしてストーリーに従い、準備された反応で答えます。例えば、ユーザーがニュースレターにサインアップしたい場合、次の2つのシナリオが考えられます:

  1. ユーザーはすでにサインアップしています。
  2. ユーザーはまだサインアップしていません。

https://miro.medium.com/v2/resize:fit:525/1*RUxaRla7NtaG98KBs_844g.png

source

ユーザーが「ニュースレターを購読するにはどうすればいいですか」と尋ねた場合、ボットはユーザーがすでに購読しているかどうかを調べてから進める必要があります。このプロセスは現在ハードコーディングされています。ボットが理解できない場合、「すみません、まだ学習中で、xyzに関してはお手伝いできます」と言います。

ボットの作成と維持における課題は、これらのプロセスを管理することです。私たちはこれを行うことで、ボットがさまざまな実世界の状況に対応できるようにしています。しかし、これらのパスを作成することは、条件を確認し、アクションを実行し、会話の目標に到達するという複雑なプロセスを伴うため、複雑になることがあります。

私たちは、このプロセスを自動化するために言語モデル(LLMs)を使用しています。LLMsを使用すると、「推論」と「計画」の能力を使ってパスを作成することができます。これについてはこちらで詳しく学ぶことができます。

例えば、あなたがカスタマーサービスのエージェントだとします。ユーザーがあなたのサービスの購読方法を尋ねます。次のステップをどのように決定しますか?それは完全に自由な形式であることはできませんが、コストの考慮からあまりにもスクリプト化されすぎることもできません。しかし、以下のようなガイドラインがあったらどうでしょうか:

条件 - メールが存在する場合、ユーザーは購読できますツール - check_subscription、add_subscription

この場合、次のようなプランを作成することができます:

  1. ユーザーは購読を希望している、という声明に基づく - 「どうやって購読しますか?」
  2. ユーザーにメールを聞く - 「あなたのメールアドレスは何ですか?」
  3. 彼が有効なメールを提供した場合、ツールをトリガー - check_subscription
  4. ユーザーがまだ購読していない場合、トリガー add_subscription
  5. 成功または失敗を返答

これがLLMが行いたいことです - 参照し、行動に移すことができる「プラン」を作成すること。このプロセスについてはこちらで詳しく学ぶことができます。

では、モジュールテンプレートでのプランナーがどのように見えるか見てみましょう。

https://miro.medium.com/v2/resize:fit:525/1*zr1GwtkatJLacabou3TMEQ.png

上記のプランナーは、ツールや条件を使用して、設計時または実行時にプランやストーリーを作成することができます。これについての実例を研究で見てみましょう:

Untitled%2077bf2364b2f445d0b3e70ff2fc6d969a/Untitled%203.png)

KnowAgent: Knowledge-Augmented Planning for LLM-Based Agents

大規模な言語モデル(LLMs)は複雑な推論タスクにおいて大きな可能性を示していますが、それでも不足しているときには…

arxiv.org

https://miro.medium.com/v2/resize:fit:750/1*0fzrq1pycvs5i24e260RNw.png

KnowAgent: Knowledge-Augmented Planning for LLM-Based Agents

プランナーが信頼性のある推論でパスを決定するためのツールは何がありますか?

  1. 同様の発言によって引き起こされた過去のパス。
  2. 行動の企業グラフ、および行動間の依存関係。これにより、プランナーはある行動が正しい結果につながるかどうかを決定し、次にそれが次の行動につながり、再帰的に問題を解決するまで進むことができます。その関連のある実世界の統合については、こちらこちらを参照してください。これは、Neo4JとLangchainを使用した知識グラフと計画の統合であり、必ずしも計画パスに関連しているわけではありません。
  3. ユーザー/会話の現在の状態。

デプロイメントパターン3 - 複数のエージェント

多数のエージェントが存在する設定では、LLM(Large Language Models)ベースのジェネレーターに役割とタスクを割り当てることが目指されています。これらのジェネレーターは、特定のツールを装備しており、スマートな答えや解決策を提案するために協力して働きます。

明確に割り当てられた役割とサポートモデルのおかげで、各エージェントは "プラン" の一部または小さな目標を "エキスパート"に引き渡すことができます。彼らは次にこの出力を使用して次のステップを決定します。詳細については、最後にあるGPTPilotの例をご覧ください。

大規模言語モデルに基づくインテリジェントエージェントの探求:定義、方法、展望

Untitled%2077bf2364b2f445d0b3e70ff2fc6d969a/Untitled%204.png)

Intelligent agents could be a step towards artificial general intelligence (AGI). Therefore, researchers have…

このようなパターンは、実行チェーンの次のステップを定義する権限を制御する以下の通信パターンを使用して実行されます。これについての詳細は Orchestrating Agentic Systemsとしての詳しい情報はこちらをご覧ください。

https://miro.medium.com/v2/resize:fit:525/1*1_x-pFbfY5_o9OqES1BpgQ.png

エージェント/モジュールがどのようにコミュニケーションを取り、現実世界のCoPilotsを構築するかhttps://arxiv.org/pdf/2402.01680v1.pdf

マルチエージェントデザインの利点

  • 関心の分離:各エージェントは、それぞれの指示とフューショットの例を持つことができ、別々の微調整された言語モデルによって支えられ、さまざまなツールによって支援されます。タスクをエージェント間で分割すると、結果が向上します。各エージェントは、多数のツールから選択するのではなく、特定のタスクに集中できます。
  • モジュラリティ:マルチエージェントデザインでは、複雑な問題を管理可能な作業単位に分割し、それを専門のエージェントと言語モデルで対象とすることができます。マルチエージェントデザインでは、全体のアプリケーションを混乱させることなく、各エージェントを独立して評価し、改善することができます。ツールと責任をグループ化すると、より良い結果が得られます。エージェントは、特定のタスクに集中しているときに成功する可能性が高くなります。
  • 多様性:エージェントチームに強い多様性を持ち込むことで、異なる視点を取り入れ、出力を洗練させ、ホールシネーションとバイアスを避けます。(典型的な人間のチームのように)。
  • 再利用性:エージェントが構築されたら、これらのエージェントを異なるユースケースで再利用する機会があり、エージェントのエコシステムを考え、適切なコレオグラフィー/オーケストレーションフレームワーク(AutoGen、Crew.aiなど)を用いて問題を解決することができます。

デプロイメントパターン4 - CoPilot

CoPilotシステムで私が見る唯一の違いは、ユーザーとテスト機能とのインタラクションによって得られる学習です。

続きは後ほど...

フレームワークと実装

これらのCoPilotsを構築するフレームワークと、実際のCoPilotsの実装(GPT Pilotやaiderなど)を区別することは重要です。ほとんどのシナリオでは、オープンソースのCoPilotsはフレームワーク上に開発されておらず、すべての実装はゼロから開発されています。

レビューする人気のある実装:OpenDevinGPT Pilot

レビューする人気のある研究論文:AutoDevAgentCoder

人気のあるフレームワーク - FabricLangGraphDSPyCrew AIAutoGen、Meta GPT、Super AGIなど。

可能な限り、LLMベースのマルチエージェントについて以下の定義に従おうと試みます。

Deep Dive — アプリ

GPT Pilot

GPT Pilot プロジェクトは、創造的なプロンプトエンジニアリングとLLMのレスポンスを「レイヤー化」した流れで連鎖させることで、複雑に見えるタスクを実行する優れた例です。

レイヤー化されたコミュニケーション方式で働くいくつかのプロファイルがあります。下の緑のボックスをご覧ください:

https://miro.medium.com/v2/resize:fit:525/0*dDPEruR-hgVjnDrA

https://www.reddit.com/r/MachineLearning/comments/165gqam/p_i_created_gpt_pilot_a_research_project_for_a/

個々のエージェントは、階層的な方法で相互作用し、エージェントは一つのノードから次のノードにトリガーされ、決定を下すエージェントは存在しません。

https://miro.medium.com/v2/resize:fit:525/0*PzC0XCylWtjr3uxi

この製品は、効果的に動作するためのいくつかの素晴らしい戦略を使用しています:

  1. LLMがコードを作成できるように、タスクを小さく、管理しやすい部分に分割します。
  2. テスト駆動開発を使用し、ユーザーから有用な例を収集して正確で効果的な更新を保証します。
  3. コンテキストの巻き戻しとコードの要約を使用します。

設計プロセスは複雑であることがありますが、各エージェントを調整してコストを抑え、すべてが正確に動作するようにすることが非常に重要です。

Function Calling(機能)を用いて自律的なAIエージェントを構築する

Function Calling(機能)を用いて自律的なAIエージェントを構築する

https://towardsdatascience.com/build-autonomous-ai-agents-with-function-calling-0bb483753975

OpenAIは2023年7月からGPTモデルにFunction Callingを使用し始めました。現在、GoogleのGemini APIやAnthropicのClaudeなどもこれを使用しています。このツールは大規模言語モデル(LLM)に非常に役立ち、それをどのように使用するかを知ることは重要です。

Function Callingを実際にどのように使用するか、完全に自己機能するAIエージェントを作成する方法、さらに、Streamlitと接続してChatGPTのようなチャットインターフェースを作る方法も紹介します。このガイドはOpenAIに基づいていますが、GeminiのようなFunction Callingを使用する他のLLMでも使用できます。

Function Callingは何のために使うのか?

Function Callingは、開発者が関数(ツールとも呼ばれますが、これはモデルが行うアクション、つまり計算を行ったり、注文を行ったりすることを考えることができます)を記述し、モデルがそれらの関数を呼び出すための引数を含むJSONオブジェクトを出力することを知的に選択することを可能にします。より簡単な言葉で言えば、次のことが可能になります:

  • 自律的な意思決定:モデルは知的にツールを選択して質問に応答することができます。
  • 信頼性のあるパーシング:レスポンスはJSON形式で、より一般的な対話型のレスポンスではなく。初めて見た時には大したことがないように思えますが、これがLLMを、例えば構造化された入力を持つAPIなどの外部システムに接続することを可能にしています。

これにより、多くの可能性が開かれます:

  • 自律的なAIアシスタント:ボットは、問い合わせに対する回答を提供するだけでなく、顧客の注文や返品などの内部システムとのタスクを交換することができます。
  • 個人の研究アシスタント:旅行の計画を立てている場合など、アシスタントはWebを検索し、コンテンツをクロールし、オプションを比較し、結果をExcelでまとめることができます。
  • IoT音声コマンド:モデルは、ACの温度を調整するなど、検出された意図に基づいてデバイスを制御したり、アクションを提案したりすることができます。

Function Callingの構造

GeminiのFunction Callingドキュメンテーションから借りて、関数呼び出しには以下の構造があり、これはOpenAIでも同様に機能します。

https://miro.medium.com/v2/resize:fit:413/0*rrgnwWr7Hif3TfH_

GeminiのFunction Callingドキュメンテーションからの画像

  1. ユーザーがアプリケーションにプロンプトを発行します
  2. アプリケーションは、ユーザーが提供したプロンプトとFunction Declaration(モデルが使用可能なツールの説明)を渡します
  3. Function Declarationに基づいて、モデルは使用するツールと関連するリクエストパラメータを示します。モデルが関数を実際に呼び出すことなく、提案されたツールとパラメータのみを出力することに注意してください
  4. & 5. レスポンスに基づいて、アプリケーションは関連するAPIを呼び出します
  5. & 7. APIからのレスポンスは再度モデルにフィードされ、人間が読むことができるレスポンスを出力します
  6. アプリケーションは最終的なレスポンスをユーザーに返し、1から繰り返します。

これは複雑に見えるかもしれませんが、例を用いて詳細に説明される概念です。

アーキテクチャ

コードに深く入る前に、デモアプリケーションのアーキテクチャについて少し説明します。

解決策

ここでは、ホテルを訪れる観光客のためのアシスタントを構築します。アシスタントは次のツールにアクセスでき、これによりアシスタントは外部アプリケーションにアクセスできます。

  • get_items, purchase_item:APIを介してデータベースに保存された商品カタログに接続し、商品リストの取得と購入をそれぞれ行います。
  • rag_pipeline_func:検索強化生成(RAG)を備えたドキュメントストアに接続し、構造化されていないテキストから情報を取得します。例えば、ホテルのパンフレットなど。

https://miro.medium.com/v2/resize:fit:560/1*2aXhxSCJKyOVzkC0IJofiQ.png

技術スタック

  • Embedding モデル: all-MiniLM-L6-v2
  • ベクトルデータベース: HaystackのInMemoryDocumentStore
  • LLM: OpenRouter経由でアクセス可能なGPT-4 Turbo。OpenRouterを使用すると、VPNなしで香港から異なるLLM APIにアクセスできます。他のLLMを使用するためには、Function Callingをサポートしていれば、コードを少し変更して流れを適応させることが可能です、例えばGemini
  • LLMフレームワーク: Haystack。使いやすさ、素晴らしいドキュメンテーション、パイプライン構築の透明性を提供しています。このチュートリアルは実際には、同じトピックの彼らの素晴らしいチュートリアルの拡張版です。

サンプルアプリケーション

準備

Githubに移動して私のコードをクローンしてください。以下の内容はfunction_calling_demoノートブックにあります。

また、仮想環境を作成して有効化し、pip install -r requirements.txtを実行して必要なパッケージをインストールしてください。

初期化

まず、OpenRouterに接続します。あるいは、api_base_urlを上書きせずにオリジナルのOpenAIChatGeneratorを使用することも可能ですが、その場合はOpenAI APIキーが必要です。

import os
from dotenv import load_dotenv
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
from haystack.components.generators.utils import print\_streaming\_chunk

load_dotenv()
OPENROUTER\_API\_KEY = os.environ.get('OPENROUTER\_API\_KEY')

chat\_generator = OpenAIChatGenerator(api\_key=Secret.from\_env\_var("OPENROUTER\_API\_KEY"),
  api\_base\_url="https://openrouter.ai/api/v1",
  model="openai/gpt-4-turbo-preview",
        streaming\_callback=print\_streaming_chunk)

次に、chat_generatorが正常に呼び出されるかどうかをテストします

chat\_generator.run(messages=\[ChatMessage.from\_user("Return this text: 'test'")\])
\-\-\-\-\-\-\-\-\-\- The response should look like this ----------
{'replies': \[ChatMessage(content="'test'", role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'stop', 'usage': {}})\]}

ステップ1:データストアの構築

ここでは、アプリケーションと二つのデータソース:非構造化テキストのための ドキュメントストアと、API経由のアプリケーションデータベースとの間の接続を確立します。

パイプラインでドキュメントをインデックス化

documentsには、モデルがRetrival Augmented Generation (RAG)を実行するためのサンプルテキストを提供します。これらのテキストはエンベディングに変換され、インメモリのドキュメントストアに保存されます。

from haystack import Pipeline, Document
from haystack.document\_stores.in\_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.components.embedders import SentenceTransformersDocumentEmbedder

documents = \[
    Document(content="Coffee shop opens at 9am and closes at 5pm."),
    Document(content="Gym room opens at 6am and closes at 10pm.")
\]

document_store = InMemoryDocumentStore()

indexing_pipeline = Pipeline()
indexing\_pipeline.add\_component(
    "doc_embedder", SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
)
indexing\_pipeline.add\_component("doc_writer", DocumentWriter(document\_store=document\_store))

indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")

indexing_pipeline.run({"doc_embedder": {"documents": documents}})

これは、作成したサンプルとしての documents に対応する出力です

{'doc_writer': {'documents_written': 2}}

APIサーバーの起動

Flaskを使用したAPIサーバーは、SQLiteに接続するために db_api.pyの下に作成されます。ターミナルでpython db_api.pyを実行して起動してください。

https://miro.medium.com/v2/resize:fit:560/1*S4Ty6XitWFrOHY5wWENaXg.png

これは、正常に実行された場合にターミナルに表示されます

また、db_api.pyに初期データが追加されたことにも注意してください

https://miro.medium.com/v2/resize:fit:486/1*tASIXxNUJThtf0-HuPtISw.png

データベースのサンプルデータ

ステップ2:関数の定義

ここでは、モデルが関数呼び出し(関数呼び出しの構造で説明されているステップ4-5)の後に**呼び出すための実際の関数を準備します。

RAG関数

具体的には、rag_pipeline_funcです。これはモデルがドキュメントストアに格納されたテキストを検索して答えを提供するためのものです。まず、RAGの検索をHaystackパイプラインとして定義します

from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGenerator

template = """
Answer the questions based on the given context.

Context:
{% for document in documents %}
    {{ document.content }}
{% endfor %}
Question: {{ question }}
Answer:
"""

rag_pipe = Pipeline()
rag\_pipe.add\_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
rag\_pipe.add\_component("retriever", InMemoryEmbeddingRetriever(document\_store=document\_store))
rag\_pipe.add\_component("prompt_builder", PromptBuilder(template=template))

rag\_pipe.add\_component("llm", OpenAIGenerator(api\_key=Secret.from\_env_var("OPENROUTER\_API\_KEY"),
  api\_base\_url="https://openrouter.ai/api/v1",
  model="openai/gpt-4-turbo-preview"))

rag_pipe.connect("embedder.embedding", "retriever.query_embedding")
rag_pipe.connect("retriever", "prompt_builder.documents")
rag_pipe.connect("prompt_builder", "llm")

関数が機能するかテストしてください

query = “When does the coffee shop open?”
rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})

これにより、次の出力が得られます。モデルが提供したrepliesは、以前に提供したサンプルドキュメントからのものであることに注意してください。

{'llm': {'replies': \['The coffee shop opens at 9am.'\],
  'meta': \[{'model': 'openai/gpt-4-turbo-preview',
    'index': 0,
    'finish_reason': 'stop',
    'usage': {'completion_tokens': 9,
     'prompt_tokens': 60,
     'total_tokens': 69,
     'total_cost': 0.00087}}\]}}

その後、rag_pipeを関数に変換することができます。これにより、他の詳細を追加せずにrepliesのみが提供されます。

def rag\_pipeline\_func(query: str):
    result = rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})

    return {"reply": result\["llm"\]\["replies"\]\[0\]}

APIの呼び出し

データベースとの対話のために、 get_items および purchase_item 関数を定義します

db\_base\_url = 'http://127.0.0.1:5000'

import requests
import json

def get_categories():
    response = requests.get(f'{db\_base\_url}/category')
    data = response.json()
    return data

def get_items(ids=None,categories=None):
    params = {
        'id': ids,
        'category': categories,
    }
    response = requests.get(f'{db\_base\_url}/item', params=params)
    data = response.json()
    return data

def purchase_item(id,quantity):

    headers = {
    'Content-type':'application/json',
    'Accept':'application/json'
    }

    data = {
        'id': id,
        'quantity': quantity,
    }
    response = requests.post(f'{db\_base\_url}/item/purchase', json=data, headers=headers)
    return response.json()

ツールリストを定義する

関数が定義されたので、モデルがこれらの関数を認識し、それらがどのように使用されるかを指示するために、それらの説明を提供する必要があります。

ここではOpenAIを使用しているため、toolsは以下のようにフォーマットされています。これはOpen AIによって必要とされる形式に従っています。

tools = \[
    {
        "type": "function",
        "function": {
            "name": "get_items",
            "description": "Get a list of items from the database",
            "parameters": {
                "type": "object",
                "properties": {
                    "ids": {
                        "type": "string",
                        "description": "Comma separated list of item ids to fetch",
                    },
                    "categories": {
                        "type": "string",
                        "description": "Comma separated list of item categories to fetch",
                    },
                },
                "required": \[\],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "purchase_item",
            "description": "Purchase a particular item",
            "parameters": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "description": "The given product ID, product name is not accepted here. Please obtain the product ID from the database first.",
                    },
                    "quantity": {
                        "type": "integer",
                        "description": "Number of items to purchase",
                    },
                },
                "required": \[\],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "rag\_pipeline\_func",
            "description": "Get information from hotel brochure",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "The query to use in the search. Infer this from the user's message. It should be a question or a statement",
                    }
                },
                "required": \["query"\],
            },
        },
    }
\]

ステップ3:すべてをまとめる

これでFunction Callingのテストに必要な入力が揃いました!ここではいくつかのことを行います:

  1. モデルに初期のプロンプトを提供し、一部のコンテキストを与えます
  2. ユーザーが生成したメッセージのサンプルを提供します
  3. 最も重要なのは、tools内のチャットジェネレータにツールリストを渡すことです
context = f"""You are an assistant to tourists visiting a hotel.
You have access to a database of items (which includes {get_categories()}) that tourists can buy, you also have access to the hotel's brochure.
If the tourist's question cannot be answered from the database, you can refer to the brochure.
If the tourist's question cannot be answered from the brochure, you can ask the tourist to ask the hotel staff.
"""
messages = \[
    ChatMessage.from_system(context),

    ChatMessage.from_user("Can I buy a coffee?"),
    \]

response = chat\_generator.run(messages=messages, generation\_kwargs= {"tools": tools})
response
\-\-\-\-\-\-\-\-\-\- Response ----------
{'replies': \[ChatMessage(content='\[{"index": 0, "id": "call\_AkTWoiJzx5uJSgKW0WAI1yBB", "function": {"arguments": "{\\\"categories\\\":\\\"Food and beverages\\\"}", "name": "get\_items"}, "type": "function"}\]', role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish\_reason': 'tool\_calls', 'usage': {}})\]}

さあ、レスポンスを調査してみましょう。モデルが選択した関数と、選択した関数を呼び出すための引数の両方を、関数呼び出しが返すことに注目してください。

function_call = json.loads(response\["replies"\]\[0\].content)\[0\]
function\_name = function\_call\["function"\]\["name"\]
function\_args = json.loads(function\_call\["function"\]\["arguments"\])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
\-\-\-\-\-\-\-\-\-\- Response ----------
Function Name: get_items
Function Arguments: {‘categories’: ‘Food and beverages’}

別の質問が提示されたとき、モデルはより関連性の高い別のツールを使用します

messages.append(ChatMessage.from_user("Where's the coffee shop?"))

response = chat\_generator.run(messages=messages, generation\_kwargs= {"tools": tools})
function_call = json.loads(response\["replies"\]\[0\].content)\[0\]
function\_name = function\_call\["function"\]\["name"\]
function\_args = json.loads(function\_call\["function"\]\["arguments"\])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
\-\-\-\-\-\-\-\-\-\- Response ----------
Function Name: rag\_pipeline\_func
Function Arguments: {'query': "Where's the coffee shop?"}

再度、ここでは実際の関数が呼び出されていないことに注意してください、これは次に行うことです!

関数の呼び出し

その後、選択した関数に引数をフィードできます

available_functions = {"get_items": get_items, "purchase_item": purchase_item,"rag\_pipeline\_func": rag\_pipeline\_func}
function\_to\_call = available\_functions\[function\_name\]
function\_response = function\_to\_call(**function\_args)
print("Function Response:", function_response)
\-\-\-\-\-\-\-\-\-\- Response ----------
Function Response: {'reply': 'The provided context does not specify a physical location for the coffee shop, only its operating hours. Therefore, I cannot determine where the coffee shop is located based on the given information.'}

rag_pipeline_funcからの応答は、それをmessagesの下に追加することでチャットのコンテキストとして渡すことができ、モデルは最終的な答えを提供します。

messages.append(ChatMessage.from\_function(content=json.dumps(function\_response), name=function_name))
response = chat_generator.run(messages=messages)
response_msg = response\["replies"\]\[0\]

print(response_msg.content)
\-\-\-\-\-\-\-\-\-\- Response ----------
For the location of the coffee shop within the hotel, I recommend asking the hotel staff directly. They will be able to guide you to it accurately.

以上でチャットサイクルが完了です。

ステップ4:対話型チャットに変換する

上記のコードは関数呼び出しがどのように行われるかを示していますが、これをさらに一歩進めて対話型のチャットに変えたいと思います。

ここでは、ノートブック自体にダイアログを表示するよりも原始的な input() から、それをStreamlit を通じてレンダリングしてChatGPTのようなUIを提供するまで、それを行うための2つの方法を紹介します。

input()** ループ

このコードは Haystackのチュートリアル からコピーされており、モデルを素早くテストすることができます。注:このアプリケーションは関数呼び出しの考え方を示すために作られたものであり、同時に複数のアイテムの順序をサポートしたり、幻覚を起こさないなど、完全に堅牢であることを意味していません。

import json
from haystack.dataclasses import ChatMessage, ChatRole

response = None
messages = \[
    ChatMessage.from_system(context)
\]

while True:

    if response and response\["replies"\]\[0\].meta\["finish_reason"\] == "tool_calls":
        function_calls = json.loads(response\["replies"\]\[0\].content)

        for function_call in function_calls:

            function\_name = function\_call\["function"\]\["name"\]
            function\_args = json.loads(function\_call\["function"\]\["arguments"\])

            function\_to\_call = available\_functions\[function\_name\]
            function\_response = function\_to\_call(**function\_args)

            messages.append(ChatMessage.from\_function(content=json.dumps(function\_response), name=function_name))

    else:

        if not messages\[-1\].is_from(ChatRole.SYSTEM):
            messages.append(response\["replies"\]\[0\])

        user_input = input("ENTER YOUR MESSAGE 👇 INFO: Type 'exit' or 'quit' to stop\\n")
        if user_input.lower() == "exit" or user_input.lower() == "quit":
            break
        else:
            messages.append(ChatMessage.from\_user(user\_input))

    response = chat\_generator.run(messages=messages, generation\_kwargs={"tools": tools})

https://miro.medium.com/v2/resize:fit:560/1*A5nwZCw3JO_553WiBEGaDg.png

IDEでの対話型チャットの実行

それが機能している間、もっと見栄えのいいものを持つことを望むかもしれません。

Streamlit インターフェース

Streamlitはデータスクリプトを共有可能なWebアプリに変換し、私たちのアプリケーションに対してきちんとしたUIを提供します。上記に示したコードは、私のリポジトリのstreamlitフォルダの中にあるStreamlitアプリケーションに適応されています。

実行するには:

  1. まだ行っていない場合、python db_api.pyでAPIサーバーを起動します。
  2. 環境変数としてOPENROUTER_API_KEYを設定します。例:export OPENROUTER_API_KEY = ‘@YOUR API KEYに置き換えてください’ Linux / git bashで実行する場合を仮定しています。
  3. ターミナルでstreamlitフォルダに移動します。cd streamlitを使用します。
  4. streamlit run app.pyでStreamlitを実行します。新しいタブが自動的にブラウザで開かれ、アプリケーションが実行されるはずです。

Neo4j GDSでのセマンティック検索を向上させるトピックの抽出技術

Neo4j GDSでのセマンティック検索を向上させるトピックの抽出技術

https://medium.com/neo4j/topic-extraction-with-neo4j-graph-data-science-for-better-semantic-search-c5b7f56c7715

セマンティック検索は、ドキュメントに正確なキーワードが含まれていなくても、クエリの意味に一致するドキュメントを見つけるのに役立ちます。これは、特にRetrieval Augmented Generation (RAG)アプリケーションでの生成型AIに非常に有用です。

RAGアプリケーションは、セマンティック検索を使用して関連するドキュメントを見つけ、それらのドキュメントに基づいて大規模な言語モデルを使用して質問に答えます。

セマンティック検索は、テキストドキュメントの要約を数字、またはベクトルに変換して使用します。これらのベクトルは、Neo4jのようなベクトルストアに保存されます。ユーザーがクエリを作成すると、それはベクトルに変換されます。最も似ているベクトルを持つドキュメントが返されます。

長いドキュメントをベクトルの要約のためのより小さなチャンクに分解することは難しいです。チャンクが大きすぎると、一部の詳細が見逃されるかもしれません。チャンクが小さすぎると、重要な文脈が多くのチャンクに散らばる可能性があります。

この問題を解決する良い方法は、検索のためにドキュメントのトピックを使用することです。ユーザーの質問に一致するトピックを見つけ、それらのトピックを言及しているすべてのドキュメントを見つけます。

Neo4jはこれに強力なツールです。ドキュメントとそれらのトピックの知識グラフを作成できます。Neo4jはベクトル(数学的なオブジェクト)を使用してトピックとドキュメントを検索できます。Neo4j Graph Data Science (GDS)機能を使用すると、より良い検索結果のために重複するトピックを見つけて組み合わせることができます。

映画のプロットのデータベースでテストを行い、グラフベースのトピッククラスタを検索に使用すると、通常の検索よりも27%多くの関連結果を得られることがわかりました。

最近の映画のデータセット

テストでは、TMDB.orgから得た映画情報を使用し、Neo4j AuraDSグラフデータベースにロードしました。私は2023年9月1日以降に公開された映画のみを使用しました。これは、私が使用した大規模な言語モデルが映画について事前に何も知らないようにするためです。使用した映画は、大きなオスカー受賞作から学生の短編映画まで様々でした。

テストには16,156の映画ノードのデータセットを使用しました。各ノードにはタイトルとプロットの概要がありました。私は映画をDowload_TMDB_movies.ipynbノートブックのコードを使用してNeo4jにロードしました。これは私のプロジェクトリポジトリにあります。

https://miro.medium.com/v2/resize:fit:525/1*Mdb4j-qw71woKiOK6HJGRQ.png

Neo4j Bloomには、タイトルと概要のあるMovieノードが表示されます。

LLMで映画のテーマを抽出

私は映画のデータをNeo4jにロードし、LLMを使用して映画のタイトルと概要における主要なテーマを特定しました。これらは特定のオブジェクト、設定、またはアイデアに関連する可能性があります。これらは、人々が映画を検索するときに思い浮かぶ要素かもしれません。このタスクは、伝統的な名前エンティティ認識(ER)よりも少し簡単でした。なぜなら、私は見つかったテーマのタイプをカテゴライズしなかったからです。ERでは、アルゴリズムが日付、人、または組織などの特定のタイプのエンティティを見つけようとします。

こちらが私が使用した指示です:

You are a movie expert.
You are given the tile and overview of the plot of a movie.
Summarize the most memorable themes, settings, and public figures in the movie
into a list of up to eight one-to-two word phrases.
Only include the names of people if the person is a famous public figure.
Prioritize any phrases that appear in the movie's title.
You can provide fewer than eight phrases.
Return the phrases as a pipe separated list.
Return only the list without a heading.

このタスクのためにAnthropicのClaude 3 Sonnetモデルを選びました。理由は、彼らの新しいモデルをテストしたかったからです。他のLLMも良いかもしれません。

下記がインプットのサンプルです。

title: Maestro
overview: A towering and fearless love story chronicling the lifelong
relationship between Leonard Bernstein and Felicia Montealegre Cohn Bernstein.
A love letter to life and art, Maestro at its core is an emotionally epic
portrayal of family and love.

下記がLLMからの回答です。

meastro|family bonds|Emotional epic|Fearless passion|Lifelong relationship|
Towering love|Art devition

これらのLLMからの応答を、Neo4jのHAS_THEME関係によってMovieノードに接続されたThemeノードに変換しました。

https://miro.medium.com/v2/resize:fit:525/1*tTA9fGLipzFp1xhfbyGzOg.png

テーマ(青いノード)は、HAS_THEME関係で映画(黄色いノード)に接続します。

プロジェクトリポジトリ内のノートブックExtract themes.ipynbには、このプロセスのこのステップのコードが含まれています。

テーマの整理とテキスト埋め込みの生成

LLMはパイプで区切られたテーマのリストだけを完璧に返すわけではありませんでした。いくつかのケースでは、リストの前に「この映画の記憶に残るテーマ、設定、公的人物は:...」のような余分なテキストが付けられていました。場合によっては、LLMがテーマを見つけられなかったため、空のリストの代わりにそのことを伝える文を返しました。いくつかのケースでは、LLMは概要で説明された内容があまりにも過敏または露骨であると判断しました。これらの応答を整理するために使用したコードは、プロジェクトリポジトリのノートブックClean up themes and get embeddings.ipynbにあります。LLMの予測不能な性質のため、コードを実行する場合、テーマを整理するために少し異なる手順を取る必要があるかもしれません。

テーマを整理した後、私はOpenAIのtext-embedding-3-smallモデルを使用してテーマの埋め込みベクトルを生成しました。これらのベクトルはNeo4jの Theme ノードのプロパティとして保存しました。また、映画のタイトルと映画の概要を連結した文字列の埋め込みも生成しました。これらの埋め込みはNeo4jの Movie ノードのプロパティとして保存されました。

私は Theme ベクトルと Movie ベクトルのNeo4jベクトルインデックスを作成しました。これらのインデックスにより、私が提供するクエリベクトルに対するコサイン類似性に基づいて、最も類似した埋め込みベクトルを持つノードを効率的に見つけることができました。

Neo4jグラフデータサイエンスを使用したクラスタテーマ

LLMは言語を操作するのに驚くべき仕事をしますが、その出力を標準化するのは難しいです。私はLLMが近い同義語となるテーマをいくつか特定したことに気づきました。私はテーマのセマンティック検索をより効率的にするために、重複したテーマや非常に密接に関連したテーマを組み合わせることができることを望んでいました。テーマのクラスタリングと重複排除のすべてのコードは、プロジェクトリポジトリのノートブックCluster themes.ipynbに含まれています。

ステムを共有するテーマを見つけるための伝統的なNLPテクニックを使用する

私は伝統的な自然言語処理手法を使用して、同じ語幹に基づく単語を特定することから始めました。これは、語幹と呼ばれます。私はNLTK Python packageからのWordNet lemmatizerを使用して、共通の語幹を見つけるためにテーマから接頭辞と接尾辞を削除しました。私はグラフ内に Stem ノードを作成し、それらを HAS_STEM 関係でテーマにリンクしました。私は最も多くの Movie ノードに接続されている語幹グループ内の Theme ノードからの埋め込みベクトルを Stem ノードの埋め込みベクトルとして割り当てました。

https://miro.medium.com/v2/resize:fit:525/1*HUkXkvmIZ8W0z_pILbKI0Q.png

例としてステム(緑色のノード)と関連するテーマ(青色のノード)があります。

ステムを共有しない同様のテーマを探索する

グラフの中には、ステムを共有していないにもかかわらず、非常に似た意味を持つ他のグループのテーマがありました。それらを特定し始めるために、グラフからいくつかのテーマノードを選択し、その他のテーマノードと最も類似したエンベッディングを見つけるためにベクトルインデックスに対してクエリを実行しました。私は、下記のコサイン類似性の妥当なカットオフを見つけようとしていましたが、これはおそらく二つのテーマの同義語ではありません。また、データセット内でテーマが持つ可能性のある近い同義語の数のカットオフを見つけようとしていました。

ここにテーマ「水中」の例があります:

https://miro.medium.com/v2/resize:fit:277/1*WQgBz09nYTrEiAX0g7EtZg.png

「underwater」に最も類似したテーマを示す表。

私は「undersea」、「sub aquatic」、「underwater world」、「undersea world」はすべて基本的に「underwater」と同じものだと思いました。 「underwater music」テーマは、私が別のテーマとして保持したい新しいアイデアを導入し始めます。 0.880904以上の類似性のカットオフまたは4以下のトップkは、「underwater」と一緒にまとめるべきでないテーマを除外します。

ここに「fast food」のテーブルの最初の部分があります。

https://miro.medium.com/v2/resize:fit:275/1*KnT2g3kezhIec-nnxnz4Gg.png

「fast food restaurant」と「Fast-food burger」の最初の2つのテーマは、1つの概念として十分に関連していると思いました。「street food」のテーマは、異なるグループにするに値するように思えます。0.804882以上の類似性カットオフまたは2以下のトップkを維持すれば、street foodとfast foodの違いを保つことができます。

「Africa」に最も似ているテーマは「Asia」でした。これはまったく別の大陸です。これら二つのテーマがクラスタに結合されることがないようにするには、0.829958以上の類似性カットオフが必要となります。

https://miro.medium.com/v2/resize:fit:261/1*jipax5B-Hba_A1Mxnb1csA.png

「Africa」に関する類似したテーマを示す表。

K Nearest Neighborsアルゴリズムを使用してIS_SIMILAR関係を作成する

いくつかの他のテーマの類似性を見てみた後、類似度のカットオフ値を0.83、トップkを2にすることにしました。Stemノードと関連性のないすべてのThemeノードを含むグラフの投影を作成しました。その後、選択した類似度のカットオフ値とトップkの閾値を超えるノード間にIS_SIMILAR関係を追加するために、K Nearest Neighborsアルゴリズムを使用してグラフの投影を変更しました。

連結の弱いコンポーネントコミュニティをテストする

Weakly Connected Componentのコミュニティ検出アルゴリズムは、無向パスによって接続されているノードを同じコンポーネントに割り当てます。IS_SIMILAR関係を作成したときに非常に厳格な基準を選んだ場合、WCCは類義語のクラスターを特定するための実行可能なアプローチであったかもしれません。AがBの類義語で、BがCの類義語であるという推移的な仮定を立てることができます。つまり、AはCの類義語です。

WCCを実行したところ、結果は理想的ではありませんでした。WCCの類似度の閾値を0.875に設定してみました。これは、私がKNN用に使用したカットオフ値よりも高い値です。非常に緊密に関連しているテーマだけがまとめられるようにしたかったのです。最大のWCCコミュニティには29のテーマが含まれていました。それらすべてにはChristmasに関する何かが含まれていましたが、「Christmas Terror」と「Christmas Magic」は、おそらく非常に異なる雰囲気の映画に関連するでしょう。

https://miro.medium.com/v2/resize:fit:302/1*JpYoQXkWCVksSf7paIkGIw.png

0.875のカットオフを持つ最大のWCCコミュニティのテーマ。

高い類似性のカットオフ、例えば0.875を設定すると、「乾燥した風景」や「荒廃した風景」のようなテーマが別々のコミュニティに分かれてしまうという問題がありました。これらは私が考えるに一緒にあるべきテーマです。そこで、WCCの代わりにLeidenコミュニティ検出アルゴリズムを試すことにしました。

Leidenコミュニティは大きなまたは小さなコミュニティに対して調整可能です

Leidenコミュニティ検出アルゴリズムは、コミュニティ内の関係がランダムに分布していると想定した場合よりも、コミュニティ内で始まりと終わりが高い割合を持つコミュニティを識別します。

ガンマというパラメータを調整することで、Leidenが生成するコミュニティの数を大きくしたり小さくしたりすることができます。私はガンマを、「コミュニティとしてラベル付けされたノード群が、関係がランダムに分布していると想定した場合よりもどれだけ相互接続されているか」を指定するものと考えています。ガンマが増加すると、クラスタ定義の基準がより厳格になります。高いガンマ値では、大規模で疎に接続されたコミュニティは基準をクリアできず、小規模で密に接続されたコミュニティが残ります。

Leidenを実行する前に、グラフプロジェクションを修正する必要がありました。Leidenは無向グラフで実行する必要がありますが、KNNは有向関係を生み出します。私はgds.graph.relationships.toUndirected()手順を使用して、グラフプロジェクション中のIS_SIMILAR関係をUNDIRECTED_SIMILAR関係に変換しました。

Leidenは重み付きの関係で実行することができます。K最近傍用に選んだ類似性カットオフのため、UNDIRECTED_SIMILAR関係上のすべての類似性スコアは0.83から1.0の間でした。私はmin-maxスケーリング式を使用して、これらの重みを0.0から1.0の範囲に変換し、近い接続を遠い接続よりも相対的に影響力があるようにしました。

私はアルファ値が1.0、2.0、4.0、8.0、16.0、32.0、64.0、128.0、256.0、512.0、および1024.0のLeidenを実行するテストを行いました。

https://miro.medium.com/v2/resize:fit:525/1*XelqPiYCAJcdBjO73tudVw.png

ガンマの増加値におけるコミュニティサイズの表。

この表から、ガンマを増加させると最大のコミュニティのサイズが93ノードから9ノードへと減少することがわかります。私は、小さなコミュニティの方がテーマ間の微妙な違いを捉える可能性が高いと考えました。

私はいくつかのテーマを選び、ガンマの様々なレベルでそのコミュニティに含まれる他のテーマを見てみました。

https://miro.medium.com/v2/resize:fit:525/1*B8S5B8fsg1TgMa0f03oYjQ.png

ガンマ値が32.0の場合、Christmasに関連するすべてのテーマは何らかの形で休日と関連しています。しかし、「Christmas Terro」と「Christmas Miracle」は、私がWCCを試したときと同様にまだ一緒にいました。ガンマを128以上に上げると、他のテーマは消えましたが、「Xmas」および「Christmas」は一緒に残りました。

さまざまなガンマレベルでいくつかの異なるテーマを見ることに基づいて、私は同じライデンコミュニティ内のテーマをガンマレベル256.0で同じテーマグループに収集することを選びました。各コミュニティに対して、ThemeGroupノードを作成しました。コミュニティのThemeノードをThemeGroupに関連付けるIN_GROUP関係を作成しました。

https://miro.medium.com/v2/resize:fit:525/1*qZ8u3fxuEUW9N3uwqbSEaQ.png

失恋に関連するテーマ(青いノード)を持つ例のテーマグループ(ピンクのノード)。

テーマグループが整えられたことで、私は同じか類似のテーマを共有する映画をグラフから検索し始めることができました。以下の例では、2つの映画が「Opulent neighborhood」というテーマを共有しています。また、両方のテーマが同じテーマグループ内にあるため、「opulent home」というテーマを持つ第3の映画とも関連しています。

https://miro.medium.com/v2/resize:fit:525/1*nmezXilD3m5q058HBN8fJA.png

映画(黄色のノード)の視覚化とテーマ(青色のノード)がテーマグループ(ピンクのノード)内にあります。

Theme Groupのサマリー

一旦テーマグループが決まったら、私はOpenAIのChatGPT-3.5-turboにテーマグループの短い説明を書いてもらいました。このタスクのコードは、Summarize theme groups.ipynbノートブックにあります。私は「これらの映画はテーマ...を扱っています」というパターンに従って説明の最初の文を提供しました。スターターの文と一緒に、私はLLMにテーマを含む最大20のタイトルと概要のサンプルを提供しました。これが私が使用したプロンプトです。

You are a movie expert.
You will be given a list of information about movies and the first
sentence of a short paragraph that summarizes themes in the movies.
Write one or two additional sentences to complete the paragraph.
Do not repeat the first sentence in your answer.
Use the example movie information to guide your description of the
themes but do not include the titles of any movies in your sentences.

「locals vs visitors」、「guest」、「guests」、「visitor」、「visitors」というテーマグループを含むLLMは、次のような要約を作り出しました:

これらの映画は、「locals vs visitors」、「guest」、「guests」、「visitor」、「visitors」というテーマを扱っています。これらの映画は、地元の人々と訪問者との間のダイナミクスを探求し、異なる背景を持つ個々の人々が交流する際に生じる緊張と興奮をしばしば強調します。物語は、もてなし、関係性、そしてゲストとホスト、訪問者と地元の人々との間に形成される予想外のつながりの複雑さに深く踏み込みます。「locals vs visitors」や「guest」のテーマが物語の中に巧妙に織り込まれており、人間の経験と交流の豊かな織物を提供しています。

「scamming」、「Swindling」、「scam artist」、「swindle」というテーマグループは次のような要約を作り出しました:

これらの映画は、「Swindling」、「scam artist」、「scamming」、「swindle」というテーマを扱っています。これらの映画は、登場人物が自身の目標を達成するために詐欺や詐欺行為に訴える、欺瞞と操作の世界に深く踏み込みます。物語は、これらの行動の結果を探求し、人間の性質の複雑さと個人的な利得の追求における正と不正のぼやけた境界線を明らかにします。欺瞞を通じた自己発見の旅は、社会の暗い側面と個々が望みを叶えるためにどこまで行くかを明らかにします。

各要約は、スターターセンテンスに関連するすべてのテーマを含んでいたので、要約からキーワードが漏れることはありませんでした。LLMは、映画の例から引き出して1つまたは2つの単語のテーマを周りの文脈を提供するのに良い仕事をしたようです。

このLLMが生成したテーマグループの長い説明に加えて、私は「~についての映画」というパターンに従った短い説明を作成しました。上記のテーマグループの短い要約は「詐欺、詐欺師、詐欺行為、ねずみ講についての映画」でした。これにより、LLMによって提供される追加の文脈がより良い検索結果をもたらすかどうかをテストすることができました。

私は各テーマグループに関連した3つのベクトルプロパティを作成しました。OpenAIのtext-embedding-3-smallモデルを使用して、短いサマリーと長いサマリーの埋め込みを生成しました。3つ目の埋め込みは、テーマグループに関連するテーマキーワードの埋め込みの平均でした。

異なるベクトルインデックスに基づいてリトリーバーを比較する

この段階で、映画コンテンツに関連する5つのベクトルインデックスを作成しました:

  • タイトルと概要をカバーするMovieノードのインデックス
  • テーマワードをカバーするThemeノードのインデックス
  • テーマの長いLLM生成サマリーをカバーするThemeGroupノードのインデックス
  • *テーマの短いサマリ”Movies about …”をカバーするThemeGroupノードのインデックス
  • ThemeGroupに関連するThemeノードからのテーマベクトルの平均をカバーするThemeGroupノードのインデックス

すべてのインデックスを同じ一連の質問でテストし、どのインデックスが関連する映画を最もよく取り出すことができるかを見ました。各質問について、各インデックスが最大50本の映画を見つけることを許しました。私は、各インデックスの質問に一致する結果の映画の数を数えることによってリコールに焦点を当てました。私はその指標を選んだのは、取得の下流のプロセスが誤検出をフィルタリングし、ドキュメントをランク付けすることができるからです。これらの比較のためのコードは、Compare retrievers.ipynbノートブックにあります。

私がテストした質問は以下の通りです:

  • 画家についてのドキュメンタリーは何がありますか?
  • クラシック音楽についての映画は何がありますか?
  • アイスホッケーについての映画は何がありますか?
  • 野球についての映画は何がありますか?
  • どの映画が犯人ですか?
  • ダークコメディーは何がありますか?
  • 1960年代のヨーロッパを舞台にしたものは何がありますか?
  • 先コロンブス時代のアメリカについての映画は何がありますか?
  • 鳥についての映画は何がありますか?
  • 犬についての映画は何がありますか?

Neo4j知識グラフからのデータの取得は、私がクエリに似たベクトルをセマンティックに検索し、グラフの関係をたどって探していた映画を見つけるための柔軟性を与えてくれました。

映画のインデックスについては、クエリベクトルに最も近いベクトルを持つ50本の映画を取得しました。これは最も基本的な取得でした。以下は私が使用したCypherクエリです。

CALL db.index.vector.queryNodes("movie\_text\_vectors", 50, $query_vector)
YIELD node, score
RETURN $queryString AS query,
"movie" AS index,
score, node.tmdbId AS tmdbId, node.title AS title, node.overview AS overview,
node{question: $queryString, .title, .overview} AS map
ORDER BY score DESC

テーマインデックスのために、最も近いベクトルを持つ25のテーマノードを取得し、グラフ内のHAS_THEME関連性を使用してそれらのテーマに関連するすべての映画を見つけました。クエリとのテーマの類似性によってソートされ、次にクエリとの映画の類似性によってソートされたテーマに関連するトップ50の映画を返しました。このCypherクエリを使用しました。

CALL db.index.vector.queryNodes("theme_vectors", 50, $query_vector)
YIELD node, score
MATCH (node)<-\[:HAS_THEME\]-(m)
RETURN $queryString AS query,
"theme" AS index,
collect(node.description) AS theme,
gds.similarity.cosine(m.embedding, $query_vector) AS score,
m.tmdbId AS tmdbId, m.title AS title, m.overview AS overview,
m{question: $queryString, .title, .overview} AS map
ORDER BY score DESC, gds.similarity.cosine(m.embedding, $query_vector) DESC
LIMIT 50

テーマグループに関連するインデックスについて、クエリベクトルに最も近い25のテーマグループを見つけました。IN GROUPの関係を使用して、それらのテーマグループに関連するすべてのテーマを見つけました。次に、HAS_THEMEの関係を使用して、それらのテーマに関連するすべての映画を見つけました。クエリに対するテーマグループの類似性、そして映画の類似性によってソートされた上位50の映画を返しました。以下は、テーマグループの長いサマリーインデックスに基づいて映画を取得するために使用したCypherクエリです。短いサマリーと平均インデックスのクエリは、インデックス名を除いて同じでした。

CALL db.index.vector.queryNodes("theme\_group\_long\_summary\_vectors",
  25, $query_vector) YIELD node, score
MATCH (node)<-\[:IN_GROUP\]-()<-\[:HAS_THEME\]-(m)
RETURN $queryString AS query,
"theme\_group\_long" AS index,
collect(node.descriptions) AS theme,
gds.similarity.cosine(m.embedding, $query_vector) AS score,
m.tmdbId AS tmdbId, m.title AS title, m.overview AS overview,
m{question: $queryString, .title, .overview} AS map
ORDER BY score DESC,
gds.similarity.cosine(m.embedding, $query_vector) DESC
LIMIT 50

私はChatGPT-3.5-turboに、取得した映画が質問にマッチするかどうかを判断してもらいました。また、私自身も映画を判断して、それが私の質問の意図に合っているかどうかを見ました。場合によっては、微妙な判断が求められました。野球の質問に対してソフトボールの映画はカウントすべきでしょうか?ホラー映画はどの程度ダークコメディとしてカウントされるべきでしょうか?私は一貫性を持って答えを出すように努め、映画の答えを評価した際にどのインデックスが映画を返したかを知らないようにしました。私が映画が質問にマッチするかどうかについてChatGPTと74%の時間で一致するという結果が出ました。全体として、私はChatGPTよりも取得した映画の多くを質問に関連があると分類しました。

すべての問いに関連すると私が判断した映画の総数を合計すると、テーマグループインデックスが他を明らかに上回り、映画インデックスよりも27%多くの関連映画を見つけ出しました。また、Graph Data Scienceを使用してThemeGroupノードを作成すると、LLMによって生成された生のThemeノードを使用するよりも良い結果を得られることがわかりました。

https://miro.medium.com/v2/resize:fit:489/1*jbGlHhNbvvNvixdzsKGFEA.png

インデックスによる検索結果の全体結果。

以下のチャートは、クエリごとの結果の内訳を示しています。長いサマリーインデックスは常に良好なパフォーマンスを示し、常に基本的な映画インデックスを上回りましたが、すべての質問でトップのインデックスではありませんでした。

テーマインデックスはダークコメディを見つけるのに非常に優れていました。それは「ホラーコメディ」のテーマを独自に見つけ、ダークネスについてのテーマを優先する長いサマリーインデックスをパスしました。

短いサマリーインデックスは、クラシック音楽の質問で他のインデックスを上回りました。それは「作曲家」という言葉を含むテーマグループを他のインデックスが見逃したのを見つけました。

https://miro.medium.com/v2/resize:fit:515/1*n1VttniTUrO6zKvWqvn_7w.png

各インデックスに対する質問ごとの結果。

ChatGPT-3.5-turboが関連性があると考えた映画の総数を見ると、長い要約インデックスが他を上回っていましたが、映画インデックスを13%しか上回りませんでした。これは、LLMが取得したドキュメントを分析し、ユーザーに結果を返すときに何を言い換えるかを決定するRetrieval Augmented Generationアプリケーションで見ることができるより現実的な期待値かもしれません。

https://miro.medium.com/v2/resize:fit:489/1*vpWNwJTWMD5lUL3B66NOEA.png

結論

私は、意味的検索のトピックモデリングにNeo4j Graph Data Scienceを使用する3つの主要な利点を見つけました。

  1. Neo4j Graph Data Scienceで作成された長いサマリーテーマグループインデックスは、他のインデックス戦略よりも多くの関連検索結果を提供しました。
  2. テーマグループを作成することで、非構造化テキストをデータの分析と視覚化に使用できる知識グラフに変えました。
  3. Neo4jでトピックモデリングを実行することは、トピックの抽出、語幹化、トピッククラスタリングの各層を通じて私の作業を整理し、表示するための素晴らしい方法を提供しました。データの取り込みとクリーニング、アルゴリズム分析、ベクトルベースの取得がすべて同じグラフプラットフォームで行われました。

OpenDevinの紹介:AIによるソフトウェア開発の可能性

Introduction to OpenDevin: The Aspiring AI-Powered Software Development Platform | by Elmo | May, 2024 | AI Advances

OpenDevin logo

OpenDevin is an innovative, open-source project that aims to revolutionize software development by introducing an autonomous AI software engineer. This AI assistant has the mission to tackle complex engineering tasks and actively collaborate with human users on various software projects. It’s a work in progress, but the alpha version, already released, offers a clear glimpse into the exciting future of collaborative AI development.

Break. End of the introduction. More details in:

  1. OpenDevin’s Core Functionalities: What it Does (or at least What it Aspires to Do)
  2. Getting Started with OpenDevin
  3. Understanding OpenDevin’s Architecture
  4. OpenDevin’s Agents: Specialized AI Assistants
  5. OpenDevin’s Potential Applications
  6. Conclusion

Imagine having a tireless teammate who can tackle complex coding tasks, understand your intent, and offer suggestions and explanations alongside the way. That’s the potential future of OpenDevin. This project aspires to create a comprehensive AI assistant that can:

  • Automate Repetitive Tasks: OpenDevin aims to handle various coding chores, including generating code, fixing bugs, and refactoring existing code, freeing up developers to focus on more strategic aspects of the project.
  • Collaborate Like a Partner: OpenDevin isn’t just an automated tool; it strives to be a collaborative partner. It will understand your intent through natural language processing and provide intelligent suggestions, explanations, and code examples tailored to your specific needs.
  • Continuously Learn and Adapt: OpenDevin aspires to be a fast learner: by interacting with users and analyzing past projects, it’s designed to continuously improve its performance and adapt to different coding styles and project requirements.

OpenDevin primarily operates within a Docker container, providing a controlled environment for its operations. Here’s a basic guide to get started:

  1. Set up your workspace: Choose a directory where OpenDevin can access and modify your code.
  2. Configure your LLM: Select and configure your desired Large Language Model (LLM) by setting relevant environment variables like LLM_API_KEY and LLM_MODEL. OpenDevin supports various LLMs, including GPT-4, Claude, Azure OpenAI, Google Gemini/Vertex, and local models like the ones provided by Ollama.
  3. Run OpenDevin: Use the provided Docker command with appropriate environment variables to launch OpenDevin.
  4. Access the interface: Once running, OpenDevin is accessible through a web interface at http://localhost:3000.

As shown in the documentation, to start you just need to use these lines of code:

export WORKSPACE_BASE=$(pwd)/workspace;
docker run \
    --pull=always \
    -e SANDBOX\_USER\_ID=$(id -u) \
    -e WORKSPACE\_MOUNT\_PATH=$WORKSPACE_BASE \
    -v $WORKSPACE\_BASE:/opt/workspace\_base \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -p 3000:3000 \
    --add-host host.docker.internal=host-gateway \
    ghcr.io/opendevin/opendevin:0.5

An important note: while OpenDevin operates within a Docker sandbox for isolation, ensure your chosen workspace directory is backed up as files might be modified or deleted during operation!

Also, it is important to note that OpenDevin will issue many prompts to the configured LLM, which may incur costs. Therefore, setting spending limits and monitoring usage is essential.

OpenDevin’s system architecture consists of two primary components:

Frontend: This is the user interface where developers interact with OpenDevin. It allows users to input tasks, view code suggestions, and manage project settings.

Backend: This component handles the core functionalities of OpenDevin, including:

  • LLM interaction: Sending prompts to the chosen LLM and receiving responses.
  • Agent execution: Utilizing various agents to process information and perform actions.
  • Workspace management: Interacting with the code within the designated workspace directory.

Their interconnection and their building blocks are shown in the following image.

Architecture of OpenDevin

The key components of OpenDevin’s system architecture are:

  • Chat Interface: User interface for interacting with the chat application
  • App: Main component that houses various sub-components for user experience
  • settingsService: Manages user preferences and personalization options
  • chatService: Handles core chat functionalities (message transmission, encryption, storage)
  • socket: Enables real-time communication between frontend and backend
  • Client WS: WebSocket on the client-side for real-time data exchange
  • Server WS: WebSocket on the server-side for real-time data exchange
  • VITE_TERMINAL_WS_URL: Configuration element specifying the web address for WebSocket connection
  • AgentController: Manages automated agents or chatbots for intelligent chat functionalities

OpenDevin utilizes different agents, each with unique capabilities to tackle specific tasks:

  • CodeAct Agent: The CodeAct Agent embodies the concept of consolidating LLM agents’ actions into a unified code action space. This agent can converse with users, execute Linux bash commands, and run Python code through an interactive interpreter. It leverages OpenDevin’s plugin system, including the Jupyter plugin for IPython execution and the SWE-agent tool plugin for software development tasks. This agent is shown in action in the following image.

CodeAct Agent

  • Monologue Agent: The Monologue Agent harnesses the power of long-term and short-term memory to complete tasks efficiently. It stores long-term memory as a LongTermMemory object, allowing the model to search for relevant examples from the past. Short-term memory is maintained as a Monologue object, which the model can condense as needed. This agent supports a wide range of actions, including running commands, reading/writing files, recalling information, browsing URLs, and pushing to GitHub.
  • Planner Agent: The Planner Agent employs a unique prompting strategy to create long-term plans for solving problems. It receives previous action-observation pairs, the current task, and hints based on the last action taken at every step. This agent checks if the current step is completed and either returns an AgentFinishAction or creates a plan prompt to send to the model for inference.

OpenDevin’s capabilities (and similar projects) open doors for various applications within the software development lifecycle:

  • Rapid Prototyping: Quickly generate and iterate on code prototypes based on user specifications and feedback.
  • Automated Code Generation: Automate repetitive coding tasks, such as creating boilerplate code or implementing standard algorithms.
  • Intelligent Code Completion: Receive context-aware code suggestions and completions to improve coding efficiency and accuracy.
  • Bug Detection and Fixing: Identify and fix bugs in existing code with AI-powered analysis and suggestions.
  • Code Refactoring: Improve code quality and maintainability by automatically refactoring existing code.
  • Learning and Education: Tools like OpenDevin can serve as a valuable aid for learning new programming languages and concepts by providing interactive explanations and code examples.

OpenDevin represents a nice challenge in AI-assisted software development and it’s splendidly done in an open-source way. As OpenDevin continues to evolve, it promises to give super power to developers, accelerating development cycles, and ultimately shaping the future of software development. When will this juicy fruit be ripe enough to be eaten, coff, used?

( text taken from this page from https://didyouknowbg8.wordpress.com/ )

ローカルのSWEエージェント環境を5分以内にをセットアップする方法

ローカルのSWEエージェント環境を5分以内にをセットアップする方法

https://medium.com/@init/how-to-set-up-your-local-swe-agent-dev-environment-in-5-minutes-or-less-278072100e62

SWE-agentは、プリンストン大学のプロジェクトであり、実際のGitHubのリポジトリに入り、問題を自己解決するツールです。これは、GPT-4のような言語モデルをソフトウェアエンジニアリングの助けに変え、SWE-bench データセットのソフトウェア問題の12%以上を修正できました。 12%はそれほど多くはないかもしれませんが、古い記録のわずか3.79%から大幅に跳ね上がっています。これは、AIがソフトウェアの作成と維持にますます有用になっていることを示しています。

これは、dev containersを使用したローカル開発環境でSWE-agentを使用するためのガイドです。

必要なものはDockerとVS Codeだけです。

  • セットアップ: 自分のローカルdev環境を設定する方法をステップバイステップで説明します
  • 推論ステップのデバッグ方法: 問題を解決している途中でエージェントを一時停止する方法を示します

このブログ投稿は2部構成になっています:

これは、SWE-agentを始めるための非公式のCONTRIBUTING.mdと考えてください。

このガイドのコードはGitHubで利用でき、コミット580efbeでテスト済みです。

セットアップ

ステップ1:Makefileを作成する

SWEエージェントプロジェクトを稼働させるには、それぞれが独自のコマンドと設定を持ついくつかのステップが必要です。 このプロセスを合理化し、エラーの可能性を最小限に抑えるために、Makefileを使用します。 このアプローチにより、セットアップ手順をシンプルで実行可能なコマンドにまとめることができ、全体のプロセスが問題になる可能性を減らします。

まず、Makefileを作成しましょう。ターミナルを開き、次のコマンドを実行します:

touch makefile

このコマンドは、ターゲットを定義する空のMakefileを作成します。 次に、Makefileに次のターゲットを追加しましょう:

  • create-keys-config: このターゲットは、keys.cfgファイルがすでに存在するかどうかを確認します。存在しない場合、ターゲットはそれを作成し、さまざまなAPIキーのプレースホルダーでそれを埋めます。このファイルは、SWEエージェントが使用する可能性のあるさまざまなモデルへのアクセスを設定するために使用されます。
 create-keys-config:
   @if \[ -f keys.cfg \]; then \
          echo "keys.cfg file already exists. Skipping creation."; \
      else \
          echo "Creating keys.cfg file..."; \
          echo "GITHUB_TOKEN: 'GitHub Token Here (required)'" \> keys.cfg; \
          echo "OPENAI\_API\_KEY: 'OpenAI API Key Here if using OpenAI Model (optional)'" >\> keys.cfg; \
          echo "ANTHROPIC\_API\_KEY: 'Anthropic API Key Here if using Anthropic Model (optional)'" >\> keys.cfg; \
          echo "TOGETHER\_API\_KEY: 'Together API Key Here if using Together Model (optional)'" >\> keys.cfg; \
          echo "AZURE\_OPENAI\_API_KEY: 'Azure OpenAI API Key Here if using Azure OpenAI Model (optional)'" >\> keys.cfg; \
          echo "AZURE\_OPENAI\_ENDPOINT: 'Azure OpenAI Endpoint Here if using Azure OpenAI Model (optional)'" >\> keys.cfg; \
          echo "AZURE\_OPENAI\_API_VERSION: 'Azure OpenAI API Version Here if using Azure OpenAI Model (optional)'" >\> keys.cfg; \
          echo "OPENAI\_API\_BASE_URL: 'LLM base URL here if using Local or alternative api Endpoint (optional)'" >\> keys.cfg; \
          echo "keys.cfg file has been created."; \
      fi
  • install-editable:このターゲットは、SWE-agentパッケージを編集可能なモードでインストールする責任があります。これは、パッケージのコードに対して行う任意の変更がすぐにインストールされたパッケージに影響を与えることを意味します。これにより、開発とテストが容易になります。
 install-editable:
   @echo "Installing the SWE-Agent package in editable mode..."
   @pip install -e .
   @echo "Installation complete."
  • docker-imagesのビルド: このターゲットは、SWEエージェントが問題の修正を試みるためのコマンドを実行するために使用するdockerイメージをビルドするスクリプトを実行します。これはエージェントが遊ぶためのサンドボックスと考えてください。
 build-docker-images:
   ./setup.sh
  • セットアップ:これは、環境が正しく設定されていることを保証するために、先に定義されたターゲットを順序通りに実行する集約ターゲットです。make setupを呼び出すことで、編集可能なモードでのインストールを開始し、キーコンフィギュレーションファイルを作成し、必要なDockerイメージをビルドします。
 setup:
   @make install-editable
   @make create-keys-config
   @make build-docker-images

ステップ2:Devコンテナ設定の作成

次に、開発環境を表すコンテナを設定するファイルを作成します。このファイルはdevcontainer.jsonという名前で、プロジェクトのルートにある.devcontainerフォルダに配置されます。このファイルはVS Codeによって、開発環境を包含する開発コンテナを作成するために使用されます。

設定方法は以下の通りです:

まず、プロジェクトのルートディレクトリにいることを確認します。

次に、次のコマンドを実行して.devcontainerフォルダを作成します:

 mkdir .devcontainer

次に、devcontainer.json ファイルを作成します:

 touch .devcontainer/devcontainer.json

エディタで devcontainer.json ファイルを開き、次の設定を貼り付けてください:

{
    "name": "Python 3",
    "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bookworm",
    "features": {
     "ghcr.io/devcontainers/features/docker-in-docker:2": {}
    },
    "customizations": {
     "vscode": {
      "extensions": \[
       "GitHub.copilot",
       "GitHub.copilot-chat",
       "GitHub.vscode-pull-request-github",
       "eamodio.gitlens",
       "ms-python.python",
       "ms-toolsai.jupyter"
      \]
     }
    },
    "postCreateCommand": "make setup"
   }

このファイルでは行われている事を説明します。

  • 環境の選択:私たちは開発環境の基盤としてPython 3.12を指定しています。
  • 開発ツールfeaturesセクションではDocker-in-Dockerを有効にしており、devコンテナ内にコンテナを作成することが可能です。これは、SWE-agentのように実行の一部としてコンテナのビルドと実行が必要なプロジェクトに特に有用です。
  • VS Code Extensionscustomizations.vscode.extensionsリストには、いくつかの便利なVS Code拡張機能が含まれています。これには、AIによるコードの提案を行うGitHub Copilot、強化されたGitの洞察を提供するGitLens、改善されたPython言語サポートを提供するPython拡張機能、ノートブックの作業に使用するJupyterが含まれています。これらの拡張機能はdevコンテナに自動的にインストールされ、すぐに豊かな開発体験を提供します。
  • 自動設定postCreateCommandはVS Codeにmake setupをdevコンテナを作成した後に実行するよう指示します。このコマンドは、前のステップで定義したMakefileのターゲットを実行し、コンテナ内の環境設定を自動化します。つまり、devコンテナを起動するとすぐに、それは完全に設定され、SWE-agentの開発作業にすぐに使える状態になります!

これらの手順に従うことで、SWE-agentの開発を簡単にし、開発に集中できるように、セットアップの不一致や異なるプラットフォームによる環境の問題について心配せずに、完全に設定され、一貫した開発環境を持つことができます。

ステップ3:デバッグ設定を作成する

SWE-agentの推論ステップをデバッグするには、VS Codeでデバッグ設定を作成する必要があります。この設定により、コードにブレークポイントを設定し、それらのブレークポイントで実行を一時停止するデバッガを起動することができます。

デバッグ設定を作成するには、プロジェクトのルートにある.vscodeフォルダを開く(または作成する)と、以下の内容を持つlaunch.jsonファイルを作成します:

{
    "version": "0.2.0",
    "configurations": \[
        {
            "name": "Debug run.py",
            "type": "debugpy",
            "request": "launch",
            "program": "run.py",
            "console": "integratedTerminal",
            "args": \[
                "--model_name",
                "gpt4",
                "--instance_filter",
                "marshmallow-code__marshmallow-1359"
            \],
            "justMyCode": false
        }
    \]
}

この設定は、SWEエージェントの推論ステップのエントリーポイントである run.py スクリプトのデバッグセッションを設定します。それはモデル名 (gpt4) と、推論プロセス中に使用するインスタンスフィルタ (marshmallow-code__marshmallow-1359) を指定します。

marshmallow-code__marshmallow-1359 は、問題であるissue #1357 と、解決策であるPR #1359marshmallow リポジトリに対応します。

これらの値は、あなたの要件に基づいて調整することができます。

ステップ4:開発コンテナを開始する

単にVSコードのコマンドパレット (Ctrl+Shift+P) を呼び出し、 Dev Containers: Rebuild and Reopen in Container を選択します。

https://miro.medium.com/v2/resize:fit:640/format:webp/0*QiR0BSmPtUCeu0UO.png

図1:devコンテナ内でのSW-agentの起動

これにより、VS Codeがdevコンテナ内で再開し、SWE-agentプロジェクトの作業を開始できます。

devコンテナが起動し動作を開始したら、必要なAPIキーでkeys.cfgファイルを更新する必要があります。keys.cfgファイルを開き、プレースホルダーの値を実際のAPIキーに置き換えてください:

ステップ5:keys.cfgを更新する




GITHUB_TOKEN: 'GitHub Token Here (required)' OPENAI_API_KEY: 'OpenAI API Key Here if using OpenAI Model (optional)' ANTHROPIC_API_KEY: 'Anthropic API Key Here if using Anthropic Model (optional)' TOGETHER_API_KEY: 'Together API Key Here if using Together Model (optional)' AZURE_OPENAI_API_KEY: 'Azure OpenAI API Key Here if using Azure OpenAI Model (optional)' AZURE_OPENAI_ENDPOINT: 'Azure OpenAI Endpoint Here if using Azure OpenAI Model (optional)' AZURE_OPENAI_API_VERSION: 'Azure OpenAI API Version Here if using Azure OpenAI Model (optional)'




推論ステップのデバッグ方法

ステップ1:ブレークポイントを設定する

SWE-agentの推論ステップをデバッグするためには、run.pyスクリプトにブレークポイントを設定する必要があります。VS Codeでrun.pyファイルを開き、ブレークポイントを設定したい行番号の左マージンをクリックします。赤いドットが表示され、ブレークポイントが設定されたことを示します。

たとえば、ここでは、エージェントが問題を解決し始める直前(行106)にブレークポイントを設定します:

https://miro.medium.com/v2/resize:fit:640/format:webp/0*S7HfncLpbRWa4VTw.png

図2:run.pyでブレークポイントを設定する

ステップ2:デバッガーの起動

デバッガーを開始するには、サイドバーの Run and Debug アイコンをクリックし、「Debug run.py」の設定を実行する(または、ただ F5 を押す):

https://miro.medium.com/v2/resize:fit:640/format:webp/0*fDc-0HB_07Fh1ctl.png

図3: デバッガの起動

これにより、設定したブレークポイントでデバッガが起動し、実行が一時停止します:

https://miro.medium.com/v2/resize:fit:640/format:webp/0*WeAeFH8INcb9wA1o.png

図4: _run.py_ のブレークポイントでデバッガーが一時停止

これで、変数を検査したり、コードをステップスルーしたり、推論ステップ中のSWEエージェントの挙動を分析することができます!

結論

私たちの目標は、ローカルのSWEエージェント開発環境を迅速に設定することでしたが、それがまさに達成されました。

しかし、見落としてはいけない重要な点があります。私たちはすでに、プロダクションデプロイメントのためにソリューションをコンテナ化するという概念に慣れていますが、このガイドは、デプロイメントだけでなく開発フェーズでもその価値を明らかにしています。それは、私たちがどのように提供するかだけでなく、どのように開発しテストするかを効率化することができるコンテナを示しています。

これが、開発ワークフローのすべてのフェーズを強化するために、私たちがコンテナをどのように活用できるかについての広範な探求の始まりとなります!

OpenAI社の新しいGPT-4o モデルについて

キーポイントはマルチモーダル(Omni-Modal)で特に音声認識+画像認識が劇的に強化された事と、デスクトップバージョンがリリースされた事

https://miro.medium.com/v2/resize:fit:630/1*-bTsggApvkUHAq57YhSd-A.png

OpenAIのSpring Updateにおける Mira Murati (CTO)

今朝(5/13)の大きな発表で、OpenAIは、テキスト、音声、ビデオを扱う能力を持つ新バージョンのChatGPTシステム、GPT-4oのリリースを発表しました。「o」は「omni」を意味し、GPT-4oは今後数週間で会社の開発者向けおよび消費者向け製品に「反復的に」展開される予定です。

OpenAIによると、GPT-4oは約50の言語でのパフォーマンスが向上し、さらに多言語対応になっています。また、OpenAIのAPIでは、GPT-4oはGPT-4 Turboの2倍の速さで、半分の価格で、そしてより高いレート制限を持っていると会社は述べています。

GPT-4oは、今日からChatGPTの無料枠で利用可能であり、OpenAIのプレミアムChatGPT PlusTeamプランの加入者には、「5倍高い」メッセージ制限で提供されます。

現在、全ての顧客に対してGPT-4o APIの一部として音声は提供されていません。OpenAIは、誤用のリスクを引き合いに出し、GPT-4oの新たな音声機能のサポートを「信頼できるパートナーの小グループ」に対して初めて提供する予定であると述べています。

関連ニュースとして、OpenAIはウェブ上で新しく「より会話的」なホーム画面とメッセージレイアウトを備えたChatGPT UIをリリースし、キーボードショートカットを介して質問をしたり、スクリーンショットを取って議論したりするためのmacOS向けのChatGPTデスクトップバージョンを発表しました。ChatGPT Plusのユーザーは、今日からアプリにアクセスできるようになり、Windows版は今年後半にリリースされる予定です。

OpenAIエグゼクティブからのコメント

OpenAI CEOのSam Altman: 今日の発表から強調したい2つの点があります。 まず、私たちのミッションの重要な部分は、非常に有能なAIツールを無料で(または素晴らしい価格で)人々の手に渡すことです。私たちはChatGPTで世界最高のモデルを無料で、広告など一切なしで利用可能にしたことを非常に誇りに思っています。 OpenAIを始めた当初の考えは、私たちがAIを作り、それを使って世界中にさまざまな利益をもたらすというものでした。しかし、今見えてくるのは、私たちがAIを作り、それを他の人々が使って私たち全員が利益を得る素晴らしいものを作るということです。 私たちはビジネスであり、課金するためのたくさんのものを見つけ出し、それが(願わくば)何十億人もの人々に無料で優れたAIサービスを提供するのに役立つでしょう。 次に、新しい音声(およびビデオ)モードは、私が今まで使った中で最高のコンピューターインターフェースです。映画の中のAIのような感覚で、まだそれが本当だということが少し驚きです。人間レベルの応答時間と表現力を得ることが大きな変化となりました。 元のChatGPTは、言語インターフェースで可能なことの一端を示していました。この新しいものは、肌感覚で違います。それは速く、賢く、楽しく、自然で、そして役立ちます。 コンピュータと話すことが私にとって本当に自然に感じたことはありませんでした。今はそうです。私たちが(オプションで)個人化、あなたの情報へのアクセス、あなたの代わりに行動を起こす能力などを追加するにつれて、私たちはコンピュータを使って今まで以上に多くのことを行うことができる興奮する未来を本当に見ることができます。 最後に、この実現に向けて非常に多くの労力を注いだチームに巨大な感謝を!

OpenAI CTOのMira Murati GPT-4レベルの知能だけでなく、複数のモーダリティとメディアにおいてGPT-4の能力を改善します。 GPT-4oは音声、テキスト、視覚を超えて理由を立て、これは非常に重要です。なぜなら、私たちは自分自身と機械との間の未来の相互作用を見ているからです。

GPT-4oのマルチモーダル機能

OpenAIの前のトップモデルであるGPT-4 Turboは、画像とテキストの両方で訓練されました。画像とテキストを見て、画像からテキストを引き出したり、画像の内容を説明したりするような仕事を行うことができました。しかし、GPT-4oはこの組み合わせに音声を追加します。

GPT-4oはOpenAIのAI駆動型チャットボット、ChatGPTでの体験を大幅に改善します。このプラットフォームは、長い間、テキスト・トゥ・スピーチモデルを使用してチャットボットの応答を書き起こす音声モードを提供してきましたが、GPT-4oはこれを超充電し、ユーザーがChatGPTとよりアシスタントのように対話できるようにします。

たとえば、ユーザーはGPT-4o搭載のChatGPTに質問をし、ChatGPTが答えを出す中で中断することができます。このモデルは、「リアルタイム」の反応性を提供し、OpenAIは言います、ユーザーの声のニュアンスをさえつかむことができ、応答として「さまざまな感情的なスタイル」(歌を含む)の声を生成することができます。

GPT-4oの視覚機能。

デモンストレーターは数学の問題を書き留め、それを解く方法についてのガイダンスを求めます。

https://youtu.be/DQacCB9tDaw?t=884

このデモでは、コーディングタスクでChatGPTのデスクトップアプリが使われています。ChatGPTにはエディターのスクリーンショットが提供され、そのコードが何をするのかを分析し説明するように求められます。

https://youtu.be/DQacCB9tDaw?t=1103

OpenAIの新モデル - GPT-4o

GPT-4oの古いモデルは3つの異なるモデルの組み合わせでした。

  • まず、トランスクリプションモデルがあなたの声をテキストに変換します。
  • このテキストは、GPT-4をサポートする同じシステムであるインテリジェンスモデルによって処理されます。
  • インテリジェンスモデルがテキストを生成し、そのテキストはChatGPTによってコンピュータ化された声に戻されます。これにより、システムは一部の制限を伴いながらも会話能力を持つことができます。

このシステムとのコミュニケーションは、異なるモデル間でコンテンツを転送する必要があるため、遅くて不便なことがあります。私のテストでは、システムに話しかけてから応答を受け取るまでに3から5秒の遅延があることに気付きました。

GPT-4oは、音声認識、音声生成、インテリジェンスを一つのシステムに統合します。

これは、会話を作り出すために3つの異なるモデルを使用していた古いシステムがなくなったことを意味します。今では、新しいバージョンのChatGPTは音声を聞き、それを即座に処理し、現実的な音声で応答することができます

あなたはChatGPTとより自然に話すことができます。あなたの質問を入力する必要はなく、友達に話すようにアプリに話しかけるだけでよいのです。

ライブデモでは、OpenAIのエンジニアが、システムがユーザーの声を聞き、ミリ秒単位で賢い返答をすることができることを示しました。

この速度は、新モデルがタスク間を切り替える必要がないため可能です。つまり、一つのステップで音声を聞き、音声で応答することができます。

GPT-4oはまた、感情を理解し、表現することもできます。

会話デモ

デモでは、OpenAIのスタッフメンバーがシステムに呼吸エクササイズの指導を求めました。彼は呼吸が速くなるふりをし、ChatGPTは彼の速い呼吸とパニックに気づき、彼にゆっくりと深呼吸をするように指示しました。

https://youtu.be/DQacCB9tDaw?t=600

感情を込めた寝る前の物語

システムは自己の応答に感情を変えることもできます。別のデモでは、スタッフはGPT-4oに、よりドラマチックな声で寝る前の物語を読むように頼みました。そしてそれは、興奮しすぎた中学生の俳優のように聞こえました!

https://youtu.be/DQacCB9tDaw?t=740

この新システムは、GPT-4の視覚機能と統合されているため、人の顔の感情を理解することもできます。これは、システムをより良い会話相手にする可能性があります。

他の新機能も役立つでしょう。ユーザーはGPT-4oが話している間に割り込むことができます。

デモ中、OpenAIのスタッフメンバーは、モデルが他の何かについて話し始めたとき、友人に質問の答えを求めるようにしばしばモデルを遮りました。

リアルタイム翻訳

観客の質問に基づいて、OpenAIのスタッフメンバーはシステムがイタリア語の話し言葉を聞き取り、それを迅速かつ正確に英語の話し言葉に翻訳する方法を示しました。また、その逆も可能です。

https://youtu.be/DQacCB9tDaw?t=1357

これは、GPT-4oが英語とスペイン語の会話をリアルタイムで翻訳できることを示す別のデモ例です。

https://youtu.be/WzUnEfiIqP4

市場に対するインパクト

  • GPT-4oは今後APIも公開されるので、このAPIをベースにした新規アプリの開発が立ち上がると想定されます。高度な音声認識機能によって、上記のデモにもあった「感情表現」や自動翻訳などを使ったアプリが出てくると思います。また、画像/映像認識と組み合わせるとさらの広範囲のアプリが期待できると感じます。
  • すでに音声や画像認識を採用しているアプリケーションは世の中にいっぱいあると思いますが、GPT-4oの持つ機能ははるかに優れているのでそれらを置き換える動きは出てくると思います。監視カメラ等のセキュリティの世界、顧客サービス等のチャットボット、自動運転などの安全性要件が高いアプリ、果ては医療の世界などでもいろいろなアイディアが出てくると考えられます。

複数のAIエージェント:LangGraphとLangChainを使用したマルチエージェントワークフローの作成

複数のAIエージェント:LangGraphとLangChainを使用したマルチエージェントワークフローの作成

Tags: staged for publishing Created: May 11, 2024 11:49 AM Updated: May 12, 2024 11:36 PM

自己反射型AI (Self-Reflective AI)は、ジェネレーターを使用して出力を作成し、リフレクターを使用してそれをチェックします。両方ともLarge Language Model(LLM)を使用しますが、プロンプトは異なり、自己チェック型AIが作成されます。

これは、LangChainのLangGraphにより可能になります。これにより、同じLLMが2つの異なる役割で使用できます。

LangGraphはまた、マルチエージェントワークフローの構築を支援します。ここでは、LLMは複数の役割を果たすことができ、それぞれが異なるAIエージェントとして働きます。この設定はマルチエージェントと呼ばれます。

マルチエージェントシステムでは、それぞれがLLMによって動かされる別々のアクターが特定の方法で接続されています。

各エージェントは、自分のプロンプト、LLM、ツール、カスタムコードを持つことができ、他のエージェントと共同作業を行うことができます。しかし、同じLLMも与えられたプロンプトに基づいて異なる役割を果たすことができます。

LangGraphは、2つ以上のエージェントをグラフとして接続させることを可能にするため、マルチエージェントワークフローを作成するのに最適です。各エージェントは独立しており、エージェント間の接続(エッジ)は情報の流れをガイドする制御条件を持つことができます。各エージェントの状態は、この流れの間に情報で更新することができます。

人間が協力して働くように、マルチエージェントワークフローの異なるAIエージェントは、メッセージの共有スクラッチパッドを使用して通信します。これにより、各エージェントは他のエージェントの作業と各ステップを視認することができます。

異なるAIエージェントはすべて同じLLMに基づいている可能性がありますが、役割は異なります。

さて、過去5年間のマレーシアのGDPを示すチャートを作成するためのマルチエージェントワークフローを作成しましょう。

これには、オンラインで過去5年間のマレーシアのGDPデータを調査する研究者と、研究者のデータからPythonコードを使用してチャートを作成するチャートジェネレーターが必要です。

したがって、エージェントは次のとおりです:

  1. 研究者 - インターネットを使用して問題を調査します。
  2. チャートジェネレーター - 研究者のデータを使用してチャートを作成します。

エージェントノードとその接続を示すグラフの設計図を作成します。

https://miro.medium.com/v2/resize:fit:640/format:webp/1*ffzadZCKXJT7n4JaRVFvcQ.jpeg

  1. ユーザーがリサーチャーに入力を提供します。
  2. リサーチャーはこの入力をルーターに渡します。
  3. ルーターは、入力に基づいてツールを使用するか、メッセージをチャートジェネレーターに送ります。
  4. ツールは入力を処理し、応答をリサーチャーに戻します。リサーチャーは再度ツールを使用するか、メッセージをチャートジェネレーターに送るかもしれません。ルーターもメッセージに従って同様に行います。
  5. チャートジェネレーターはその返答をルーターに送り、ルーターは再度ツールを使用するか、リサーチャーに返すかします。
  6. 最後に、リサーチャーやチャートジェネレーターが最終的な返答を送ると、ルーターはこの最終回答をユーザーに送ります。

さて、上記のステップに従って、複数のエージェントを持つワークフローを作成しましょう。

まず、langchainパッケージをすべてインストールする必要があります。

pip install langchain langchain\_openai langgraph langchain\_core langsmith langchain_experimental

データの保存(pandas)およびチャート作成(matplotlib)のためのいくつかのパッケージもインストールする必要があります。

pip install pandas matplotlib

次に、マルチエージェントワークフローを作成するために必要なライブラリをインポートします。

import json

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    ChatMessage,
    FunctionMessage,
    HumanMessage,
)
from langchain.tools.render import format\_tool\_to\_openai\_function
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.graph import END, StateGraph
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation

次に、ツールを設定します。インターネット検索ツールが必要です。それにはtavilyを使用します。

import os
os.environ\['TAVILY\_API\_KEY'\] = "<Your Tavily API Key here>"

from langchain_core.tools import tool
from langchain\_community.tools.tavily\_search import TavilySearchResults
tavily\_tool = TavilySearchResults(max\_results=5)

また、Pythonコードを実行するツールも必要とします。ここでは、langchain_experimental.utilitiesからPythonREPLというツールを使用します。

from langchain_experimental.utilities import PythonREPL
from typing import Annotated

repl = PythonREPL()
@tool
def python_repl(code: Annotated\[str, "The python code to execute to generate your chart."\]):
    """Use this to execute python code. If you want to see the output of a value, you should print it out with \`print(...)\`. This is visible to the user."""
    try:
        result = repl.run(code)
    except BaseException as e:
        return f"Failed to execute. Error: {repr(e)}"
    return f"Succesfully executed:\\\\\\n\`python\\\\\\\\\\\\n{code}\\\\\\\\\\\\n\`\\\\\\nStdout: {result}"

ツールをリストに入れます。

tools = \[tavily\_tool, python\_repl\]

次に、状態のオブジェクトを作成します。この状態はメッセージのリストになります。これらのメッセージは一つのノードから別のノードに送信されます。

import operator
from typing import Annotated, List, Sequence, Tuple, TypedDict, Union

from langchain.agents import create\_openai\_functions_agent
from langchain.tools.render import format\_tool\_to\_openai\_function
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_openai import ChatOpenAI
from typing_extensions import TypedDict\# This defines the object that is passed between each node
\# in the graph. We will create different nodes for each agent and tool
class AgentState(TypedDict):
    messages: Annotated\[Sequence\[BaseMessage\], operator.add\]
    sender: str

メッセージの内容に応じてツールを選択します。選ばれたツールをメッセージと共に実行するtool_nodeという関数を作成します。

tool_executor = ToolExecutor(tools)

def tool_node(state):
    """This runs tools in the graph It takes in an agent action and calls that tool and returns the result."""
    messages = state\["messages"\]
    # Based on the continue condition
    # we know the last message involves a function call
    last_message = messages\[-1\]
    # We construct an ToolInvocation from the function_call
    tool_input = json.loads(
        last\_message.additional\_kwargs\["function_call"\]\["arguments"\]
    )
    # We can pass single-arg inputs by value
    if len(tool\_input) == 1 and "\_\_arg1" in tool_input:
        tool\_input = next(iter(tool\_input.values()))
    tool\_name = last\_message.additional\_kwargs\["function\_call"\]\["name"\]
    action = ToolInvocation(
        tool=tool_name,
        tool\_input=tool\_input,
    )
    # We call the tool_executor and get back a response
    response = tool_executor.invoke(action)
    # We use the response to create a FunctionMessage
    function_message = FunctionMessage(
        content=f"{tool_name} response: {str(response)}", name=action.tool
    )
    # We return a list, because this will get added to the existing list
    return {"messages": \[function_message\]}

ツールノードの設定を行った後、状態からのメッセージ内容に基づいてデータを送信する場所を選択するルーターを作成しましょう。

def router(state):

    messages = state\["messages"\]
    last_message = messages\[-1\]
    if "function_call" in last\_message.additional\_kwargs:

        return "call_tool"
    if "FINAL ANSWER" in last_message.content:

        return "end"
    return "continue"

次に、AIエージェントのリサーチャーとチャートジェネレーターを設定します。

まず、llmを作成しましょう。

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(api_key="<Your API Key here>")

まず、'create_agent'という関数を作ります。この関数はエージェントを作成し、それに与えられたシステムメッセージを追加します。この関数は後で'researcher'と'chart generator'を作成するために使用します。

import json

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    ChatMessage,
    FunctionMessage,
    HumanMessage,
)
from langchain\_core.utils.function\_calling import convert\_to\_openai_function
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.graph import END, StateGraph
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocationdef create\_agent(llm, tools, system\_message: str):
    """Create an agent."""
    functions = \[convert\_to\_openai_function(t) for t in tools\] prompt = ChatPromptTemplate.from_messages(
        \[
            (
                "system",
                "You are a helpful AI assistant, collaborating with other assistants."
                " Use the provided tools to progress towards answering the question."
                " If you are unable to fully answer, that's OK, another assistant with different tools "
                " will help where you left off. Execute what you can to make progress."
                " If you or any of the other assistants have the final answer or deliverable,"
                " prefix your response with FINAL ANSWER so the team knows to stop."
                " You have access to the following tools: {tool\_names}.\\\\\\n{system\_message}",
            ),
            MessagesPlaceholder(variable_name="messages"),
        \]
    )
    prompt = prompt.partial(system\_message=system\_message)
    prompt = prompt.partial(tool_names=", ".join(\[tool.name for tool in tools\]))
    return prompt | llm.bind_functions(functions)

次に、メッセージと送信者の名前を追加するタスクを行うagent_node関数を作成します。これはルーティングの目的で使用されます。

def agent_node(state, agent, name):
    result = agent.invoke(state)

    if isinstance(result, FunctionMessage):
        pass
    else:
        result = HumanMessage(**result.dict(exclude={"type", "name"}), name=name)
    return {
        "messages": \[result\],

        "sender": name,
    }

さて、研究者エージェントとチャート生成エージェントを作成します。

research\_agent= create\_agent(
    llm,
    \[tavily_tool\],
    system_message="You should provide accurate data for the chart generator to use.",
)

chart\_agent= create\_agent(
    llm,
    \[python_repl\],
    system_message="Any charts you display will be visible by the user.",
)

次に、 'research_agent'と 'chart_agent'の関数を単純化して、 'research_node'と 'chart_node'の関数を形成します。既存の関数から新しい関数 'agent_node'を導き出すために 'functools'ライブラリを使用します。

import functools
research\_node= functools.partial(agent\_node, agent=research_agent, name="Researcher")

chart\_node= functools.partial(agent\_node, agent=chart_agent, name="Chart Generator")

すべてのノード、つまりresearch_node、chart_node、tool_nodeを、「workflow」と呼ぶグラフに配置することができます。また、特定の条件に基づいてメッセージを送信する条件付きエッジを追加します。Researcherノードを開始点として設定し、ユーザーの入力が最初に研究者に行くようにします。最後に、グラフをコンパイルします。

workflow= StateGraph(AgentState)

workflow.add\_node("Researcher", research\_node)
workflow.add\_node("Chart Generator", chart\_node)
workflow.add\_node("call\_tool", tool_node)workflow.add\_conditional\_edges(
    "Researcher",
    router,
    {"continue": "Chart Generator", "call\_tool": "call\_tool", "end": END},
)
workflow.add\_conditional\_edges(
    "Chart Generator",
    router,
    {"continue": "Researcher", "call\_tool": "call\_tool", "end": END},
)workflow.add\_conditional\_edges(
    "call_tool",
\# Each agent node updates the 'sender' field# the tool calling node does not, meaning
\# this edge will route back to the original agent# who invoked the tool
        lambda x: x\["sender"\],
    {
        "Researcher": "Researcher",
        "Chart Generator": "Chart Generator",
    },
)
workflow.set\_entry\_point("Researcher")
graph= workflow.compile()

これで、マルチエージェントワークフローを呼び出す準備ができました。

for s in graph.stream(
    {
        "messages": \[
            HumanMessage(
                content="Fetch the Malaysia's GDP over the past 5 years,"
                " then draw a line graph of it."
                " Once you code it up, finish."
            )
        \],
    },

    {"recursion_limit": 150},
):
    print(s)
    print("----")

アウトプットは次のとおりです。

{'Researcher': {'messages': \[HumanMessage(content='', additional\_kwargs={'function\_call': {'arguments': '{"query":"Malaysia GDP by year"}', 'name': 'tavily\_search\_results\_json'}}, response\_metadata={'token\_usage': {'completion\_tokens': 22, 'prompt\_tokens': 221, 'total\_tokens': 243}, 'model\_name': 'gpt-3.5-turbo', 'system\_fingerprint': 'fp\_3b956da36b', 'finish\_reason': 'function\_call', 'logprobs': None}, name='Researcher', id='run-313ac64c-fed0-4505-b00d-715c7ff0aa59-0', tool\_calls=\[\], invalid\_tool\_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'call\_tool': {'messages': \[FunctionMessage(content="tavily\_search\_results\_json response: \[{'url': '<https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=MY>', 'content': 'GDP growth (annual %) - Malaysia from The World Bank: Data.'}, {'url': '<https://tradingeconomics.com/malaysia/gdp>', 'content': 'GDP in Malaysia averaged 109.79 USD Billion from 1960 until 2022, reaching an all time high of 407.03 USD Billion in 2022 and a record low of 1.90 USD Billion\\\\\\xa0...'}, {'url': '<https://www.macrotrends.net/global-metrics/countries/MYS/malaysia/gdp-gross-domestic-product>', 'content': 'Malaysia gdp for 2019 was $365.18B, a 1.78% increase from 2018. Download Historical Data Save as Image\\\\\\xa0...'}, {'url': '<https://data.worldbank.org/indicator/NY.GDP.MKTP.CD?locations=MY>', 'content': '1960 1980 2000 2020 0.00 225b 450b Malaysia. 1960 - 2022. 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007\\\\\\xa0...'}, {'url': '<https://www.worldometers.info/gdp/malaysia-gdp/>', 'content': 'Nominal (current) Gross Domestic Product (GDP) of Malaysia is $406,306,000,000 (USD) as of 2022. · Real GDP (constant, inflation adjusted) of Malaysia reached\\\\\\xa0...'}\]", name='tavily\_search\_results_json')\]}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content='', additional\_kwargs={'function\_call': {'arguments': '{"query":"Malaysia GDP historical data"}', 'name': 'tavily\_search\_results\_json'}}, response\_metadata={'token\_usage': {'completion\_tokens': 22, 'prompt\_tokens': 632, 'total\_tokens': 654}, 'model\_name': 'gpt-3.5-turbo', 'system\_fingerprint': 'fp\_3b956da36b', 'finish\_reason': 'function\_call', 'logprobs': None}, name='Researcher', id='run-442c7d99-8b3c-4a32-99ec-e0739ac059dd-0', tool\_calls=\[\], invalid\_tool\_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'call\_tool': {'messages': \[FunctionMessage(content='tavily\_search\_results\_json response: \[{\\\'url\\\': \\\'<https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=MY&view=chart\\\>', \\\'content\\\': \\\'GDP growth (annual %) - Malaysia. World Bank national accounts data, and OECD National Accounts data files. License : CC BY-4.0. LineBarMap. Also Show Share Details. Label. 1961 - 2022.\\\'}, {\\\'url\\\': \\\'<https://tradingeconomics.com/malaysia/gdp\\\>', \\\'content\\\': \\\'The Gross Domestic Product (GDP) in Malaysia was worth 407.03 billion US dollars in 2022, according to official data from the World Bank. The GDP value of Malaysia represents 0.17 percent of the world economy. This page provides - Malaysia GDP - actual values, historical data, forecast, chart, statistics, economic calendar and news.\\\'}, {\\\'url\\\': \\\'<https://www.macrotrends.net/global-metrics/countries/MYS/malaysia/gdp-growth-rate\\\>', \\\'content\\\': \\\'Malaysia gdp growth rate for 2022 was 8.65%, a 5.35% increase from 2021. Malaysia gdp growth rate for 2021 was 3.30%, a 8.75% increase from 2020. Malaysia gdp growth rate for 2020 was -5.46%, a 9.87% decline from 2019. Malaysia gdp growth rate for 2019 was 4.41%, a 0.43% decline from 2018. Download Historical Data. Save as Image.\\\'}, {\\\'url\\\': \\\'<https://data.worldbank.org/indicator/NY.GDP.MKTP.CD?locations=MY&view=chart\\\>', \\\'content\\\': \\\'All Countries and Economies. Country. Most Recent Year. Most Recent Value(Millions) GDP (current US$) - Malaysia from The World Bank: Data.\\\'}, {\\\'url\\\': \\\'<https://www.macrotrends.net/global-metrics/countries/MYS/malaysia/gdp-gross-domestic-product\\\>', \\\'content\\\': "Malaysia gdp for 2022 was $407.03B, a 8.88% increase from 2021. Malaysia gdp for 2021 was $373.83B, a 10.78% increase from 2020. Malaysia gdp for 2020 was $337.46B, a 7.59% decline from 2019. Malaysia gdp for 2019 was $365.18B, a 1.78% increase from 2018. GDP at purchaser\\\'s prices is the sum of gross value added by all resident producers in the ..."}\]', name='tavily\_search\_results_json')\]}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content="Based on the search results, I found the GDP data for Malaysia for the past 5 years as follows:\\\n\\\n- 2018: $365.18 billion\\\n- 2019: $337.46 billion\\\n- 2020: $373.83 billion\\\n- 2021: $407.03 billion\\\n- 2022: $407.03 billion\\\n\\\nNow, I will proceed to create a line graph to visualize this data.\\\nI will now generate a line graph to visualize Malaysia's GDP over the past 5 years.", response\_metadata={'token\_usage': {'completion\_tokens': 113, 'prompt\_tokens': 1210, 'total\_tokens': 1323}, 'model\_name': 'gpt-3.5-turbo', 'system\_fingerprint': 'fp\_3b956da36b', 'finish\_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-e498d73b-c688-4e38-aa9b-b4cfa39c34c7-0', tool\_calls=\[\], invalid\_tool\_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='', additional\_kwargs={'function\_call': {'arguments': '{"code":"import matplotlib.pyplot as plt\\\\\\n\\\\\\ndata = {\\\\\\n    \\\'Year\\\': \[2018, 2019, 2020, 2021, 2022\],\\\\\\n    \\\'GDP (in billion USD)\\\': \[365.18, 337.46, 373.83, 407.03, 407.03\]\\\\\\n}\\\\\\n\\\\\\nplt.figure(figsize=(10, 6))\\\\\\nplt.plot(data\[\\\'Year\\\'\], data\[\\\'GDP (in billion USD)\\\'\], marker=\\\'o\\\', color=\\\'b\\\', linestyle=\\\'-\\\', linewidth=2)\\\\\\nplt.title(\\\'Malaysia GDP Over the Past 5 Years\\\')\\\\\\nplt.xlabel(\\\'Year\\\')\\\\\\nplt.ylabel(\\\'GDP (in billion USD)\\\')\\\\\\nplt.grid(True)\\\\\\nplt.show()"}', 'name': 'python\_repl'}}, response\_metadata={'token\_usage': {'completion\_tokens': 175, 'prompt\_tokens': 1338, 'total\_tokens': 1513}, 'model\_name': 'gpt-3.5-turbo', 'system\_fingerprint': 'fp\_3b956da36b', 'finish\_reason': 'function\_call', 'logprobs': None}, name='Chart Generator', id='run-11c4756f-097b-4733-8ecf-6091f5be5bd4-0', tool\_calls=\[\], invalid\_tool\_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-

https://miro.medium.com/v2/resize:fit:640/format:webp/1*Sz0Eyg6eNKzOR7VNULwDNg.png

{'call_tool': {'messages': \[FunctionMessage(content="python_repl response: Succesfully executed:\\\\\\n\`python\\\\\\\\\\\\nimport matplotlib.pyplot as plt\\\n\\\ndata = {\\\n    'Year': \[2018, 2019, 2020, 2021, 2022\],\\\n    'GDP (in billion USD)': \[365.18, 337.46, 373.83, 407.03, 407.03\]\\\n}\\\n\\\nplt.figure(figsize=(10, 6))\\\nplt.plot(data\['Year'\], data\['GDP (in billion USD)'\], marker='o', color='b', linestyle='-', linewidth=2)\\\nplt.title('Malaysia GDP Over the Past 5 Years')\\\nplt.xlabel('Year')\\\nplt.ylabel('GDP (in billion USD)')\\\nplt.grid(True)\\\nplt.show()\\\\\\\\\\\\n\`\\\\\\nStdout: ", name='python_repl')\]}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content="The line graph showing Malaysia's GDP over the past 5 years has been successfully generated. The graph displays the GDP (in billion USD) on the y-axis and the years from 2018 to 2022 on the x-axis. The GDP values are plotted as points connected by lines.\\\n\\\nIs there anything else you would like to analyze or any other assistance needed?", response_metadata={'token_usage': {'completion_tokens': 75, 'prompt_tokens': 1514, 'total_tokens': 1589}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-a6d15c89-6cb8-4f98-9cbb-67bb92214dc5-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content="I have successfully generated the line graph showing Malaysia's GDP over the past 5 years. If you have any more data to analyze or need further assistance, feel free to let me know!", response_metadata={'token_usage': {'completion_tokens': 39, 'prompt_tokens': 1579, 'total_tokens': 1618}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-9c1709e6-f6ee-4a8c-b0ae-f363d65a5a2f-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content="Great! If you have any more data to analyze or any other tasks you'd like assistance with, feel free to ask. I'm here to help!", response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 1634, 'total_tokens': 1666}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-1f869abb-930c-4b19-ab7e-c068a407f134-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content="I'm glad the line graph for Malaysia's GDP over the past 5 years was successfully generated. If you have any more data to analyze or any other tasks you'd like assistance with, feel free to ask. I'm here to help!", response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 1656, 'total_tokens': 1706}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-4c614614-5fc6-4d6b-abd8-5b1b85607f5a-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='Thank you! If you have any more data to analyze or if you need assistance with any other tasks in the future, feel free to reach out. Have a great day!', response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 1722, 'total_tokens': 1758}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-659e7259-1906-4c07-8bb5-53c8d6ac7dd2-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content='Thank you! If you have any more data to analyze or if you need assistance with any other tasks in the future, feel free to reach out. Have a great day!', response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 1748, 'total_tokens': 1784}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-f87114d6-08aa-4a54-8490-1a1145a30263-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='Great collaboration! If you have any more questions or need assistance in the future, feel free to ask. Have a wonderful day!', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 1800, 'total_tokens': 1827}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-b12ec2b6-61bb-485c-81cf-279ade302e02-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content="Thank you! It was a pleasure collaborating with you. If you have any more questions or need assistance in the future, don't hesitate to ask. Have a wonderful day!", response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 1817, 'total_tokens': 1853}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-3c7cf207-ce49-416f-bb02-81a2a3410d5a-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='It was a pleasure collaborating with you! If you have any more questions or need assistance in the future, feel free to reach out. Have a great day!', response_metadata={'token_usage': {'completion_tokens': 33, 'prompt_tokens': 1869, 'total_tokens': 1902}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-1d5d0ed0-c20a-4745-a16f-6adcaa8c2c81-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content='Likewise! If you ever have more questions or need help in the future, feel free to ask. Have a fantastic day!', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 1892, 'total_tokens': 1919}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-bb88218f-ab2f-4ee5-b381-d75e1bd1d1cf-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='Likewise! If you ever have more questions or need help in the future, feel free to ask. Have a fantastic day!', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 1935, 'total_tokens': 1962}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-50293d69-34ff-455d-8b34-3a20a0a1bb91-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}}
\-\-\-\-
{'Researcher': {'messages': \[HumanMessage(content="I'm glad we were able to collaborate effectively! If you have any more questions or need assistance in the future, feel free to reach out. Have a wonderful day!", response_metadata={'token_usage': {'completion_tokens': 35, 'prompt_tokens': 1952, 'total_tokens': 1987}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-67208ed1-0d31-4b43-b2e8-92619b806deb-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
{'Researcher': {'messages': \[HumanMessage(content="Thank you! It was a pleasure collaborating with you. If you have any more questions or need assistance in the future, don't hesitate to ask. Have a wonderful day!", response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 2087, 'total_tokens': 2123}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-bc8186f2-1f7e-477e-b1ff-0020c7171aa3-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Researcher'}}
\-\-\-\-
{'Chart Generator': {'messages': \[HumanMessage(content='FINAL ANSWER', response_metadata={'token_usage': {'completion_tokens': 4, 'prompt_tokens': 2139, 'total_tokens': 2143}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, name='Chart Generator', id='run-45177a17-7ae8-43b0-b73e-f9d3d5c5d456-0', tool\_calls=\[\], invalid\_tool_calls=\[\])\], 'sender': 'Chart Generator'}

お疲れ様です!マルチエージェントワークフローを作成しました。これで、複雑なタスクに取り組む複数のAIエージェントを持つ方向に進んでいます。