NovitaとCrewAIでマルチエージェントシステムを構築する

NovitaとCrewAIでマルチエージェントシステムを構築する

AI業界が大規模言語モデル(LLM)の推論能力を認識し始めたことを受け、私たちはより複雑なアクションを実行できるようにすることで、LLMをよりエージェント化することにしました。この種類の大規模言語モデルはエージェントとして知られるようになりました。

こうして単一エージェントシステムが誕生しました。しかしすぐに、単一のエージェントに複数のアクションを割り当てることが、エージェントシステムを構築する最も賢明な方法ではないことが明らかになりました。より良いアプローチは、それぞれ独自のタスクを持ち、ユーザーが定義した目標を達成するために協力して動作する複数のエージェントを使用することでした。

しかし、別の問題が発生しました。複数のエージェントを管理することは、単一のエージェントを管理するほど簡単ではありません。この問題を解決するため、いくつかのマルチエージェントシステムフレームワークが導入されており、最も有望なものの1つがCrewAIです。

CrewAIは開発者がマルチエージェントシステムを構築するのを支援します。CrewAIを使用することで、エージェント、そのタスク、ワークフローを管理できます。この記事では、CrewAIとNovitaで利用可能な多数のLLMを組み合わせて、実用的でインテリジェントなマルチエージェントシステムを構築します。

マルチエージェントシステムとは何か、そしてなぜ必要なのか?

大規模言語モデル(LLM)の導入以来、それらをより自律的にするためのいくつかのアプローチが探求されてきました。最も人気のあるものの1つは、論文Synergizing Reasoning and Acting in Language Modelsで紹介されたReActパターンです。LangChainなどのフレームワークは、LLMにエージェント機能を付与するために、迅速にReActを実装しました。

AutoGPTなどのツールは、テキストプロンプトだけでユーザーが望むほぼすべてのタスクを実行できる、より堅牢なAIエージェントを構築することで、さらに一歩進んだ取り組みを行いました。これらの単一エージェントシステムは大きな可能性を示しましたが、制限もありました。単一のエージェントは通常、すべてのタスクを処理するために1つのLLMに依存しており、これがいくつかの重大な欠点を引き起こします:

  • コンテキストの制限: 単一のエージェントは、1つのコンテキストウィンドウを使用してすべてのタスクを管理する必要があります。ワークフローが進むにつれ、エージェントは最終的にコンテキスト領域を使い果たしてしまいます。
  • 複雑性の増大: より多くのタスクが単一のエージェントに割り当てられるにつれ、推論の管理が難しくなり、エージェントが幻覚(ハルシネーション)や失敗を起こしやすくなります。
  • 単一障害点: 1つのエージェントがすべての責任を持つ場合、バックアップがありません。エージェントがタスクを処理できない場合、システムは完全に失敗します。
  • 並列化の欠如: タスクは1つずつ順番に処理されます。処理を高速化するためにエージェントが並列で動作する組み込みサポートはありません。

マルチエージェントシステムは、これらの課題に対処するために導入されました。複数のタスクを処理するために単一のエージェントに依存する代わりに、マルチエージェントシステムは責任を協力して動作する複数のエージェントに分散します。これにより、各エージェントがワークフローの独自の部分のみを管理する必要があるため、コンテキスト長を超えるような問題を回避でき、全体的なコンテキスト負荷が削減されます。

マルチエージェントのセットアップでは、異なる目的のために異なるモデルを使用することもできます。特定のタスク向けにファインチューニングされたモデル、事前定義されたプロンプトを備えたモデル、専門ツールを装備したモデル、推論向けに最適化されたモデルなどです。この多様性はパフォーマンスを向上させるだけでなく、並列化を可能にし、マルチエージェントシステムを単一エージェントシステムよりも高速で効率的にします。

マルチエージェントシステムを構築するには、2つの核心的なコンポーネントが必要です。LLMを提供するモデルプロバイダーと、エージェントワークフロー全体を管理するマルチエージェントフレームワークです。この記事では、モデルプロバイダーとしてNovita、フレームワークとしてCrewAIを使用します。

CrewAIのようなフレームワークが必要な理由

