T-CREATOR

LangChain マルチエージェント設計パターン:役割分担/階層化/仲裁/投票

LangChain マルチエージェント設計パターン:役割分担/階層化/仲裁/投票

LLMを活用したアプリケーション開発において、単一のエージェントだけでは複雑なタスクに対応しきれないケースが増えています。そんな課題を解決する鍵となるのが「マルチエージェント」という考え方です。

LangChainでは、複数のAIエージェントを組み合わせることで、より高度で柔軟なシステムを構築できるんです。本記事では、実務でも活用できる4つの代表的な設計パターン「役割分担」「階層化」「仲裁」「投票」について、それぞれの特徴と実装方法を詳しく解説していきます。

マルチエージェントシステムを理解することで、あなたのLLMアプリケーションはさらに進化するでしょう。

背景

マルチエージェントシステムとは

マルチエージェントシステムとは、複数の自律的なAIエージェントが協調・競合しながら目標を達成するシステムのことです。人間の組織と同じように、各エージェントが専門性を持ち、役割を分担することで、単一のエージェントでは実現できない複雑なタスクに対応できます。

従来のLLMアプリケーションは、1つのプロンプトで全てを処理しようとするため、以下のような制約がありました。

#制約内容
1コンテキストの限界1つのプロンプトで扱える情報量には上限がある
2専門性の不足全ての領域に精通した万能なプロンプトは作りにくい
3スケーラビリティタスクが増えるほどプロンプトが複雑化し保守性が下がる

LangChainにおけるエージェントの役割

LangChainでは、エージェントは「思考」と「行動」を繰り返すことでタスクを遂行します。各エージェントはToolsを使って外部APIを呼び出したり、データベースにアクセスしたりできるんです。

マルチエージェント構成では、このようなエージェントを複数組み合わせることで、より柔軟で強力なシステムを実現できます。

下記の図は、LangChainにおける基本的なマルチエージェント構成を示しています。

mermaidflowchart TB
    user["ユーザー"] -->|リクエスト| orchestrator["オーケストレーター"]
    orchestrator -->|タスク割り当て| agent1["エージェント1<br/>(専門A)"]
    orchestrator -->|タスク割り当て| agent2["エージェント2<br/>(専門B)"]
    orchestrator -->|タスク割り当て| agent3["エージェント3<br/>(専門C)"]

    agent1 -->|結果| orchestrator
    agent2 -->|結果| orchestrator
    agent3 -->|結果| orchestrator

    orchestrator -->|統合結果| user

この図からわかるように、オーケストレーターが各エージェントに適切にタスクを振り分け、結果を統合することで、効率的な処理が実現されています。

課題

単一エージェントの限界

実務でLLMアプリケーションを開発していると、以下のような課題に直面することがあります。

コンテキストウィンドウの制約

単一のエージェントで全てを処理しようとすると、LLMのコンテキストウィンドウ(一度に処理できるトークン数)が不足しがちです。GPT-4でも最大128kトークンという制限があり、大量のドキュメントを扱う場合には工夫が必要になります。

専門性とジェネラリストのトレードオフ

汎用的なプロンプトは幅広いタスクに対応できますが、専門的なタスクでの精度が落ちてしまいます。逆に、特定領域に特化したプロンプトは専門性は高いものの、他の領域には対応できません。

デバッグと保守の困難さ

1つの巨大なプロンプトで全てを制御しようとすると、どこで問題が起きているのか特定しづらくなります。また、機能追加や修正のたびに全体への影響を考慮する必要があり、保守コストが増大するんです。

マルチエージェント設計の必要性

これらの課題を解決するために、マルチエージェント設計が注目されています。しかし、適切な設計パターンを選択しないと、以下のような新たな問題が発生する可能性があります。

#問題具体例
1エージェント間の競合複数のエージェントが同時に同じリソースにアクセスして衝突
2責任の所在の不明確さどのエージェントがどのタスクを担当するのか曖昧
3無限ループのリスクエージェント間の循環参照により処理が終わらない
4コスト増加不要なAPI呼び出しでトークン使用量が膨大に

下記の図は、設計パターンを適用しない場合の混沌とした状態を示しています。

mermaidflowchart LR
    user["ユーザー"] --> agent1["エージェント1"]
    agent1 -.->|相互参照| agent2["エージェント2"]
    agent2 -.->|相互参照| agent3["エージェント3"]
    agent3 -.->|相互参照| agent1
    agent2 -.->|重複処理| agent4["エージェント4"]
    agent3 -.->|重複処理| agent4

    agent4 -->|混乱した結果| user

    style agent1 fill:#ffcccc
    style agent2 fill:#ffcccc
    style agent3 fill:#ffcccc
    style agent4 fill:#ffcccc

この図が示すように、適切な設計パターンなしでは、エージェント間の関係が複雑化し、システム全体が管理不能になってしまうリスクがあります。

解決策

マルチエージェント設計の4つのパターン

LangChainでは、以下の4つの設計パターンを活用することで、マルチエージェントシステムの課題を効果的に解決できます。それぞれのパターンには明確な使い分けの基準があり、タスクの性質に応じて適切なパターンを選択することが重要です。

#パターン特徴適用場面
1役割分担各エージェントが専門領域を担当異なる専門知識が必要なタスク
2階層化マネージャーエージェントが複数のワーカーを管理複雑なワークフローの制御が必要
3仲裁ルーターエージェントが最適なエージェントを選択タスクごとに最適な専門家を割り当てたい
4投票複数エージェントの出力を集約して最良の結果を選択高い精度や信頼性が求められる

それでは、各パターンについて詳しく見ていきましょう。

パターン1: 役割分担(Division of Labor)

概要

役割分担パターンでは、各エージェントが特定の専門領域を担当します。まるで企業の部署のように、それぞれが得意分野に集中することで、全体として高い品質を実現するんです。

アーキテクチャ

下記の図は、役割分担パターンの処理フローを示しています。

mermaidflowchart TD
    input["入力:<br/>複合的なタスク"] --> coordinator["コーディネーター"]

    coordinator -->|調査タスク| researcher["リサーチャー<br/>エージェント"]
    coordinator -->|執筆タスク| writer["ライター<br/>エージェント"]
    coordinator -->|校正タスク| reviewer["レビュアー<br/>エージェント"]

    researcher -->|調査結果| coordinator
    writer -->|ドラフト| coordinator
    reviewer -->|フィードバック| coordinator

    coordinator --> output["最終成果物"]

この図が示すように、コーディネーターが各専門エージェントに適切にタスクを振り分け、結果を統合することで効率的な処理を実現します。

メリットとデメリット

#メリットデメリット
1各エージェントが専門性に特化できるエージェント間の調整が必要
2プロンプトがシンプルで保守しやすいコーディネーターの設計が重要
3並列処理により高速化が可能エージェント数が増えるとコストが増加

