refactor: extract message conversion logic to toMessages method in VertexAI client

- Extract message conversion into dedicated `toMessages` helper method
- Add proper role handling for system, user, and assistant messages
- Prepend system content to first user message per Anthropic format
- Enforce user/assistant message alternation with placeholder messages
- Skip empty messages during conversion processing
- Concatenate multiple text blocks in response output
- Add validation for empty message arrays before sending
- Handle edge case when only system content is provided
This commit is contained in:
Kayvan Sylvan
2025-12-30 09:43:22 -08:00
parent 8ed2c7986f
commit 31a52f7191

View File

@@ -3,6 +3,7 @@ package vertexai
import (
"context"
"fmt"
"strings"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/vertex"
@@ -74,9 +75,9 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
}
// Convert chat messages to Anthropic format
anthropicMessages := make([]anthropic.MessageParam, len(msgs))
for i, msg := range msgs {
anthropicMessages[i] = anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content))
anthropicMessages := c.toMessages(msgs)
if len(anthropicMessages) == 0 {
return "", fmt.Errorf("no valid messages to send")
}
// Create the request
@@ -92,11 +93,18 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
}
// Extract text from response
if len(response.Content) > 0 {
return response.Content[0].Text, nil
var textParts []string
for _, block := range response.Content {
if block.Type == "text" && block.Text != "" {
textParts = append(textParts, block.Text)
}
}
return "", fmt.Errorf("no content in response")
if len(textParts) == 0 {
return "", fmt.Errorf("no content in response")
}
return strings.Join(textParts, ""), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) error {
@@ -109,9 +117,9 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
ctx := context.Background()
// Convert chat messages to Anthropic format
anthropicMessages := make([]anthropic.MessageParam, len(msgs))
for i, msg := range msgs {
anthropicMessages[i] = anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content))
anthropicMessages := c.toMessages(msgs)
if len(anthropicMessages) == 0 {
return fmt.Errorf("no valid messages to send")
}
// Create streaming request
@@ -133,6 +141,70 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
return stream.Err()
}
func (c *Client) toMessages(msgs []*chat.ChatCompletionMessage) []anthropic.MessageParam {
// Convert messages to Anthropic format with proper role handling
// - System messages become part of the first user message
// - Messages must alternate user/assistant
// - Skip empty messages
var anthropicMessages []anthropic.MessageParam
var systemContent string
isFirstUserMessage := true
lastRoleWasUser := false
for _, msg := range msgs {
if strings.TrimSpace(msg.Content) == "" {
continue // Skip empty messages
}
switch msg.Role {
case chat.ChatMessageRoleSystem:
// Accumulate system content to prepend to first user message
if systemContent != "" {
systemContent += "\\n" + msg.Content
} else {
systemContent = msg.Content
}
case chat.ChatMessageRoleUser:
userContent := msg.Content
if isFirstUserMessage && systemContent != "" {
userContent = systemContent + "\\n\\n" + userContent
isFirstUserMessage = false
}
if lastRoleWasUser {
// Enforce alternation: add a minimal assistant message
anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(anthropic.NewTextBlock("Okay.")))
}
anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewTextBlock(userContent)))
lastRoleWasUser = true
case chat.ChatMessageRoleAssistant:
// If first message is assistant and we have system content, prepend user message
if isFirstUserMessage && systemContent != "" {
anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewTextBlock(systemContent)))
lastRoleWasUser = true
isFirstUserMessage = false
} else if !lastRoleWasUser && len(anthropicMessages) > 0 {
// Enforce alternation: add a minimal user message
anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewTextBlock("Hi")))
lastRoleWasUser = true
}
anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)))
lastRoleWasUser = false
default:
// Other roles are ignored for Anthropic's message structure
continue
}
}
// If only system content was provided, create a user message with it
if len(anthropicMessages) == 0 && systemContent != "" {
anthropicMessages = append(anthropicMessages, anthropic.NewUserMessage(anthropic.NewTextBlock(systemContent)))
}
return anthropicMessages
}
func (c *Client) NeedsRawMode(modelName string) bool {
return false
}