マルチエージェントシステムの構築は、単一エージェントシステムの構築よりもはるかに複雑です。例えば、Novitaの任意のモデルに関数呼び出しを使用してツールを提供することで、簡単にエージェントにすることができます。しかし、複数のエージェントの接続を開始すると、複雑さが大幅に増加します。以下を含むいくつかの可動部分を管理する必要があります:

  • 通信とタスク委任: エージェントが互いに通信する方法と、タスクがエージェント間でどのように引き継がれるかを調整する必要があります。
  • ツール管理: フレームワークがない場合、ツール定義やJSONスキーマを手動で管理する必要があり、これはすぐに混乱を招く可能性があります。
  • エージェントアーキテクチャとパターン: マルチエージェントフレームワークには、確立されたエージェントパターンの組み込みサポートが含まれていることがよくあります。ゼロから構築する場合は、これらの進化し続けるパターンを常に最新の状態に保ち、自分で実装する必要があります。
  • 可観測性(オブザーバビリティ): フレームワークがない場合、エージェントの相互作用やパフォーマンスを監視、デバッグ、可視化するための独自のツールをセットアップする責任も負うことになります。

マルチエージェントフレームワークは、これらの複雑さのすべてを管理するのに役立ちます。この記事では、フレームワークとしてCrewAIを使用します。CrewAIは他のフレームワークと比較していくつかの利点があります:直感的な概念を使用してマルチエージェントシステムをモデル化するシンプルさで際立っていること、成熟していて確立されていること、優れた開発者体験を提供することです。

CrewAIの核心概念を理解する

CrewAIでマルチエージェントシステムを構築するには、フレームワークが導入したいくつかの核心概念に精通している必要があります。以下に、この記事で扱う主要な概念の概要を説明します:

  • エージェント
  • タスク
  • クルー
  • プロセス
  • フロー

エージェント

エージェントはCrewAIの核心です。これらはLLMによって駆動される実際のAIワーカーを表します。CrewAIの各エージェントは、役割、意思決定を導く目標、性格を形作るバックストーリーによって定義されます。これらはすべてLLMに送信されるテキストプロンプトとして表現されます。エージェントはツールを装備することもでき、特定のアクションを実行するためにツールを選択できます。CrewAIにおけるエージェントの主な目的は、割り当てられた1つまたは複数のタスクを完了することです。

タスク

タスクは完了する必要のある作業を定義します。CrewAIでは、タスクを作成し、特定のエージェントに直接割り当てるか、タスクの要件に一致する場合にエージェントがタスクを取得するようにすることができます。各タスクには通常、明確な説明、期待される出力、タスクを処理できるオプションのエージェントなどの関連パラメータが含まれます。

クルー

これはCrewAIの核心概念です。CrewAIでは、タスクを達成するために協力して動作するエージェントのグループをクルーと呼びます。クルーは、その内部のエージェントのワークフローも定義します。

プロセス

プロセスは、クルーがタスクを実行する方法を指示する事前定義されたワークフローです。CrewAIは2つの主要なタイプのプロセスをサポートしています:

  • シーケンシャル(順次): タスクはクルー内のエージェントによって1つずつ順番に処理されます。
  • 階層的: このプロセスでは、1つのエージェントがマネージャーとして機能し、他のエージェントを調整し、必要に応じてタスクを委任します。

フロー

プロセスが事前定義されたワークフローであるのに対し、CrewAIのフローは開発者にカスタムエージェントワークフローを設計する柔軟性を提供します。フローにはエージェント、タスク、さらにはクルー全体を含めることができ、より複雑な相互作用を可能にします。

Novitaでクルーを構築する

CrewAIの核心概念を説明したので、次にクルーの構築について説明します。この例では、ユーザーが最小実行可能製品(MVP)を迅速に構築するのを支援するクルーを作成します。このクルーは3つの専門エージェントで構成されています:

  • アーキテクト: ソフトウェア構造を定義し、MVPの範囲を概要します。このエージェントはNovitaでホストされているmoonshotai/kimi-k2-instructモデルによって駆動されます。
  • コーダー: アーキテクトの計画を実装し、実際のコードを作成します。すべてのプロジェクトファイルを作成するためにFileWriterツールを使用し、Novitaのqwen/qwen3-coder-480b-a35b-instructモデルで実行されます。
  • レビュアー: コーダーが生成したコードを分析し、diff形式のフィードバックを使用して改善を提案します。アーキテクトと同様に、moonshotai/kimi-k2-instructモデルも使用します。