パターン2: 階層化(Hierarchical)

概要

階層化パターンでは、マネージャーエージェントがワーカーエージェントを管理する組織構造を採用します。複雑なワークフローを段階的に分解し、制御することができるんです。

アーキテクチャ

下記の図は、階層化パターンの構造を示しています。

mermaidflowchart TB
    user["ユーザー"] --> manager["マネージャー<br/>エージェント"]

    manager -->|サブタスク1| worker1["ワーカー1"]
    manager -->|サブタスク2| worker2["ワーカー2"]
    manager -->|サブタスク3| worker3["ワーカー3"]

    worker1 -->|結果1| manager
    worker2 -->|結果2| manager
    worker3 -->|結果3| manager

    worker2 -.->|必要に応じて| subworker1["サブワーカー1"]
    worker2 -.->|必要に応じて| subworker2["サブワーカー2"]

    manager --> result["統合結果"]
    result --> user

マネージャーエージェントは全体の進行を監督し、各ワーカーは割り当てられたサブタスクに集中します。必要に応じてワーカー自身がさらに下位のエージェントを持つこともできるんです。

メリットとデメリット

#メリットデメリット
1複雑なワークフローを段階的に管理マネージャーの判断精度が全体に影響
2タスクの依存関係を明確に制御できる階層が深くなるとレイテンシが増加
3エラーハンドリングを階層ごとに実装可能設計の複雑さが増す

パターン3: 仲裁(Routing)

概要

仲裁パターンでは、ルーターエージェントが入力内容を分析し、最適な専門エージェントを動的に選択します。まるで受付係のように、適切な専門家に案内する役割を果たすんです。

アーキテクチャ

下記の図は、仲裁パターンの処理フローを示しています。

mermaidflowchart TD
    input["入力:<br/>ユーザーのクエリ"] --> router["ルーター<br/>エージェント"]

    router -->|質問内容を分析| decision{"タスクの<br/>種類判定"}

    decision -->|技術的質問| tech["技術サポート<br/>エージェント"]
    decision -->|ビジネス相談| business["ビジネス<br/>エージェント"]
    decision -->|一般的な質問| general["汎用<br/>エージェント"]

    tech --> output["回答"]
    business --> output
    general --> output

    output --> user["ユーザー"]

ルーターは入力を分析して適切なエージェントを選択するため、無駄なエージェント呼び出しを削減できます。

メリットとデメリット

#メリットデメリット
1必要なエージェントだけを呼び出しコスト削減ルーティングロジックの精度が重要
2新しい専門エージェントの追加が容易ルーター自体がボトルネックになる可能性
3各エージェントの専門性を最大限活用境界が曖昧なタスクの判定が難しい

パターン4: 投票(Voting/Consensus)

概要

投票パターンでは、複数のエージェントが同じタスクに対して独立に回答を生成し、それらを集約して最終的な出力を決定します。民主主義のように、多様な意見から最良の結果を導き出すんです。

アーキェクチャ

下記の図は、投票パターンの処理フローを示しています。

mermaidflowchart TB
    input["入力:<br/>同じクエリ"] --> agent1["エージェント1<br/>(アプローチA)"]
    input --> agent2["エージェント2<br/>(アプローチB)"]
    input --> agent3["エージェント3<br/>(アプローチC)"]

    agent1 -->|回答1| aggregator["アグリゲーター"]
    agent2 -->|回答2| aggregator
    agent3 -->|回答3| aggregator

    aggregator --> evaluation["評価・集約"]
    evaluation --> decision{"集約方法"}

    decision -->|多数決| majority["多数決で決定"]
    decision -->|品質評価| quality["品質スコアで決定"]
    decision -->|統合| synthesis["複数回答を統合"]

    majority --> final["最終回答"]
    quality --> final
    synthesis --> final

    final --> user["ユーザー"]

複数のエージェントが異なるアプローチで問題に取り組むことで、単一のエージェントでは見逃してしまう視点を補完できます。

メリットとデメリット

#メリットデメリット
1単一エージェントより高精度な結果複数のエージェント呼び出しでコスト増
2異なるアプローチで多様な視点を獲得レスポンスタイムが長くなる
3エージェントの偏りや誤りを相互補完集約ロジックの設計が複雑

パターンの選択基準

各パターンをどのように使い分ければよいのか、判断基準をまとめました。

#判断基準推奨パターン
1タスクが明確に異なる専門領域に分割できる役割分担
2タスクに依存関係があり順序制御が必要階層化
3入力に応じて動的にエージェントを選びたい仲裁
4高い精度と信頼性が最優先投票

具体例

役割分担パターンの実装

技術記事を自動生成するシステムを例に、役割分担パターンを実装してみましょう。このシステムでは、リサーチャー、ライター、レビュアーの3つのエージェントが協力します。

必要なパッケージのインストール

まず、必要なパッケージをインストールします。

bashyarn add langchain @langchain/openai @langchain/core

環境変数の設定

OpenAI APIキーを環境変数に設定します。

typescript// .env ファイル
OPENAI_API_KEY=your_openai_api_key_here

型定義とインターフェース

各エージェントが扱うデータ構造を定義します。

typescript// types.ts

// リサーチャーの出力型
interface ResearchResult {
  topic: string;
  keyPoints: string[];
  references: string[];
}

// ライターの出力型
interface DraftArticle {
  title: string;
  content: string;
  wordCount: number;
}

// レビュアーの出力型
interface ReviewResult {
  feedback: string;
  suggestions: string[];
  approved: boolean;
}

これらの型定義により、エージェント間のデータ受け渡しが型安全になります。

リサーチャーエージェントの実装

リサーチャーエージェントは、指定されたトピックに関する情報を収集します。

typescript// researcher-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

export class ResearcherAgent {
  private llm: ChatOpenAI;

  constructor() {
    // GPT-4を使用してより高度なリサーチを実現
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.3, // 事実に基づく情報なので低めに設定
    });
  }

  // トピックに関する情報をリサーチする
  async research(topic: string): Promise<ResearchResult> {
    const systemPrompt = `
あなたは技術情報のリサーチを専門とするエージェントです。
与えられたトピックについて、以下を調査してください:
- 主要なポイント(5つ程度)
- 関連する技術や概念
- 参考になる情報源
`;

    const userPrompt = `
トピック: ${topic}

上記のトピックについてリサーチを行い、
記事執筆に必要な情報をまとめてください。
`;

    const messages = [
      new SystemMessage(systemPrompt),
      new HumanMessage(userPrompt),
    ];

    const response = await this.llm.invoke(messages);

    // レスポンスをパースして構造化データに変換
    return this.parseResearchResult(response.content as string);
  }

  // LLMの出力をパースする補助メソッド
  private parseResearchResult(content: string): ResearchResult {
    // 実際にはより堅牢なパースロジックが必要
    // ここでは簡略化のため基本的な実装のみ
    const lines = content.split('\n').filter(line => line.trim());

    return {
      topic: lines[0] || '',
      keyPoints: lines.slice(1, 6),
      references: lines.slice(6),
    };
  }
}

