mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-10 10:26:48 +08:00
refactor(translator): simplify response logic and enhance thinking compatibility handling
- Consolidated thinking content block handling for improved clarity and efficiency. - Unified `generationConfig` handling, including normalization of `thinkingConfig` settings. - Added `applyOpenAIThinkingCompatibilityToAntigravity` for consistent reasoning and thought configuration compatibility. Closes: #4067
This commit is contained in:
@@ -212,83 +212,51 @@ func ConvertAntigravityResponseToClaude(ctx context.Context, _ string, originalR
|
||||
|
||||
// Handle text content (both regular content and thinking)
|
||||
if partTextResult.Exists() {
|
||||
// Process thinking content (internal reasoning)
|
||||
if partResult.Get("thought").Bool() || hasThoughtSignature {
|
||||
if hasThoughtSignature {
|
||||
// log.Debug("Branch: signature_delta")
|
||||
|
||||
// Flush co-located text before emitting the signature
|
||||
if partText := partTextResult.String(); partText != "" {
|
||||
if params.ResponseType != 2 {
|
||||
if params.ResponseType != 0 {
|
||||
appendEvent("content_block_stop", fmt.Sprintf(`{"type":"content_block_stop","index":%d}`, params.ResponseIndex))
|
||||
params.ResponseIndex++
|
||||
}
|
||||
appendEvent("content_block_start", fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"thinking","thinking":""}}`, params.ResponseIndex))
|
||||
params.ResponseType = 2
|
||||
params.CurrentThinkingText.Reset()
|
||||
}
|
||||
partText := partTextResult.String()
|
||||
if partResult.Get("thought").Bool() {
|
||||
if partText != "" {
|
||||
if params.ResponseType == 2 {
|
||||
params.CurrentThinkingText.WriteString(partText)
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex)), "delta.thinking", partText)
|
||||
appendEvent("content_block_delta", string(data))
|
||||
}
|
||||
|
||||
appendThinkingSignature(thoughtSignatureResult.String())
|
||||
} else if params.ResponseType == 2 { // Continue existing thinking block if already in thinking state
|
||||
params.CurrentThinkingText.WriteString(partTextResult.String())
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex)), "delta.thinking", partTextResult.String())
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.HasContent = true
|
||||
} else {
|
||||
// Transition from another state to thinking
|
||||
// First, close any existing content block
|
||||
if params.ResponseType != 0 {
|
||||
if params.ResponseType == 2 {
|
||||
// output = output + "event: content_block_delta\n"
|
||||
// output = output + fmt.Sprintf(`data: {"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":null}}`, params.ResponseIndex)
|
||||
// output = output + "\n\n\n"
|
||||
}
|
||||
appendEvent("content_block_stop", fmt.Sprintf(`{"type":"content_block_stop","index":%d}`, params.ResponseIndex))
|
||||
params.ResponseIndex++
|
||||
}
|
||||
|
||||
// Start a new thinking content block
|
||||
appendEvent("content_block_start", fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"thinking","thinking":""}}`, params.ResponseIndex))
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex)), "delta.thinking", partTextResult.String())
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.ResponseType = 2 // Set state to thinking
|
||||
params.HasContent = true
|
||||
// Start accumulating thinking text for signature caching
|
||||
params.CurrentThinkingText.Reset()
|
||||
params.CurrentThinkingText.WriteString(partTextResult.String())
|
||||
}
|
||||
} else {
|
||||
finishReasonResult := gjson.GetBytes(rawJSON, "response.candidates.0.finishReason")
|
||||
if partTextResult.String() != "" || !finishReasonResult.Exists() {
|
||||
// Process regular text content (user-visible output)
|
||||
// Continue existing text block if already in content state
|
||||
if params.ResponseType == 1 {
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex)), "delta.text", partTextResult.String())
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.HasContent = true
|
||||
} else {
|
||||
// Transition from another state to text content
|
||||
// First, close any existing content block
|
||||
if params.ResponseType != 0 {
|
||||
if params.ResponseType == 2 {
|
||||
// output = output + "event: content_block_delta\n"
|
||||
// output = output + fmt.Sprintf(`data: {"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":null}}`, params.ResponseIndex)
|
||||
// output = output + "\n\n\n"
|
||||
}
|
||||
appendEvent("content_block_stop", fmt.Sprintf(`{"type":"content_block_stop","index":%d}`, params.ResponseIndex))
|
||||
params.ResponseIndex++
|
||||
}
|
||||
if partTextResult.String() != "" {
|
||||
// Start a new text content block
|
||||
appendEvent("content_block_start", fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"thinking","thinking":""}}`, params.ResponseIndex))
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex)), "delta.thinking", partText)
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.ResponseType = 2
|
||||
params.HasContent = true
|
||||
params.CurrentThinkingText.Reset()
|
||||
params.CurrentThinkingText.WriteString(partText)
|
||||
}
|
||||
}
|
||||
if hasThoughtSignature {
|
||||
appendThinkingSignature(thoughtSignatureResult.String())
|
||||
}
|
||||
} else {
|
||||
if hasThoughtSignature {
|
||||
appendThinkingSignature(thoughtSignatureResult.String())
|
||||
}
|
||||
finishReasonResult := gjson.GetBytes(rawJSON, "response.candidates.0.finishReason")
|
||||
if partText != "" || !finishReasonResult.Exists() {
|
||||
if params.ResponseType == 1 {
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex)), "delta.text", partText)
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.HasContent = true
|
||||
} else {
|
||||
if params.ResponseType != 0 {
|
||||
appendEvent("content_block_stop", fmt.Sprintf(`{"type":"content_block_stop","index":%d}`, params.ResponseIndex))
|
||||
params.ResponseIndex++
|
||||
}
|
||||
if partText != "" {
|
||||
appendEvent("content_block_start", fmt.Sprintf(`{"type":"content_block_start","index":%d,"content_block":{"type":"text","text":""}}`, params.ResponseIndex))
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex)), "delta.text", partTextResult.String())
|
||||
data, _ := sjson.SetBytes([]byte(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex)), "delta.text", partText)
|
||||
appendEvent("content_block_delta", string(data))
|
||||
params.ResponseType = 1 // Set state to content
|
||||
params.ResponseType = 1
|
||||
params.HasContent = true
|
||||
}
|
||||
}
|
||||
@@ -556,8 +524,8 @@ func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, or
|
||||
sig = part.Get("thought_signature")
|
||||
}
|
||||
hasThoughtSignature := sig.Exists() && sig.String() != "" && !part.Get("functionCall").Exists()
|
||||
isThought := part.Get("thought").Bool() || hasThoughtSignature
|
||||
if hasThoughtSignature {
|
||||
isThought := part.Get("thought").Bool()
|
||||
if hasThoughtSignature && (isThought || thinkingBuilder.Len() > 0) {
|
||||
thinkingSignature = sig.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -755,3 +755,105 @@ func TestConvertAntigravityResponseToClaudeNonStream_SignatureOnlyPartWithoutTho
|
||||
t.Fatalf("expected signature %q, got %q: %s", validSignature, got, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertAntigravityResponseToClaudeNonStream_TextWithThoughtSignatureStaysText(t *testing.T) {
|
||||
previousCache := cache.SignatureCacheEnabled()
|
||||
cache.SetSignatureCacheEnabled(false)
|
||||
defer cache.SetSignatureCacheEnabled(previousCache)
|
||||
|
||||
requestJSON := []byte(`{"model":"gemini-3.1-pro-low"}`)
|
||||
translatedRequestJSON := []byte(`{"model":"gemini-3.1-pro-low"}`)
|
||||
responseJSON := []byte(`{
|
||||
"response": {
|
||||
"candidates": [{
|
||||
"content": {
|
||||
"parts": [
|
||||
{"text": "I need to multiply 17 by 24.", "thought": true},
|
||||
{"text": "408", "thoughtSignature": "sig-final-answer"}
|
||||
]
|
||||
},
|
||||
"finishReason": "STOP"
|
||||
}],
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 16,
|
||||
"candidatesTokenCount": 3,
|
||||
"thoughtsTokenCount": 42,
|
||||
"totalTokenCount": 61
|
||||
},
|
||||
"modelVersion": "gemini-3.1-pro-low",
|
||||
"responseId": "resp-text-sig"
|
||||
}
|
||||
}`)
|
||||
|
||||
output := ConvertAntigravityResponseToClaudeNonStream(context.Background(), "gemini-3.1-pro-low", requestJSON, translatedRequestJSON, responseJSON, nil)
|
||||
if got := gjson.GetBytes(output, "content.#").Int(); got != 2 {
|
||||
t.Fatalf("content block count = %d, want 2. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "content.0.type").String(); got != "thinking" {
|
||||
t.Fatalf("content.0.type = %q, want thinking. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "content.0.thinking").String(); got != "I need to multiply 17 by 24." {
|
||||
t.Fatalf("thinking = %q, want thought text. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "content.0.signature").String(); got != "sig-final-answer" {
|
||||
t.Fatalf("signature = %q, want sig-final-answer. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "content.1.type").String(); got != "text" {
|
||||
t.Fatalf("content.1.type = %q, want text. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "content.1.text").String(); got != "408" {
|
||||
t.Fatalf("text = %q, want final answer. Output: %s", got, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertAntigravityResponseToClaudeStream_TextWithThoughtSignatureStaysText(t *testing.T) {
|
||||
previousCache := cache.SignatureCacheEnabled()
|
||||
cache.SetSignatureCacheEnabled(false)
|
||||
defer cache.SetSignatureCacheEnabled(previousCache)
|
||||
|
||||
requestJSON := []byte(`{"model":"gemini-3.1-pro-low"}`)
|
||||
translatedRequestJSON := []byte(`{"model":"gemini-3.1-pro-low"}`)
|
||||
thoughtChunk := []byte(`{
|
||||
"response": {
|
||||
"candidates": [{"content": {"parts": [{"text": "I need to multiply 17 by 24.", "thought": true}]}}],
|
||||
"modelVersion": "gemini-3.1-pro-low",
|
||||
"responseId": "resp-text-sig"
|
||||
}
|
||||
}`)
|
||||
textChunk := []byte(`{
|
||||
"response": {
|
||||
"candidates": [{"content": {"parts": [{"text": "408", "thoughtSignature": "sig-final-answer"}]}}],
|
||||
"modelVersion": "gemini-3.1-pro-low",
|
||||
"responseId": "resp-text-sig"
|
||||
}
|
||||
}`)
|
||||
finishChunk := []byte(`{
|
||||
"response": {
|
||||
"candidates": [{"finishReason": "STOP"}],
|
||||
"usageMetadata": {"promptTokenCount": 16, "candidatesTokenCount": 3, "thoughtsTokenCount": 42, "totalTokenCount": 61},
|
||||
"modelVersion": "gemini-3.1-pro-low",
|
||||
"responseId": "resp-text-sig"
|
||||
}
|
||||
}`)
|
||||
|
||||
var param any
|
||||
ctx := context.Background()
|
||||
output := bytes.Join(ConvertAntigravityResponseToClaude(ctx, "gemini-3.1-pro-low", requestJSON, translatedRequestJSON, thoughtChunk, ¶m), nil)
|
||||
output = append(output, bytes.Join(ConvertAntigravityResponseToClaude(ctx, "gemini-3.1-pro-low", requestJSON, translatedRequestJSON, textChunk, ¶m), nil)...)
|
||||
output = append(output, bytes.Join(ConvertAntigravityResponseToClaude(ctx, "gemini-3.1-pro-low", requestJSON, translatedRequestJSON, finishChunk, ¶m), nil)...)
|
||||
output = append(output, bytes.Join(ConvertAntigravityResponseToClaude(ctx, "gemini-3.1-pro-low", requestJSON, translatedRequestJSON, []byte("[DONE]"), ¶m), nil)...)
|
||||
outputText := string(output)
|
||||
|
||||
if !strings.Contains(outputText, `"delta":{"type":"signature_delta","signature":"sig-final-answer"}`) {
|
||||
t.Fatalf("expected signature delta for thinking block: %s", outputText)
|
||||
}
|
||||
if !strings.Contains(outputText, `"content_block":{"type":"text","text":""}`) {
|
||||
t.Fatalf("expected text content block after thinking: %s", outputText)
|
||||
}
|
||||
if !strings.Contains(outputText, `"delta":{"type":"text_delta","text":"408"}`) {
|
||||
t.Fatalf("expected final answer as text delta: %s", outputText)
|
||||
}
|
||||
if strings.Contains(outputText, `"delta":{"type":"thinking_delta","thinking":"408"}`) {
|
||||
t.Fatalf("final answer must not be emitted as thinking delta: %s", outputText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
// Let user-provided generationConfig pass through
|
||||
if genConfig := gjson.GetBytes(rawJSON, "generationConfig"); genConfig.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig", []byte(genConfig.Raw))
|
||||
} else if genConfig := gjson.GetBytes(rawJSON, "generation_config"); genConfig.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig", []byte(genConfig.Raw))
|
||||
}
|
||||
|
||||
// Apply thinking configuration: convert OpenAI reasoning_effort to Antigravity thinkingConfig.
|
||||
@@ -55,6 +57,7 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
}
|
||||
}
|
||||
}
|
||||
out = applyOpenAIThinkingCompatibilityToAntigravity(out, rawJSON, modelName)
|
||||
|
||||
// Temperature/top_p/top_k/max_tokens
|
||||
if tr := gjson.GetBytes(rawJSON, "temperature"); tr.Exists() && tr.Type == gjson.Number {
|
||||
@@ -451,5 +454,83 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
return common.AttachDefaultSafetySettings(out, "request.safetySettings")
|
||||
}
|
||||
|
||||
func applyOpenAIThinkingCompatibilityToAntigravity(out []byte, rawJSON []byte, modelName string) []byte {
|
||||
out = normalizeAntigravityOpenAIThinkingConfig(out)
|
||||
|
||||
for _, path := range []string{
|
||||
"thinking.includeThoughts",
|
||||
"thinking.include_thoughts",
|
||||
"reasoning.includeThoughts",
|
||||
"reasoning.include_thoughts",
|
||||
} {
|
||||
if value := gjson.GetBytes(rawJSON, path); value.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", value.Bool())
|
||||
}
|
||||
}
|
||||
|
||||
if exclude := gjson.GetBytes(rawJSON, "reasoning.exclude"); exclude.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", !exclude.Bool())
|
||||
}
|
||||
|
||||
if !gjson.GetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts").Exists() && antigravityOpenAIDefaultIncludeThoughts(modelName) {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", true)
|
||||
}
|
||||
|
||||
return normalizeAntigravityOpenAIThinkingConfig(out)
|
||||
}
|
||||
|
||||
func normalizeAntigravityOpenAIThinkingConfig(out []byte) []byte {
|
||||
for _, prefix := range []string{
|
||||
"request.generationConfig.thinking_config",
|
||||
"request.generationConfig.thinkingConfig",
|
||||
} {
|
||||
if includeThoughts := gjson.GetBytes(out, prefix+".includeThoughts"); includeThoughts.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts.Bool())
|
||||
}
|
||||
if includeThoughts := gjson.GetBytes(out, prefix+".include_thoughts"); includeThoughts.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts.Bool())
|
||||
}
|
||||
if thinkingLevel := gjson.GetBytes(out, prefix+".thinkingLevel"); thinkingLevel.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingLevel", []byte(thinkingLevel.Raw))
|
||||
}
|
||||
if thinkingLevel := gjson.GetBytes(out, prefix+".thinking_level"); thinkingLevel.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingLevel", []byte(thinkingLevel.Raw))
|
||||
}
|
||||
if thinkingBudget := gjson.GetBytes(out, prefix+".thinkingBudget"); thinkingBudget.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingBudget", []byte(thinkingBudget.Raw))
|
||||
}
|
||||
if thinkingBudget := gjson.GetBytes(out, prefix+".thinking_budget"); thinkingBudget.Exists() {
|
||||
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingBudget", []byte(thinkingBudget.Raw))
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range []string{
|
||||
"request.generationConfig.includeThoughts",
|
||||
"request.generationConfig.include_thoughts",
|
||||
} {
|
||||
if includeThoughts := gjson.GetBytes(out, path); includeThoughts.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts.Bool())
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range []string{
|
||||
"request.generationConfig.thinking_config",
|
||||
"request.generationConfig.thinkingConfig.include_thoughts",
|
||||
"request.generationConfig.thinkingConfig.thinking_level",
|
||||
"request.generationConfig.thinkingConfig.thinking_budget",
|
||||
"request.generationConfig.includeThoughts",
|
||||
"request.generationConfig.include_thoughts",
|
||||
} {
|
||||
out, _ = sjson.DeleteBytes(out, path)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func antigravityOpenAIDefaultIncludeThoughts(modelName string) bool {
|
||||
modelName = strings.ToLower(modelName)
|
||||
return strings.Contains(modelName, "gemini-3")
|
||||
}
|
||||
|
||||
// itoa converts int to string without strconv import for few usages.
|
||||
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
||||
|
||||
+69
@@ -54,3 +54,72 @@ func TestConvertOpenAIRequestToAntigravitySkipsEmptyTextPartsWithoutNulls(t *tes
|
||||
t.Fatalf("functionCall missing. Output: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIRequestToAntigravityThinkingAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Default Gemini include thoughts",
|
||||
body: `{
|
||||
"model":"gemini-3.1-pro-low",
|
||||
"messages":[{"role":"user","content":"hi"}]
|
||||
}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GenerationConfig snake include thoughts",
|
||||
body: `{
|
||||
"model":"gemini-3.1-pro-low",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"generationConfig":{"thinkingConfig":{"include_thoughts":true}}
|
||||
}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Top-level thinking include thoughts",
|
||||
body: `{
|
||||
"model":"gemini-3.1-pro-low",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"thinking":{"include_thoughts":true}
|
||||
}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Reasoning exclude false includes thoughts",
|
||||
body: `{
|
||||
"model":"gemini-3.1-pro-low",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"reasoning":{"exclude":false}
|
||||
}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Reasoning exclude true hides thoughts",
|
||||
body: `{
|
||||
"model":"gemini-3.1-pro-low",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"reasoning":{"exclude":true}
|
||||
}`,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ConvertOpenAIRequestToAntigravity("gemini-3.1-pro-low", []byte(tt.body), false)
|
||||
includeThoughts := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts")
|
||||
if !includeThoughts.Exists() {
|
||||
t.Fatalf("includeThoughts missing. Output: %s", result)
|
||||
}
|
||||
if got := includeThoughts.Bool(); got != tt.want {
|
||||
t.Fatalf("includeThoughts = %v, want %v. Output: %s", got, tt.want, result)
|
||||
}
|
||||
if snake := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.include_thoughts"); snake.Exists() {
|
||||
t.Fatalf("include_thoughts should be normalized away. Output: %s", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -126,3 +126,40 @@ func TestNoFinishReasonOnIntermediateChunks(t *testing.T) {
|
||||
t.Errorf("Expected no finish_reason on intermediate chunk, got: %v", fr2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertAntigravityResponseToOpenAINonStreamIncludesReasoningContent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
responseJSON := []byte(`{
|
||||
"response": {
|
||||
"candidates": [{
|
||||
"index": 0,
|
||||
"content": {
|
||||
"parts": [
|
||||
{"text": "I need to multiply 17 by 24.", "thought": true},
|
||||
{"text": "408", "thoughtSignature": "sig-final-answer"}
|
||||
]
|
||||
},
|
||||
"finishReason": "STOP"
|
||||
}],
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 16,
|
||||
"candidatesTokenCount": 3,
|
||||
"thoughtsTokenCount": 42,
|
||||
"totalTokenCount": 61
|
||||
},
|
||||
"modelVersion": "gemini-3.1-pro-low",
|
||||
"responseId": "resp-reasoning"
|
||||
}
|
||||
}`)
|
||||
|
||||
output := ConvertAntigravityResponseToOpenAINonStream(ctx, "gemini-3.1-pro-low", nil, nil, responseJSON, nil)
|
||||
if got := gjson.GetBytes(output, "choices.0.message.reasoning_content").String(); got != "I need to multiply 17 by 24." {
|
||||
t.Fatalf("reasoning_content = %q, want thought text. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "choices.0.message.content").String(); got != "408" {
|
||||
t.Fatalf("content = %q, want final answer. Output: %s", got, output)
|
||||
}
|
||||
if got := gjson.GetBytes(output, "usage.completion_tokens_details.reasoning_tokens").Int(); got != 42 {
|
||||
t.Fatalf("reasoning_tokens = %d, want 42. Output: %s", got, output)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user