このクルーはシーケンシャルプロセスに従います。

インストールとセットアップ

計画が整ったので、環境のセットアップを始めましょう。まず、組み込みツールサポートを含むCrewAIをインストールします:

pip install 'crewai[tools]'

次に、Novita APIキーが必要です。APIキーを取得したら、環境変数にNOVITA_API_KEYとして追加してください:

export NOVITA_API_KEY=your_api_key_here

これでクルーの構築を開始する準備が整いました。

LLMの作成

まず、必要な依存関係をインポートしましょう:

import os
from crewai import Agent, Task, Crew, Process, LLM
from crewai_tools import FileWriterTool

次に、コーダーエージェントがファイルをディスクに書き込むために使用するFileWriterToolのインスタンスを作成します:

file_writer_tool = FileWriterTool()

ツールの準備が整ったので、各エージェントのLLMを初期化できます:

architect_llm = LLM(
   model="novita/moonshotai/kimi-k2-instruct",
   temperature=0.5,
   api_base="https://api.novita.ai/v3/openai",
   api_key=os.environ['NOVITA_API_KEY']
)

coder_llm = LLM(
   model="novita/qwen/qwen3-coder-480b-a35b-instruct",
   temperature=0.4,
   api_base="https://api.novita.ai/v3/openai",
   api_key=os.environ['NOVITA_API_KEY']
)

reviewer_llm = LLM(
   model="novita/moonshotai/kimi-k2-instruct",
   temperature=0.5,
   api_base="https://api.novita.ai/v3/openai",
   api_key=os.environ['NOVITA_API_KEY']
)

これらの3つのLLMインスタンスは、計画で説明したモデルに対応しています。これらはNovitaでホストされており、CrewAIを介してアクセスされます。内部では、CrewAIはLiteLLMを使用してこれらのモデルに接続し、管理します。

エージェントの定義

では、3つのエージェントを作成しましょう:

architect = Agent(
   role="Software Architect for MVP Projects",
   goal="Define a basic system structure and simple feature set to guide MVP development",
   backstory="""You specialize in quickly outlining software architectures and project scopes for
minimum viable products. Your plans help guide coders with enough structure to get started while staying lean.""",
   llm=architect_llm,
   verbose=True
)

coder = Agent(
   role="Developer for MVP Projects",
   goal="Implement the MVP using simple, clear code, and write all necessary code files using the FileWriter tool",
   backstory="""You're a practical developer focused on speed. You prioritize working code over polish.
Simulate any runtime behavior if needed, and make sure to keep code clear and modular.""",
   llm=coder_llm,
   verbose=True,
   tools=[file_writer_tool]
)

reviewer = Agent(
   role="Code Reviewer for MVP Projects",
   goal="Read the code files, provide helpful feedback, and write improvements as diffs into a markdown file",
   backstory="""You're a fast but thoughtful reviewer. You check code for clarity, obvious bugs, and
provide improvements as diffs. Instead of modifying code directly, you 
save your suggestions in a markdown file
for easy inspection.""",
   llm=reviewer_llm,
   verbose=True,
   tools=[file_writer_tool]
)

各エージェントには特定の役割、目標、バックストーリーが割り当てられます。

タスクの定義

エージェントのタスクを作成しましょう:

architect_task = Task(
   description="""
Create a basic plan for building a simple MVP version of a {project}.
Focus on the core features, basic file structure, and tech stack.
Make the structure minimal but enough to get a working demo.
""",
   expected_output="""
A simple architectural overview with:
- Project goals
- Key components or files
- Basic data flow or structure
""",
   agent=architect,
   output_file="architecture.md"
)

