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 を使い続けます。
処理フロー
- SQS イベントを受信し、ペイロードをパース・バリデーションする
- DocumentDB へ接続する(失敗時の記録にも DB を使うため、生成本体より先に接続する)
- 対象の
articleがstatus=generatingか確認する。違えば何もせず終了する(破棄済み記事への再投入対策) - レース結果に着順があるか(不成立・中止でないか)を確認する。無ければ
status=failedにして終了する - S3 からメディアを取得する
- レース動画
{racing_type}/video/{race_id}.mp4— S3 URI として Bedrock に渡す(サイズが大きく、Converse API のインライン上限 25MB を超えるため) - コース画像
horse_racing/course_image/{race_id}.gif(競馬のみ)— インラインバイトで渡す - どちらも「無ければ無いまま続行」する。欠けたメディアで生成ごと失敗させない
- レース動画
- NG ワードを DocumentDB から読み込み、Aho-Corasick マッチャを構築する
- 1段目:
race_analysis_agentでレースを総合解析する - 2段目:
article_generation_agentで記事を執筆する(後述のガードを通す) - 精度検証用データ(
accuracy_test_data)を組み立てる - 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 が担当します。