概要
#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.ts の convertToSafeFilename にハッシュ接尾辞が入りました。今回のログを見ると、そこで拾いきれていないケースが残っているようです。
- (a) ハッシュが付くのは置換が発生したときだけなので、ASCII のみの名前には付きません
- (b)
lastDotIndex により拡張子が落ちます。テストにも同じ挙動が入っています(packages/cdk/test/lambda/utils/fileNameUtils.test.ts:5-8、convertToSafeFilename('document.pdf') の期待値が 'document')
そのため、日本語名でなくても衝突します。
再現例: 同一の会話で report.pdf を添付し、別のターンで report.docx を添付する。どちらも document 名が report になり、以後その会話が続けられなくなります。
document 名を一意にしている箇所は見当たりませんでした。関係しそうなのは次のあたりです。
この 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:402 の disabledExec は props.isLoading || loading と errorMessages.length > 0 の 2 条件なので、プロンプトが空でも実行に進みます
useChat.ts:758-760 は stopReason === 'error' のときだけ throw するため、:648 で break する forcedStop(停止ボタン)や max_tokens は通過します。1 文字も出力される前に停止すると content: '' のまま保存されるように見えます
- retry 時は content が
' ' になるため、must contain non-whitespace text(46 件)はこちらに対応していそうです
ChatPage.tsx:440 の updateSystemContext(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-326 と UseCaseBuilderView.tsx:402 に空チェックを足す
なお strandsUtils.ts:29-31 に同様のチェックがあるので、実装の参考になりそうです(ただしこちらは document 同伴時の text 必須までは見ていないため、そのまま揃えるのは避けた方がよさそうです)。
3. 添付 5 件超(250 件 / 36 日)
useFiles.ts の validateUploadedFiles は毎回 let fileCount = 0 から数え直し、引数も現在アップロード中のファイルのみです。ChatPage.tsx:283 の onSend が送信ごとに clearFiles()(:287)を呼ぶため、カウンタはメッセージごとにリセットされます。
一方で過去ターンの添付は再送されているようです。useChat.ts:566 で履歴全体が渡り、同 :359 で base64Cache から base64 を復元しています。base64Cache は ChatMessage.tsx:82 が過去メッセージの添付を表示する際に埋めるため、リロード後の会話でも再送されます。その結果、添付が累積して上限に当たっているのではないかと思います。
画像も同じ形です(ChatPage.tsx:50 の maxImageFileCount: 20 もメッセージ単位)。
案: validateUploadedFiles に会話内の既存添付数を渡し、新規分と合算して判定する。maxImageFileCount も同様かと思います。
補足
なお 1 と 3 について、AWS の Message リファレンス は「document は 5 件まで」「document を含むなら text ブロックも必ず含める」を Message オブジェクト単位で記述しており、リクエスト全体という明記は見つけられませんでした(document 名の一意性は DocumentBlock にも記述がありません)。本文で「会話履歴全体で効いているように見える」と書いた箇所は、エラー文言が in a request であることと発生状況からの推測です。認識違いがあれば教えていただけると助かります。
概要
#1671 / #1663 / #1399 に PR(#1677 / #1678 / #1679)を出していただき、ありがとうございます。
#1672 で「添付が失敗したときに理由が利用者に伝わらない」ことを報告しましたが、そのうち何件かは送信前に検知できそうなものに見えたので、社内ログを追った結果を共有します。エラー文言の細分化(#1672)とは別に、こちらは送る前に減らせないか、というご相談です。
自社環境の本番 Lambda ログ、直近 60 日の件数です。特定日のバーストではなく、ほぼ毎営業日発生しています(#1672 は 7 日分の集計なので期間が異なります)。
Messages can't contain duplicate document names.text content blocks must be non-empty(313) /must contain non-whitespace text(46)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.tsのconvertToSafeFilenameにハッシュ接尾辞が入りました。今回のログを見ると、そこで拾いきれていないケースが残っているようです。lastDotIndexにより拡張子が落ちます。テストにも同じ挙動が入っています(packages/cdk/test/lambda/utils/fileNameUtils.test.ts:5-8、convertToSafeFilename('document.pdf')の期待値が'document')そのため、日本語名でなくても衝突します。
再現例: 同一の会話で
report.pdfを添付し、別のターンでreport.docxを添付する。どちらも document 名がreportになり、以後その会話が続けられなくなります。document 名を一意にしている箇所は見当たりませんでした。関係しそうなのは次のあたりです。
packages/cdk/lambda/utils/models.ts:475packages/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:102はname: 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 されています。message.contentが空文字・空白のみのときもそのまま送るため、Bedrock 側で弾かれています。predictStream.tsにも検証は見当たりませんでした。1 点補足で、Message リファレンスに「document を含むなら text ブロックも必ず含める」とあるので、「空なら落とす」方向だと document 同伴時に別のエラーになってしまいそうです。空でない text を必ず 1 つ入れる形が安全かと思います。
送信ボタン(
InputChatContent.tsx:116)は既に空をガードしているので、発生源は別のようです。いずれも通常操作では踏みにくい経路ですが、次の入り口からcontent: ''が入るように見えました。ChatMessage.tsx:320-326メッセージ編集で本文を全消しして確定すると、そのままcontent: ''で送信されますUseCaseBuilderView.tsx:402のdisabledExecはprops.isLoading || loadingとerrorMessages.length > 0の 2 条件なので、プロンプトが空でも実行に進みますuseChat.ts:758-760はstopReason === 'error'のときだけ throw するため、:648で break するforcedStop(停止ボタン)やmax_tokensは通過します。1 文字も出力される前に停止するとcontent: ''のまま保存されるように見えます' 'になるため、must contain non-whitespace text(46 件)はこちらに対応していそうですChatPage.tsx:440のupdateSystemContext(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-326とUseCaseBuilderView.tsx:402に空チェックを足すなお
strandsUtils.ts:29-31に同様のチェックがあるので、実装の参考になりそうです(ただしこちらは document 同伴時の text 必須までは見ていないため、そのまま揃えるのは避けた方がよさそうです)。3. 添付 5 件超(250 件 / 36 日)
useFiles.tsのvalidateUploadedFilesは毎回let fileCount = 0から数え直し、引数も現在アップロード中のファイルのみです。ChatPage.tsx:283のonSendが送信ごとにclearFiles()(:287)を呼ぶため、カウンタはメッセージごとにリセットされます。一方で過去ターンの添付は再送されているようです。
useChat.ts:566で履歴全体が渡り、同:359でbase64Cacheから base64 を復元しています。base64CacheはChatMessage.tsx:82が過去メッセージの添付を表示する際に埋めるため、リロード後の会話でも再送されます。その結果、添付が累積して上限に当たっているのではないかと思います。画像も同じ形です(
ChatPage.tsx:50のmaxImageFileCount: 20もメッセージ単位)。案:
validateUploadedFilesに会話内の既存添付数を渡し、新規分と合算して判定する。maxImageFileCountも同様かと思います。補足
なお 1 と 3 について、AWS の Message リファレンス は「document は 5 件まで」「document を含むなら text ブロックも必ず含める」を Message オブジェクト単位で記述しており、リクエスト全体という明記は見つけられませんでした(document 名の一意性は DocumentBlock にも記述がありません)。本文で「会話履歴全体で効いているように見える」と書いた箇所は、エラー文言が
in a requestであることと発生状況からの推測です。認識違いがあれば教えていただけると助かります。