| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- package service
- import (
- "encoding/json"
- "testing"
- "github.com/stretchr/testify/require"
- )
- func TestGeminiResponseToChatCompletionsPreservesInlineData(t *testing.T) {
- tests := []struct {
- name string
- parts []any
- want string
- }{
- {
- name: "image only",
- parts: []any{
- map[string]any{"inlineData": map[string]any{"mimeType": "image/png", "data": "aW1hZ2U="}},
- },
- want: "",
- },
- {
- name: "text and image",
- parts: []any{
- map[string]any{"text": "rendered image:\n"},
- map[string]any{"inlineData": map[string]any{"mimeType": "image/webp", "data": "d2VicA=="}},
- },
- want: "rendered image:\n",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- geminiResp := map[string]any{
- "candidates": []any{map[string]any{
- "content": map[string]any{"parts": tt.parts},
- "finishReason": "STOP",
- }},
- }
- rawData, err := json.Marshal(geminiResp)
- require.NoError(t, err)
- got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
- require.NoError(t, err)
- require.Len(t, got.Choices, 1)
- var content string
- require.NoError(t, json.Unmarshal(got.Choices[0].Message.Content, &content))
- require.Equal(t, tt.want, content)
- require.Equal(t, "stop", got.Choices[0].FinishReason)
- })
- }
- }
- func TestGeminiResponseToChatCompletionsOmitsInvalidInlineData(t *testing.T) {
- tests := []struct {
- name string
- inlineData map[string]any
- }{
- {
- name: "unsupported MIME type",
- inlineData: map[string]any{"mimeType": "image/svg+xml", "data": "PHN2Zz48L3N2Zz4="},
- },
- {
- name: "malformed MIME type",
- inlineData: map[string]any{"mimeType": "image/png; charset=utf-8", "data": "aW1hZ2U="},
- },
- {
- name: "malformed base64",
- inlineData: map[string]any{"mimeType": "image/png", "data": "not-valid-base64!!!"},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- geminiResp := map[string]any{
- "candidates": []any{map[string]any{
- "content": map[string]any{"parts": []any{map[string]any{"text": "before"}, map[string]any{"inlineData": tt.inlineData}, map[string]any{"text": "after"}}},
- "finishReason": "STOP",
- }},
- }
- rawData, err := json.Marshal(geminiResp)
- require.NoError(t, err)
- got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
- require.NoError(t, err)
- var content string
- require.NoError(t, json.Unmarshal(got.Choices[0].Message.Content, &content))
- require.Equal(t, "beforeafter", content)
- })
- }
- }
- func TestConvertGeminiToClaudeMessageOmitsInlineDataForAnthropicMessages(t *testing.T) {
- geminiResp := map[string]any{
- "candidates": []any{map[string]any{
- "content": map[string]any{"parts": []any{
- map[string]any{"text": "before"},
- map[string]any{"inlineData": map[string]any{"mimeType": "image/png", "data": "aW1hZ2U="}},
- map[string]any{"functionCall": map[string]any{"name": "get_weather", "args": map[string]any{"city": "Paris"}}},
- map[string]any{"text": "after"},
- }},
- "finishReason": "STOP",
- }},
- }
- rawData, err := json.Marshal(geminiResp)
- require.NoError(t, err)
- withInlineData, _ := convertGeminiToClaudeMessage(geminiResp, "gemini-test", rawData, true)
- contentWithInlineData, ok := withInlineData["content"].([]any)
- require.True(t, ok)
- require.Len(t, contentWithInlineData, 4)
- require.Equal(t, map[string]any{"type": "text", "text": "before"}, contentWithInlineData[0])
- require.Equal(t, map[string]any{"type": "text", "text": ""}, contentWithInlineData[1])
- toolUse, ok := contentWithInlineData[2].(map[string]any)
- require.True(t, ok)
- require.Equal(t, "tool_use", toolUse["type"])
- require.Equal(t, "get_weather", toolUse["name"])
- require.Equal(t, map[string]any{"type": "text", "text": "after"}, contentWithInlineData[3])
- withoutInlineData, _ := convertGeminiToClaudeMessage(geminiResp, "gemini-test", rawData, false)
- contentWithoutInlineData, ok := withoutInlineData["content"].([]any)
- require.True(t, ok)
- require.Len(t, contentWithoutInlineData, 3)
- require.Equal(t, map[string]any{"type": "text", "text": "before"}, contentWithoutInlineData[0])
- toolUseWithoutInlineData, ok := contentWithoutInlineData[1].(map[string]any)
- require.True(t, ok)
- require.Equal(t, "tool_use", toolUseWithoutInlineData["type"])
- require.Equal(t, "get_weather", toolUseWithoutInlineData["name"])
- require.Equal(t, map[string]any{"type": "text", "text": "after"}, contentWithoutInlineData[2])
- }
- func TestGeminiResponseToChatCompletionsRetainsTextAndToolBehavior(t *testing.T) {
- geminiResp := map[string]any{
- "candidates": []any{map[string]any{
- "content": map[string]any{"parts": []any{
- map[string]any{"text": "checking"},
- map[string]any{"functionCall": map[string]any{
- "name": "get_weather",
- "args": map[string]any{"city": "Paris"},
- }},
- }},
- "finishReason": "STOP",
- }},
- }
- rawData, err := json.Marshal(geminiResp)
- require.NoError(t, err)
- got, _, err := geminiResponseToChatCompletions(geminiResp, "gemini-test", rawData, nil)
- require.NoError(t, err)
- require.Len(t, got.Choices, 1)
- choice := got.Choices[0]
- var content string
- require.NoError(t, json.Unmarshal(choice.Message.Content, &content))
- require.Equal(t, "checking", content)
- require.Equal(t, "tool_calls", choice.FinishReason)
- require.Len(t, choice.Message.ToolCalls, 1)
- require.Equal(t, "get_weather", choice.Message.ToolCalls[0].Function.Name)
- require.JSONEq(t, `{"city":"Paris"}`, choice.Message.ToolCalls[0].Function.Arguments)
- }
|