Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/api/providers/__tests__/anthropic-vertex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,4 +809,96 @@ describe("VertexHandler", () => {
)
})
})

describe("reasoning message filtering", () => {
it("should filter out reasoning type messages before sending to API", async () => {
handler = new AnthropicVertexHandler({
apiModelId: "claude-3-5-sonnet-v2@20241022",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
})

// Include a reasoning type message that should be filtered out
const messagesWithReasoning = [
{
role: "user",
content: "Hello",
} as Anthropic.Messages.MessageParam,
{
type: "reasoning",
encrypted_content: "encrypted_payload",
summary: [],
} as any, // This should be filtered out
{
role: "assistant",
content: "Hi there!",
} as Anthropic.Messages.MessageParam,
]

const mockStream = [
{
type: "message_start",
message: {
usage: {
input_tokens: 10,
output_tokens: 0,
},
},
},
{
type: "content_block_start",
index: 0,
content_block: {
type: "text",
text: "Response",
},
},
]

const asyncIterator = {
async *[Symbol.asyncIterator]() {
for (const chunk of mockStream) {
yield chunk
}
},
}

const mockCreate = vitest.fn().mockResolvedValue(asyncIterator)
;(handler["client"].messages as any).create = mockCreate

const stream = handler.createMessage("System prompt", messagesWithReasoning)
const chunks: ApiStreamChunk[] = []

for await (const chunk of stream) {
chunks.push(chunk)
}

// Verify that the reasoning message was filtered out
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Hello",
cache_control: { type: "ephemeral" },
},
],
},
{
role: "assistant",
content: "Hi there!",
},
],
}),
)

// Verify the reasoning message was not included
const calledMessages = mockCreate.mock.calls[0][0].messages
expect(calledMessages).toHaveLength(2)
expect(calledMessages.every((msg: any) => msg.type !== "reasoning")).toBe(true)
})
})
})
16 changes: 15 additions & 1 deletion src/api/providers/anthropic-vertex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
reasoning: thinking,
} = this.getModel()

// Filter out reasoning type messages that are not valid for Vertex AI API
// The message list can include provider-specific meta entries such as
// `{ type: "reasoning", ... }` that are intended only for providers like
// openai-native. Vertex AI doesn't recognize these and will return an error.
type ReasoningMetaLike = { type?: string }

const filteredMessages = messages.filter((message): message is Anthropic.Messages.MessageParam => {
const meta = message as ReasoningMetaLike
if (meta.type === "reasoning") {
return false
}
return true
})

/**
* Vertex API has specific limitations for prompt caching:
* 1. Maximum of 4 blocks can have cache_control
Expand All @@ -92,7 +106,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
system: supportsPromptCache
? [{ text: systemPrompt, type: "text" as const, cache_control: { type: "ephemeral" } }]
: systemPrompt,
messages: supportsPromptCache ? addCacheBreakpoints(messages) : messages,
messages: supportsPromptCache ? addCacheBreakpoints(filteredMessages) : filteredMessages,
stream: true,
}

Expand Down
Loading