🏗️

Python主導LLMアプリケーションの設計スタイルを考える

に公開

はじめに

本記事は、「Pythonが制御を握り、LLMが知性を提供する」という設計思想を、プロトタイプで試した際のアーキテクチャパターンをまとめたものです。

前提知識:
https://zenn.dev/akring/articles/874140ac7bdb8c

本記事で扱う内容:

  • LLMとPythonの役割分担
  • Phase分離による処理の構造化
  • 各Phaseの責務と境界
  • データフローの設計

本記事に含まれないもの:

  • 数値比較・ベンチマーク(→ 比較分析レポートを参照)
  • 実装コードの全体像(概念的な説明、部分的な説明のみ)

注意:
これは小規模プロトタイプでの実験結果に基づく整理です。本番運用では別のアプローチが適している場合もあります。

システム概要:自然言語検索の実現

提供機能

仮想のアプリケーションとして、AWS What's New情報を自然言語で検索するパイプラインを想定しています。実装も確認済みですが、この記事では概念的なアーキテクチャにフォーカスします。

検証用に実装されたWebアプリ
検証用Webアプリ - 裏でPython主導LLMアプリケーションが動作

検索例:

✅ "先月の東京のEC2"
   → 2025年12月の ap-northeast-1 での Amazon EC2 関連アップデート

✅ "クリスマス頃のASEANでコンテナ関連を要約"
   → 2025年12月中旬~下旬の東南アジアでのECS/EKS関連アップデートの要約

3つの検索軸

ユーザー入力は3つの検索軸に分解され、検索が実行されます。

自然言語表現例 解釈内容
時間 「先月」「クリスマス頃」「最近3ヶ月」 相対表現・イベント基準・具体日付
サービス 「EC2」「機械学習」「コンテナ」 略称・カテゴリ・複数指定
リージョン 「東京」「ASEAN」「アジア」 都市名・組織名・地域名

対応範囲:

  • 順不同・複数指定・部分指定に対応
  • 相対表現(「先月」)、イベント(「クリスマス頃」)、組織名(「ASEAN」)にも対応

Phase分離アーキテクチャ

ここで一度、今回のアーキテクチャ全体像を図で整理します。

以下は、LLM主導(Agent型)と、本記事で採用している Python主導(パイプライン型)の設計スタイルの違いを示したものです。


Python主導パイプライン型アーキテクチャの全体像

この図の右側に示した Python主導のパイプラインを、次節以降で Phase 単位に分解して説明していきます。

設計の考え方

今回のアプローチは、Pythonが処理フローを制御することです。整理のポイントは:

  1. 制御フローはPythonで明示的に定義
  2. 各Phaseは単一責任
  3. Phase間のデータフローは構造化データ
  4. LLMの役割は各Phaseで限定

4つのPhaseと責務

Phase 責務 入力 出力 技術選択
Phase 1 自然言語の構造化 自然言語クエリ 構造化された意図 LLM
Phase 2 キーワード解決 標準化された値 検索条件 Python(ルール+グラフ探索) + LLMフォールバック
Phase 3 データベース検索 検索条件 検索結果 PythonでSQL組み立て+実行
Phase 4 結果整形 検索結果 ユーザー向け出力 Pythonテンプレート or LLM

LLMをPythonの制御フローに組み込めた理由

本プロトタイプでは、Strands Agentsが提供する構造化出力(Structured Output)機能を利用しています。 この機能を使うことで、すべてのPhaseにおいて、LLMの戻り値を明示的に定義された構造化データとして扱うことができます。

構造は pydantic で定義され、LLMの出力はそのスキーマに一致する形で生成されます。

# Pydantic モデルで戻り値の構造を定義
class IntentResult(BaseModel):
    time_expression: Optional[str]
    region_keywords: Optional[list[str]]
    service_keywords: Optional[list[str]]
    action: Literal["list", "count", "latest", "summarize", "analyze"]
    max_results: int

# LLM 呼び出し時にスキーマを指定
agent = create_intent_understanding_agent()
invocation = agent(
    user_request,
    structured_output_model=IntentResult  # ← スキーマを強制
)

# 戻り値は必ず IntentResult 型として取得可能
intent: IntentResult = invocation.structured_output
print(intent.service_keywords)  # type-safe

https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/structured-output/

この仕組みにより、Pythonで処理した場合でも、LLMにフォールバックした場合でも、戻り値の型を常に揃えられるという状態が成立します。その結果、制御フローをPython側に固定したまま、LLMをPythonコードの中に安全に埋め込むことが可能になりました。

これが、構造化出力をすべてのLLM呼び出しで採用した最大の理由です。

Phase 1: Intent Understanding

自然言語クエリを構造化データに変換

入力: "先月の東京のEC2"

↓ LLM処理

出力: {
  time_expression: "last month",
  region_keywords: ["Tokyo"],
  service_keywords: ["EC2"],
  action: "list"
}

ポイント:

  • 順不同入力にも対応
  • 表現揺れの吸収(EC2, Elastic Compute Cloudなど)
  • 部分指定もOK
  • 再現性確保のためtemperature=0でLLM使用

Phase 2: Keyword Resolution

Phase 2a: 時間表現の正規化

入力: "last month" (+ 現在時刻)

↓ Rule-based処理

出力: {
  date_from: "2025-12-01",
  date_to: "2026-01-01"
}

実装:

  • Fast Path: ルールベースで高速処理
  • Slow Path: 未定義表現はLLMにフォールバック

Phase 2b: キーワード拡張・解決

入力: {region_keywords: ["Tokyo"], service_keywords: ["EC2"]}

↓ Graph探索 + LLMフォールバック

出力: {
  region_codes: ["ap-northeast-1"],
  service_keywords: ["EC2", "Amazon EC2", "Elastic Compute Cloud", ...]
}
  • 頻出パターンはPythonで高速処理
  • 複雑な表現はLLMで解決
  • グラフ探索で関係性を展開

Phase 3: Search Execution

構造化検索条件でデータベースを検索

入力: {
  date_from: "2025-12-01",
  date_to: "2026-01-01",
  region_codes: ["ap-northeast-1"],
  service_keywords: ["EC2", ...]
}

↓ SQL実行

出力: {
  items: [{id: "item-001", title: "...", url: "...", service: "EC2"}, ...]
}
  • PythonでSQL組み立て+実行
  • 再現性・高速性・デバッグ容易性を確保

Phase 4: Result Formatting

検索結果をユーザー向けに整形

  • 単純なlist/count/latest → Pythonテンプレートで高速出力
  • 複雑な要約・分析 → LLMで自然言語生成
入力: {items: [...]}

↓ Pythonテンプレート展開

出力:
## 検索結果: 5件
- [EC2 GameLift Streams](...)
- [RDS Zero-ETL to EC2](...)
入力: {items: [11件のアップデート情報]}

↓ LLM要約

出力:
## 要約
ASEAN地域では、主にAuroraとDynamoDBの機能拡張が行われました。...

データフローの全体像

Phase 1 → IntentResult →
Phase 2 → KeywordResolutionResult →
Phase 3 → SearchResult →
Phase 4 → FormattedOutput
  • Phase間は構造化データで接続
  • 型安全・デバッグ容易・疎結合を実現

まとめ

設計原則:

  1. Pythonが制御
  2. LLMは知性提供に徹する
  3. 決定論的処理を最大化

効果:

  • 高速性
  • 再現性
  • コスト効率
  • 柔軟性
  • 保守性
  • デバッグ容易性

注意: 小規模プロトタイプの結果であり、本番運用では別アプローチが必要な場合もあります。

次のステップ

https://zenn.dev/akring/articles/874140ac7bdb8c
https://zenn.dev/akring/articles/8b073fca0ba078

Discussion