coder_task = Task(
   description="""
Based on the architect's plan, implement the core parts of the {project} MVP.
Keep it lean and functional. Use the FileWriter tool to save all your code files.
If you need to simulate behavior (e.g. without a real interpreter), do so with clear comments or mocked logic.
""",
   expected_output="""
Working code files saved using the FileWriter tool that implement the key features defined in the plan.
Code should be readable and logically structured.
""",
   agent=coder,
   context=[architect_task]
)

review_task = Task(
   description="""
Review the code files created for the {project} MVP. Read each file using the FileRead tool.
Suggest improvements using diffs. Do not modify the original code files directly.
Instead, save all your suggested diffs into a markdown file using the FileWriter tool.
""",
   expected_output="""
A markdown file containing diff-style suggestions for each file reviewed.
""",
   agent=reviewer,
   context=[coder_task],
   output_file="code_review_diffs.md"
)

クルーの作成

エージェントとそれぞれのLLM、タスクを定義したので、クルーを組み立ててすべてをまとめましょう:

code_crew = Crew(
   agents=[architect, coder, reviewer],
   tasks=[architect_task, coder_task, review_task],
   process=Process.sequential,
   verbose=True
)

すべてのセットアップが完了したので、クルーを実行できます:

if __name__ == "__main__":
   code_crew.kickoff(inputs={"project": "Todo App"})

これで、ユーザー定義の仕様に基づいてMVPプロジェクトを生成できる完全に機能するクルーができました。上記の例では、クルーにシンプルなToDoアプリの構築を依頼しました。

動作の流れは以下の通りです:

  1. アーキテクトエージェントがソフトウェアアーキテクチャを設計し、計画をarchitecture.mdというファイルに保存することで開始します。
  2. 次にコーダーエージェントがアーキテクトの出力を取得し、ToDoアプリを実装するために必要なディレクトリとコードファイルを作成します。
  3. 最後にレビュアーエージェントが生成されたコードを確認し、diffを使用して改善提案を行い、フィードバックをcode_review_diffs.mdというファイルに保存します。

このクルーベースの開発ワークフローは強力でモジュール性がありますが、いくつかの制限もあります:

  • ハードコードされたプロンプト: プロンプトをコードに直接埋め込むと、時間の経過とともに保守や改善が難しくなります。
  • 分散した構造: エージェントとタスクがインラインで定義されていると、プロジェクトが成長するにつれて、混乱しやすくナビゲートが困難なスクリプトになる可能性があります。

次のセクションでは、エージェント定義とタスクプロンプトをYAMLファイルに外部化することで、これを整理する方法について説明します。

クラスベースのクルーの構築

CrewAIを使用すると、クラスベースのクルーを作成できます。この構造に従うように以前のコードを更新しましょう。まず、configというフォルダを作成します。このフォルダには、エージェントとタスクの設定を定義するYAMLファイルが含まれます。

agents.yamlというファイルを作成しましょう。これはエージェント設定ファイルとして機能します:

architect:
 role: >
   Software Architect for MVP Projects
 goal: >
   Define a basic system structure and simple feature set to guide MVP development
 backstory: >
   You specialize in quickly outlining software architectures and project scopes for minimum viable products.
  Your plans help guide coders with enough structure to get started while staying lean.

coder:
 role: >
   Developer for MVP Projects
 goal: >
   Implement the MVP using simple, clear code, and write all necessary code files using the FileWriter tool
 backstory: >
   You're a practical developer focused on speed. You prioritize working code over polish.
  Simulate any runtime behavior if needed, and make sure to keep code clear and modular.

reviewer:
 role: >
   Code Reviewer for MVP Projects
 goal: >
   Read the code files, provide helpful feedback, and write improvements as diffs into a markdown file
 backstory: >
   You're a fast but thoughtful reviewer. You check code for clarity, obvious bugs, and provide improvements
  as diffs. Instead of modifying code directly, you save your suggestions in a markdown file for easy inspection.

このファイルは、各エージェントの役割、目標、バックストーリーを定義します。

次に、タスク設定を保持するtasks.yamlファイルを作成します:

代わりに、FileWriterツールを使用して、提案したすべてのdiffをMarkdownファイルに保存してください。

