Skip to content

Converse 呼び出しで ValidationException が継続的に発生(同名 document / 添付5件超 / 空テキストブロック) #1685

Description

@UramotoKazushi

概要

#1671 / #1663 / #1399 に PR(#1677 / #1678 / #1679)を出していただき、ありがとうございます。

#1672 で「添付が失敗したときに理由が利用者に伝わらない」ことを報告しましたが、そのうち何件かは送信前に検知できそうなものに見えたので、社内ログを追った結果を共有します。エラー文言の細分化(#1672)とは別に、こちらは送る前に減らせないか、というご相談です。

自社環境の本番 Lambda ログ、直近 60 日の件数です。特定日のバーストではなく、ほぼ毎営業日発生しています(#1672 は 7 日分の集計なので期間が異なります)。

件数 発生日数 Bedrock が返しているエラー
824 53 日 Messages can't contain duplicate document names.
359 38 日 text content blocks must be non-empty (313) / must contain non-whitespace text (46)
250 36 日 You can't include more than 5 documents in a request.

確認バージョン: main (b956b65a)

3 件とも送信前の検証という点で共通しているため 1 本にまとめましたが、分けた方が扱いやすければ Issue を分割します。

1. 同名 document(824 件 / 53 日)

#1143 / #1250(いずれも PR #1319 で close)で「日本語の別名ファイルが同じ名前に潰れる」問題が修正され、新設された fileNameUtils.tsconvertToSafeFilename にハッシュ接尾辞が入りました。今回のログを見ると、そこで拾いきれていないケースが残っているようです。

  • (a) ハッシュが付くのは置換が発生したときだけなので、ASCII のみの名前には付きません
  • (b) lastDotIndex により拡張子が落ちます。テストにも同じ挙動が入っています(packages/cdk/test/lambda/utils/fileNameUtils.test.ts:5-8convertToSafeFilename('document.pdf') の期待値が 'document'

そのため、日本語名でなくても衝突します。

再現例: 同一の会話で report.pdf を添付し、別のターンで report.docx を添付する。どちらも document 名が report になり、以後その会話が続けられなくなります。

document 名を一意にしている箇所は見当たりませんでした。関係しそうなのは次のあたりです。

  • packages/cdk/lambda/utils/models.ts:475
  • packages/cdk/lambda/utils/bedrockAgentApi.ts:158(全メッセージの extraData を横断して files に集約しており、同様に一意化なし)
  • packages/web/src/utils/strandsUtils.ts:259 — こちらは PR 🐛 fix(duplicate-ja-doc-bug): centralize filename sanitization logic #1319 の対象外だったため 'X' 置換のままです(🐛 fix(duplicate-ja-doc-bug): centralize filename sanitization logic #1319 の変更ファイルは bedrockAgentApi.ts / fileNameUtils.ts / models.ts とそのテストの 4 つ)。ハッシュ接尾辞が無いので、日本語名は文字数が同じだと同じ名前に潰れます。AgentCore 経路で実際にどの程度起きているかは未確認です
  • strandsUtils.ts:102name: data.name で、置換処理を通していません

この 2 点は全角スペース・タブ・連続空白とは別の条件なので、PR #1677(レビュー中)とは切り分けて見ていただく形になるかと思います。

: content block を組み立てる際に document 名を確認し、同一内容の再添付は 1 つにまとめ、別内容の同名には接尾辞を付ける形が考えられます。拡張子を許可文字の範囲で名前に含める(例 report (pdf))と report.pdf / report.docx の衝突も避けられそうです。DocumentBlock.name の許可文字は英数字・空白・ハイフン・丸括弧・角括弧なので、ピリオドはそのままでは使えないかと思います。

2. 空テキストブロック(359 件 / 38 日)

models.ts:505 で text ブロックが無条件に push されています。

contentBlocks.push({ text: message.content });

message.content が空文字・空白のみのときもそのまま送るため、Bedrock 側で弾かれています。predictStream.ts にも検証は見当たりませんでした。

1 点補足で、Message リファレンスに「document を含むなら text ブロックも必ず含める」とあるので、「空なら落とす」方向だと document 同伴時に別のエラーになってしまいそうです。空でない text を必ず 1 つ入れる形が安全かと思います。

送信ボタン(InputChatContent.tsx:116)は既に空をガードしているので、発生源は別のようです。いずれも通常操作では踏みにくい経路ですが、次の入り口から content: '' が入るように見えました。

  • ChatMessage.tsx:320-326 メッセージ編集で本文を全消しして確定すると、そのまま content: '' で送信されます
  • UseCaseBuilderView.tsx:402disabledExecprops.isLoading || loadingerrorMessages.length > 0 の 2 条件なので、プロンプトが空でも実行に進みます
  • useChat.ts:758-760stopReason === 'error' のときだけ throw するため、:648 で break する forcedStop(停止ボタン)や max_tokens は通過します。1 文字も出力される前に停止すると content: '' のまま保存されるように見えます
  • retry 時は content が ' ' になるため、must contain non-whitespace text(46 件)はこちらに対応していそうです
  • ChatPage.tsx:440updateSystemContext(params.systemContext ?? '') から空文字が入ると、models.ts:446[{ text: '' }] を作ります。クエリパラメータ経路の ChatPage.tsx:265 と同じガードを入れれば揃いそうです

空のまま DDB に保存されると restore() で毎回復活するため、そのスレッドは以後の送信が通らなくなります(#1396 と似た状態です)。

:

  • models.ts:505:446 にバックストップを置く(落とすのではなく、空ならプレースホルダで埋める)
  • useChat.ts:758-760 の判定を広げ、本文 0 文字の assistant メッセージは保存前に除く
  • ChatMessage.tsx:320-326UseCaseBuilderView.tsx:402 に空チェックを足す

なお strandsUtils.ts:29-31 に同様のチェックがあるので、実装の参考になりそうです(ただしこちらは document 同伴時の text 必須までは見ていないため、そのまま揃えるのは避けた方がよさそうです)。

3. 添付 5 件超(250 件 / 36 日)

useFiles.tsvalidateUploadedFiles は毎回 let fileCount = 0 から数え直し、引数も現在アップロード中のファイルのみです。ChatPage.tsx:283onSend が送信ごとに clearFiles():287)を呼ぶため、カウンタはメッセージごとにリセットされます。

一方で過去ターンの添付は再送されているようです。useChat.ts:566 で履歴全体が渡り、同 :359base64Cache から base64 を復元しています。base64CacheChatMessage.tsx:82 が過去メッセージの添付を表示する際に埋めるため、リロード後の会話でも再送されます。その結果、添付が累積して上限に当たっているのではないかと思います。

画像も同じ形です(ChatPage.tsx:50maxImageFileCount: 20 もメッセージ単位)。

: validateUploadedFiles に会話内の既存添付数を渡し、新規分と合算して判定する。maxImageFileCount も同様かと思います。

補足

なお 1 と 3 について、AWS の Message リファレンス は「document は 5 件まで」「document を含むなら text ブロックも必ず含める」を Message オブジェクト単位で記述しており、リクエスト全体という明記は見つけられませんでした(document 名の一意性は DocumentBlock にも記述がありません)。本文で「会話履歴全体で効いているように見える」と書いた箇所は、エラー文言が in a request であることと発生状況からの推測です。認識違いがあれば教えていただけると助かります。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions