0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

推論設計のAIエージェント構築(提案)

0
Posted at

はじめに

本記事では、Mastra(TypeScript製のエージェントフレームワーク)で組んでいた 役割ごとに6個の専門エージェントを並列実行する構成 が運用面で詰まり、po3rin氏のスライド「検索設計から推論設計への重心移動とRecall-First Retrieval」から着想を得て 「情報収集 + 推論」の2層モデル に作り直した経験を書きます。マルチエージェントを役割ごとに増やしたら辛くなった人向けに、設計の重心をどこに置くか を整理する内容です。


1. 6個のエージェントで作っていた頃の話

最初に組んだ構成はとてもシンプルな発想でした。

様々な情報を必要としているエージェントを作成している中で、領域や情報ごとに専門エージェントを作り、それぞれが独立に情報を集めて、最後に統合エージェントが束ねればいい。

これは当然のように思いつくアイデアで、Anthropicの"Building Effective Agents"にもRoutingやOrchestrator-Workersといったパターンが紹介されています。なので私も6個のエージェントを用意し、Mastra Workflowの.parallel()で同時に走らせ、最後に統合エージェントで1つの回答にまとめるパイプラインを組みました。

import { createWorkflow, createStep } from "@mastra/core/workflow";

export const oldWorkflow = createWorkflow({
  id: "old-multi-agent",
  inputSchema:  z.object({ question: z.string() }),
  outputSchema: z.object({ answer: z.string() }),
})
  .parallel([AgentA, AgentB, AgentC, AgentD, AgentE, AgentF])
  .then(IntegratorAgent)
  .commit();

.parallel()で6個を同時に走らせ、.then()で統合エージェントに渡す、という素直な書き方です。コード上は綺麗に見えるのですが、運用に入っていくつもの課題が出てきました。

  • LLM呼び出し回数:質問1件につき並列6個 + 統合1個で 合計7回。レイテンシ・コスト・レート制限の三方向から効いてきます
  • 統合ロジックの認知負荷:統合エージェントは6個の出力に部分矛盾があると重み付けが難しく、ぼやけた回答を返しがちでした
  • コード量が線形に増える:新しい領域を追加するたびにAgent作成・統合との繋ぎ込み・Workflow編集… 変更箇所が線形に増えます
  • デバッグがきつい:並列実行は「どこで失敗したか」は掴めても、「どこを改良すべきか」を読み解くのが面倒でした

つまり「役割ごとに専門Agentを並べる」設計は、綺麗に見えて運用に入った際に線形にコスト増加する構造 だったわけです。


2. Recall-First Retrievalから着想を得る

このマルチエージェント増殖問題に対して、po3rinさんのスライド『検索設計から推論設計への重心移動とRecall-First Retrieval』が、考え方の突破口を示してくれました。

https://speakerdeck.com/po3rin/jian-suo-she-ji-kara-tui-lun-she-ji-henozhong-xin-yi-dong-to-recall-first-retrieval

スライドの中心命題はこうです。

RAGの品質改善の重心は、「検索設計」から「推論設計」へ移りつつある。

従来のRAGは「いかに正確なドキュメントだけを検索段階で拾うか」に労力を割いてきましたが、LLMが十分強くなった現在、検索段階ではむしろ「漏れなく取る(高recall)」ほうが効き、LLMが後段で不正確な情報を濾別・補完する というパラダイムシフトです。Amazon Researchの「Keyword Search Is All You Need」やRich Suttonの The Bitter Lesson にも接続される考え方で、人間が手で組み込んだ知識よりも、汎用的な計算のスケール(強いLLM)のほうが長期的には勝つ、という構図です。

これを読んだとき、自分のマルチエージェント構成が抱えていた問題はまさに RAGが乗り越えようとしている問題と同じ構造 だと気付きました。マルチエージェントも、子エージェントごとに「いかに適切な情報を統合エージェントに渡すか」の精査に労力をかけていたからです。

RAG検索の文脈 マルチエージェント設計への転用
検索の高recall化 情報収集Agentの責務
LLMによる濾別・補完 推論Agentの責務
検索チューニングが終わらない 役割ごとのAgent増殖が終わらない
The Bitter Lesson 強いLLMにまとめて推論させる

つまり、6個のエージェントに「自分の領域内で結論を出す」と重い責務を課す代わりに、情報を漏らさず一箇所に集めて、強いLLMに一気に推論させればいい、というのが新しい設計方針です。


3. 情報収集 + 推論の2層モデル

新しい構成は、責務を2層に分けます。

  • 情報収集層:関連しうるデータを 漏らさず 取得する役割
  • 推論層:集まったデータを読み込み、結論を出す 役割。ここに高性能LLMを充てる

ここで重要なのは、情報収集側はもう"結論を出さなくていい" ということです。彼らの仕事は「自分の領域に関係するデータを正確に拾って渡す」だけ。判断は推論層に集約します。これにより、独立した統合エージェントは要らなくなり、統合の責務は推論層が兼ねる 形になります。

なお「6個から2個」というのは概念モデルとしての話で、実装上は情報収集層に複数のAgentが並んでいて構いません。重要なのは 意思決定の責務が推論層に集中していること、情報収集層のAgentが独立して結論を出さないこと、統合エージェントが要らなくなること の3点です。


4. Mastraでの実装

実装はAnthropic "Building Effective Agents"の Routingパターン に近い形になります。

推論層は1個のCoordinator Agentで実装し、toolsプロパティに情報収集側を渡します。Coordinatorのinstructionsには「質問の意図に応じて、関連するToolを1つだけ呼び出して情報を取り、自分でユーザー向けの回答を作る」と書きます。

