リリース: 行き先相談エージェントの乗換駅提案とモデルのgpt-6-lunaへの切り替え - #45
Conversation
* TTSの既定ボイスを日英で分けて早口寄りに調整する 日英とも nova だった既定ボイスを、日本語は shimmer、英語は coral に変更する。 どちらも多言語を読めるが、各言語で最も明瞭に聞こえるボイスを選んだ。 あわせて instructions を明るく張りのある調子へ書き換え、読み上げ速度を 通常より一段速い早口気味に指示する。gpt-4o-mini-tts は speed パラメータが 効かないため、速度指定は instructions で行う必要がある。 日英で別ボイスになったことで実態と食い違ったコメントも更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s * 読み上げ速度の指示をさらに急ぎ目へ強める 「一段速い早口気味」では十分に速くならなかったため、ラッシュ時の自動放送を 引き合いに出して速さを指示し、句読点・文の切れ目での間と語尾の伸ばしを 明示的に抑える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* AIチャットのモデルにGemini(Vertex AI)を追加する AGENT_MODEL に "google:<model>" を追加し、対話本体を Gemini でも動かせるように する。dev は google:gemini-3.7-flash、本番は openai のまま据え置く。 Vertex AI は API キーではなく ADC(サービスアカウント)認証が前提だが Workers に ADC は無いため、鍵 JSON を GOOGLE_VERTEX_SA_KEY で受け取り、JWT 署名と トークン交換は @ai-sdk/google-vertex の edge 版に行わせる(google-auth-library は バンドルに含めない)。project は鍵の project_id を既定とし、location の既定は global。AI Gateway 経由では google-vertex-ai/v1beta1 のモデルパスを baseURL に 組み、直行時と API バージョンを揃える。 思考の抑制は providerOptions を自前で組まず AI SDK 共通の reasoning 設定に委ねる。 ただし 3 系へ 'none'(= thinkingLevel: minimal)を送ると Vertex が 400 "Thinking level is unsupported: THINKING_LEVEL_MINIMAL" を返すため、受理される 最小値の 'low' まで下げる。2.5 系は 'none'(thinkingBudget: 0)で完全に止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes プロバイダ切り替えが「vars のみの変更」で済むのは、そのプロバイダのシークレットを 投入済みの場合に限ることを README に明記する。未投入だと resolveAgentModel が "<SECRET> is not configured" を投げて /agent/chat が全リクエスト失敗するため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する 合成エンジンを OpenAI(gpt-4o-mini-tts) から Cloud Text-to-Speech へ差し替える。 既定ボイスは Firebase Functions 時代の Google TTS 実装と同じ ja-JP-Standard-B / en-US-Standard-G に揃え、Android の端末内蔵 TTS と同水準の音質にする。 Standard / Wavenet / Neural2 以外(Studio・Chirp3-HD・Gemini-TTS)は単価が 桁違いのためクライアントから名指しできないようにする。 認証は API キーではなくサービスアカウント(GOOGLE_TTS_SA_KEY)で、既存の getGoogleAccessToken を再利用する。用途ごとに別の鍵を使うようになるため、 トークンキャッシュのキーを scope 単体からサービスアカウント × scope に変える。 Cloud TTS は AI Gateway の対応プロバイダではないので Google へ直行する。 Standard 系には読み方のプロンプト指示が無いため TTS_INSTRUCTIONS_* は廃止し、 速さは TTS_SPEED(speakingRate)、高さは TTS_PITCH に置き換える。リクエストの model / instructions* は受け取っても無視するため、旧アプリからの呼び出しは そのまま通る。エンジン差し替えで同じ入力でも音声が変わるので、キャッシュキーの 版を 13 から 14 へ上げて旧キャッシュとは分離する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * 鍵ファイルが空のときに明示的なエラーにする <NAME>_FILE で指定した鍵 JSON が空でも、これまでは値なしとして黙って捨てられ、 「投入対象のシークレットがありません」としか出ないため原因を追いにくかった。 失敗した `gcloud iam service-accounts keys create` は出力先ファイルを空のまま 残すため、この事故は実際に起こる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes ボイス名の検証を形式一致から実在ボイスの allowlist へ変更する。 形式だけを見ていたため ja-US-Standard-A や ja-JP-Standard-Z のような実在しない 名前が検証を通過し、Cloud TTS が 400 "Voice ... does not exist" を返して /tts 全体が失敗していた(未知の名前は既定値へ倒す、というこのモジュールの前提が 成り立っていなかった)。allowlist は voices.list で実在を確認した ja-JP 11 件・ en-US 29 件。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える BFF 廃止に伴い、staging の Service Binding を stationapi-stg へ移す。 GraphQL のクエリとレスポンス構造は sapi-bff と同一(stationsByName / stationGroupStations とも引数・フィールド名が変わらない)ため、 クエリ側の処理には手を入れていない。 本番の route 移管はまだ済んでいないので、production は SAPI_BFF → sapi-bff のまま残し、STATION_API を優先して 未設定なら SAPI_BFF へ落ちる順序にした。 なお stationapi は GraphQL をサブドメイン直下(POST /)で受けるため、 STATION_API 経由の POST 先は /graphql ではなくルート直下にしている。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z * STATION_API 経由のテストで POST であることも検証する CodeRabbit の指摘(PR #7)。テスト名は「サブドメイン直下へ POST する」 なのに URL しか見ておらず、method はスイート全体でも未検証だった。 リクエスト本文の variables は既存テストがカバー済みのため触っていない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 本番の駅検索を sapi-bff から stationapi へ切り替える BFF 廃止に伴い、production の Service Binding を SAPI_BFF → sapi-bff から STATION_API → stationapi へ移す。staging(#7)と揃い、両環境とも stationapi を直に叩く構成になったため、SAPI_BFF への分岐は不要になった。 postGraphQL から SAPI_BFF 分岐を落とし、Service Binding 不使用時の フォールバック環境変数も SAPI_BFF_GRAPHQL_URL から STATION_API_GRAPHQL_URL へ改名する(wrangler.jsonc では未設定のため リネームによる実害はない)。 分岐が消えたことで「STATION_API があれば優先し、サブドメイン直下へ POST する」テストは意味を失うので削除するが、stationapi が GraphQL を POST / でのみ受けるという前提の検証は残す必要があるため、POST 先 URL と method のアサーションは既存の Service Binding テストへ畳み込んでいる。 なお Service Binding は対象 Worker が存在しないとデプロイに失敗するため、 stationapi(production)のデプロイ完了後にこの変更をデプロイし、 BFF の削除はその後に行うこと。 Closes #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G * fix: apply CodeRabbit auto-fixes STATION_API と STATION_API_GRAPHQL_URL の優先順位を回帰テストで固定する。 SAPI_BFF 分岐の削除に伴い「STATION_API があれば優先」テストを落としたが、 残る 2 分岐(Service Binding / URL フォールバック)の順序を確かめる テストが無くなっていた。既存テストは片方だけを設定するため、postGraphQL の 分岐順を入れ替えても全て通ってしまう。 グローバル fetch はスパイに実装を持たせ、分岐順が壊れた場合でも実 ネットワークへ出ないようにする(jest.config.js に restoreMocks が無いため finally で明示的に戻す)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する 原因が特定できたフィードバックを該当する公開リポジトリにも起票できるようにし、 あわせて Issue #11 で報告された Spam 誤判定とタイトル破損を修正する。 ## 公開リポジトリへの振り分け - トリアージに原因コンポーネントの判定(component / componentConfidence)を追加 - component が特定でき信頼度 0.7 以上なら、TrainLCD/MobileApp・StationAPI・ Functions・Website のいずれかにスタブ Issue を起票する - 公開リポジトリなのでフィードバックの内容は一切載せず、管理 Issue 番号と チケットID だけを持たせる。管理 Issue 側にもコメントで相互リンクを残す - クラッシュ・スパム・スパム疑い・質問・称賛・トリアージ失敗は振り分けない - 追跡のため、管理 Issue の本文にもチケットID を追加 ## Spam 誤判定の修正(不具合A) - looksLikeSpam の「停車駅/方面」「駅名・路線名の併記」への単独加点を撤回し、 放送定型句との共起を必須にした。本アプリのドメイン語彙そのものであり、 正確な報告ほどスパム判定される構造だったため - 「違います」「反映されない」「ほしい」など、報告で頻出する言い回しを追加 - モデルの非スパム判定をヒューリスティックが無条件に上書きするのをやめ、 モデルの confidence が高い場合は分類を維持して ❓ Unknown Type で人手確認に回す - 感謝・称賛を 💩 Spam に分類していたのをやめ、praise カテゴリ(💚 Praise)を新設 ## タイトル破損の修正(不具合B) - AI_TRIAGE_MODEL を @cf/google/gemma-4-26b-a4b-it に変更(日本語生成品質) - gemma-4 は response を返さず choices[0].message.content のみのため、 両形式を受ける pickModelResponse を追加。従来の取り出し方だと全件失敗する - 推論トレースで JSON が途中で切れる(finish_reason: length)ため max_tokens を 2048 に - category / triageLevel / component を JSON Schema の required に追加。 optional だったため、モデルが省略した非スパムが軒並み question に落ちていた - 未取得・文字化け・生成ループ・助詞連続などの破損タイトルを検知し、 要約失敗マーカー付きで起票する。破損タイトルを要約へ伝播させない - 破損の理由とモデル名をログに残し、破損率を計測できるようにした Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: CodeRabbit 指摘対応(助詞連続の誤検知・README の条件・推論例外の握り) - particle_run が「のでは」「ものには」など正常な日本語を破損と誤検知していた。 誤検知するとトリアージ結果ごと破棄されるため、同一助詞の 3 連続に限定する - 公開リポジトリへの起票条件を README と実装で一致させる(reportType・ トリアージ成否・スパム疑い・対象カテゴリ・信頼度のすべてを明記) - env.AI.run の例外を試行ループ内で捕捉する。JSON Mode を満たせない場合や AI 側の一時障害で throw すると queue の再試行を使い切ってフィードバックが 消えるため、生成失敗として扱い「要約失敗」で起票して原文を残す なお AI_TRIAGE_MODEL の JSON Mode 非対応の指摘は、実機検証で反証済みのため モデルは変更しない(詳細は PR のレビュー返信を参照)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度を 0..1 に制限する モデルが "90"(パーセント表記のつもり)や負値を返した場合、componentConfidence が そのまま閾値判定を通過し、公開リポジトリへ内容を出すべきでないフィードバックが 起票されうる状態だった。 - TRIAGE_JSON_SCHEMA の confidence / componentConfidence に minimum: 0 と maximum: 1 を追加 - coerceReport でも範囲を検証し、範囲外は値を信用せず既定値へ倒す (componentConfidence は 0 = 公開起票しない、confidence は 0.5 = 従来の既定) 制約追加後も gemma-4 が JSON Mode を満たすことは実機で確認済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度は数値のみ受け付ける Number() 任せの変換だと componentConfidence: true や [1] が 1 に化け、 component と対象カテゴリが揃うと公開リポジトリへの起票条件を通過してしまう。 数値、または数値だけの文字列に限定し、それ以外は既定値へ倒す。 - boolean・配列・オブジェクト・null・空文字は既定値(componentConfidence は 0) - 範囲検証のテストで category を省いていたため question 扱いで弾かれ、 信頼度の検証に到達していなかった。category: 'bug' を明示して実際に 信頼度のガードを検証するようにした(ガードを外すと落ちることを確認済み) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * few-shot に functions / website の例を追加し、唯一例の抽出漏れを防ぐ 原因コンポーネントの判定を入れたが、few-shot に functions / website の例が 1 件も無く、この 2 リポジトリへは実質振り分けられない状態だった。両方の例を 追加する。 few-shot は毎回 FEW_SHOT_LIMIT 件をランダム抽出するため、カテゴリや コンポーネント唯一の例(praise / functions / website)は抽出から漏れると モデルがその値を出さなくなる。weight を付けて残りやすくし、あわせて FEW_SHOT_LIMIT を 12 から 16 に引き上げる。README に weight / disabled と FEW_SHOT_LIMIT の関係を追記した。 KV の実データ(dev / production)にも同じ方針で再投入済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4r9HW8s5VHfRoFEJN8bAU --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* フィードバックのキューに dead letter queue を設定する consumer は max_retries のみで、使い切ったメッセージの退避先が無かった。 processFeedbackMessage() は queue に retry させるためエラーを再送出するが、 権限やリポジトリ設定のような恒久的な原因だと retry しても回復しないため、 3 回失敗した時点でメッセージごと破棄され、フィードバックが失われていた。 dev / production 両方の consumer に dead_letter_queue を追加する。DLQ 側に consumer は付けない。同じハンドラを回しても同じ理由で落ちるだけで、復旧は 原因を直してから手動で流し直すのが正しい。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ の保持期限と再実行手順をドキュメントに追加する CodeRabbit のレビュー指摘への対応。 - DLQ にも保持期限があり、期限切れでメッセージが消えることを明記した。 DLQ を置いただけでは「失わない」保証にならず、再実行には期限がある。 - 再実行用の一時 consumer には別の DLQ を付けるよう明記した。再実行は同じ ハンドラを回すため、DLQ が無いと max_retries 超過でそのまま削除される。 - Setup のリソース作成コマンドが dev 名のみだったので、production 側の 名前について補足した。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ の保持期限更新手順を dev 側にも適用する形にする CodeRabbit のレビュー指摘への対応。保持期限はキュー単位の設定で、 feedback-triage-dev-dlq が prod 側の設定を継承することはないため、 コマンド例を両方のキューを回す形に変えた。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ 再実行の CLI 手順を README に追加する CodeRabbit のレビュー指摘(outside diff)への対応。復旧手順が散文だけで コマンドが無かったため、dev / production 双方のキュー名・スクリプト名で 実行できる形にした。一時 quarantine DLQ の作成、consumer add での接続、 排出後の consumer remove までを含む。 保持期限の確認については、wrangler 4.103 の `queues info` が retention を 表示しないため、CLI では読み出せない旨を明記した(指摘では `queues info` で確認する手順が求められていたが、実際には出力されない)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ 手順の保持期限・バッチサイズ・quarantine の後始末を補う CodeRabbit のレビュー指摘への対応。 - 保持期限を RETENTION 変数にまとめ、free tier では 86400 が上限で 1209600 は弾かれることを明記した。 - quarantine DLQ の作成にも --message-retention-period-secs を渡すように した。指定しないとアカウント既定に落ちる。 - 再実行用 consumer に --batch-size 5 を付けた。既定の 10 は 1 メッセージ あたり 5〜17 秒の推論が入る本ワーカーには 1 起動あたりの負荷が大きい。 - quarantine DLQ は incident 後も残るため、再利用条件と queues delete に よる後始末を追記した。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * 再実行手順のブロック内で RETENTION を定義する CodeRabbit のレビュー指摘への対応。RETENTION を保持期限の設定ブロック側 だけで定義していたため、再実行手順のブロックだけをコピーして実行すると 空になり、quarantine DLQ に保持期限が渡らなかった。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
DLQ への移送は Cloudflare 側が内部で行うため、正しく設定されていても `wrangler queues list` 上の DLQ は producer / consumer とも 0 のままになる。 紐付けは移送元キューの consumer 設定に載るので、確認は `wrangler queues consumer list <移送元キュー>` で行う必要がある。 設定は deploy して初めて反映される点も併記した。dead_letter_queue が "-" のままなら、その環境は今も retry 枯渇でメッセージを捨てている。 Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* GitHub Actions で dev・master からのデプロイを自動化する 検証 (lint / typecheck / test / wrangler の dry-run) を composite action へ 切り出し、CI とデプロイ 2 環境で同じ手順を踏ませる。デプロイ先はブランチを 式で判定せず、ファイルとトリガで固定する。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWZaSGKrw2MDZy5SYhuGiY * デプロイ workflow の変更でも ci.yml が起動するようにする deploy_dev.yml / deploy_production.yml はどちらも pull_request では起動せず、 ci.yml の paths にも載っていなかったため、この 2 ファイルだけを変更した PR が どの workflow も通らないままマージできてしまう状態だった。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWZaSGKrw2MDZy5SYhuGiY --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1LSBnYTMfm7CyrzqKxuW Co-authored-by: Claude <noreply@anthropic.com>
* リポジトリガイドラインを追加する * リリース: TTS の Google Cloud 移行・エージェントの Gemini 化・駅検索の stationapi 移行 (#10) * TTSをAzure SpeechからOpenAI gpt-4o-mini-ttsの女性声へ全面移行 (#2) * TTSの既定ボイスを日英で分けて早口寄りに調整する (#3) * TTSの既定ボイスを日英で分けて早口寄りに調整する 日英とも nova だった既定ボイスを、日本語は shimmer、英語は coral に変更する。 どちらも多言語を読めるが、各言語で最も明瞭に聞こえるボイスを選んだ。 あわせて instructions を明るく張りのある調子へ書き換え、読み上げ速度を 通常より一段速い早口気味に指示する。gpt-4o-mini-tts は speed パラメータが 効かないため、速度指定は instructions で行う必要がある。 日英で別ボイスになったことで実態と食い違ったコメントも更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s * 読み上げ速度の指示をさらに急ぎ目へ強める 「一段速い早口気味」では十分に速くならなかったため、ラッシュ時の自動放送を 引き合いに出して速さを指示し、句読点・文の切れ目での間と語尾の伸ばしを 明示的に抑える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * AIチャットのモデルにGemini(Vertex AI)を追加する (#4) * AIチャットのモデルにGemini(Vertex AI)を追加する AGENT_MODEL に "google:<model>" を追加し、対話本体を Gemini でも動かせるように する。dev は google:gemini-3.7-flash、本番は openai のまま据え置く。 Vertex AI は API キーではなく ADC(サービスアカウント)認証が前提だが Workers に ADC は無いため、鍵 JSON を GOOGLE_VERTEX_SA_KEY で受け取り、JWT 署名と トークン交換は @ai-sdk/google-vertex の edge 版に行わせる(google-auth-library は バンドルに含めない)。project は鍵の project_id を既定とし、location の既定は global。AI Gateway 経由では google-vertex-ai/v1beta1 のモデルパスを baseURL に 組み、直行時と API バージョンを揃える。 思考の抑制は providerOptions を自前で組まず AI SDK 共通の reasoning 設定に委ねる。 ただし 3 系へ 'none'(= thinkingLevel: minimal)を送ると Vertex が 400 "Thinking level is unsupported: THINKING_LEVEL_MINIMAL" を返すため、受理される 最小値の 'low' まで下げる。2.5 系は 'none'(thinkingBudget: 0)で完全に止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes プロバイダ切り替えが「vars のみの変更」で済むのは、そのプロバイダのシークレットを 投入済みの場合に限ることを README に明記する。未投入だと resolveAgentModel が "<SECRET> is not configured" を投げて /agent/chat が全リクエスト失敗するため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する (#5) * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する 合成エンジンを OpenAI(gpt-4o-mini-tts) から Cloud Text-to-Speech へ差し替える。 既定ボイスは Firebase Functions 時代の Google TTS 実装と同じ ja-JP-Standard-B / en-US-Standard-G に揃え、Android の端末内蔵 TTS と同水準の音質にする。 Standard / Wavenet / Neural2 以外(Studio・Chirp3-HD・Gemini-TTS)は単価が 桁違いのためクライアントから名指しできないようにする。 認証は API キーではなくサービスアカウント(GOOGLE_TTS_SA_KEY)で、既存の getGoogleAccessToken を再利用する。用途ごとに別の鍵を使うようになるため、 トークンキャッシュのキーを scope 単体からサービスアカウント × scope に変える。 Cloud TTS は AI Gateway の対応プロバイダではないので Google へ直行する。 Standard 系には読み方のプロンプト指示が無いため TTS_INSTRUCTIONS_* は廃止し、 速さは TTS_SPEED(speakingRate)、高さは TTS_PITCH に置き換える。リクエストの model / instructions* は受け取っても無視するため、旧アプリからの呼び出しは そのまま通る。エンジン差し替えで同じ入力でも音声が変わるので、キャッシュキーの 版を 13 から 14 へ上げて旧キャッシュとは分離する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * 鍵ファイルが空のときに明示的なエラーにする <NAME>_FILE で指定した鍵 JSON が空でも、これまでは値なしとして黙って捨てられ、 「投入対象のシークレットがありません」としか出ないため原因を追いにくかった。 失敗した `gcloud iam service-accounts keys create` は出力先ファイルを空のまま 残すため、この事故は実際に起こる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes ボイス名の検証を形式一致から実在ボイスの allowlist へ変更する。 形式だけを見ていたため ja-US-Standard-A や ja-JP-Standard-Z のような実在しない 名前が検証を通過し、Cloud TTS が 400 "Voice ... does not exist" を返して /tts 全体が失敗していた(未知の名前は既定値へ倒す、というこのモジュールの前提が 成り立っていなかった)。allowlist は voices.list で実在を確認した ja-JP 11 件・ en-US 29 件。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の AGENT_MODEL を Gemini(Vertex AI)へ切り替える (#6) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える (#7) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える BFF 廃止に伴い、staging の Service Binding を stationapi-stg へ移す。 GraphQL のクエリとレスポンス構造は sapi-bff と同一(stationsByName / stationGroupStations とも引数・フィールド名が変わらない)ため、 クエリ側の処理には手を入れていない。 本番の route 移管はまだ済んでいないので、production は SAPI_BFF → sapi-bff のまま残し、STATION_API を優先して 未設定なら SAPI_BFF へ落ちる順序にした。 なお stationapi は GraphQL をサブドメイン直下(POST /)で受けるため、 STATION_API 経由の POST 先は /graphql ではなくルート直下にしている。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z * STATION_API 経由のテストで POST であることも検証する CodeRabbit の指摘(PR #7)。テスト名は「サブドメイン直下へ POST する」 なのに URL しか見ておらず、method はスイート全体でも未検証だった。 リクエスト本文の variables は既存テストがカバー済みのため触っていない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の駅検索を sapi-bff から stationapi へ切り替える (#9) * 本番の駅検索を sapi-bff から stationapi へ切り替える BFF 廃止に伴い、production の Service Binding を SAPI_BFF → sapi-bff から STATION_API → stationapi へ移す。staging(#7)と揃い、両環境とも stationapi を直に叩く構成になったため、SAPI_BFF への分岐は不要になった。 postGraphQL から SAPI_BFF 分岐を落とし、Service Binding 不使用時の フォールバック環境変数も SAPI_BFF_GRAPHQL_URL から STATION_API_GRAPHQL_URL へ改名する(wrangler.jsonc では未設定のため リネームによる実害はない)。 分岐が消えたことで「STATION_API があれば優先し、サブドメイン直下へ POST する」テストは意味を失うので削除するが、stationapi が GraphQL を POST / でのみ受けるという前提の検証は残す必要があるため、POST 先 URL と method のアサーションは既存の Service Binding テストへ畳み込んでいる。 なお Service Binding は対象 Worker が存在しないとデプロイに失敗するため、 stationapi(production)のデプロイ完了後にこの変更をデプロイし、 BFF の削除はその後に行うこと。 Closes #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G * fix: apply CodeRabbit auto-fixes STATION_API と STATION_API_GRAPHQL_URL の優先順位を回帰テストで固定する。 SAPI_BFF 分岐の削除に伴い「STATION_API があれば優先」テストを落としたが、 残る 2 分岐(Service Binding / URL フォールバック)の順序を確かめる テストが無くなっていた。既存テストは片方だけを設定するため、postGraphQL の 分岐順を入れ替えても全て通ってしまう。 グローバル fetch はスパイに実装を持たせ、分岐順が壊れた場合でも実 ネットワークへ出ないようにする(jest.config.js に restoreMocks が無いため finally で明示的に戻す)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * リリース: フィードバックの原因リポジトリ振り分け・トリアージ品質改善・読み上げ速度のアプリ指定対応 (#18) * TTSをAzure SpeechからOpenAI gpt-4o-mini-ttsの女性声へ全面移行 (#2) * TTSの既定ボイスを日英で分けて早口寄りに調整する (#3) * TTSの既定ボイスを日英で分けて早口寄りに調整する 日英とも nova だった既定ボイスを、日本語は shimmer、英語は coral に変更する。 どちらも多言語を読めるが、各言語で最も明瞭に聞こえるボイスを選んだ。 あわせて instructions を明るく張りのある調子へ書き換え、読み上げ速度を 通常より一段速い早口気味に指示する。gpt-4o-mini-tts は speed パラメータが 効かないため、速度指定は instructions で行う必要がある。 日英で別ボイスになったことで実態と食い違ったコメントも更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s * 読み上げ速度の指示をさらに急ぎ目へ強める 「一段速い早口気味」では十分に速くならなかったため、ラッシュ時の自動放送を 引き合いに出して速さを指示し、句読点・文の切れ目での間と語尾の伸ばしを 明示的に抑える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * AIチャットのモデルにGemini(Vertex AI)を追加する (#4) * AIチャットのモデルにGemini(Vertex AI)を追加する AGENT_MODEL に "google:<model>" を追加し、対話本体を Gemini でも動かせるように する。dev は google:gemini-3.7-flash、本番は openai のまま据え置く。 Vertex AI は API キーではなく ADC(サービスアカウント)認証が前提だが Workers に ADC は無いため、鍵 JSON を GOOGLE_VERTEX_SA_KEY で受け取り、JWT 署名と トークン交換は @ai-sdk/google-vertex の edge 版に行わせる(google-auth-library は バンドルに含めない)。project は鍵の project_id を既定とし、location の既定は global。AI Gateway 経由では google-vertex-ai/v1beta1 のモデルパスを baseURL に 組み、直行時と API バージョンを揃える。 思考の抑制は providerOptions を自前で組まず AI SDK 共通の reasoning 設定に委ねる。 ただし 3 系へ 'none'(= thinkingLevel: minimal)を送ると Vertex が 400 "Thinking level is unsupported: THINKING_LEVEL_MINIMAL" を返すため、受理される 最小値の 'low' まで下げる。2.5 系は 'none'(thinkingBudget: 0)で完全に止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes プロバイダ切り替えが「vars のみの変更」で済むのは、そのプロバイダのシークレットを 投入済みの場合に限ることを README に明記する。未投入だと resolveAgentModel が "<SECRET> is not configured" を投げて /agent/chat が全リクエスト失敗するため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する (#5) * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する 合成エンジンを OpenAI(gpt-4o-mini-tts) から Cloud Text-to-Speech へ差し替える。 既定ボイスは Firebase Functions 時代の Google TTS 実装と同じ ja-JP-Standard-B / en-US-Standard-G に揃え、Android の端末内蔵 TTS と同水準の音質にする。 Standard / Wavenet / Neural2 以外(Studio・Chirp3-HD・Gemini-TTS)は単価が 桁違いのためクライアントから名指しできないようにする。 認証は API キーではなくサービスアカウント(GOOGLE_TTS_SA_KEY)で、既存の getGoogleAccessToken を再利用する。用途ごとに別の鍵を使うようになるため、 トークンキャッシュのキーを scope 単体からサービスアカウント × scope に変える。 Cloud TTS は AI Gateway の対応プロバイダではないので Google へ直行する。 Standard 系には読み方のプロンプト指示が無いため TTS_INSTRUCTIONS_* は廃止し、 速さは TTS_SPEED(speakingRate)、高さは TTS_PITCH に置き換える。リクエストの model / instructions* は受け取っても無視するため、旧アプリからの呼び出しは そのまま通る。エンジン差し替えで同じ入力でも音声が変わるので、キャッシュキーの 版を 13 から 14 へ上げて旧キャッシュとは分離する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * 鍵ファイルが空のときに明示的なエラーにする <NAME>_FILE で指定した鍵 JSON が空でも、これまでは値なしとして黙って捨てられ、 「投入対象のシークレットがありません」としか出ないため原因を追いにくかった。 失敗した `gcloud iam service-accounts keys create` は出力先ファイルを空のまま 残すため、この事故は実際に起こる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes ボイス名の検証を形式一致から実在ボイスの allowlist へ変更する。 形式だけを見ていたため ja-US-Standard-A や ja-JP-Standard-Z のような実在しない 名前が検証を通過し、Cloud TTS が 400 "Voice ... does not exist" を返して /tts 全体が失敗していた(未知の名前は既定値へ倒す、というこのモジュールの前提が 成り立っていなかった)。allowlist は voices.list で実在を確認した ja-JP 11 件・ en-US 29 件。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の AGENT_MODEL を Gemini(Vertex AI)へ切り替える (#6) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える (#7) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える BFF 廃止に伴い、staging の Service Binding を stationapi-stg へ移す。 GraphQL のクエリとレスポンス構造は sapi-bff と同一(stationsByName / stationGroupStations とも引数・フィールド名が変わらない)ため、 クエリ側の処理には手を入れていない。 本番の route 移管はまだ済んでいないので、production は SAPI_BFF → sapi-bff のまま残し、STATION_API を優先して 未設定なら SAPI_BFF へ落ちる順序にした。 なお stationapi は GraphQL をサブドメイン直下(POST /)で受けるため、 STATION_API 経由の POST 先は /graphql ではなくルート直下にしている。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z * STATION_API 経由のテストで POST であることも検証する CodeRabbit の指摘(PR #7)。テスト名は「サブドメイン直下へ POST する」 なのに URL しか見ておらず、method はスイート全体でも未検証だった。 リクエスト本文の variables は既存テストがカバー済みのため触っていない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の駅検索を sapi-bff から stationapi へ切り替える (#9) * 本番の駅検索を sapi-bff から stationapi へ切り替える BFF 廃止に伴い、production の Service Binding を SAPI_BFF → sapi-bff から STATION_API → stationapi へ移す。staging(#7)と揃い、両環境とも stationapi を直に叩く構成になったため、SAPI_BFF への分岐は不要になった。 postGraphQL から SAPI_BFF 分岐を落とし、Service Binding 不使用時の フォールバック環境変数も SAPI_BFF_GRAPHQL_URL から STATION_API_GRAPHQL_URL へ改名する(wrangler.jsonc では未設定のため リネームによる実害はない)。 分岐が消えたことで「STATION_API があれば優先し、サブドメイン直下へ POST する」テストは意味を失うので削除するが、stationapi が GraphQL を POST / でのみ受けるという前提の検証は残す必要があるため、POST 先 URL と method のアサーションは既存の Service Binding テストへ畳み込んでいる。 なお Service Binding は対象 Worker が存在しないとデプロイに失敗するため、 stationapi(production)のデプロイ完了後にこの変更をデプロイし、 BFF の削除はその後に行うこと。 Closes #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G * fix: apply CodeRabbit auto-fixes STATION_API と STATION_API_GRAPHQL_URL の優先順位を回帰テストで固定する。 SAPI_BFF 分岐の削除に伴い「STATION_API があれば優先」テストを落としたが、 残る 2 分岐(Service Binding / URL フォールバック)の順序を確かめる テストが無くなっていた。既存テストは片方だけを設定するため、postGraphQL の 分岐順を入れ替えても全て通ってしまう。 グローバル fetch はスパイに実装を持たせ、分岐順が壊れた場合でも実 ネットワークへ出ないようにする(jest.config.js に restoreMocks が無いため finally で明示的に戻す)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する (#14) * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する 原因が特定できたフィードバックを該当する公開リポジトリにも起票できるようにし、 あわせて Issue #11 で報告された Spam 誤判定とタイトル破損を修正する。 ## 公開リポジトリへの振り分け - トリアージに原因コンポーネントの判定(component / componentConfidence)を追加 - component が特定でき信頼度 0.7 以上なら、TrainLCD/MobileApp・StationAPI・ Functions・Website のいずれかにスタブ Issue を起票する - 公開リポジトリなのでフィードバックの内容は一切載せず、管理 Issue 番号と チケットID だけを持たせる。管理 Issue 側にもコメントで相互リンクを残す - クラッシュ・スパム・スパム疑い・質問・称賛・トリアージ失敗は振り分けない - 追跡のため、管理 Issue の本文にもチケットID を追加 ## Spam 誤判定の修正(不具合A) - looksLikeSpam の「停車駅/方面」「駅名・路線名の併記」への単独加点を撤回し、 放送定型句との共起を必須にした。本アプリのドメイン語彙そのものであり、 正確な報告ほどスパム判定される構造だったため - 「違います」「反映されない」「ほしい」など、報告で頻出する言い回しを追加 - モデルの非スパム判定をヒューリスティックが無条件に上書きするのをやめ、 モデルの confidence が高い場合は分類を維持して ❓ Unknown Type で人手確認に回す - 感謝・称賛を 💩 Spam に分類していたのをやめ、praise カテゴリ(💚 Praise)を新設 ## タイトル破損の修正(不具合B) - AI_TRIAGE_MODEL を @cf/google/gemma-4-26b-a4b-it に変更(日本語生成品質) - gemma-4 は response を返さず choices[0].message.content のみのため、 両形式を受ける pickModelResponse を追加。従来の取り出し方だと全件失敗する - 推論トレースで JSON が途中で切れる(finish_reason: length)ため max_tokens を 2048 に - category / triageLevel / component を JSON Schema の required に追加。 optional だったため、モデルが省略した非スパムが軒並み question に落ちていた - 未取得・文字化け・生成ループ・助詞連続などの破損タイトルを検知し、 要約失敗マーカー付きで起票する。破損タイトルを要約へ伝播させない - 破損の理由とモデル名をログに残し、破損率を計測できるようにした Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: CodeRabbit 指摘対応(助詞連続の誤検知・README の条件・推論例外の握り) - particle_run が「のでは」「ものには」など正常な日本語を破損と誤検知していた。 誤検知するとトリアージ結果ごと破棄されるため、同一助詞の 3 連続に限定する - 公開リポジトリへの起票条件を README と実装で一致させる(reportType・ トリアージ成否・スパム疑い・対象カテゴリ・信頼度のすべてを明記) - env.AI.run の例外を試行ループ内で捕捉する。JSON Mode を満たせない場合や AI 側の一時障害で throw すると queue の再試行を使い切ってフィードバックが 消えるため、生成失敗として扱い「要約失敗」で起票して原文を残す なお AI_TRIAGE_MODEL の JSON Mode 非対応の指摘は、実機検証で反証済みのため モデルは変更しない(詳細は PR のレビュー返信を参照)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度を 0..1 に制限する モデルが "90"(パーセント表記のつもり)や負値を返した場合、componentConfidence が そのまま閾値判定を通過し、公開リポジトリへ内容を出すべきでないフィードバックが 起票されうる状態だった。 - TRIAGE_JSON_SCHEMA の confidence / componentConfidence に minimum: 0 と maximum: 1 を追加 - coerceReport でも範囲を検証し、範囲外は値を信用せず既定値へ倒す (componentConfidence は 0 = 公開起票しない、confidence は 0.5 = 従来の既定) 制約追加後も gemma-4 が JSON Mode を満たすことは実機で確認済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度は数値のみ受け付ける Number() 任せの変換だと componentConfidence: true や [1] が 1 に化け、 component と対象カテゴリが揃うと公開リポジトリへの起票条件を通過してしまう。 数値、または数値だけの文字列に限定し、それ以外は既定値へ倒す。 - boolean・配列・オブジェクト・null・空文字は既定値(componentConfidence は 0) - 範囲検証のテストで category を省いていたため question 扱いで弾かれ、 信頼度の検証に到達していなかった。category: 'bug' を明示して実際に 信頼度のガードを検証するようにした(ガードを外すと落ちることを確認済み) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * few-shot に functions / website の例を追加し、唯一例の抽出漏れを防ぐ 原因コンポーネントの判定を入れたが、few-shot に functions / website の例が 1 件も無く、この 2 リポジトリへは実質振り分けられない状態だった。両方の例を 追加する。 few-shot は毎回 FEW_SHOT_LIMIT 件をランダム抽出するため、カテゴリや コンポーネント唯一の例(praise / functions / website)は抽出から漏れると モデルがその値を出さなくなる。weight を付けて残りやすくし、あわせて FEW_SHOT_LIMIT を 12 から 16 に引き上げる。README に weight / disabled と FEW_SHOT_LIMIT の関係を追記した。 KV の実データ(dev / production)にも同じ方針で再投入済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4r9HW8s5VHfRoFEJN8bAU --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * アプリ指定の読み上げ速度を受け付け既定速度を等速へ変更 (#16) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * リリース: フィードバックのキューに dead letter queue を設定する (#21) * TTSをAzure SpeechからOpenAI gpt-4o-mini-ttsの女性声へ全面移行 (#2) * TTSの既定ボイスを日英で分けて早口寄りに調整する (#3) * TTSの既定ボイスを日英で分けて早口寄りに調整する 日英とも nova だった既定ボイスを、日本語は shimmer、英語は coral に変更する。 どちらも多言語を読めるが、各言語で最も明瞭に聞こえるボイスを選んだ。 あわせて instructions を明るく張りのある調子へ書き換え、読み上げ速度を 通常より一段速い早口気味に指示する。gpt-4o-mini-tts は speed パラメータが 効かないため、速度指定は instructions で行う必要がある。 日英で別ボイスになったことで実態と食い違ったコメントも更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s * 読み上げ速度の指示をさらに急ぎ目へ強める 「一段速い早口気味」では十分に速くならなかったため、ラッシュ時の自動放送を 引き合いに出して速さを指示し、句読点・文の切れ目での間と語尾の伸ばしを 明示的に抑える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * AIチャットのモデルにGemini(Vertex AI)を追加する (#4) * AIチャットのモデルにGemini(Vertex AI)を追加する AGENT_MODEL に "google:<model>" を追加し、対話本体を Gemini でも動かせるように する。dev は google:gemini-3.7-flash、本番は openai のまま据え置く。 Vertex AI は API キーではなく ADC(サービスアカウント)認証が前提だが Workers に ADC は無いため、鍵 JSON を GOOGLE_VERTEX_SA_KEY で受け取り、JWT 署名と トークン交換は @ai-sdk/google-vertex の edge 版に行わせる(google-auth-library は バンドルに含めない)。project は鍵の project_id を既定とし、location の既定は global。AI Gateway 経由では google-vertex-ai/v1beta1 のモデルパスを baseURL に 組み、直行時と API バージョンを揃える。 思考の抑制は providerOptions を自前で組まず AI SDK 共通の reasoning 設定に委ねる。 ただし 3 系へ 'none'(= thinkingLevel: minimal)を送ると Vertex が 400 "Thinking level is unsupported: THINKING_LEVEL_MINIMAL" を返すため、受理される 最小値の 'low' まで下げる。2.5 系は 'none'(thinkingBudget: 0)で完全に止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes プロバイダ切り替えが「vars のみの変更」で済むのは、そのプロバイダのシークレットを 投入済みの場合に限ることを README に明記する。未投入だと resolveAgentModel が "<SECRET> is not configured" を投げて /agent/chat が全リクエスト失敗するため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する (#5) * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する 合成エンジンを OpenAI(gpt-4o-mini-tts) から Cloud Text-to-Speech へ差し替える。 既定ボイスは Firebase Functions 時代の Google TTS 実装と同じ ja-JP-Standard-B / en-US-Standard-G に揃え、Android の端末内蔵 TTS と同水準の音質にする。 Standard / Wavenet / Neural2 以外(Studio・Chirp3-HD・Gemini-TTS)は単価が 桁違いのためクライアントから名指しできないようにする。 認証は API キーではなくサービスアカウント(GOOGLE_TTS_SA_KEY)で、既存の getGoogleAccessToken を再利用する。用途ごとに別の鍵を使うようになるため、 トークンキャッシュのキーを scope 単体からサービスアカウント × scope に変える。 Cloud TTS は AI Gateway の対応プロバイダではないので Google へ直行する。 Standard 系には読み方のプロンプト指示が無いため TTS_INSTRUCTIONS_* は廃止し、 速さは TTS_SPEED(speakingRate)、高さは TTS_PITCH に置き換える。リクエストの model / instructions* は受け取っても無視するため、旧アプリからの呼び出しは そのまま通る。エンジン差し替えで同じ入力でも音声が変わるので、キャッシュキーの 版を 13 から 14 へ上げて旧キャッシュとは分離する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * 鍵ファイルが空のときに明示的なエラーにする <NAME>_FILE で指定した鍵 JSON が空でも、これまでは値なしとして黙って捨てられ、 「投入対象のシークレットがありません」としか出ないため原因を追いにくかった。 失敗した `gcloud iam service-accounts keys create` は出力先ファイルを空のまま 残すため、この事故は実際に起こる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes ボイス名の検証を形式一致から実在ボイスの allowlist へ変更する。 形式だけを見ていたため ja-US-Standard-A や ja-JP-Standard-Z のような実在しない 名前が検証を通過し、Cloud TTS が 400 "Voice ... does not exist" を返して /tts 全体が失敗していた(未知の名前は既定値へ倒す、というこのモジュールの前提が 成り立っていなかった)。allowlist は voices.list で実在を確認した ja-JP 11 件・ en-US 29 件。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の AGENT_MODEL を Gemini(Vertex AI)へ切り替える (#6) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える (#7) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える BFF 廃止に伴い、staging の Service Binding を stationapi-stg へ移す。 GraphQL のクエリとレスポンス構造は sapi-bff と同一(stationsByName / stationGroupStations とも引数・フィールド名が変わらない)ため、 クエリ側の処理には手を入れていない。 本番の route 移管はまだ済んでいないので、production は SAPI_BFF → sapi-bff のまま残し、STATION_API を優先して 未設定なら SAPI_BFF へ落ちる順序にした。 なお stationapi は GraphQL をサブドメイン直下(POST /)で受けるため、 STATION_API 経由の POST 先は /graphql ではなくルート直下にしている。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z * STATION_API 経由のテストで POST であることも検証する CodeRabbit の指摘(PR #7)。テスト名は「サブドメイン直下へ POST する」 なのに URL しか見ておらず、method はスイート全体でも未検証だった。 リクエスト本文の variables は既存テストがカバー済みのため触っていない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の駅検索を sapi-bff から stationapi へ切り替える (#9) * 本番の駅検索を sapi-bff から stationapi へ切り替える BFF 廃止に伴い、production の Service Binding を SAPI_BFF → sapi-bff から STATION_API → stationapi へ移す。staging(#7)と揃い、両環境とも stationapi を直に叩く構成になったため、SAPI_BFF への分岐は不要になった。 postGraphQL から SAPI_BFF 分岐を落とし、Service Binding 不使用時の フォールバック環境変数も SAPI_BFF_GRAPHQL_URL から STATION_API_GRAPHQL_URL へ改名する(wrangler.jsonc では未設定のため リネームによる実害はない)。 分岐が消えたことで「STATION_API があれば優先し、サブドメイン直下へ POST する」テストは意味を失うので削除するが、stationapi が GraphQL を POST / でのみ受けるという前提の検証は残す必要があるため、POST 先 URL と method のアサーションは既存の Service Binding テストへ畳み込んでいる。 なお Service Binding は対象 Worker が存在しないとデプロイに失敗するため、 stationapi(production)のデプロイ完了後にこの変更をデプロイし、 BFF の削除はその後に行うこと。 Closes #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G * fix: apply CodeRabbit auto-fixes STATION_API と STATION_API_GRAPHQL_URL の優先順位を回帰テストで固定する。 SAPI_BFF 分岐の削除に伴い「STATION_API があれば優先」テストを落としたが、 残る 2 分岐(Service Binding / URL フォールバック)の順序を確かめる テストが無くなっていた。既存テストは片方だけを設定するため、postGraphQL の 分岐順を入れ替えても全て通ってしまう。 グローバル fetch はスパイに実装を持たせ、分岐順が壊れた場合でも実 ネットワークへ出ないようにする(jest.config.js に restoreMocks が無いため finally で明示的に戻す)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する (#14) * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する 原因が特定できたフィードバックを該当する公開リポジトリにも起票できるようにし、 あわせて Issue #11 で報告された Spam 誤判定とタイトル破損を修正する。 ## 公開リポジトリへの振り分け - トリアージに原因コンポーネントの判定(component / componentConfidence)を追加 - component が特定でき信頼度 0.7 以上なら、TrainLCD/MobileApp・StationAPI・ Functions・Website のいずれかにスタブ Issue を起票する - 公開リポジトリなのでフィードバックの内容は一切載せず、管理 Issue 番号と チケットID だけを持たせる。管理 Issue 側にもコメントで相互リンクを残す - クラッシュ・スパム・スパム疑い・質問・称賛・トリアージ失敗は振り分けない - 追跡のため、管理 Issue の本文にもチケットID を追加 ## Spam 誤判定の修正(不具合A) - looksLikeSpam の「停車駅/方面」「駅名・路線名の併記」への単独加点を撤回し、 放送定型句との共起を必須にした。本アプリのドメイン語彙そのものであり、 正確な報告ほどスパム判定される構造だったため - 「違います」「反映されない」「ほしい」など、報告で頻出する言い回しを追加 - モデルの非スパム判定をヒューリスティックが無条件に上書きするのをやめ、 モデルの confidence が高い場合は分類を維持して ❓ Unknown Type で人手確認に回す - 感謝・称賛を 💩 Spam に分類していたのをやめ、praise カテゴリ(💚 Praise)を新設 ## タイトル破損の修正(不具合B) - AI_TRIAGE_MODEL を @cf/google/gemma-4-26b-a4b-it に変更(日本語生成品質) - gemma-4 は response を返さず choices[0].message.content のみのため、 両形式を受ける pickModelResponse を追加。従来の取り出し方だと全件失敗する - 推論トレースで JSON が途中で切れる(finish_reason: length)ため max_tokens を 2048 に - category / triageLevel / component を JSON Schema の required に追加。 optional だったため、モデルが省略した非スパムが軒並み question に落ちていた - 未取得・文字化け・生成ループ・助詞連続などの破損タイトルを検知し、 要約失敗マーカー付きで起票する。破損タイトルを要約へ伝播させない - 破損の理由とモデル名をログに残し、破損率を計測できるようにした Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: CodeRabbit 指摘対応(助詞連続の誤検知・README の条件・推論例外の握り) - particle_run が「のでは」「ものには」など正常な日本語を破損と誤検知していた。 誤検知するとトリアージ結果ごと破棄されるため、同一助詞の 3 連続に限定する - 公開リポジトリへの起票条件を README と実装で一致させる(reportType・ トリアージ成否・スパム疑い・対象カテゴリ・信頼度のすべてを明記) - env.AI.run の例外を試行ループ内で捕捉する。JSON Mode を満たせない場合や AI 側の一時障害で throw すると queue の再試行を使い切ってフィードバックが 消えるため、生成失敗として扱い「要約失敗」で起票して原文を残す なお AI_TRIAGE_MODEL の JSON Mode 非対応の指摘は、実機検証で反証済みのため モデルは変更しない(詳細は PR のレビュー返信を参照)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度を 0..1 に制限する モデルが "90"(パーセント表記のつもり)や負値を返した場合、componentConfidence が そのまま閾値判定を通過し、公開リポジトリへ内容を出すべきでないフィードバックが 起票されうる状態だった。 - TRIAGE_JSON_SCHEMA の confidence / componentConfidence に minimum: 0 と maximum: 1 を追加 - coerceReport でも範囲を検証し、範囲外は値を信用せず既定値へ倒す (componentConfidence は 0 = 公開起票しない、confidence は 0.5 = 従来の既定) 制約追加後も gemma-4 が JSON Mode を満たすことは実機で確認済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度は数値のみ受け付ける Number() 任せの変換だと componentConfidence: true や [1] が 1 に化け、 component と対象カテゴリが揃うと公開リポジトリへの起票条件を通過してしまう。 数値、または数値だけの文字列に限定し、それ以外は既定値へ倒す。 - boolean・配列・オブジェクト・null・空文字は既定値(componentConfidence は 0) - 範囲検証のテストで category を省いていたため question 扱いで弾かれ、 信頼度の検証に到達していなかった。category: 'bug' を明示して実際に 信頼度のガードを検証するようにした(ガードを外すと落ちることを確認済み) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * few-shot に functions / website の例を追加し、唯一例の抽出漏れを防ぐ 原因コンポーネントの判定を入れたが、few-shot に functions / website の例が 1 件も無く、この 2 リポジトリへは実質振り分けられない状態だった。両方の例を 追加する。 few-shot は毎回 FEW_SHOT_LIMIT 件をランダム抽出するため、カテゴリや コンポーネント唯一の例(praise / functions / website)は抽出から漏れると モデルがその値を出さなくなる。weight を付けて残りやすくし、あわせて FEW_SHOT_LIMIT を 12 から 16 に引き上げる。README に weight / disabled と FEW_SHOT_LIMIT の関係を追記した。 KV の実データ(dev / production)にも同じ方針で再投入済み。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4r9HW8s5VHfRoFEJN8bAU --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * アプリ指定の読み上げ速度を受け付け既定速度を等速へ変更 (#16) * フィードバックのキューに dead letter queue を設定する (#19) * フィードバックのキューに dead letter queue を設定する consumer は max_retries のみで、使い切ったメッセージの退避先が無かった。 processFeedbackMessage() は queue に retry させるためエラーを再送出するが、 権限やリポジトリ設定のような恒久的な原因だと retry しても回復しないため、 3 回失敗した時点でメッセージごと破棄され、フィードバックが失われていた。 dev / production 両方の consumer に dead_letter_queue を追加する。DLQ 側に consumer は付けない。同じハンドラを回しても同じ理由で落ちるだけで、復旧は 原因を直してから手動で流し直すのが正しい。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ の保持期限と再実行手順をドキュメントに追加する CodeRabbit のレビュー指摘への対応。 - DLQ にも保持期限があり、期限切れでメッセージが消えることを明記した。 DLQ を置いただけでは「失わない」保証にならず、再実行には期限がある。 - 再実行用の一時 consumer には別の DLQ を付けるよう明記した。再実行は同じ ハンドラを回すため、DLQ が無いと max_retries 超過でそのまま削除される。 - Setup のリソース作成コマンドが dev 名のみだったので、production 側の 名前について補足した。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ の保持期限更新手順を dev 側にも適用する形にする CodeRabbit のレビュー指摘への対応。保持期限はキュー単位の設定で、 feedback-triage-dev-dlq が prod 側の設定を継承することはないため、 コマンド例を両方のキューを回す形に変えた。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ 再実行の CLI 手順を README に追加する CodeRabbit のレビュー指摘(outside diff)への対応。復旧手順が散文だけで コマンドが無かったため、dev / production 双方のキュー名・スクリプト名で 実行できる形にした。一時 quarantine DLQ の作成、consumer add での接続、 排出後の consumer remove までを含む。 保持期限の確認については、wrangler 4.103 の `queues info` が retention を 表示しないため、CLI では読み出せない旨を明記した(指摘では `queues info` で確認する手順が求められていたが、実際には出力されない)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * DLQ 手順の保持期限・バッチサイズ・quarantine の後始末を補う CodeRabbit のレビュー指摘への対応。 - 保持期限を RETENTION 変数にまとめ、free tier では 86400 が上限で 1209600 は弾かれることを明記した。 - quarantine DLQ の作成にも --message-retention-period-secs を渡すように した。指定しないとアカウント既定に落ちる。 - 再実行用 consumer に --batch-size 5 を付けた。既定の 10 は 1 メッセージ あたり 5〜17 秒の推論が入る本ワーカーには 1 起動あたりの負荷が大きい。 - quarantine DLQ は incident 後も残るため、再利用条件と queues delete に よる後始末を追記した。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc * 再実行手順のブロック内で RETENTION を定義する CodeRabbit のレビュー指摘への対応。RETENTION を保持期限の設定ブロック側 だけで定義していたため、再実行手順のブロックだけをコピーして実行すると 空になり、quarantine DLQ に保持期限が渡らなかった。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * DLQ の紐付けを確認する手順を README に追加する (#22) DLQ への移送は Cloudflare 側が内部で行うため、正しく設定されていても `wrangler queues list` 上の DLQ は producer / consumer とも 0 のままになる。 紐付けは移送元キューの consumer 設定に載るので、確認は `wrangler queues consumer list <移送元キュー>` で行う必要がある。 設定は deploy して初めて反映される点も併記した。dead_letter_queue が "-" のままなら、その環境は今も retry 枯渇でメッセージを捨てている。 Claude-Session: https://claude.ai/code/session_018n3eJcHFb6E2BDzPCcwKZc Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * リリース: 英語TTSの京成・西武の誤読修正、フィードバック再試行の重複起票防止、Gemini 3.8 Flash 化、デプロイ自動化 (#28) * TTSをAzure SpeechからOpenAI gpt-4o-mini-ttsの女性声へ全面移行 (#2) * TTSの既定ボイスを日英で分けて早口寄りに調整する (#3) * TTSの既定ボイスを日英で分けて早口寄りに調整する 日英とも nova だった既定ボイスを、日本語は shimmer、英語は coral に変更する。 どちらも多言語を読めるが、各言語で最も明瞭に聞こえるボイスを選んだ。 あわせて instructions を明るく張りのある調子へ書き換え、読み上げ速度を 通常より一段速い早口気味に指示する。gpt-4o-mini-tts は speed パラメータが 効かないため、速度指定は instructions で行う必要がある。 日英で別ボイスになったことで実態と食い違ったコメントも更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s * 読み上げ速度の指示をさらに急ぎ目へ強める 「一段速い早口気味」では十分に速くならなかったため、ラッシュ時の自動放送を 引き合いに出して速さを指示し、句読点・文の切れ目での間と語尾の伸ばしを 明示的に抑える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ns9SudVEY9UzSUiaHoDy5s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * AIチャットのモデルにGemini(Vertex AI)を追加する (#4) * AIチャットのモデルにGemini(Vertex AI)を追加する AGENT_MODEL に "google:<model>" を追加し、対話本体を Gemini でも動かせるように する。dev は google:gemini-3.7-flash、本番は openai のまま据え置く。 Vertex AI は API キーではなく ADC(サービスアカウント)認証が前提だが Workers に ADC は無いため、鍵 JSON を GOOGLE_VERTEX_SA_KEY で受け取り、JWT 署名と トークン交換は @ai-sdk/google-vertex の edge 版に行わせる(google-auth-library は バンドルに含めない)。project は鍵の project_id を既定とし、location の既定は global。AI Gateway 経由では google-vertex-ai/v1beta1 のモデルパスを baseURL に 組み、直行時と API バージョンを揃える。 思考の抑制は providerOptions を自前で組まず AI SDK 共通の reasoning 設定に委ねる。 ただし 3 系へ 'none'(= thinkingLevel: minimal)を送ると Vertex が 400 "Thinking level is unsupported: THINKING_LEVEL_MINIMAL" を返すため、受理される 最小値の 'low' まで下げる。2.5 系は 'none'(thinkingBudget: 0)で完全に止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes プロバイダ切り替えが「vars のみの変更」で済むのは、そのプロバイダのシークレットを 投入済みの場合に限ることを README に明記する。未投入だと resolveAgentModel が "<SECRET> is not configured" を投げて /agent/chat が全リクエスト失敗するため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する (#5) * TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する 合成エンジンを OpenAI(gpt-4o-mini-tts) から Cloud Text-to-Speech へ差し替える。 既定ボイスは Firebase Functions 時代の Google TTS 実装と同じ ja-JP-Standard-B / en-US-Standard-G に揃え、Android の端末内蔵 TTS と同水準の音質にする。 Standard / Wavenet / Neural2 以外(Studio・Chirp3-HD・Gemini-TTS)は単価が 桁違いのためクライアントから名指しできないようにする。 認証は API キーではなくサービスアカウント(GOOGLE_TTS_SA_KEY)で、既存の getGoogleAccessToken を再利用する。用途ごとに別の鍵を使うようになるため、 トークンキャッシュのキーを scope 単体からサービスアカウント × scope に変える。 Cloud TTS は AI Gateway の対応プロバイダではないので Google へ直行する。 Standard 系には読み方のプロンプト指示が無いため TTS_INSTRUCTIONS_* は廃止し、 速さは TTS_SPEED(speakingRate)、高さは TTS_PITCH に置き換える。リクエストの model / instructions* は受け取っても無視するため、旧アプリからの呼び出しは そのまま通る。エンジン差し替えで同じ入力でも音声が変わるので、キャッシュキーの 版を 13 から 14 へ上げて旧キャッシュとは分離する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * 鍵ファイルが空のときに明示的なエラーにする <NAME>_FILE で指定した鍵 JSON が空でも、これまでは値なしとして黙って捨てられ、 「投入対象のシークレットがありません」としか出ないため原因を追いにくかった。 失敗した `gcloud iam service-accounts keys create` は出力先ファイルを空のまま 残すため、この事故は実際に起こる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi * fix: apply CodeRabbit auto-fixes ボイス名の検証を形式一致から実在ボイスの allowlist へ変更する。 形式だけを見ていたため ja-US-Standard-A や ja-JP-Standard-Z のような実在しない 名前が検証を通過し、Cloud TTS が 400 "Voice ... does not exist" を返して /tts 全体が失敗していた(未知の名前は既定値へ倒す、というこのモジュールの前提が 成り立っていなかった)。allowlist は voices.list で実在を確認した ja-JP 11 件・ en-US 29 件。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4jE3NdeZJ5Ny7Zjue9tzi --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の AGENT_MODEL を Gemini(Vertex AI)へ切り替える (#6) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える (#7) * staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える BFF 廃止に伴い、staging の Service Binding を stationapi-stg へ移す。 GraphQL のクエリとレスポンス構造は sapi-bff と同一(stationsByName / stationGroupStations とも引数・フィールド名が変わらない)ため、 クエリ側の処理には手を入れていない。 本番の route 移管はまだ済んでいないので、production は SAPI_BFF → sapi-bff のまま残し、STATION_API を優先して 未設定なら SAPI_BFF へ落ちる順序にした。 なお stationapi は GraphQL をサブドメイン直下(POST /)で受けるため、 STATION_API 経由の POST 先は /graphql ではなくルート直下にしている。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z * STATION_API 経由のテストで POST であることも検証する CodeRabbit の指摘(PR #7)。テスト名は「サブドメイン直下へ POST する」 なのに URL しか見ておらず、method はスイート全体でも未検証だった。 リクエスト本文の variables は既存テストがカバー済みのため触っていない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM76omHQmBawZ3EfBsuU7z --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 本番の駅検索を sapi-bff から stationapi へ切り替える (#9) * 本番の駅検索を sapi-bff から stationapi へ切り替える BFF 廃止に伴い、production の Service Binding を SAPI_BFF → sapi-bff から STATION_API → stationapi へ移す。staging(#7)と揃い、両環境とも stationapi を直に叩く構成になったため、SAPI_BFF への分岐は不要になった。 postGraphQL から SAPI_BFF 分岐を落とし、Service Binding 不使用時の フォールバック環境変数も SAPI_BFF_GRAPHQL_URL から STATION_API_GRAPHQL_URL へ改名する(wrangler.jsonc では未設定のため リネームによる実害はない)。 分岐が消えたことで「STATION_API があれば優先し、サブドメイン直下へ POST する」テストは意味を失うので削除するが、stationapi が GraphQL を POST / でのみ受けるという前提の検証は残す必要があるため、POST 先 URL と method のアサーションは既存の Service Binding テストへ畳み込んでいる。 なお Service Binding は対象 Worker が存在しないとデプロイに失敗するため、 stationapi(production)のデプロイ完了後にこの変更をデプロイし、 BFF の削除はその後に行うこと。 Closes #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G * fix: apply CodeRabbit auto-fixes STATION_API と STATION_API_GRAPHQL_URL の優先順位を回帰テストで固定する。 SAPI_BFF 分岐の削除に伴い「STATION_API があれば優先」テストを落としたが、 残る 2 分岐(Service Binding / URL フォールバック)の順序を確かめる テストが無くなっていた。既存テストは片方だけを設定するため、postGraphQL の 分岐順を入れ替えても全て通ってしまう。 グローバル fetch はスパイに実装を持たせ、分岐順が壊れた場合でも実 ネットワークへ出ないようにする(jest.config.js に restoreMocks が無いため finally で明示的に戻す)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXt1cqwoy71GakDQMGC3G --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する (#14) * フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する 原因が特定できたフィードバックを該当する公開リポジトリにも起票できるようにし、 あわせて Issue #11 で報告された Spam 誤判定とタイトル破損を修正する。 ## 公開リポジトリへの振り分け - トリアージに原因コンポーネントの判定(component / componentConfidence)を追加 - component が特定でき信頼度 0.7 以上なら、TrainLCD/MobileApp・StationAPI・ Functions・Website のいずれかにスタブ Issue を起票する - 公開リポジトリなのでフィードバックの内容は一切載せず、管理 Issue 番号と チケットID だけを持たせる。管理 Issue 側にもコメントで相互リンクを残す - クラッシュ・スパム・スパム疑い・質問・称賛・トリアージ失敗は振り分けない - 追跡のため、管理 Issue の本文にもチケットID を追加 ## Spam 誤判定の修正(不具合A) - looksLikeSpam の「停車駅/方面」「駅名・路線名の併記」への単独加点を撤回し、 放送定型句との共起を必須にした。本アプリのドメイン語彙そのものであり、 正確な報告ほどスパム判定される構造だったため - 「違います」「反映されない」「ほしい」など、報告で頻出する言い回しを追加 - モデルの非スパム判定をヒューリスティックが無条件に上書きするのをやめ、 モデルの confidence が高い場合は分類を維持して ❓ Unknown Type で人手確認に回す - 感謝・称賛を 💩 Spam に分類していたのをやめ、praise カテゴリ(💚 Praise)を新設 ## タイトル破損の修正(不具合B) - AI_TRIAGE_MODEL を @cf/google/gemma-4-26b-a4b-it に変更(日本語生成品質) - gemma-4 は response を返さず choices[0].message.content のみのため、 両形式を受ける pickModelResponse を追加。従来の取り出し方だと全件失敗する - 推論トレースで JSON が途中で切れる(finish_reason: length)ため max_tokens を 2048 に - category / triageLevel / component を JSON Schema の required に追加。 optional だったため、モデルが省略した非スパムが軒並み question に落ちていた - 未取得・文字化け・生成ループ・助詞連続などの破損タイトルを検知し、 要約失敗マーカー付きで起票する。破損タイトルを要約へ伝播させない - 破損の理由とモデル名をログに残し、破損率を計測できるようにした Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: CodeRabbit 指摘対応(助詞連続の誤検知・README の条件・推論例外の握り) - particle_run が「のでは」「ものには」など正常な日本語を破損と誤検知していた。 誤検知するとトリアージ結果ごと破棄されるため、同一助詞の 3 連続に限定する - 公開リポジトリへの起票条件を README と実装で一致させる(reportType・ トリアージ成否・スパム疑い・対象カテゴリ・信頼度のすべてを明記) - env.AI.run の例外を試行ループ内で捕捉する。JSON Mode を満たせない場合や AI 側の一時障害で throw すると queue の再試行を使い切ってフィードバックが 消えるため、生成失敗として扱い「要約失敗」で起票して原文を残す なお AI_TRIAGE_MODEL の JSON Mode 非対応の指摘は、実機検証で反証済みのため モデルは変更しない(詳細は PR のレビュー返信を参照)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013z8ZwcVqMewc7fZx2gAzxF * fix: 信頼度を 0..1 に制限する モデルが "90"(パーセント表記のつもり)や負値を返した場合、componentConfidence が そのまま閾値判定を通過し、公開リポジトリへ内容を出すべきでないフィードバックが 起票されうる状態だった。 - TRIAGE_JSON_SCHEMA の confidence / componentConfidence に minimum: 0 と maximum: 1 を追加 - coerceReport でも範囲を検証し、範囲外は値を信用せず既定値へ倒す (componentConfidence は 0 = 公開起票しない、confidence は 0.5 = 従来の既定) 制約追加後も gemma-4 が JSON Mode を満たすことは実機で確認済み。 Co-Authored-By: Claude Opus…
* TypeSafeの判定精度を実測するトリアージスパイクを追加 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * シークレットのテンプレートにTYPESAFE_API_KEYを追加 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * トリアージ判定用のTYPESAFE_MODELをvarsに追加しスパイクから参照する Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * スパイクに不一致の内訳出力と生データの書き出しを追加 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 実測に基づき合成ロジックと質問定義を見直す - スパム判定: 放送の書き起こしを praise ゲートの外に出し、閾値を分離幅の中央へ - triageLevel: 加重和をやめ、カテゴリで分岐して bug にのみ severity を適用 - breadth: 正解レベルと相関しなかったため質問ごと削除 - component: 値の誤りと描画の誤りの境界を criteria で明示 - is_spam: 送信テストの投稿を criteria に追加 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 路線ロゴの出所を踏まえてcomponentのcriteriaを訂正 路線ロゴの画像は StationAPI ではなくアプリに同梱されたローカルアセット (MobileApp の src/lineSymbolImage.ts が路線 ID で require している)なので、 ロゴの取り違えは station_api ではなく mobile_app に分類する。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 感謝ゲートを固定値比較から大小比較に変える is_praise_only がちょうど 0.50 のとき、is_spam 0.96 の明確なスパムが praise < 0.5 の条件で弾かれていた。守りたいのは感謝の方がスパムらしさより 強い場合だけなので、両者の大小で判定する。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * トリアージ判定モジュールを切り出してスパイクと共有する 質問定義・閾値・合成ロジックを src/consumers/typesafeTriage.ts に移し、 計測スクリプトはそれを import する。別々に持つと、スパイクで測ったものと 本番で動くものが食い違う。 feedbackTriage.ts からの呼び出しはまだ行っていない。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * トリアージの判定をTypeSafeに切り替える Workers AI の担当をタイトルと要約の生成だけに絞り、スパム・カテゴリ・優先度・ 原因コンポーネントの判定は TypeSafe の型付き判定を使う。 few-shot は判定を含んだ完全な形で KV に保つ(計測の正解データを兼ねるため)が、 プロンプトへ渡す際にタイトルと要約だけへ射影する。例にだけ余分なフィールドが あるとモデルがそれを真似てスキーマと食い違う。 判定を取得できなかった場合は、生成したタイトルと要約を保ったまま原因を 絞り込めなかった扱いに倒す。componentConfidence が 0 になるため公開リポジトリ への起票は行われず、フィードバック自体も失われない。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 一時的なAPIエラーの再試行と引数検証を追加 CodeRabbit の指摘を原文とドキュメントで確認して対応した。 - 429 / 529 は指数バックオフで再試行する(retry-after があれば優先)。逐次実行 なので、後半で落ちるとそこまでの計測が無駄になる - 生データは 1 件ごとに書き出す。途中で失敗しても収集済みを失わない - --limit は正の整数のみ受理する。Number('foo') も 0 になり全件実行だった - --out は値の存在を確認する。`--out --json` が `--json` というファイルを作る 同じ再試行の欠落が本番側の judgeFeedback にもあったため、あわせて対応した。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 確認待ちの判定を引き継ぎ、バックオフが効かない条件を直す CodeRabbit の指摘をコードで確認して対応した。 - compose が立てた needsSpamReview を triageFeedback が捨てていた。 resolvePublicIssueRepo はこのフラグで公開リポジトリへの起票を止めるため、 落とすとスパム確定ではないが確認が必要なフィードバックが公開リポジトリに出る。 - retry-after ヘッダが無いと get() は null を返し、Number(null) は 0。 isFinite(0) が true のため待機時間が 0 になり、バックオフが効いていなかった。 いずれも回帰テストを追加した。 Refs #31 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 提案駅をTypeSafeのnoulで判定するリランクを追加する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * リランクをオフラインで実測するスパイクと評価セットを追加する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 判定できない候補を黙って除外せずnullを返すようにする レビュー指摘(Fable / CodeRabbit)への対応。案Yは200でも読める回答が0件のとき []を返し、案Xは一部のリクエストが失敗した候補を黙って落としていた。戻り値が 「完全な判定結果」として扱われるため、429の着順で提案が揺れる。全候補を判定 できたときだけ結果を返すようにし、nullを返す経路でもonUsageは呼ぶ。 あわせてエラー種別だけをログに出し、SyntaxError経由で応答本文が載るのを止める。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * スパイクの採点の抜けと引数検証を直す レビュー指摘(Fable / CodeRabbit)への対応。同名別レコードの確率を先頭1件しか 見ていなかったため分離幅が歪む点、候補0件の項目がexpectEmptyを無条件で正解に していた点、--limit/--shapeの不正値が黙って「全件・両案」に倒れて有料APIを 余計に叩く点を直す。分母から除いたexpectの件数も出す。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 同名駅が提案枠を食う問題を直し評価セットを実測に合わせる stationsByNameは同一物理駅を路線別レコード(別stationId・同一groupId)で返すため、 確率順に切ると枠が同じ駅で埋まる。実測では「海が見える駅」の上位5件が熱海の 4レコードと真鶴になり根府川と早川が押し出された(recall 14/19)。groupIdで畳んで 17/19。評価セットは--recordの実プールに合わせて9項目直した。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * リランクを対話ターンへ組み込み既定無効のまま切り替えられるようにする 本文を書く前に「提案してよい駅」をsystemメッセージで渡す形にした。replyが提案 集合に条件付けられるので本文と提案カードが食い違わず、判定できなかったときは 何も注入せず今の挙動(モデルが自分で選ぶ)へフォールバックできる。agentOutputSchema からsuggestionsを外すとこのフォールバックが消えるため残した。 有効化と閾値はconfig:remoteのagent_rerank_thresholdで、既定は無効。判定はprepareStep が複数回走ってもターンに1回だけ(都度判定すると最大3往復ぶんのレイテンシが乗る)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * リランク閾値に真偽値や配列が入ったときは無効に倒す Number()に直接かけていたためagent_rerank_threshold: trueが1として有効になり、 「とりあえずtrueで有効化」という書き方でほぼ全候補が棄却されて提案が静かに 消える。配列もNumber([0.7])で通っていた。数値と数値形式の文字列だけを通す。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* リランクの注記を user ロールで注入し Gemini でのターン失敗を直す Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * リランク注記の注入位置をツール結果の後ろとして検証する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* フィードバックから自動で修正PRを出すワークフローを追加 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * 共通アクションの参照をコミットSHAからv1タグに戻した v1が作成者の許可リストを含む406de87を指すようになったため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * トリアージ条件にP3も足した P3のBugはBugなので、直す場所が決まっている点ではP2のBugと変わらない。作るものから 決めるFeature RequestとImprovementはカテゴリの条件が弾く。件数も2026年で3件、 月0.3件なので費用の理由も立たない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * 外部actionをcommit SHAで固定した このジョブはcontents: writeとpull-requests: writeを持ち、secretも渡している。 可変タグのままだと、タグが差し替えられた時点でその内容がここで動く。SHAは git ls-remoteで確認している。あわせて月あたりの件数を6件弱から6件程度に直した。 54件を8.6か月で割ると6.25件で、弱ではない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * 共通アクションの参照もcommit SHAで固定した prepareにはISSUES_REPO_TOKENを渡していて、非公開の管理チケットを読める。外部の3つ より権限が大きいので、こちらこそタグではなくSHAで固定する。v1タグは今日1度動かして いるので、タグが動く前提も実際に成り立っている。版はコメントで残す。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * モデルを選んだ理由をコメントに書いた Sonnetのままにする。まず費用を見たいため。上げるかどうかは「直せない」と誤って 判断した件数で決める。その経路だけは人のレビューで止まらず、管理チケットに 「済み」の目印が残って手つかずのまま対応済みに見える。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw * 消えたv1タグを参照していたコメントを直した 共通アクションのv1タグは、作りかけの実装に打ってしまったため削除された。SHAは devの先頭のままなので動作は変わらないが、# v1 と書いたままだと存在しないタグを 指すことになる。マージ元のPR番号に差し替えた。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw --------- Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jUHQb3vwnKPNYL2Cm1mxw Co-authored-by: Claude <noreply@anthropic.com>
* 本文が空白のみのフィードバックをpostFeedbackで弾くようにする アプリ側の50文字下限を撤廃したため、空白のみを弾く条件はサーバでも持ちます。 文字数の下限は設けません。短い本文やクラッシュレポートの短いエラーメッセージは これまでどおり受け付けます。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018a5y38tYxDTfNPq95txKzf * フィードバック本文検証のテストでエラーコードと非文字列も固定する CallableError であることしか見ていなかったため、code を internal へ変えても テストが通っていました。code は HTTP ステータスへ直結するので、 invalid-argument であることまで確かめます。数値と null のケースも足して、 typeof の検証を欠落値以外でも固定します。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018a5y38tYxDTfNPq95txKzf --------- Co-authored-by: Claude <noreply@anthropic.com>
* 英語TTSでMineを「マイン」と読む誤読を修正する 英語 TTS が駅名・路線名の「Mine(みね)」を英単語 "mine" として 「まいん」と読むため、辞書語の連結 "Me-nay" に置換して /mi.neɪ/ に寄せる。 Keisei・Seibu と同様に単語境界で一致させ、別語の一部は置換しない。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 語中に埋まったmineも「みね」と読むよう置換する Takamine や Minezaki のように英名の語中・語頭に含まれる mine も 置換対象にし、前後に英字が続く場合はハイフンで区切って Me-nay を独立させる。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 宇都宮の嶺駅を想定したMine置換のテストを追加する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * テストコメントの駅名を宇都宮ライトレールの峰駅に訂正する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Essentials Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Warning
マージ前に確認してください。
AGENT_MODELがgoogle:gemini-3.8-flashからopenai:gpt-6-lunaに切り替わります。production のOPENAI_API_KEYが有効か確認してください。無効だと/agent/chatが毎回internalエラーになります。概要
devにマージした #44・#46・#47 をmaster(リリースブランチ)に反映します。変更内容
deadline-exceeded)になっていたため、AGENT_MODELをopenai:gpt-5.6-lunaに戻すAGENT_MODELをopenai:gpt-6-lunaに更新し、reasoningEffort: 'none'を付けるモデル判定に GPT-6 系を追加#44: 乗り換えが必要な駅の提案
stationsByNameの検索結果に、乗り換えれば行ける駅も含まれるという前提を、システムプロンプト・コンテキスト文・ツール説明に反映します。noticeの引き直し先を「同じ地域の別の駅」に変えました。#46・#47: 対話モデルの切り替え
wrangler.jsoncのAGENT_MODELを、dev・production の両方でopenai:gpt-6-lunaにします。本番ではgoogle:gemini-3.8-flashからの切り替えです(行き先相談エージェントのモデルをGemini 3.8 Flashからgpt-5.6-lunaへ戻して応答の期限切れを防ぐ #46 で gpt-5.6-luna に戻したものを、行き先相談エージェントのモデルをgpt-6-lunaへ更新する #47 で gpt-6-luna に上げています)。src/agent/llm.tsのOPENAI_REASONING_NONE_MODELSを/^gpt-(?:5\.[1-9]|6(?:[.-]|$))/に広げました。従来の/^gpt-5\.[1-9]/のままでは GPT-6 Luna にreasoningEffort: 'none'が付かず、既定の reasoning で応答が遅くなるためです。GPT-6 Luna がnoneに対応していることは OpenAI の API ドキュメント で確認しました。AGENT_MODELの例も更新しています。差分は全体で10ファイルです(94行追加、54行削除)。
コンフリクトの解消について
masterは squash merge でリリースしているため、devと履歴が分かれています。そこでorigin/devから切ったブランチにorigin/masterをマージしました。src/agent/prompt.test.tsとsrc/cli/typesafe-rerank-spike.tsで衝突しましたが、masterのツリーは #44 直前のdev(69917f7)と完全に同じだったため、dev側を採用しています。その後origin/devをマージして #46・#47 を取り込みました(衝突なし)。解消後のツリーはorigin/devと一致します。デプロイ
src/**とwrangler.jsoncを変更しているため、マージすると production への自動デプロイ(deploy_production.yml)が起動します。上の前提条件を満たしてからマージしてください。確認に使ったコマンド
すべて通過しました(321 tests passed)。
バインディング・シークレット等の変更
AGENT_MODELを変更(dev・production ともopenai:gpt-6-luna)。OPENAI_API_KEYを使い始めるため、有効なキーが設定済みである必要があります。🤖 Generated with Claude Code