From 66843cc95cdbc7e39902082e01693613f836d2ad Mon Sep 17 00:00:00 2001 From: wucm667 Date: Tue, 22 Sep 2026 19:07:04 +0800 Subject: [PATCH] fix(moderation): handle trailing Anthropic system messages --- .../internal/service/content_moderation_input.go | 12 +++++++++--- .../service/content_moderation_input_test.go | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/internal/service/content_moderation_input.go b/backend/internal/service/content_moderation_input.go index 013c10c13d..3886bfff8e 100644 --- a/backend/internal/service/content_moderation_input.go +++ b/backend/internal/service/content_moderation_input.go @@ -91,13 +91,19 @@ func (collector moderationTextCollector) collectLastAnthropicUserMessage(message if len(array) == 0 { return } - last := array[len(array)-1] - if strings.ToLower(strings.TrimSpace(last.Get("role").String())) != "user" { + // Anthropic requests may append a system message after the user turn. It + // does not represent a new model/tool turn, so retain the latest user + // content while continuing to reject assistant/tool-ended loops. + lastUser := len(array) - 1 + for lastUser >= 0 && strings.ToLower(strings.TrimSpace(array[lastUser].Get("role").String())) == "system" { + lastUser-- + } + if lastUser < 0 || strings.ToLower(strings.TrimSpace(array[lastUser].Get("role").String())) != "user" { return } var candidate []string var candidateImages []string - collector.collectAnthropicUserContentValue(last.Get("content"), &candidate, &candidateImages) + collector.collectAnthropicUserContentValue(array[lastUser].Get("content"), &candidate, &candidateImages) if normalizeContentModerationText(strings.Join(candidate, "\n")) == "" && len(candidateImages) == 0 { return } diff --git a/backend/internal/service/content_moderation_input_test.go b/backend/internal/service/content_moderation_input_test.go index d51dc21ba7..2ba8890524 100644 --- a/backend/internal/service/content_moderation_input_test.go +++ b/backend/internal/service/content_moderation_input_test.go @@ -50,6 +50,21 @@ func TestExtractContentModerationInput_AnthropicMultiTurnExtractsLatestUser(t *t require.Equal(t, "Q2", input.Text) } +func TestExtractContentModerationInput_AnthropicTrailingSystemExtractsLatestUser(t *testing.T) { + body := []byte(`{ + "messages": [ + {"role":"user","content":"Q1"}, + {"role":"assistant","content":"A1"}, + {"role":"user","content":"Q2"}, + {"role":"system","content":"request metadata"} + ] + }`) + + input := ExtractContentModerationInput(ContentModerationProtocolAnthropicMessages, body) + + require.Equal(t, "Q2", input.Text) +} + func TestExtractContentModerationInput_AnthropicStreamResendExtractsResend(t *testing.T) { body := []byte(`{ "messages": [