import { Agent } from "@mastra/core/agent";
import { specialistTools } from "../tools/specialist-tools";

export const coordinatorAgent = new Agent({
  name: "coordinator",
  instructions: `
あなたは質問を受け取り、以下の手順で回答する Coordinator です。
1. 質問の意図を特定する
2. 関連する情報収集 Tool を 1つだけ 呼び出す
3. 取得した情報をもとに、自分で最終回答を作成する
  `,
  model: highCapableModel,
  tools: specialistTools,
});

情報収集側は Sub-agent as Toolパターン を使います。各エージェントをcreateTool()でラップして、外からは関数として見える形にします。descriptionに「情報を漏らさず取得する」と書くのは、Recall-Firstの哲学をToolの責務にそのまま落とし込んだものです。Toolは結論を出さず情報を返すだけ に徹します。

import { createTool } from "@mastra/core/tools";
import { z } from "zod";

const wrapAsTool = (id: string, description: string, agent: Agent) =>
  createTool({
    id,
    description,
    inputSchema:  z.object({ question: z.string() }),
    outputSchema: z.object({ retrieved_context: z.string() }),
    execute: async ({ context }) => {
      const result = await agent.generate(context.question);
      return { retrieved_context: result.text };
    },
  });

export const specialistTools = {
  domain_a: wrapAsTool("domain_a", "領域Aの情報を漏らさず取得する", AgentA),
  domain_b: wrapAsTool("domain_b", "領域Bの情報を漏らさず取得する", AgentB),
  // 必要な領域だけ追加していく
};

そしてWorkflow側で面白いのが、.parallel()の用途が完全に変わる ことです。旧構成では.parallel()は「6個のAgentに同時に応答を作らせる」ために使っていましたが、新構成では応答生成はCoordinator 1個で完結します。代わりに .parallel()応答品質を評価する複数軸のEvalを並列に走らせる のに使い、.branch()HITL(Human-in-the-loop)エスカレーション に使います。

export const recallFirstWorkflow = createWorkflow({
  id: "recall-first",
  inputSchema:  z.object({ question: z.string() }),
  outputSchema: z.object({ answer: z.string(), composite_score: z.number() }),
})
  .then(stepCoordinatorRoute)        // ← 推論層: 1回のLLM呼び出しで完結
  .parallel([                         // ← .parallel() は Eval 複数軸の並列に
    stepFaithfulness,
    stepAnswerRelevancy,
    stepHallucination,
    stepActionability,
  ])
  .then(stepMergeEvals)
  .branch([                           // ← .branch() は HITL エスカレーションに
    [({ inputData }) => inputData.composite_score < 0.65, stepHitlSuspend],
    [({ inputData }) => inputData.composite_score >= 0.65, stepAutoApprove],
  ])
  .then(stepFinalize)
  .commit();

.parallel()応答ではなく評価に.branch()HITLエスカレーションに 使う。これがMastraの制御フローAPIの 本来の用途に戻った 形だと感じています。


5. 何が楽になったか、そして限界

2層モデルに作り直して、運用面では3つの大きな変化がありました。

楽になった3点

① LLM呼び出し回数の激減

旧構成 新構成
並列実行のAgent 6回
Coordinator / 統合 1回 1回
選ばれたTool 1回
合計 7回 2回(約 1/3.5)

質問が特定領域に閉じている場合、関係ない領域のToolは呼ばれません。「どれが当たりかを事前に決めず、LLMにまとめて判断させる」というRecall-First的アプローチの直接効果です。

② ドメイン追加コストの低下

新しい領域を1つ追加するときの変更箇所を比較すると、こうなります。

変更箇所 旧構成 新構成
Agentファイル作成
Workflowに createStep 追加
.parallel([...]) の配列に追加
統合役プロンプト改修
統合役の出力スキーマ調整
tools への登録

Workflowを一切触らなくていい のが本質で、Workflowが領域追加に対して閉じている(Open/Closed Principle)状態になります。

③ デバッグの直線化

並列分岐の段数が 1段 → 0段 になり、追うべきトレースが直線になりました。「最終回答がぼやけている」というフィードバックを受けたときも、Coordinator の Tool call ログを見れば呼ばれた Tool が1つだけ分かる ので、原因特定が早くなります。「6個の主張をどう融合するか」という根本的に難しい問題そのものが消滅したのが大きいです。

限界と注意点

ただしこのモデルは万能ではありません。次の3点は前提として押さえておく必要があります。

  • 強いLLMが前提 — 推論層に判断責務を集約するため、推論力が弱いモデルでは品質が崩れます
  • 領域分離が曖昧な質問 — Toolを1つだけ呼ぶ設計だと情報が片寄ります。複数Tool呼び出しや動的反復探索を許す設計で補う必要があります
  • Coordinatorのinstructionsが品質を決める — 統合ロジックは消えたのではなく 推論層に折り畳まれた だけです。フォールバックやトーン指定が薄いと、場当たり的な判断になります

まとめ

役割ごとに専門Agentを並列に走らせて統合する構成は、運用に入ると線形にコストが増えていく 構造でした。これに対して 情報収集 + 推論 の2層モデルは管理コードを薄く保て、ドメイン追加に閉じていて、デバッグも直線で済みます。po3rin氏のRecall-First Retrievalは検索設計の話でしたが、エージェント設計にもそのまま転用できる発想 でした。

これはAnthropicの "find the simplest solution possible" にも、Suttonの The Bitter Lesson にも整合する方向です。エージェント設計でも、役割ごとに増やす方向に手を伸ばす前に、情報収集と推論を分離する方向 を試してみる価値はあるはずです。


参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?