リサーチャーは事実に基づく情報を収集するため、temperatureを低めに設定しているのがポイントです。

ライターエージェントの実装

ライターエージェントは、リサーチ結果を基に記事を執筆します。

typescript// writer-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { ResearchResult, DraftArticle } from "./types";

export class WriterAgent {
  private llm: ChatOpenAI;

  constructor() {
    // 創造的な文章生成のためtemperatureを高めに設定
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.7,
    });
  }

  // リサーチ結果を基に記事を執筆
  async write(research: ResearchResult): Promise<DraftArticle> {
    const systemPrompt = `
あなたは技術記事の執筆を専門とするライターです。
読みやすく、わかりやすい記事を書くことを心がけてください。

記事の構成:
- 導入(背景と課題)
- 本文(解決策と具体例)
- まとめ

文体:です・ます調で、親しみやすく
`;

    const userPrompt = `
以下のリサーチ結果を基に技術記事を執筆してください。

トピック: ${research.topic}

主要ポイント:
${research.keyPoints.map((point, i) => `${i + 1}. ${point}`).join('\n')}

参考情報:
${research.references.join('\n')}

2000〜3000文字程度で執筆してください。
`;

    const messages = [
      new SystemMessage(systemPrompt),
      new HumanMessage(userPrompt),
    ];

    const response = await this.llm.invoke(messages);
    const content = response.content as string;

    return {
      title: research.topic,
      content: content,
      wordCount: content.length,
    };
  }
}

ライターは創造性が求められるため、temperatureを0.7に設定しています。

レビュアーエージェントの実装

レビュアーエージェントは、執筆された記事を評価し、フィードバックを提供します。

typescript// reviewer-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { DraftArticle, ReviewResult } from "./types";

export class ReviewerAgent {
  private llm: ChatOpenAI;

  constructor() {
    // 客観的な評価のため中程度のtemperature
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.5,
    });
  }

  // 記事をレビューしてフィードバックを提供
  async review(draft: DraftArticle): Promise<ReviewResult> {
    const systemPrompt = `
あなたは技術記事のレビューを専門とするエディターです。
以下の観点で記事を評価してください:

1. 技術的な正確性
2. 構成の論理性
3. 読みやすさ
4. 具体例の適切さ
5. 文体の一貫性

改善点があれば具体的に指摘してください。
`;

    const userPrompt = `
以下の記事をレビューしてください。

タイトル: ${draft.title}

本文:
${draft.content}

評価結果をJSON形式で返してください:
{
  "feedback": "全体的な評価コメント",
  "suggestions": ["改善提案1", "改善提案2", ...],
  "approved": true/false
}
`;

    const messages = [
      new SystemMessage(systemPrompt),
      new HumanMessage(userPrompt),
    ];

    const response = await this.llm.invoke(messages);

    // JSON形式のレスポンスをパース
    return this.parseReviewResult(response.content as string);
  }

  // レビュー結果をパース
  private parseReviewResult(content: string): ReviewResult {
    try {
      // JSONブロックから実際のJSONを抽出
      const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
        || content.match(/\{[\s\S]*\}/);

      if (jsonMatch) {
        const jsonStr = jsonMatch[1] || jsonMatch[0];
        return JSON.parse(jsonStr);
      }
    } catch (error) {
      console.error("Failed to parse review result:", error);
    }

    // パース失敗時のフォールバック
    return {
      feedback: content,
      suggestions: [],
      approved: false,
    };
  }
}

レビュアーは客観的な評価が必要なため、temperature 0.5でバランスを取っています。

コーディネーターの実装

コーディネーターは各エージェントを統括し、全体のワークフローを制御します。

typescript// coordinator.ts
import { ResearcherAgent } from "./researcher-agent";
import { WriterAgent } from "./writer-agent";
import { ReviewerAgent } from "./reviewer-agent";
import { DraftArticle } from "./types";

export class ArticleCoordinator {
  private researcher: ResearcherAgent;
  private writer: WriterAgent;
  private reviewer: ReviewerAgent;

  constructor() {
    this.researcher = new ResearcherAgent();
    this.writer = new WriterAgent();
    this.reviewer = new ReviewerAgent();
  }

  // 記事生成の全体フローを制御
  async generateArticle(
    topic: string,
    maxRetries: number = 2
  ): Promise<DraftArticle> {
    console.log(`📚 リサーチフェーズ: ${topic}`);
    // Step 1: リサーチ
    const research = await this.researcher.research(topic);
    console.log(`✅ リサーチ完了: ${research.keyPoints.length}個のポイント`);

    let draft: DraftArticle;
    let retryCount = 0;

    // Step 2 & 3: 執筆とレビューのループ
    do {
      console.log(`✍️  執筆フェーズ (試行 ${retryCount + 1})`);
      draft = await this.writer.write(research);
      console.log(`✅ 執筆完了: ${draft.wordCount}文字`);

      console.log(`🔍 レビューフェーズ`);
      const review = await this.reviewer.review(draft);
      console.log(`✅ レビュー完了`);

      if (review.approved) {
        console.log(`🎉 記事が承認されました!`);
        break;
      }

      console.log(`⚠️  改善が必要: ${review.suggestions.length}個の提案`);
      console.log(`フィードバック: ${review.feedback}`);

      retryCount++;
    } while (retryCount < maxRetries);

    return draft;
  }
}

コーディネーターは各エージェントを順番に呼び出し、レビューが承認されるまでループします。

実行例

実際にシステムを実行してみましょう。

typescript// main.ts
import * as dotenv from "dotenv";
import { ArticleCoordinator } from "./coordinator";

// 環境変数を読み込み
dotenv.config();

async function main() {
  const coordinator = new ArticleCoordinator();

  const topic = "LangChainのマルチエージェント設計パターン";

  try {
    const article = await coordinator.generateArticle(topic);

    console.log("\n" + "=".repeat(50));
    console.log("最終的な記事:");
    console.log("=".repeat(50));
    console.log(`\n## ${article.title}\n`);
    console.log(article.content);
    console.log(`\n(文字数: ${article.wordCount})`);

  } catch (error) {
    console.error("エラーが発生しました:", error);
  }
}

main();

実行すると、各エージェントが順番に処理を行い、技術記事が生成されます。

bashyarn ts-node main.ts

このように、役割分担パターンでは各エージェントが専門性を発揮し、協力して高品質な成果物を生み出せるんです。

階層化パターンの実装

