コンテンツにスキップ

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

処理フロー

  1. SQS イベントを受信し、ペイロードをパースする
  2. DocumentDB へ接続する
  3. 対象の blog が status=generating か確認する。違えば Bedrock を呼ばずに終了する
  4. old_blog を取得する
  5. レースに紐づく場合
    • レース結果を取得し、着順テーブル(全着順)と関連情報レースカードを組み立てる
    • S3 からレース動画・コース画像(競馬のみ)を取得する
    • 1段目: race_analysis_agent でレースを総合解析する
    • レースに紐づかない場合は、この段をスキップして「レース情報なし」としてリライトする
  6. 2段目: 総合分析を踏まえて本文をリライトし、ブロック JSONとして構造化する
  7. 3段目: リライト後の本文から「この記事がわかる!10秒サマリー」を生成する
  8. 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 を物理削除し、例外を飲んで終了する