architect_task:
 description: >
   Create a basic plan for building a simple MVP version of a {project}.
  Focus on the core features, basic file structure, and tech stack.
  Make the structure minimal but enough to get a working demo.
 expected_output: >
   A simple architectural overview with:
  - Project goals
  - Key components or files
  - Basic data flow or structure
 agent: architect
 output_file: architecture.md

coder_task:
 description: >
   Based on the architect's plan, implement the core parts of the {project} MVP.
  Keep it lean and functional. Use the FileWriter tool to save all your code files.
  If you need to simulate behavior (e.g. without a real interpreter), do so with clear comments or mocked logic.
 expected_output: >
   Working code files saved using the FileWriter tool that implement the key features defined in the plan.
  Code should be readable and logically structured.
 agent: coder
 context:
   - architect_task

review_task:
 description: >
   Review the code files created for the {project} MVP. Read each file using the FileRead tool.
  Suggest improvements using diffs. Do not modify the original code files directly.
  Instead, save all your suggested diffs into a markdown file using the FileWriter tool.
 expected_output: >
   A markdown file containing diff-style suggestions for each file reviewed.
 agent: reviewer
 context:
   - coder_task
 output_file: code_review_diffs.md

次に、クラスベースのクルーを作成しましょう。これを行うには、クラスを定義し、CrewAIが提供する@CrewBaseデコレータで装飾します。

@CrewBase
class CodeCrew():
   """Three-agent code crew: architect, coder, reviewer"""

   @agent
   def architect(self) -> Agent:
       pass

   @agent
   def coder(self) -> Agent:
       pass

   @agent
   def reviewer(self) -> Agent:
       pass

   @task
   def architect_task(self) -> Task:
       pass

   @task
   def code_task(self) -> Task:
       pass

   @task
   def review_task(self) -> Task:
       pass

   @crew
   def crew(self) -> Crew:
       pass

この構造では:

  • @agentデコレータは、エージェントを生成するメソッドを定義するために使用されます。
  • @taskデコレータは、タスクを生成するメソッドを示します。
  • @crewデコレータは、クルー全体を組み立てるメソッドを指定します。

では、完全な実装を見てみましょう:

import os
from crewai import Agent, Crew, Process, Task, LLM
from crewai.project import CrewBase, agent, crew, task
from crewai.agents.agent_builder.base_agent import BaseAgent
from typing import List

from crewai_tools import FileWriterTool

# Instantiate tool
file_writer_tool = FileWriterTool()

@CrewBase
class CodeCrew():

   agents: List[BaseAgent]
   tasks: List[Task]

   agents_config = 'config/agents.yaml'
   tasks_config = 'config/tasks.yaml'
  
   @agent
   def architect(self) -> Agent:
       llm = LLM(
           model="novita/moonshotai/kimi-k2-instruct",
           temperature=0.5,
           api_base="https://api.novita.ai/v3/openai",
           api_key=os.environ['NOVITA_API_KEY']
       )
       return Agent(
           config=self.agents_config['architect'],
           llm=llm,
           verbose=True
       )

   @agent
   def coder(self) -> Agent:
       llm = LLM(
           model="novita/qwen/qwen3-coder-480b-a35b-instruct",
           temperature=0.4,
           api_base="https://api.novita.ai/v3/openai",
           api_key=os.environ['NOVITA_API_KEY']
       )
       return Agent(
           config=self.agents_config['coder'],
           llm=llm,
           verbose=True,
           tools=[file_writer_tool]
       )

   @agent
   def reviewer(self) -> Agent:
       llm = LLM(
           model="novita/moonshotai/kimi-k2-instruct",
           temperature=0.5,
           api_base="https://api.novita.ai/v3/openai",
           api_key=os.environ['NOVITA_API_KEY']
       )
       return Agent(
           config=self.agents_config['reviewer'],
           llm=llm,
           verbose=True,
       )

   @task
   def architect_task(self) -> Task:
       return Task(config=self.tasks_config['architect_task'])

   @task
   def coder_task(self) -> Task:
       return Task(config=self.tasks_config['coder_task'])

   @task
   def review_task(self) -> Task:
       return Task(config=self.tasks_config['review_task'])

   @crew
   def crew(self) -> Crew:
       return Crew(
           agents=self.agents,
           tasks=self.tasks,
           process=Process.sequential,
           verbose=True
       )

