feat(providers/bedrock): prefix user content with sender name
Bedrock's Converse API multiplexes Claude, Llama, Titan, Mistral and others through a single content-block protocol. None of those families expose a per-message author identity in a unified way, so prefixing in the text content block keeps multi-user attribution consistent regardless of which underlying model the request lands on. buildUserContent now passes msg through messageutil.ApplyUserNamePrefix before constructing the text ContentBlockMemberText. Tool result paths go through a separate code path (toolResultBlock) and are unaffected. Image media handling is unchanged. Tests: - buildUserContent with Name set produces the prefixed text block - buildUserContent without Name preserves byte-identical behavior - existing image and base64 cases continue to pass The Bedrock build is gated behind //go:build bedrock so this path is only exercised in builds that opt in via that tag. Refs #2702.
This commit is contained in:
parent
cd4372368e
commit
5b0634286c
2 changed files with 45 additions and 2 deletions
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
|
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/providers/common"
|
"github.com/sipeed/picoclaw/pkg/providers/common"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/providers/messageutil"
|
||||||
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
|
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -319,13 +320,19 @@ func convertMessages(messages []Message) ([]types.Message, []types.SystemContent
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildUserContent builds Bedrock content blocks for a user message.
|
// buildUserContent builds Bedrock content blocks for a user message.
|
||||||
|
//
|
||||||
|
// Sender attribution from msg.Name is rendered as a `[name] ` prefix on
|
||||||
|
// the text block. Bedrock's Converse API has no per-message author identity
|
||||||
|
// shared across all model families, so prefixing keeps the multi-user
|
||||||
|
// attribution behavior consistent with the Anthropic adapter regardless
|
||||||
|
// of which underlying model the request lands on.
|
||||||
func buildUserContent(msg Message) []types.ContentBlock {
|
func buildUserContent(msg Message) []types.ContentBlock {
|
||||||
var content []types.ContentBlock
|
var content []types.ContentBlock
|
||||||
|
|
||||||
// Add text content
|
// Add text content
|
||||||
if msg.Content != "" {
|
if textValue := messageutil.ApplyUserNamePrefix(msg); textValue != "" {
|
||||||
content = append(content, &types.ContentBlockMemberText{
|
content = append(content, &types.ContentBlockMemberText{
|
||||||
Value: msg.Content,
|
Value: textValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,42 @@ func TestBuildUserContent_SkipsNonBase64Data(t *testing.T) {
|
||||||
assert.Len(t, content, 1)
|
assert.Len(t, content, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBuildUserContent_UserNamePrefixed verifies that sender attribution
|
||||||
|
// from Message.Name is rendered as a `[name] ` prefix on the Bedrock text
|
||||||
|
// block. Bedrock's Converse API has no per-message author identity shared
|
||||||
|
// across model families, so prefixing keeps multi-user disambiguation
|
||||||
|
// consistent regardless of the underlying model (Claude, Llama, etc.).
|
||||||
|
func TestBuildUserContent_UserNamePrefixed(t *testing.T) {
|
||||||
|
msg := Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: "My name is Alice",
|
||||||
|
Name: "U_alice",
|
||||||
|
}
|
||||||
|
|
||||||
|
content := buildUserContent(msg)
|
||||||
|
|
||||||
|
assert.Len(t, content, 1)
|
||||||
|
textBlock, ok := content[0].(*types.ContentBlockMemberText)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "[U_alice] My name is Alice", textBlock.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildUserContent_NoNameNoPrefix protects backward compatibility for
|
||||||
|
// direct (single-user) channels: no sender, no prefix.
|
||||||
|
func TestBuildUserContent_NoNameNoPrefix(t *testing.T) {
|
||||||
|
msg := Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: "Hello",
|
||||||
|
}
|
||||||
|
|
||||||
|
content := buildUserContent(msg)
|
||||||
|
|
||||||
|
assert.Len(t, content, 1)
|
||||||
|
textBlock, ok := content[0].(*types.ContentBlockMemberText)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "Hello", textBlock.Value)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildAssistantContent_SkipsEmptyToolName(t *testing.T) {
|
func TestBuildAssistantContent_SkipsEmptyToolName(t *testing.T) {
|
||||||
msg := Message{
|
msg := Message{
|
||||||
Content: "Response",
|
Content: "Response",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue