AIタスク #10469
Redmine Admin さんが14日前に更新
# LLM対策診断ツール実装 詳細仕様 ## 背景 集客ページとして、URL入力→AI検索(LLM)に引用されやすいかを自動診断するツールをvend01(/root/website-vending-machine/vend01)に追加する。 ## スコアリング方針 ゲート判定(クロール可否)→本体スコア(重み付け加重平均)の2段階。重みはAIクローラー(GPTBot/PerplexityBot/ClaudeBot/Google-Extended)の実挙動とGEO研究ベースの確度で設定(表示速度・UXは除外、llms.txtは重み最小)。 ## ファイル構成(既存vend01の少ファイル主義を尊重、2ファイルに限定) - crawlability.js: safeFetch(url)単一実装(本文fetchとrobots.txt取得の両方が必ずこれを経由)、gate_crawlable/gate_robots_blocked判定 - scoring.js: スコア集計(機械的判定+LLM呼び出し) ## SSRF対策(safeFetch必須仕様) - スキームはhttp/httpsのみ許可 - redirect: 'manual'を指定、3xx応答を自前でハンドルしLocationを再度safeFetchに通して各ホップごとにIP検証(最大リダイレクト3回) - DNS rebinding対策: dns.lookup()で名前解決したIPを検証後、undiciのカスタムdispatcherでconnect先IPを固定し同一接続内で再解決させない(ただしTLSのSNI/証明書検証は元ホスト名ベースを使うこと) - プライベートレンジ拒否(IPv4): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, 0.0.0.0/8, 100.64.0.0/10 - プライベートレンジ拒否(IPv6): ::1, fe80::/10, fc00::/7, ::ffff:127.0.0.1等 - proxy-network内コンテナ名解決も拒否、AbortSignal.timeout(8000)をsafeFetch内に内蔵 ## 判定方式の分離 機械的判定: gate_crawlable, gate_robots_blocked, score_schema(cheerioでJSON-LD解析), score_freshness(sitemap lastmod), score_authority(外部リンクユニークドメイン数、nofollow除外) LLM判定: 抽出済み本文をLLMに渡しscore_direct_answerとscore_original_infoを1回の呼び出しでJSON一括出力 ## LLM接続先(重要) 既存vend01のcallClaude(Anthropic API直呼び)は使わず、VPS-root内部APIを使用: - 通常時: http://mcp-gateway-v2:8000/v1/chat/completions (proxy-network内、OpenAI Chat Completions互換)。modelに`claude-3-sonnet`を渡すと内部で`sonnet`エイリアスにマッピングされ常に最新Sonnetに自動追従。認証はX-API-Keyヘッダ(.envのCLAUDE_API_KEYS)。注意: 内部でClaude Code CLIをサブプロセス起動する重い処理でレイテンシ数秒〜。messagesはフラット化されるため単発呼び出し前提の実装にすること。 - 同一IPから3回以上のトライがあった場合: Gemini APIの無料枠に切り替える(IP別試行回数カウントが必要)。Gemini無料枠の容量制限に到達した場合はエラーメッセージとして「システムが混雑しています。日を改めてご利用ください」を表示する。 - error_reasonはgate:/llm:の接頭辞で区別し、llm:claude / llm:geminiのようにどちらで判定したかも記録する。 ## LLM失敗フォールバック タイムアウト/例外/JSONパース失敗はすべてfetch失敗と同じパターン(total_score=null, error_reason記録, エンドポイントは200)に合流させる。 ## DBスキーマ(既存単一DBファイルにdb.js内でCREATE TABLE IF NOT EXISTSで追記、別ファイルにしない) ```sql CREATE TABLE IF NOT EXISTS diagnosis_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, gate_crawlable INTEGER, gate_robots_blocked INTEGER, score_direct_answer REAL, score_authority REAL, score_original_info REAL, score_schema REAL, score_freshness REAL, total_score REAL, error_reason TEXT, created_at TEXT DEFAULT (datetime('now')) ); ``` UNIQUE制約なしで同一URLの再診断は毎回新規行として履歴保持。raw_html本文はDB保存しない。 ## APIエンドポイント - POST /api/diagnosis body:{url} → {gate_crawlable, gate_robots_blocked, breakdown:{...}, total_score, error_reason} - GET /api/diagnosis/:id → 過去診断結果の再取得 - 既存/sessions系と同じプレフィックスなし規約(Nginx側で/vend/01/api/を付与する前提) ## Dockerfile/package.jsonの明示変更 - Dockerfile COPY行に crawlability.js, scoring.js を明示追記 - package.jsonに cheerio を追加(robots.txtパースは自前実装、依存追加不要) ## 受入基準(検収条件) - server.js/crawlability.js/scoring.js/db.js/Dockerfile/package.jsonが上記仕様通りに実装されていること - safeFetchが本文fetchとrobots.txt取得の両方を必ず経由していること - 異常系(fetch失敗/タイムアウト/LLM失敗)がすべて200+error_reasonで返っていること(500にならない) - 既存/sessions系エンドポイントの動作を壊さないこと - 同一IP3回以上でGemini無料枠へのフォールバック、容量制限到達時のエラーメッセージ表示が実装されていること LLM対策診断ツールをvend01(/root/website-vending-machine/vend01)に追加。詳細仕様はRedmineチケット#10442のコメントで後続入力予定。先に骨格で起票。