次に、カスタマーサポートシステムを例に階層化パターンを実装してみましょう。マネージャーエージェントが問い合わせを分析し、適切なワーカーに振り分けます。

マネージャーエージェントの実装

マネージャーは全体を統括し、タスクを分解して各ワーカーに割り当てます。

typescript// manager-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

// タスクの種類を定義
type TaskType = "technical" | "billing" | "general";

interface Task {
  type: TaskType;
  description: string;
  priority: number;
}

export class ManagerAgent {
  private llm: ChatOpenAI;

  constructor() {
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.2, // 判断の一貫性を保つため低めに設定
    });
  }

  // 問い合わせを分析してタスクに分解
  async analyzeTasks(inquiry: string): Promise<Task[]> {
    const systemPrompt = `
あなたはカスタマーサポートのマネージャーです。
顧客の問い合わせを分析し、以下のカテゴリに分類してください:

- technical: 技術的な問題やエラー
- billing: 料金や請求に関する問題
- general: その他の一般的な質問

複数のカテゴリにまたがる場合は、タスクを分割してください。
各タスクに優先度(1-5)を付けてください。
`;

    const userPrompt = `
以下の問い合わせを分析し、必要なタスクをJSON配列で返してください:

問い合わせ内容:
${inquiry}

形式:
[
  {
    "type": "technical",
    "description": "タスクの説明",
    "priority": 3
  },
  ...
]
`;

    const messages = [
      new SystemMessage(systemPrompt),
      new HumanMessage(userPrompt),
    ];

    const response = await this.llm.invoke(messages);
    return this.parseTasks(response.content as string);
  }

  // LLMの出力をパースしてTask配列に変換
  private parseTasks(content: string): Task[] {
    try {
      const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
        || content.match(/\[[\s\S]*\]/);

      if (jsonMatch) {
        const jsonStr = jsonMatch[1] || jsonMatch[0];
        return JSON.parse(jsonStr);
      }
    } catch (error) {
      console.error("Failed to parse tasks:", error);
    }

    return [];
  }
}

マネージャーは問い合わせを分析し、適切なタスクに分解する役割を担います。

ワーカーエージェントの基底クラス

各ワーカーが共通で持つべき機能を基底クラスとして定義します。

typescript// worker-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

export abstract class WorkerAgent {
  protected llm: ChatOpenAI;
  protected role: string;

  constructor(role: string) {
    this.role = role;
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.5,
    });
  }

  // 各ワーカーが実装する抽象メソッド
  abstract getSystemPrompt(): string;

  // タスクを処理する共通メソッド
  async processTask(taskDescription: string): Promise<string> {
    const messages = [
      new SystemMessage(this.getSystemPrompt()),
      new HumanMessage(taskDescription),
    ];

    const response = await this.llm.invoke(messages);
    return response.content as string;
  }
}

基底クラスにより、各ワーカーの実装が統一され、保守性が向上します。

技術サポートワーカーの実装

技術的な問題に対応するワーカーを実装します。

typescript// technical-worker.ts
import { WorkerAgent } from "./worker-agent";

export class TechnicalWorker extends WorkerAgent {
  constructor() {
    super("Technical Support");
  }

  getSystemPrompt(): string {
    return `
あなたは技術サポートの専門家です。
以下の能力を持っています:

- エラーメッセージの分析と解決策の提示
- システム構成の診断
- トラブルシューティング手順の案内
- 技術ドキュメントの参照

回答は以下の形式で提供してください:
1. 問題の要約
2. 考えられる原因
3. 解決手順(ステップバイステップ)
4. 追加の推奨事項
`;
  }
}

技術サポートワーカーは技術的な問題に特化した対応を行います。

請求サポートワーカーの実装

料金や請求に関する問い合わせに対応するワーカーです。

typescript// billing-worker.ts
import { WorkerAgent } from "./worker-agent";

export class BillingWorker extends WorkerAgent {
  constructor() {
    super("Billing Support");
  }

  getSystemPrompt(): string {
    return `
あなたは請求・料金サポートの専門家です。
以下の業務を担当します:

- 料金プランの説明
- 請求書の確認と説明
- 支払い方法の案内
- 返金処理の手続き

回答は丁寧かつ明確に、金額や日付は正確に記載してください。
`;
  }
}

請求ワーカーは金銭に関わる重要な情報を扱うため、正確性が求められます。

一般サポートワーカーの実装

その他の一般的な質問に対応するワーカーです。

typescript// general-worker.ts
import { WorkerAgent } from "./worker-agent";

export class GeneralWorker extends WorkerAgent {
  constructor() {
    super("General Support");
  }

  getSystemPrompt(): string {
    return `
あなたは一般的なカスタマーサポート担当です。
幅広い質問に対応し、必要に応じて専門部署への案内も行います。

対応範囲:
- サービスの使い方
- アカウント管理
- 一般的な質問
- 専門部署へのエスカレーション

親切で分かりやすい回答を心がけてください。
`;
  }
}

一般ワーカーは幅広い質問に対応する万能タイプです。

サポートシステムの統合

マネージャーとワーカーを統合し、完全なサポートシステムを構築します。

typescript// support-system.ts
import { ManagerAgent } from "./manager-agent";
import { TechnicalWorker } from "./technical-worker";
import { BillingWorker } from "./billing-worker";
import { GeneralWorker } from "./general-worker";

export class SupportSystem {
  private manager: ManagerAgent;
  private workers: Map<string, WorkerAgent>;

  constructor() {
    this.manager = new ManagerAgent();

    // 各ワーカーを初期化
    this.workers = new Map([
      ["technical", new TechnicalWorker()],
      ["billing", new BillingWorker()],
      ["general", new GeneralWorker()],
    ]);
  }

  // 問い合わせを処理する
  async handleInquiry(inquiry: string): Promise<string[]> {
    console.log("📨 問い合わせを受信しました");
    console.log(`内容: ${inquiry}\n`);

    // Step 1: マネージャーがタスクを分析
    console.log("🔍 マネージャーがタスクを分析中...");
    const tasks = await this.manager.analyzeTasks(inquiry);
    console.log(`✅ ${tasks.length}個のタスクに分解されました\n`);

    // タスクを優先度順にソート
    tasks.sort((a, b) => b.priority - a.priority);

    // Step 2: 各タスクを適切なワーカーに割り当て
    const responses: string[] = [];

    for (const task of tasks) {
      const worker = this.workers.get(task.type);

      if (!worker) {
        console.warn(`⚠️  不明なタスクタイプ: ${task.type}`);
        continue;
      }

      console.log(`🔧 ${task.type}ワーカーがタスクを処理中...`);
      console.log(`   優先度: ${task.priority}`);

      const response = await worker.processTask(task.description);
      responses.push(`[${task.type.toUpperCase()}]\n${response}`);

      console.log(`✅ 処理完了\n`);
    }

    return responses;
  }
}

サポートシステムは問い合わせを受け取り、適切に処理して回答を返します。

実行例

実際にサポートシステムを動かしてみましょう。

typescript// support-main.ts
import * as dotenv from "dotenv";
import { SupportSystem } from "./support-system";

dotenv.config();

async function main() {
  const system = new SupportSystem();

  // 複合的な問い合わせの例
  const inquiry = `
先月の請求額が通常より高くなっているのですが、
内訳を確認したいです。
また、サービスにログインしようとすると
「Error 500: Internal Server Error」が表示されて
アクセスできません。
`;

  try {
    const responses = await system.handleInquiry(inquiry);

    console.log("\n" + "=".repeat(50));
    console.log("サポートからの回答:");
    console.log("=".repeat(50) + "\n");

    responses.forEach((response, index) => {
      console.log(response);
      if (index < responses.length - 1) {
        console.log("\n" + "-".repeat(50) + "\n");
      }
    });

  } catch (error) {
    console.error("エラーが発生しました:", error);
  }
}

main();

実行すると、マネージャーが問い合わせを分析し、請求ワーカーと技術ワーカーが並行して対応します。

階層化パターンでは、マネージャーが全体を把握しながら、各ワーカーが専門的な処理を行うことで、複雑なワークフローを効率的に管理できるんです。

仲裁パターンの実装

ドキュメント分類システムを例に、仲裁パターンを実装してみます。ルーターが文書の種類を判定し、最適な専門エージェントに振り分けます。

ルーターエージェントの実装

ルーターは入力を分析して最適なエージェントを選択します。

typescript// router-agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

type DocumentType = "contract" | "technical" | "marketing" | "financial";

interface RoutingDecision {
  documentType: DocumentType;
  confidence: number;
  reasoning: string;
}

export class RouterAgent {
  private llm: ChatOpenAI;

  constructor() {
    // ルーティング判定の精度を高めるためGPT-4を使用
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.1, // 判定の一貫性を重視
    });
  }

  // 文書の種類を判定
  async route(document: string): Promise<RoutingDecision> {
    const systemPrompt = `
あなたは文書分類のエキスパートです。
入力された文書を分析し、以下のカテゴリのいずれかに分類してください:

1. contract: 契約書、法的文書
2. technical: 技術仕様書、マニュアル
3. marketing: マーケティング資料、プレゼン
4. financial: 財務報告、予算資料

判定結果は以下のJSON形式で返してください:
{
  "documentType": "カテゴリ名",
  "confidence": 0.0-1.0の信頼度,
  "reasoning": "判定理由"
}
`;

    const userPrompt = `
以下の文書を分類してください:

${document.substring(0, 1000)}${document.length > 1000 ? '...' : ''}

(先頭1000文字を表示)
`;

    const messages = [
      new SystemMessage(systemPrompt),
      new HumanMessage(userPrompt),
    ];

    const response = await this.llm.invoke(messages);
    return this.parseRoutingDecision(response.content as string);
  }

  // ルーティング判定をパース
  private parseRoutingDecision(content: string): RoutingDecision {
    try {
      const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
        || content.match(/\{[\s\S]*?\}/);

      if (jsonMatch) {
        const jsonStr = jsonMatch[1] || jsonMatch[0];
        return JSON.parse(jsonStr);
      }
    } catch (error) {
      console.error("Failed to parse routing decision:", error);
    }

    // フォールバック
    return {
      documentType: "technical",
      confidence: 0.5,
      reasoning: "判定に失敗したためデフォルト値を使用",
    };
  }
}

ルーターは文書の内容を分析し、信頼度とともに判定結果を返します。

専門エージェントの基底クラス

各専門エージェントが実装すべきインターフェースを定義します。

typescript// document-processor.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

export interface ProcessingResult {
  summary: string;
  keyPoints: string[];
  recommendations: string[];
}

export abstract class DocumentProcessor {
  protected llm: ChatOpenAI;
  protected specialization: string;

  constructor(specialization: string) {
    this.specialization = specialization;
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.3,
    });
  }

  // 各専門エージェントが実装
  abstract getSystemPrompt(): string;

  // 文書を処理
  async process(document: string): Promise<ProcessingResult> {
    const messages = [
      new SystemMessage(this.getSystemPrompt()),
      new HumanMessage(`以下の文書を分析してください:\n\n${document}`),
    ];

    const response = await this.llm.invoke(messages);
    return this.parseResult(response.content as string);
  }

  // 結果をパース
  protected parseResult(content: string): ProcessingResult {
    // 簡略化のため基本的な実装
    const lines = content.split('\n').filter(line => line.trim());

    return {
      summary: lines[0] || '',
      keyPoints: lines.slice(1, 4),
      recommendations: lines.slice(4, 7),
    };
  }
}

基底クラスにより、各専門エージェントの実装が統一されます。

契約書処理エージェント

法的文書を専門に処理するエージェントです。

typescript// contract-processor.ts
import { DocumentProcessor } from "./document-processor";

export class ContractProcessor extends DocumentProcessor {
  constructor() {
    super("Contract Analysis");
  }

  getSystemPrompt(): string {
    return `
あなたは法的文書の分析を専門とするエージェントです。

契約書を分析する際は以下に注目してください:
- 契約当事者
- 契約期間
- 重要な条項(支払い、解約、責任制限など)
- リスク事項
- 必要なアクション

法的な観点から重要なポイントを漏らさず指摘してください。
`;
  }
}

契約書処理エージェントは法的な観点から文書を分析します。

技術文書処理エージェント

技術仕様書やマニュアルを処理するエージェントです。

typescript// technical-processor.ts
import { DocumentProcessor } from "./document-processor";

export class TechnicalProcessor extends DocumentProcessor {
  constructor() {
    super("Technical Documentation");
  }

  getSystemPrompt(): string {
    return `
あなたは技術文書の分析を専門とするエージェントです。

技術文書を分析する際は以下に注目してください:
- システム構成や技術スタック
- 主要な機能と仕様
- 制約事項や前提条件
- セキュリティ考慮事項
- 実装上の注意点

技術的な観点から重要なポイントを抽出してください。
`;
  }
}

技術文書処理エージェントは技術的な詳細を重視します。

マーケティング文書処理エージェント

マーケティング資料を処理するエージェントです。

typescript// marketing-processor.ts
import { DocumentProcessor } from "./document-processor";

export class MarketingProcessor extends DocumentProcessor {
  constructor() {
    super("Marketing Materials");
  }

  getSystemPrompt(): string {
    return `
あなたはマーケティング資料の分析を専門とするエージェントです。

マーケティング文書を分析する際は以下に注目してください:
- ターゲット顧客
- 主要なメッセージとバリュープロポジション
- 競合との差別化ポイント
- CTA(Call to Action)
- ブランドイメージ

マーケティング効果を高めるための提案も含めてください。
`;
  }
}

マーケティング処理エージェントは訴求力や効果を重視します。

財務文書処理エージェント

財務報告や予算資料を処理するエージェントです。

typescript// financial-processor.ts
import { DocumentProcessor } from "./document-processor";

export class FinancialProcessor extends DocumentProcessor {
  constructor() {
    super("Financial Documents");
  }

  getSystemPrompt(): string {
    return `
あなたは財務文書の分析を専門とするエージェントです。

財務文書を分析する際は以下に注目してください:
- 主要な財務指標(売上、利益、キャッシュフローなど)
- トレンドや変化のパターン
- リスク要因
- 予算との差異
- 改善の機会

数値の正確性と財務的な健全性を重視してください。
`;
  }
}

財務処理エージェントは数値の正確性を最優先します。

文書処理システムの統合

ルーターと各専門エージェントを統合します。

typescript// document-routing-system.ts
import { RouterAgent } from "./router-agent";
import { ContractProcessor } from "./contract-processor";
import { TechnicalProcessor } from "./technical-processor";
import { MarketingProcessor } from "./marketing-processor";
import { FinancialProcessor } from "./financial-processor";
import { ProcessingResult } from "./document-processor";

export class DocumentRoutingSystem {
  private router: RouterAgent;
  private processors: Map<string, DocumentProcessor>;

  constructor() {
    this.router = new RouterAgent();

    // 各専門プロセッサーを登録
    this.processors = new Map([
      ["contract", new ContractProcessor()],
      ["technical", new TechnicalProcessor()],
      ["marketing", new MarketingProcessor()],
      ["financial", new FinancialProcessor()],
    ]);
  }

  // 文書を処理
  async processDocument(document: string): Promise<ProcessingResult> {
    console.log("📄 文書を受信しました");
    console.log(`文字数: ${document.length}\n`);

    // Step 1: ルーターが文書を分類
    console.log("🔀 ルーターが文書を分類中...");
    const decision = await this.router.route(document);

    console.log(`✅ 分類完了:`);
    console.log(`   タイプ: ${decision.documentType}`);
    console.log(`   信頼度: ${(decision.confidence * 100).toFixed(1)}%`);
    console.log(`   理由: ${decision.reasoning}\n`);

    // 信頼度が低い場合は警告
    if (decision.confidence < 0.7) {
      console.warn("⚠️  信頼度が低いため、結果を慎重に確認してください\n");
    }

    // Step 2: 適切な専門エージェントで処理
    const processor = this.processors.get(decision.documentType);

    if (!processor) {
      throw new Error(`Unknown document type: ${decision.documentType}`);
    }

    console.log(`🔧 ${decision.documentType}専門エージェントが処理中...`);
    const result = await processor.process(document);
    console.log(`✅ 処理完了\n`);

    return result;
  }
}

ルーティングシステムは文書を自動的に分類し、最適な専門家に処理させます。

実行例

実際に文書処理システムを動かしてみましょう。

typescript// routing-main.ts
import * as dotenv from "dotenv";
import { DocumentRoutingSystem } from "./document-routing-system";

dotenv.config();

async function main() {
  const system = new DocumentRoutingSystem();

  // サンプル文書
  const documents = [
    {
      name: "技術仕様書",
      content: `
システムアーキテクチャ仕様書

本システムは、Next.js 14をフロントエンド、
Node.jsとPostgreSQLをバックエンドに採用したWebアプリケーションです。

主要な技術スタック:
- フロントエンド: Next.js 14, React 18, TypeScript
- バックエンド: Node.js 20, Express, Prisma ORM
- データベース: PostgreSQL 15
- インフラ: Docker, AWS ECS

認証はJWT方式を採用し、セッション管理にはRedisを使用します。
`,
    },
    {
      name: "契約書",
      content: `
業務委託契約書

第1条(契約の目的)
本契約は、委託者が受託者に対して、
Webシステムの開発業務を委託することを目的とする。

第2条(契約期間)
本契約の期間は、2024年4月1日から2024年9月30日までとする。

第3条(委託料)
委託料は総額500万円とし、月末締め翌月末払いとする。

第4条(秘密保持)
受託者は、本業務で知り得た情報を第三者に開示してはならない。
`,
    },
  ];

  for (const doc of documents) {
    console.log("\n" + "=".repeat(60));
    console.log(`処理対象: ${doc.name}`);
    console.log("=".repeat(60) + "\n");

    try {
      const result = await system.processDocument(doc.content);

      console.log("【分析結果】");
      console.log(`\n要約:\n${result.summary}`);
      console.log(`\n重要ポイント:`);
      result.keyPoints.forEach((point, i) => {
        console.log(`  ${i + 1}. ${point}`);
      });
      console.log(`\n推奨事項:`);
      result.recommendations.forEach((rec, i) => {
        console.log(`  ${i + 1}. ${rec}`);
      });

    } catch (error) {
      console.error("エラーが発生しました:", error);
    }
  }
}

main();

実行すると、ルーターが各文書を自動的に分類し、適切な専門エージェントが処理を行います。

仲裁パターンでは、入力に応じて動的にエージェントを選択することで、リソースを効率的に使いながら、各領域の専門性を最大限に活用できるんです。

投票パターンの実装

コード レビューシステムを例に、投票パターンを実装してみます。複数のレビュアーエージェントが独立に評価し、結果を統合します。

レビュアーエージェントの実装

異なる視点でコードをレビューするエージェントを作成します。

typescript// code-reviewer.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

export interface ReviewComment {
  severity: "critical" | "major" | "minor" | "suggestion";
  category: string;
  description: string;
  lineNumber?: number;
}

export interface ReviewResult {
  overallScore: number; // 0-100
  comments: ReviewComment[];
  recommendation: "approve" | "request_changes" | "reject";
  summary: string;
}

export abstract class CodeReviewer {
  protected llm: ChatOpenAI;
  protected perspective: string;

  constructor(perspective: string) {
    this.perspective = perspective;
    this.llm = new ChatOpenAI({
      modelName: "gpt-4",
      temperature: 0.3,
    });
  }

  // 各レビュアーが実装する専門的な視点
  abstract getSystemPrompt(): string;

  // コードをレビュー
  async review(code: string, language: string): Promise<ReviewResult> {
    const messages = [
      new SystemMessage(this.getSystemPrompt()),
      new HumanMessage(`
言語: ${language}

以下のコードをレビューしてください:

\`\`\`${language}
${code}
\`\`\`

JSON形式で結果を返してください:
{
  "overallScore": 0-100,
  "comments": [
    {
      "severity": "critical/major/minor/suggestion",
      "category": "カテゴリ名",
      "description": "コメント内容",
      "lineNumber": 行番号(オプション)
    }
  ],
  "recommendation": "approve/request_changes/reject",
  "summary": "総評"
}
`),
    ];

    const response = await this.llm.invoke(messages);
    return this.parseReview(response.content as string);
  }

  // レビュー結果をパース
  protected parseReview(content: string): ReviewResult {
    try {
      const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
        || content.match(/\{[\s\S]*?\}/);

      if (jsonMatch) {
        const jsonStr = jsonMatch[1] || jsonMatch[0];
        return JSON.parse(jsonStr);
      }
    } catch (error) {
      console.error("Failed to parse review:", error);
    }

    // フォールバック
    return {
      overallScore: 50,
      comments: [],
      recommendation: "request_changes",
      summary: "レビュー結果のパースに失敗しました",
    };
  }
}

基底クラスにより、各レビュアーの実装が統一されます。

セキュリティレビュアーの実装

セキュリティの観点からコードをレビューします。

typescript// security-reviewer.ts
import { CodeReviewer } from "./code-reviewer";

export class SecurityReviewer extends CodeReviewer {
  constructor() {
    super("Security");
  }

  getSystemPrompt(): string {
    return `
あなたはセキュリティ専門のコードレビュアーです。

以下の観点でコードを厳密にレビューしてください:

1. 入力検証の不備(SQLインジェクション、XSSなど)
2. 認証・認可の欠陥
3. 機密情報の露出(ハードコードされたシークレットなど)
4. 安全でない暗号化の使用
5. OWASP Top 10に該当する脆弱性

セキュリティリスクは severity を "critical" または "major" に設定してください。
`;
  }
}

セキュリティレビュアーは脆弱性を重点的にチェックします。

パフォーマンスレビュアーの実装

パフォーマンスの観点からコードをレビューします。

typescript// performance-reviewer.ts
import { CodeReviewer } from "./code-reviewer";

export class PerformanceReviewer extends CodeReviewer {
  constructor() {
    super("Performance");
  }

  getSystemPrompt(): string {
    return `
あなたはパフォーマンス最適化の専門家です。

以下の観点でコードをレビューしてください:

1. アルゴリズムの計算量(時間・空間)
2. 不要なループや重複処理
3. メモリリークのリスク
4. データベースクエリの効率性
5. キャッシングの機会

パフォーマンスに大きな影響がある問題は重要度を高く設定してください。
`;
  }
}

パフォーマンスレビュアーは効率性を重視します。

保守性レビュアーの実装

コードの保守性や可読性の観点からレビューします。

typescript// maintainability-reviewer.ts
import { CodeReviewer } from "./code-reviewer";

export class MaintainabilityReviewer extends CodeReviewer {
  constructor() {
    super("Maintainability");
  }

  getSystemPrompt(): string {
    return `
あなたはコードの保守性と可読性の専門家です。

以下の観点でコードをレビューしてください:

1. コードの可読性(命名、構造など)
2. 適切なコメントやドキュメント
3. DRY原則の遵守
4. SOLID原則の適用
5. テスタビリティ

保守性を向上させる具体的な提案を含めてください。
`;
  }
}

保守性レビュアーは長期的な品質を重視します。

アグリゲーターの実装

複数のレビュー結果を集約して最終判定を行います。

typescript// review-aggregator.ts
import { ReviewResult, ReviewComment } from "./code-reviewer";

export interface AggregatedReview {
  finalScore: number;
  finalRecommendation: "approve" | "request_changes" | "reject";
  allComments: ReviewComment[];
  reviewerScores: Map<string, number>;
  consensusSummary: string;
}

export class ReviewAggregator {
  // 複数のレビュー結果を集約
  aggregate(
    reviews: Map<string, ReviewResult>
  ): AggregatedReview {
    // Step 1: スコアを集約(平均値)
    let totalScore = 0;
    const reviewerScores = new Map<string, number>();

    for (const [reviewer, result] of reviews) {
      totalScore += result.overallScore;
      reviewerScores.set(reviewer, result.overallScore);
    }

    const finalScore = totalScore / reviews.size;

    // Step 2: 全てのコメントを収集
    const allComments: ReviewComment[] = [];

    for (const result of reviews.values()) {
      allComments.push(...result.comments);
    }

    // Step 3: 推奨アクションを決定
    const finalRecommendation = this.determineRecommendation(
      reviews,
      finalScore
    );

    // Step 4: 総評を生成
    const consensusSummary = this.generateSummary(
      reviews,
      finalScore,
      finalRecommendation
    );

    return {
      finalScore,
      finalRecommendation,
      allComments,
      reviewerScores,
      consensusSummary,
    };
  }

  // 最終的な推奨アクションを決定
  private determineRecommendation(
    reviews: Map<string, ReviewResult>,
    finalScore: number
  ): "approve" | "request_changes" | "reject" {
    // criticalな問題が1つでもあれば拒否
    for (const result of reviews.values()) {
      const hasCritical = result.comments.some(
        c => c.severity === "critical"
      );
      if (hasCritical) {
        return "reject";
      }
    }

    // スコアベースの判定
    if (finalScore >= 80) {
      return "approve";
    } else if (finalScore >= 60) {
      return "request_changes";
    } else {
      return "reject";
    }
  }

  // 総評を生成
  private generateSummary(
    reviews: Map<string, ReviewResult>,
    finalScore: number,
    recommendation: string
  ): string {
    const lines: string[] = [];

    lines.push(`総合スコア: ${finalScore.toFixed(1)}/100`);
    lines.push(`最終判定: ${recommendation}\n`);

    lines.push("各レビュアーの評価:");
    for (const [reviewer, result] of reviews) {
      lines.push(`  - ${reviewer}: ${result.overallScore}/100`);
      lines.push(`    ${result.summary}`);
    }

    // 重要度別のコメント数を集計
    const severityCounts = new Map<string, number>();
    for (const result of reviews.values()) {
      for (const comment of result.comments) {
        const count = severityCounts.get(comment.severity) || 0;
        severityCounts.set(comment.severity, count + 1);
      }
    }

    lines.push("\n指摘事項の内訳:");
    for (const [severity, count] of severityCounts) {
      lines.push(`  - ${severity}: ${count}件`);
    }

    return lines.join('\n');
  }
}

アグリゲーターは複数のレビューを統合し、バランスの取れた最終判定を下します。

コードレビューシステムの統合

全てのコンポーネントを統合してコードレビューシステムを構築します。

typescript// code-review-system.ts
import { SecurityReviewer } from "./security-reviewer";
import { PerformanceReviewer } from "./performance-reviewer";
import { MaintainabilityReviewer } from "./maintainability-reviewer";
import { ReviewAggregator } from "./review-aggregator";
import { CodeReviewer, ReviewResult } from "./code-reviewer";
import { AggregatedReview } from "./review-aggregator";

