blog_rewriter Lambda 概要
SQS(blog-rewriter キュー)のメッセージをトリガーに、過去ブログ(old_blog)の本文を
内容を変えずにリライトする Lambda 関数です。
レースに紐づく場合は、レース結果・レース動画・コース情報(競馬のみ)の内容も考慮します。
リライト結果は SQS(blog-builder キュー)経由で blog_builder Lambda に渡され、
HTML 化されて blog コレクションに保存されます。
トリガー
| 送信元 | タイミング |
|---|---|
App Lambda(odds_poc_app) |
リライトのリクエスト時(POST /v1/old_blogs/{old_blog_id}/rewrite) |
App Lambda(odds_poc_app) |
ブログ生成のリクエスト時(POST /v1/blogs/generation。blog-rewrite キュー経由) |
SQS イベントペイロード(受信)
{
"blog_id": "string",
"old_blog_id": "string",
"race_id": "string",
"racing_type": "horse_racing"
}
| フィールド | データ型 | 必須 | 備考 |
|---|---|---|---|
| blog_id | string | ◯ | blog テーブルの _id(generating 状態) |
| old_blog_id | string | ◯ | リライト元の old_blog._id |
| race_id | string | 紐づくレースの _id。old_blog は必ずしもレースに紐づかないため任意 |
|
| racing_type | enum | auto_racing / bicycle_racing / horse_racing |
処理フロー
- SQS イベントを受信し、ペイロードをパースする
- DocumentDB へ接続する
- 対象の
blogがstatus=generatingか確認する。違えば Bedrock を呼ばずに終了する old_blogを取得する- レースに紐づく場合
- レース結果を取得し、着順テーブル(全着順)と関連情報レースカードを組み立てる
- S3 からレース動画・コース画像(競馬のみ)を取得する
- 1段目:
race_analysis_agentでレースを総合解析する - レースに紐づかない場合は、この段をスキップして「レース情報なし」としてリライトする
- 2段目: 総合分析を踏まえて本文をリライトし、ブロック JSONとして構造化する
- 3段目: リライト後の本文から「この記事がわかる!10秒サマリー」を生成する
- SQS(
blog-builderキュー)へ送信する
flowchart TD
AppLambda([App Lambda]) -->|SQS| Start
Start[SQS トリガー受信] --> DB[DocumentDB へ接続]
DB --> Guard[blog が generating か確認]
Guard -->|generating でない| Skip[何もせず終了]
Guard -->|OK| Old[old_blog を取得]
Old -->|レース紐づきあり| Race[レース結果取得 + 着順テーブル・レースカード組み立て]
Old -->|レース紐づきなし| NoRace[レース情報なしとして続行]
Race --> Media[S3 からレース動画・コース画像を取得]
Media --> Analyze[1段目: race_analysis_agent で総合解析]
Analyze --> Rewrite
NoRace --> Rewrite[2段目: 本文をリライトしてブロック JSON 化]
Rewrite --> Summary[3段目: 10秒サマリーを生成]
Summary --> SendSQS[blog-builder キューへ送信]
SendSQS -.->|SQS| Builder([blog_builder Lambda])
リライトの方針
- 内容を変えない(原文の情報・書き手の文体を保つ)
- トーンオブボイスは
packages/tone_of_voiceの共通文面をプロンプトへ差し込む - 記事の内容に合わせて本文を「ブロック」の配列として構造化する(ブロックの種類は blog_builder を参照)
- 本文内の相対画像
srcは、掲載元記事の URL(old_blog.source_url)を基準に絶対 URL 化する- 過去ブログの本文画像は掲載元と同じホストから配信されるため。
source_urlが無い場合のみhttps://www.oddspark.com/へフォールバックする
- 過去ブログの本文画像は掲載元と同じホストから配信されるため。
- レースに紐づくブログでは、出走馬・出馬表の列挙をリライトせず削除する(出走者や着順の情報はシステムが公式データから別途表示するため)
- プロンプトで指示したうえで、LLM が従わなかった場合の安全網としてコードでも列挙ブロックを落とす
- 予想印ブロックの番号バッジの色は枠番で決まる(競馬)。馬番をそのまま色に使うと誤った色になるため、レース結果から馬名で枠番を引く。引けない場合は色なしにする(誤った色より無色の方が正確)
作り直しのガード
| ガード | 内容 | 上限 |
|---|---|---|
| ブロック JSON | 出力がブロック JSON として解釈できるか。壊れている・出力上限で途切れた場合はエラー内容を修正指示に添えて作り直す | 2回 |
| NG ワード | リライト結果に NG ワードが含まれないか | 2回 |
元ブログ由来の NG ワードは除外する
リライトは内容を変えない前提のため、元記事に既に含まれる語(例: 騎手名「土方 颯太」の「土方」)は AI が持ち込んだものではなく、作り直しても除去できません。 そのため元ブログの本文に含まれる語は検査対象から除外します。
壊れた JSON を HTML として公開しない
ブロック JSON を出そうとして壊れている場合、HTML としてフォールバックすると JSON テキストがそのまま記事本文として公開されてしまいます。 そのため公開はせず、エラー内容を修正指示に添えてプロセス内で作り直します (SQS の再配信を待つより速く、修正指示も出せるため成功率が高い)。
上限に達しても解消しない場合はエラーにして公開させません。
関連情報は扱わない
関連情報(類似ブログへのリンク)は本 Lambda では生成しません。
| 項目 | 担当 |
|---|---|
| 埋め込みベクトルの付与 | odds_poc_app(POST /v1/old_blogs で old_blog に付与) |
| ベクトルの引き継ぎ | odds_poc_app(リライトでのブログ作成時に blog へ引き継ぐ) |
| 関連情報の生成 | odds_poc_app(POST /v1/blogs/{blog_id}/related_generate) |
使用モデル
| 用途 | モデル |
|---|---|
| レース動画・コース画像の解析(1段目) | Amazon Nova Pro(動画入力対応) |
| 本文リライト・サマリー生成(2・3段目) | Claude Sonnet(BEDROCK_CLAUDE_SONNET_PROFILE_ARN 未設定時は Nova Pro) |
出力トークン上限は、長いコラムのブロック JSON が途中で切れると解釈エラーになるため、 Claude では 16384、Nova では 8192 にしています。
エラーハンドリング
| 状況 | 挙動 |
|---|---|
| 最終試行以外での失敗 | 例外を再送出し、SQS の再配信による自己復旧に委ねる |
| 最終試行での失敗 | status=generating の blog を物理削除し、例外を飲んで終了する |