コンテンツにスキップ

article_generation Lambda 概要

SQS(article-generation キュー)のメッセージをトリガーに、レース結果・レース動画・ コース画像(競馬のみ)から「レース結果10秒サマリー」(見出し1つ + 3文)を生成し、 SQS(article-builder キュー)経由で article_builder Lambda に渡す Lambda 関数です。

本 Lambda は記事 HTML を構築しません。HTML 化と DocumentDB への保存 (status の generating → draft 遷移)は article_builder Lambda が行います。

入出力スキーマは 入出力定義 を参照してください。


トリガー

送信元 タイミング
App Lambda(odds_poc_app) 記事生成のリクエスト時(POST /v1/articles/generation)
App Lambda(odds_poc_app) 生成失敗記事の再生成リクエスト時(POST /v1/articles/{article_id}/regeneration)

2段構成の生成

記事生成は 1段目=レース総合解析、2段目=記事執筆 の2段構成です。 解析(マルチモーダル)と執筆(テキスト)で適したモデルが違うため、分けています。

段 担当パッケージ モデル 内容
1段目 race_analysis_agent Amazon Nova Pro レース動画・コース画像・レース結果を総合解析し、1つの総合サマリーを出力する
2段目 article_generation_agent Claude Sonnet(未設定時は Nova Pro) 確定事実と総合サマリーを根拠に「10秒サマリー」を執筆する

2段目のモデルは環境変数 BEDROCK_CLAUDE_SONNET_PROFILE_ARN が設定されていれば Claude Sonnet、 未設定であれば Nova Pro にフォールバックします。 1段目は動画入力に対応した Nova Pro を使い続けます。


処理フロー

  1. SQS イベントを受信し、ペイロードをパース・バリデーションする
  2. DocumentDB へ接続する(失敗時の記録にも DB を使うため、生成本体より先に接続する)
  3. 対象の article が status=generating か確認する。違えば何もせず終了する(破棄済み記事への再投入対策)
  4. レース結果に着順があるか(不成立・中止でないか)を確認する。無ければ status=failed にして終了する
  5. S3 からメディアを取得する
    • レース動画 {racing_type}/video/{race_id}.mp4 — S3 URI として Bedrock に渡す(サイズが大きく、Converse API のインライン上限 25MB を超えるため)
    • コース画像 horse_racing/course_image/{race_id}.gif(競馬のみ)— インラインバイトで渡す
    • どちらも「無ければ無いまま続行」する。欠けたメディアで生成ごと失敗させない
  6. NG ワードを DocumentDB から読み込み、Aho-Corasick マッチャを構築する
  7. 1段目: race_analysis_agent でレースを総合解析する
  8. 2段目: article_generation_agent で記事を執筆する(後述のガードを通す)
  9. 精度検証用データ(accuracy_test_data)を組み立てる
  10. SQS(article-builder キュー)へ送信する
flowchart TD
    AppLambda([App Lambda]) -->|SQS| Start
    Start[SQS トリガー受信] --> Parse[ペイロードをパース]
    Parse --> DB[DocumentDB へ接続]
    DB --> Guard[article が generating か確認]
    Guard -->|generating でない| Skip[何もせず終了]
    Guard -->|OK| Void[着順の有無を確認]
    Void -->|着順なし| Failed[status を failed にして終了]
    Void -->|OK| Fetch[S3 からレース動画・コース画像を取得<br/>無ければ無いまま続行]
    Fetch --> Analyze[1段目: race_analysis_agent で総合解析]
    Analyze --> Write[2段目: article_generation_agent で執筆]
    Write -->|ガードを解消できない| Error[例外を raise]
    Write -->|成功| SendSQS[article-builder キューへ送信]
    SendSQS -.->|SQS| Builder([article_builder Lambda])
    Error -->|最終試行| MarkFailed[status を failed にし理由を保存]

生成のガード(事実の正しさ)

LLM に値の転記や多段の突合をさせると取り違えが起きるため、 解釈・突合・転記は機械が行い、LLM は日本語の表現だけを担うという分担にしています。

ガード 内容 担当
生成可否 着順が無いレース(不成立・中止)は生成しない race_result_facts.void_reason
確定事実の提示 周回別・コーナー別の先頭を「番号+名前」まで突合して渡す race_result_facts.build_fact_block
プレースホルダー差し込み 出走者の「番号+名前」・払戻の金額・組番・人気は LLM に書かせず、公式データの値をコードで差し込む article_generation_agent.placeholders
人気の自動補正 プレースホルダーを使わず直接書かれた人気・「N番人気が◯着」の取り違えを公式の値へ直す race_result_facts.article_checks
事実照合 番号と名前の対応・式別ごとの払戻が公式と一致するか機械照合する race_result_facts.find_fact_errors
NG ワード検査 生成文に NG ワードが含まれないか検査する(出走者名・騎手名への部分一致は除外) ng_word_filter
文字数 各文が 60 文字以内か検査する article_generation_agent

検出された場合は内容をプロンプトへ差し戻して作り直します(最大2回)。 それでも解消しない場合は例外を投げ、事実と異なる記事・NG ワードを含む記事は公開しません。 ただし文字数超過だけは、事実の正しさとは別問題のため、収まらなくてもログに残して公開を止めません。


エラーハンドリング

状況 挙動
最終試行以外での失敗 例外を再送出し、SQS の再配信による自己復旧に委ねる
最終試行での失敗 article を status=failed にして失敗理由(generation_error)を保存し、例外を飲んで終了する

最終試行かどうかは SQS レコードの ApproximateReceiveCount と SQS_MAX_RECEIVE_COUNT(既定 2。キューの redrive_policy.maxReceiveCount と揃える)で判定します。

最終試行で DLQ に残さない理由

同じ入力で作り直しても同じ理由で失敗するだけなので、DLQ に残す価値がありません。 作り直しは管理画面からの再生成(POST /v1/articles/{article_id}/regeneration。追加指示つき)に委ねます。 例外を飲むため、原因調査用にスタックトレースをログへ残します。

かつては生成失敗時に article を物理削除していましたが、それでは 「生成したのに記事が無い」レースが一覧から判別できませんでした。 現在は failed として残し、理由を添えて再生成の起点にしています。


Bedrock 呼び出しのリトライ

Bedrock の一過性エラーはアプリ側でリトライします(botocore は自動リトライしないため)。

エラーコード 対応
ModelErrorException(ToolUse トークン列の破損) 短い間隔でリトライ
ModelTimeoutException / ServiceUnavailableException 短い間隔でリトライ
ThrottlingException(TPM クォータ超過) 1分窓の回復を待つ長めの間隔でリトライ

使用 AWS サービス・ライブラリ

サービス / ライブラリ 用途
Amazon SQS トリガー受信(article-generation)/ 生成結果の送信(article-builder)
Amazon S3 レース動画・コース画像の取得
Amazon Bedrock 1段目の解析(Nova Pro)・2段目の執筆(Claude Sonnet / Nova Pro)
Amazon DocumentDB NG ワードの読み込み、article のステータス確認・失敗記録
LangGraph 1段目の解析エージェント(ツール呼び出しループ)の構築・実行

DocumentDB へのアクセス

本 Lambda は記事本文を保存しませんが、NG ワードの読み込みと生成失敗の記録のために DocumentDB へアクセスします。記事本文の保存は article_builder Lambda が担当します。