export class CodeReviewSystem {
  private reviewers: Map<string, CodeReviewer>;
  private aggregator: ReviewAggregator;

  constructor() {
    // 複数のレビュアーを登録
    this.reviewers = new Map([
      ["Security", new SecurityReviewer()],
      ["Performance", new PerformanceReviewer()],
      ["Maintainability", new MaintainabilityReviewer()],
    ]);

    this.aggregator = new ReviewAggregator();
  }

  // コードを複数のレビュアーでレビュー
  async reviewCode(
    code: string,
    language: string
  ): Promise<AggregatedReview> {
    console.log("📝 コードレビューを開始します");
    console.log(`言語: ${language}`);
    console.log(`コード行数: ${code.split('\n').length}\n`);

    // Step 1: 全レビュアーに並行してレビューを依頼
    const reviewPromises: Promise<[string, ReviewResult]>[] = [];

    for (const [name, reviewer] of this.reviewers) {
      console.log(`🔍 ${name}レビュアーが評価中...`);

      const promise = reviewer.review(code, language)
        .then(result => {
          console.log(`✅ ${name}レビュー完了: ${result.overallScore}/100`);
          return [name, result] as [string, ReviewResult];
        });

      reviewPromises.push(promise);
    }

    // 全レビューの完了を待機
    const reviewResults = await Promise.all(reviewPromises);
    const reviews = new Map(reviewResults);

    console.log("\n📊 全レビューが完了しました\n");

    // Step 2: レビュー結果を集約
    console.log("🔄 レビュー結果を集約中...");
    const aggregated = this.aggregator.aggregate(reviews);
    console.log("✅ 集約完了\n");

    return aggregated;
  }
}

コードレビューシステムは複数のレビュアーの意見を集約して、バランスの取れた判定を提供します。

実行例

実際にコードレビューシステムを動かしてみましょう。

typescript// review-main.ts
import * as dotenv from "dotenv";
import { CodeReviewSystem } from "./code-review-system";

dotenv.config();

async function main() {
  const system = new CodeReviewSystem();

  // レビュー対象のコード
  const sampleCode = `
function getUserData(userId) {
  // データベースから直接クエリを構築
  const query = "SELECT * FROM users WHERE id = " + userId;
  const result = db.query(query);

  // パスワードを含む全データを返す
  return result;
}

function processLargeArray(data) {
  let result = [];
  for (let i = 0; i < data.length; i++) {
    for (let j = 0; j < data.length; j++) {
      result.push(data[i] * data[j]);
    }
  }
  return result;
}
`;

  try {
    console.log("=".repeat(60));
    console.log("コードレビューシステム");
    console.log("=".repeat(60) + "\n");

    const review = await system.reviewCode(sampleCode, "javascript");

    console.log("=".repeat(60));
    console.log("【レビュー結果】");
    console.log("=".repeat(60) + "\n");

    console.log(review.consensusSummary);

    console.log("\n" + "-".repeat(60));
    console.log("【詳細な指摘事項】");
    console.log("-".repeat(60) + "\n");

    // 重要度順にソート
    const sortedComments = review.allComments.sort((a, b) => {
      const severityOrder = {
        critical: 0,
        major: 1,
        minor: 2,
        suggestion: 3,
      };
      return severityOrder[a.severity] - severityOrder[b.severity];
    });

    sortedComments.forEach((comment, index) => {
      console.log(`${index + 1}. [${comment.severity.toUpperCase()}] ${comment.category}`);
      console.log(`   ${comment.description}`);
      if (comment.lineNumber) {
        console.log(`   (行: ${comment.lineNumber})`);
      }
      console.log();
    });

    // 最終判定を表示
    console.log("=".repeat(60));
    console.log("【最終判定】");
    console.log("=".repeat(60));
    console.log(`スコア: ${review.finalScore.toFixed(1)}/100`);
    console.log(`判定: ${review.finalRecommendation}`);

    if (review.finalRecommendation === "approve") {
      console.log("✅ このコードは承認されました");
    } else if (review.finalRecommendation === "request_changes") {
      console.log("⚠️  修正後に再レビューが必要です");
    } else {
      console.log("❌ このコードは却下されました");
    }

  } catch (error) {
    console.error("エラーが発生しました:", error);
  }
}

main();

実行すると、3人のレビュアーが独立にコードを評価し、その結果が統合されて表示されます。

投票パターンでは、複数の異なる視点からの評価を組み合わせることで、単一のレビュアーでは見逃してしまう問題を発見でき、より信頼性の高い判定が可能になるんです。

パターンの組み合わせ

実際のプロダクションシステムでは、これらのパターンを組み合わせて使用することもできます。例えば、階層化パターンのマネージャーが仲裁パターンのルーターを使って適切なワーカーを選択したり、役割分担パターンの各エージェントが内部で投票パターンを使って精度を高めたりすることも可能です。

下記の図は、複数のパターンを組み合わせた高度なシステムの例です。

mermaidflowchart TB
    user["ユーザー"] --> manager["マネージャー<br/>(階層化)"]

    manager --> router1["ルーター1<br/>(仲裁)"]
    manager --> router2["ルーター2<br/>(仲裁)"]

    router1 --> team1["専門チーム1<br/>(役割分担)"]
    router2 --> team2["専門チーム2<br/>(役割分担)"]

    team1 --> voter1["投票システム<br/>(投票)"]
    team2 --> voter2["投票システム<br/>(投票)"]

    voter1 --> manager
    voter2 --> manager

    manager --> result["最終結果"]
    result --> user

このように、各パターンの強みを活かして組み合わせることで、より強力で柔軟なマルチエージェントシステムを構築できます。

まとめ

本記事では、LangChainにおけるマルチエージェント設計の4つの主要パターンについて解説しました。

各パターンの特徴を改めて整理すると、以下のようになります。

パターン強み適用場面
役割分担専門性の最大化、並列処理による高速化異なる専門知識が必要なタスク
階層化複雑なワークフローの制御、段階的な管理タスクに依存関係がある場合
仲裁コスト効率、動的な最適化入力に応じて処理を変えたい場合
投票高精度、多様な視点の獲得信頼性が最優先の場合

マルチエージェント設計を採用することで、単一のエージェントでは実現できなかった複雑なタスクにも対応できるようになります。ただし、エージェント数が増えるとコストやレイテンシも増加するため、適切なパターンを選択し、必要最小限のエージェント構成を心がけることが重要です。

実装の際は、まず小規模なプロトタイプから始めて、段階的にエージェントを追加していくアプローチをおすすめします。各エージェントの役割を明確にし、責任範囲を適切に分割することで、保守性の高いシステムを構築できるでしょう。

LangChainのマルチエージェント設計パターンを理解し活用することで、あなたのLLMアプリケーションは次のレベルへと進化していくはずです。ぜひ、実際のプロジェクトで試してみてください。

関連リンク