このコードは、必要なすべてのメソッドを定義し、エージェントとタスクの両方の属性を含んでいます。また、設定ファイルのパスを指定しており、これらはエージェントとタスクのメソッド内でそれぞれの設定を読み込むために使用されます。

次のコードをスクリプトに追加することで、クルーを実行できるようになりました:

def main():
   code_crew = CodeCrew()
   code_crew.crew().kickoff(inputs={"project": "Todo App"})
if __name__ == '__main__':
   main()

Novitaでフローを構築する

前回はシーケンシャルプロセスに従うクルーを構築しました。次に、カスタム設計のワークフローに従うフローの構築方法について説明します。

ワークフローの設計

カスタマーサポートのマルチエージェントシステムを設計します。このシステムには、カスタマーの問題を分類する責任を持つリードエージェントが含まれます。分類に基づいて、システムは問題を処理するのに最も適した専門エージェントを判断します。セットアップは4つのエージェントで構成されています:

  • カスタマーエージェント: これはリードエージェントです。カスタマーの問題を受信し、以下のカテゴリのいずれかに分類します:請求(Billing)技術(Technical)一般(General)
  • 請求エージェント: すべての請求関連の問題を処理します。
  • 技術エージェント: 技術的な問題を処理します。
  • 一般エージェント: 上記のカテゴリに該当しない問題を処理します。

The Customer Support Flow

フローの状態の定義

CrewAIのフローには共有状態があります。この状態はフロー内のすべてのエージェントとクルーからアクセス可能です。カスタマーサポートフローの場合、状態には問題ラベルとユーザーのクエリが含まれます。

必要なモジュールをインポートし、状態を定義しましょう:

import json
import os
from typing import Literal, Dict, Any
from pydantic import BaseModel, ValidationError
from crewai import Agent, LLM
from crewai.flow.flow import Flow, start, router, listen

# Define the flow state
class SupportState(BaseModel):
   issue: Literal["billing", "technical", "general"] = "general"
   message: str = ""

フローの作成

CrewAIのフローはグラフベースであり、ノード間の遷移を処理するためにイベント駆動型システムを使用します。フローを作成するには、問題を分類するノードで開始し、その後に分類に基づいてどのハンドラノードにルーティングするかを決定するルーターノードを配置します。

class CustomerSupportFlow(Flow[SupportState]):

   @start()
   def classify_issue(self):
       pass

   @router(classify_issue)
   def route_based_on_issue(self) -> str:
       print(f"📨 Routing to agent for issue type: {self.state.issue}")
       if self.state.issue == "billing":
           return "billing"
       if self.state.issue == "technical":
           return "technical"
       else:
           return "general"       

   @listen("billing")
   def handle_billing(self):
       pass

   @listen("technical")
   def handle_technical(self):
       pass

   @listen("general")
   def handle_general(self):
       pass

@start()デコレータで装飾されたメソッドはフローのエントリポイントです。この場合、問題の分類を処理します。@router()デコレータで装飾されたメソッドはclassify_issueの後に実行され、どのエージェントにルーティングするかを決定します。他のメソッドは、ルーティングされたときに応答するためにイベント駆動型システムを使用します。

フローメソッドの実装

では、各メソッドを実装し、それぞれに適切なエージェントを割り当てましょう:

class CustomerSupportFlow(Flow[SupportState]):

   @start()
   def classify_issue(self) -> Dict[str, Any]:
       intake_agent = Agent(
           role="User Intake Agent",
           goal="Classify user support issue and summarize the message",
           backstory="You classify the user query into billing, technical, or general categories.",
           llm=LLM(
               model="novita/qwen/qwen2.5-vl-72b-instruct",
               temperature=0.3,
               api_base="https://api.novita.ai/v3/openai",
               api_key=os.environ["NOVITA_API_KEY"]
           ),
           verbose=True
       )
      
       prompt = f"""
           A user submitted this message: "{self.state.message}"

           Your task:
           1. Identify whether the issue is "billing", "technical", or "general".
           2. Rephrase or extract the message clearly.

           Respond in valid JSON format like the example below:

           {{
               "issue": "billing",
               "message": "The user has been charged twice for their subscription and is requesting a refund."
           }}
       """

       output = intake_agent.kickoff(prompt, response_format=SupportState)
      
       try:
           # Parse the JSON string
           print(output.raw)
           parsed_json = json.loads(output.raw)

           # Validate with Pydantic
           validated = SupportState(**parsed_json)

           # Save to state
           self.state.issue = validated.issue
           self.state.message = validated.message

       except (json.JSONDecodeError, ValidationError) as e:
           print("❌ Failed to parse or validate response:", e)
           self.state.issue = "general"

   @router(classify_issue)
   def route_based_on_issue(self) -> str:
       print(f"📨 Routing to agent for issue type: {self.state.issue}")
       if self.state.issue == "billing":
           return "billing"
       if self.state.issue == "technical":
           return "technical"
       else:
           return "general"       

   @listen("billing")
   def handle_billing(self):
       agent = Agent(
           role="Billing Agent",
           goal="Handle billing questions, refunds, and invoice issues",
           backstory="You resolve billing-related queries effectively.",
           llm=LLM(
               model="novita/meta-llama/llama-3.3-70b-instruct",
               temperature=0.5,
               api_base="https://api.novita.ai/v3/openai",
               api_key=os.environ["NOVITA_API_KEY"]
           ),
           verbose=True
       )
       result = agent.kickoff(self.state.message)
       print("💰 Billing Agent Response:", result)

   @listen("technical")
   def handle_technical(self):
       agent = Agent(
           role="Technical Support Agent",
           goal="Help users resolve technical issues",
           backstory="You're a technical expert providing troubleshooting support.",
           llm=LLM(
               model="novita/meta-llama/llama-3.1-8b-instruct",
               temperature=0.5,
               api_base="https://api.novita.ai/v3/openai",
               api_key=os.environ["NOVITA_API_KEY"]
           ),
           verbose=True
       )
       result = agent.kickoff(self.state.message)
       print("🛠 Technical Agent Response:", result)

   @listen("general")
   def handle_general(self):
       agent = Agent(
           role="General Support Agent",
           goal="Provide helpful answers to non-technical and non-billing questions",
           backstory="You're friendly and well-informed about our services.",
           llm=LLM(
               model="novita/minimaxai/minimax-m1-80k",
               temperature=0.5,
               api_base="https://api.novita.ai/v3/openai",
               api_key=os.environ["NOVITA_API_KEY"]
           ),
           verbose=True
       )
       result = agent.kickoff(self.state.message)
       print("📋 General Agent Response:", result)

classify_issueメソッドは、モデルが有効な応答を返すことを保証するためにPydanticを使用します。このセットアップが完了したので、次のコードを使用してフローを実行できます:

def run():
   flow = CustomerSupportFlow()
   flow.plot("CustomerSupportFlowPlot")

   # Example input message
   example_input = {
       "message": "Hello, I was charged twice for my subscription and need a refund."
   }

   flow.kickoff(inputs=example_input)

if __name__ == "__main__":
   run()

まとめ

マルチエージェントシステムを構築するには、2つの重要なコンポーネントが必要です。多様なモデルを提供するモデルプロバイダーと、エージェントの相互作用を調整するマルチエージェントフレームワークです。この記事では、NovitaとCrewAIがこれらの役割を効果的に果たす方法を示しました。CrewAIの詳細については、ドキュメントを参照してください。また、クルーでより多くのモデルを試したい場合は、NovitaのLLMプレイグラウンドを確認してください。

ファイルを含むリポジトリ - https://github.com/novitalabs/Novita-CollabHub/tree/main/examples/novita-crewai

Novita AIについて

NovitaAIは、シンプルなAPIを使用してAIモデルを簡単にデプロイできる手段を開発者に提供すると同時に、構築とスケーリングのための手頃で信頼性の高いGPUクラウドを提供するAIクラウドプラットフォームです。