Skip to content

AI機能のガードレール/早期リターン(入力検証・冪等性・サイズ制限) #136

Description

@rytkhs

背景

本アプリにはAI関連機能(論題提案、論題分析、AIディベート対戦、AI判定)があるが、現状は「形式的なバリデーション/サニタイズ」はある一方で、意味的に明らかに不正な入力や判定不能なディベートでも外部LLM呼び出しが走り、トークン/遅延が無駄になり得る。

また、Queue Job の再実行/多重 dispatch により、同一debate/turnでの二重課金・二重生成が起こり得る。

対象(コード上の入口)

  • 論題提案/論題分析: app/Http/Controllers/Api/TopicSuggestionController.php → app/Services/OpenRouter/TopicGeneratorService.php
  • AIディベート対戦(応答生成): app/Jobs/GenerateAIResponseJob.php → app/Services/AIService.php → app/Services/OpenRouter/DebateOpponentMessageBuilder.php
  • AI判定: app/Jobs/EvaluateDebateJob.php → app/Services/AIEvaluationService.php → app/Services/OpenRouter/DebateEvaluationMessageBuilder.php

目標

  • 明らかな不正入力/判定不能ケースでは 外部LLMを呼ばずに早期リターンする
  • 多重実行に対して 冪等性を担保し、同一debate/turnでの二重生成を防ぐ
  • プロンプトに載る履歴の**合計サイズ上限(文字数cap)**を追加し、長文1発話等での爆発を防ぐ
  • 早期リターン理由/入力規模をログで観測できるようにする

共通ガードレール案(全AI機能)

  1. 意味不明入力の軽量検出(ヒューリスティック)
  • 空/記号のみ/同一文字連打/実質的な文字が極端に少ない/文字種がほぼ無い 等
  • 目的は「明らかに違うものを弾く」で、厳密判定はしない
  1. 論題形(resolution shape)の検査
  • 例: JA「〜すべき/是非/禁止/導入/廃止/規制/義務/許可/優先」等、EN「should/ban/legalize/abolish/is better than/THBT」等
  1. サイズ制限(合計文字数cap)
  • 既存の history_limit は発話数上限のみなので、合計文字数も別途必要
  1. 冪等性/排他
  • debate_id + turn 単位の Cache lock
  • 「既に生成済み/保存済みならスキップ」のDBチェック
  1. 観測
  • request_type 単位で skip_reason / 入力文字数 / 履歴文字数 / lock 取得可否 をログ化(本文はログしない)

機能別の提案

A. 論題提案(keywords → generate)

  • 原則: 422で落とすより keywordsを無視して一般生成へフォールバック(UX維持)
  • ただし明確なスパム(記号のみ/連打/極短)だけは 422 も可
  • 同一条件の連打対策として短TTLキャッシュを検討

B. 論題分析(base_topic → info)

  • 対象が必須なので 不正なら422でreject(フォールバックしない)

C. AIディベート対戦(応答生成)

  • GenerateAIResponseJob 実行前に lock + 既存AI発話チェックで 二重生成を防止
  • 質疑ターンは多重dispatchされやすいので、直前質問の存在などを検証
  • DebateOpponentMessageBuilder に 合計文字数cap(古い履歴を落とす/1発話を切る)

D. AI判定(評価)

  • AIEvaluationService::evaluate の OpenRouter 呼び出し前に、論題/議事録の薄さ等を検証し LLMを呼ばずに is_analyzable=false を返す(トークン節約の主目的)
  • lock + 既存評価チェックで冪等化
  • DebateEvaluationMessageBuilder に合計文字数cap
  • JSON repair は既に1回/低max_tokensだが、そもそも「評価不能」ケースでは repair に入る前に早期リターンする

実装候補(入口別)

  • 判定の早期リターン: app/Services/AIEvaluationService.php
  • 対戦の冪等性: app/Jobs/GenerateAIResponseJob.php(+必要なら app/Livewire/Debates/MessageInput.php)
  • 論題分析の422: app/Http/Controllers/Api/TopicSuggestionController.php(type=info)
  • AIディベート作成時の論題検証: app/Http/Requests/AI/AIDebateCreationRequest.php
  • 履歴文字数cap: app/Services/OpenRouter/DebateOpponentMessageBuilder.php / app/Services/OpenRouter/DebateEvaluationMessageBuilder.php

受け入れ条件(例)

  • 意味不明な論題/議事録が薄い場合、AI判定で外部LLMが呼ばれず is_analyzable=false が保存される
  • GenerateAIResponseJob が同一 debate_id + turn で重複して走っても、AI発話が複数保存されない
  • 履歴の合計文字数が上限を超える場合でも、リクエストが破綻せずに短縮された履歴で実行される
  • 早期リターンが発生したとき、skip_reason がログに残る

追加メモ

  • app/Jobs/EvaluateDebateJob.php::failed() のフォールバックは debate_evaluations のカラムと一致しないキー(summary 等)を使っているため、今回の整備と合わせて整合を確認する。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions