😏

LLM主導に違和感、Python主導に戻すとどうなるか

に公開

はじめに

本記事は、AIエージェントをアプリケーションに組み込む際に起こりうる課題を整理し、仮説検証した結果をまとめたものです。 具体的なアーキテクチャや数値比較は含みません(それらは別記事で扱います)。

あくまで実験的なプロトタイプを通じて感じたことの整理であり、本番運用で同じ結果になるかは不明です。 LLM主導の設計が向いているケースもあります。 本記事が、同じように将来を見据えて準備している方の考えるきっかけになれば幸いです。

実装中に感じた違和感

LLM主導型設計で始めた

LLMアプリケーションフレームワーク(例えば、Strands Agents)では、典型的に以下の構造が提示されています。

User → LLM Agent → [Tool 1, Tool 2, ...] → Response
  • LLMが処理フローを決定
  • Pythonコードはツールとして呼び出される
  • 複雑な条件分岐をプロンプトで表現可能

https://strandsagents.com/latest/documentation/docs/

合理的ではありますが、運用を考えると以下の点が気になりました。

気になった3つのポイント

  1. 再現性の不安

    • 同じ入力でも結果が微妙に変わる
    • 「昨日と違う」とユーザーに言われた時に説明しにくい
    • テストケースの期待値が書きづらい
  2. コスト見積もりの難しさ

    • ユーザー入力やLLMの判断によって、呼び出し回数やトークン消費が変動
    • 本番稼働時の費用が予測できない
    • PoCのような試行錯誤にかかる検証コストも予測できない
  3. 速度と軽量モデル活用の制約

    • 応答に数十秒かかることも多い
    • 高速・安価な軽量モデルを使うモチベーションが湧かない

Python主導型LLMアプリケーション

制御と知性を分離

ここで、挙動にあいまいさがあるLLMではなく、決定論的に振る舞える Python へ制御を戻してみるという発想をしてみました。

役割 担当 例
Control(制御) 明確な処理フロー、既知の値への対応 Pythonコード
Intelligence(知性) 曖昧な値の解釈、未知の入力対応 LLM
  • 決定論的処理はPythonで
  • 柔軟な判断や自然言語理解はLLMに任せる

例:処理フローの逆転

LLM主導

agent = Agent(
    tools=[search_db, expand_keywords, format_result]
)
response = agent.run("東京のEC2の更新")
# LLMが実行時にどのツールを呼ぶかを決定

Python主導

def search_pipeline(query: str) -> str:
    intent = llm_understand_intent(query)       # LLMで自然言語理解
    keywords = expand_keywords(intent.keywords) # Pythonで決定論的処理
    results = search_db(keywords)               # Pythonで決定論的処理
    return format_result(results)               # Pythonで決定論的整形

response = search_pipeline("東京のEC2の更新")
# 関数の中でLLMが呼ばれる回数は決定している
  • フローが固定され、動きを追いやすい
  • LLMはStep 1だけ → 問題特定しやすい、呼ばれる回数も推測可能
  • Step 2-4は毎回同じ動作 → テスト・デバッグ容易

適材適所のLLM利用

頻出する単純パターンはPythonで処理:

if expr == "先月":
    return calculate_last_month(now)  # 高速・安定

複雑な表現だけLLMに任せる:

if is_simple_pattern(expr):
    return rule_based_normalize(expr)  # 決定論的処理
else:
    return llm_normalize(expr)          # 柔軟性が必要な部分のみ

効果:高速化・コスト削減・柔軟性維持

コストとパフォーマンスの改善

  • システムプロンプトがコンパクトで固定化 → キャッシュ効率UP
  • ユーザープロンプトが小さい → 軽量モデル利用可能
  • 消費トークンあたりのコスト削減 → PoCでも安心

例:時間表現抽出

agent = Agent(
    model=BedrockModel(model_id="amazon.nova-2-lite-v1:0"),
    system_prompt=SYSTEM_PROMPT
)

result = agent.run(
    user_input="クリスマス頃",
    structured_output=TimePeriodFilter
)
  • Pythonが制御、LLMは複雑な推論のみ担当
  • 高速な軽量モデルでも十分に機能

懸念の解消

懸念 Python主導での解決策
再現性 フロー固定、LLM以外は決定論的 → テスト可能
コスト 呼び出し回数・プロンプトサイズ固定 → 月次予測可能
速度 複雑推論のみLLM → 軽量モデル利用可

LLMの役割

  • 自然言語の構造化:入力の順序や表現揺れに対応
  • 曖昧性の解消:文脈依存の表現の解釈
  • 要約・分析:複数情報を統合して自然な文章で出力

まとめ

  • 制御はPython、知性はLLMで分離
  • 処理フローが見えやすく、デバッグ・テストが容易
  • 高速・安価・安定性と柔軟性を両立可能
  • 実運用ではケースによってLLM主導が向く場合もある

目指したのは:自然言語入力の柔軟性 × 高速・安定・低コストの両立

次のステップ

https://zenn.dev/akring/articles/9b81ce41b80656

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

Discussion