Spaces:
Running
Running
| import { describe, test, expect } from "bun:test" | |
| import { z } from "zod" | |
| import type { AnthropicMessagesPayload } from "~/routes/messages/anthropic-types" | |
| import { translateToOpenAI } from "../src/routes/messages/non-stream-translation" | |
| // Zod schema for a single message in the chat completion request. | |
| const messageSchema = z.object({ | |
| role: z.enum([ | |
| "system", | |
| "user", | |
| "assistant", | |
| "tool", | |
| "function", | |
| "developer", | |
| ]), | |
| content: z.union([z.string(), z.object({}), z.array(z.any())]), | |
| name: z.string().optional(), | |
| tool_calls: z.array(z.any()).optional(), | |
| tool_call_id: z.string().optional(), | |
| }) | |
| // Zod schema for the entire chat completion request payload. | |
| // This is derived from the openapi.documented.yml specification. | |
| const chatCompletionRequestSchema = z.object({ | |
| messages: z.array(messageSchema).min(1, "Messages array cannot be empty."), | |
| model: z.string(), | |
| frequency_penalty: z.number().min(-2).max(2).optional().nullable(), | |
| logit_bias: z.record(z.string(), z.number()).optional().nullable(), | |
| logprobs: z.boolean().optional().nullable(), | |
| top_logprobs: z.number().int().min(0).max(20).optional().nullable(), | |
| max_tokens: z.number().int().optional().nullable(), | |
| n: z.number().int().min(1).max(128).optional().nullable(), | |
| presence_penalty: z.number().min(-2).max(2).optional().nullable(), | |
| response_format: z | |
| .object({ | |
| type: z.enum(["text", "json_object", "json_schema"]), | |
| json_schema: z.object({}).optional(), | |
| }) | |
| .optional(), | |
| seed: z.number().int().optional().nullable(), | |
| stop: z | |
| .union([z.string(), z.array(z.string())]) | |
| .optional() | |
| .nullable(), | |
| stream: z.boolean().optional().nullable(), | |
| temperature: z.number().min(0).max(2).optional().nullable(), | |
| top_p: z.number().min(0).max(1).optional().nullable(), | |
| tools: z.array(z.any()).optional(), | |
| tool_choice: z.union([z.string(), z.object({})]).optional(), | |
| user: z.string().optional(), | |
| }) | |
| /** | |
| * Validates if a request payload conforms to the OpenAI Chat Completion v1 shape using Zod. | |
| * @param payload The request payload to validate. | |
| * @returns True if the payload is valid, false otherwise. | |
| */ | |
| function isValidChatCompletionRequest(payload: unknown): boolean { | |
| const result = chatCompletionRequestSchema.safeParse(payload) | |
| return result.success | |
| } | |
| describe("Anthropic to OpenAI translation logic", () => { | |
| test("should translate minimal Anthropic payload to valid OpenAI payload", () => { | |
| const anthropicPayload: AnthropicMessagesPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user", content: "Hello!" }], | |
| max_tokens: 0, | |
| } | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) | |
| }) | |
| test("should translate comprehensive Anthropic payload to valid OpenAI payload", () => { | |
| const anthropicPayload: AnthropicMessagesPayload = { | |
| model: "gpt-4o", | |
| system: "You are a helpful assistant.", | |
| messages: [ | |
| { role: "user", content: "What is the weather like in Boston?" }, | |
| { | |
| role: "assistant", | |
| content: "The weather in Boston is sunny and 75°F.", | |
| }, | |
| ], | |
| temperature: 0.7, | |
| max_tokens: 150, | |
| top_p: 1, | |
| stream: false, | |
| metadata: { user_id: "user-123" }, | |
| tools: [ | |
| { | |
| name: "getWeather", | |
| description: "Gets weather info", | |
| input_schema: { location: { type: "string" } }, | |
| }, | |
| ], | |
| tool_choice: { type: "auto" }, | |
| } | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) | |
| }) | |
| test("should handle missing fields gracefully", () => { | |
| const anthropicPayload: AnthropicMessagesPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user", content: "Hello!" }], | |
| max_tokens: 0, | |
| } | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) | |
| }) | |
| test("should handle invalid types in Anthropic payload", () => { | |
| const anthropicPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user", content: "Hello!" }], | |
| temperature: "hot", // Should be a number | |
| } | |
| // @ts-expect-error intended to be invalid | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| // Should fail validation | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(false) | |
| }) | |
| test("should handle thinking blocks in assistant messages", () => { | |
| const anthropicPayload: AnthropicMessagesPayload = { | |
| model: "claude-3-5-sonnet-20241022", | |
| messages: [ | |
| { role: "user", content: "What is 2+2?" }, | |
| { | |
| role: "assistant", | |
| content: [ | |
| { | |
| type: "thinking", | |
| thinking: "Let me think about this simple math problem...", | |
| }, | |
| { type: "text", text: "2+2 equals 4." }, | |
| ], | |
| }, | |
| ], | |
| max_tokens: 100, | |
| } | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) | |
| // Check that thinking content is combined with text content | |
| const assistantMessage = openAIPayload.messages.find( | |
| (m) => m.role === "assistant", | |
| ) | |
| expect(assistantMessage?.content).toContain( | |
| "Let me think about this simple math problem...", | |
| ) | |
| expect(assistantMessage?.content).toContain("2+2 equals 4.") | |
| }) | |
| test("should handle thinking blocks with tool calls", () => { | |
| const anthropicPayload: AnthropicMessagesPayload = { | |
| model: "claude-3-5-sonnet-20241022", | |
| messages: [ | |
| { role: "user", content: "What's the weather?" }, | |
| { | |
| role: "assistant", | |
| content: [ | |
| { | |
| type: "thinking", | |
| thinking: | |
| "I need to call the weather API to get current weather information.", | |
| }, | |
| { type: "text", text: "I'll check the weather for you." }, | |
| { | |
| type: "tool_use", | |
| id: "call_123", | |
| name: "get_weather", | |
| input: { location: "New York" }, | |
| }, | |
| ], | |
| }, | |
| ], | |
| max_tokens: 100, | |
| } | |
| const openAIPayload = translateToOpenAI(anthropicPayload) | |
| expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) | |
| // Check that thinking content is included in the message content | |
| const assistantMessage = openAIPayload.messages.find( | |
| (m) => m.role === "assistant", | |
| ) | |
| expect(assistantMessage?.content).toContain( | |
| "I need to call the weather API", | |
| ) | |
| expect(assistantMessage?.content).toContain( | |
| "I'll check the weather for you.", | |
| ) | |
| expect(assistantMessage?.tool_calls).toHaveLength(1) | |
| expect(assistantMessage?.tool_calls?.[0].function.name).toBe("get_weather") | |
| }) | |
| }) | |
| describe("OpenAI Chat Completion v1 Request Payload Validation with Zod", () => { | |
| test("should return true for a minimal valid request payload", () => { | |
| const validPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user", content: "Hello!" }], | |
| } | |
| expect(isValidChatCompletionRequest(validPayload)).toBe(true) | |
| }) | |
| test("should return true for a comprehensive valid request payload", () => { | |
| const validPayload = { | |
| model: "gpt-4o", | |
| messages: [ | |
| { role: "system", content: "You are a helpful assistant." }, | |
| { role: "user", content: "What is the weather like in Boston?" }, | |
| ], | |
| temperature: 0.7, | |
| max_tokens: 150, | |
| top_p: 1, | |
| frequency_penalty: 0, | |
| presence_penalty: 0, | |
| stream: false, | |
| n: 1, | |
| } | |
| expect(isValidChatCompletionRequest(validPayload)).toBe(true) | |
| }) | |
| test('should return false if the "model" field is missing', () => { | |
| const invalidPayload = { | |
| messages: [{ role: "user", content: "Hello!" }], | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if the "messages" field is missing', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if the "messages" array is empty', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: [], | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if "model" is not a string', () => { | |
| const invalidPayload = { | |
| model: 12345, | |
| messages: [{ role: "user", content: "Hello!" }], | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if "messages" is not an array', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: { role: "user", content: "Hello!" }, | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if a message in the "messages" array is missing a "role"', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: [{ content: "Hello!" }], | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test('should return false if a message in the "messages" array is missing "content"', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user" }], | |
| } | |
| // Note: Zod considers 'undefined' as missing, so this will fail as expected. | |
| const result = chatCompletionRequestSchema.safeParse(invalidPayload) | |
| expect(result.success).toBe(false) | |
| }) | |
| test('should return false if a message has an invalid "role"', () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "customer", content: "Hello!" }], | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test("should return false if an optional field has an incorrect type", () => { | |
| const invalidPayload = { | |
| model: "gpt-4o", | |
| messages: [{ role: "user", content: "Hello!" }], | |
| temperature: "hot", // Should be a number | |
| } | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test("should return false for a completely empty object", () => { | |
| const invalidPayload = {} | |
| expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) | |
| }) | |
| test("should return false for null or non-object payloads", () => { | |
| expect(isValidChatCompletionRequest(null)).toBe(false) | |
| expect(isValidChatCompletionRequest(undefined)).toBe(false) | |
| expect(isValidChatCompletionRequest("a string")).toBe(false) | |
| expect(isValidChatCompletionRequest(123)).toBe(false) | |
| }) | |
| }) | |