mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-26 21:05:46 +00:00
Fixed: #1135
**test(translator): add tests for `tool_choice` handling in Claude request conversions**
This commit is contained in:
@@ -431,6 +431,33 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
out, _ = sjson.SetRaw(out, "request.tools", toolsJSON)
|
out, _ = sjson.SetRaw(out, "request.tools", toolsJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tool_choice
|
||||||
|
toolChoiceResult := gjson.GetBytes(rawJSON, "tool_choice")
|
||||||
|
if toolChoiceResult.Exists() {
|
||||||
|
toolChoiceType := ""
|
||||||
|
toolChoiceName := ""
|
||||||
|
if toolChoiceResult.IsObject() {
|
||||||
|
toolChoiceType = toolChoiceResult.Get("type").String()
|
||||||
|
toolChoiceName = toolChoiceResult.Get("name").String()
|
||||||
|
} else if toolChoiceResult.Type == gjson.String {
|
||||||
|
toolChoiceType = toolChoiceResult.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch toolChoiceType {
|
||||||
|
case "auto":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "AUTO")
|
||||||
|
case "none":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "NONE")
|
||||||
|
case "any":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
case "tool":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
if toolChoiceName != "" {
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.allowedFunctionNames", []string{toolChoiceName})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
|
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
|
||||||
if t := gjson.GetBytes(rawJSON, "thinking"); enableThoughtTranslate && t.Exists() && t.IsObject() {
|
if t := gjson.GetBytes(rawJSON, "thinking"); enableThoughtTranslate && t.Exists() && t.IsObject() {
|
||||||
switch t.Get("type").String() {
|
switch t.Get("type").String() {
|
||||||
|
|||||||
@@ -193,6 +193,42 @@ func TestConvertClaudeRequestToAntigravity_ToolDeclarations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertClaudeRequestToAntigravity_ToolChoice_SpecificTool(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gemini-3-flash-preview",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "hi"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "json",
|
||||||
|
"description": "A JSON tool",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tool_choice": {"type": "tool", "name": "json"}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertClaudeRequestToAntigravity("gemini-3-flash-preview", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
if got := gjson.Get(outputStr, "request.toolConfig.functionCallingConfig.mode").String(); got != "ANY" {
|
||||||
|
t.Fatalf("Expected toolConfig.functionCallingConfig.mode 'ANY', got '%s'", got)
|
||||||
|
}
|
||||||
|
allowed := gjson.Get(outputStr, "request.toolConfig.functionCallingConfig.allowedFunctionNames").Array()
|
||||||
|
if len(allowed) != 1 || allowed[0].String() != "json" {
|
||||||
|
t.Fatalf("Expected allowedFunctionNames ['json'], got %s", gjson.Get(outputStr, "request.toolConfig.functionCallingConfig.allowedFunctionNames").Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertClaudeRequestToAntigravity_ToolUse(t *testing.T) {
|
func TestConvertClaudeRequestToAntigravity_ToolUse(t *testing.T) {
|
||||||
inputJSON := []byte(`{
|
inputJSON := []byte(`{
|
||||||
"model": "claude-3-5-sonnet-20240620",
|
"model": "claude-3-5-sonnet-20240620",
|
||||||
|
|||||||
@@ -172,6 +172,33 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tool_choice
|
||||||
|
toolChoiceResult := gjson.GetBytes(rawJSON, "tool_choice")
|
||||||
|
if toolChoiceResult.Exists() {
|
||||||
|
toolChoiceType := ""
|
||||||
|
toolChoiceName := ""
|
||||||
|
if toolChoiceResult.IsObject() {
|
||||||
|
toolChoiceType = toolChoiceResult.Get("type").String()
|
||||||
|
toolChoiceName = toolChoiceResult.Get("name").String()
|
||||||
|
} else if toolChoiceResult.Type == gjson.String {
|
||||||
|
toolChoiceType = toolChoiceResult.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch toolChoiceType {
|
||||||
|
case "auto":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "AUTO")
|
||||||
|
case "none":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "NONE")
|
||||||
|
case "any":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
case "tool":
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
if toolChoiceName != "" {
|
||||||
|
out, _ = sjson.Set(out, "request.toolConfig.functionCallingConfig.allowedFunctionNames", []string{toolChoiceName})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map Anthropic thinking -> Gemini CLI thinkingConfig when enabled
|
// Map Anthropic thinking -> Gemini CLI thinkingConfig when enabled
|
||||||
// Translator only does format conversion, ApplyThinking handles model capability validation.
|
// Translator only does format conversion, ApplyThinking handles model capability validation.
|
||||||
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
|
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package claude
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertClaudeRequestToCLI_ToolChoice_SpecificTool(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gemini-3-flash-preview",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "hi"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "json",
|
||||||
|
"description": "A JSON tool",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tool_choice": {"type": "tool", "name": "json"}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertClaudeRequestToCLI("gemini-3-flash-preview", inputJSON, false)
|
||||||
|
|
||||||
|
if got := gjson.GetBytes(output, "request.toolConfig.functionCallingConfig.mode").String(); got != "ANY" {
|
||||||
|
t.Fatalf("Expected request.toolConfig.functionCallingConfig.mode 'ANY', got '%s'", got)
|
||||||
|
}
|
||||||
|
allowed := gjson.GetBytes(output, "request.toolConfig.functionCallingConfig.allowedFunctionNames").Array()
|
||||||
|
if len(allowed) != 1 || allowed[0].String() != "json" {
|
||||||
|
t.Fatalf("Expected allowedFunctionNames ['json'], got %s", gjson.GetBytes(output, "request.toolConfig.functionCallingConfig.allowedFunctionNames").Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,6 +153,33 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tool_choice
|
||||||
|
toolChoiceResult := gjson.GetBytes(rawJSON, "tool_choice")
|
||||||
|
if toolChoiceResult.Exists() {
|
||||||
|
toolChoiceType := ""
|
||||||
|
toolChoiceName := ""
|
||||||
|
if toolChoiceResult.IsObject() {
|
||||||
|
toolChoiceType = toolChoiceResult.Get("type").String()
|
||||||
|
toolChoiceName = toolChoiceResult.Get("name").String()
|
||||||
|
} else if toolChoiceResult.Type == gjson.String {
|
||||||
|
toolChoiceType = toolChoiceResult.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch toolChoiceType {
|
||||||
|
case "auto":
|
||||||
|
out, _ = sjson.Set(out, "toolConfig.functionCallingConfig.mode", "AUTO")
|
||||||
|
case "none":
|
||||||
|
out, _ = sjson.Set(out, "toolConfig.functionCallingConfig.mode", "NONE")
|
||||||
|
case "any":
|
||||||
|
out, _ = sjson.Set(out, "toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
case "tool":
|
||||||
|
out, _ = sjson.Set(out, "toolConfig.functionCallingConfig.mode", "ANY")
|
||||||
|
if toolChoiceName != "" {
|
||||||
|
out, _ = sjson.Set(out, "toolConfig.functionCallingConfig.allowedFunctionNames", []string{toolChoiceName})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map Anthropic thinking -> Gemini thinking config when enabled
|
// Map Anthropic thinking -> Gemini thinking config when enabled
|
||||||
// Translator only does format conversion, ApplyThinking handles model capability validation.
|
// Translator only does format conversion, ApplyThinking handles model capability validation.
|
||||||
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
|
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package claude
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertClaudeRequestToGemini_ToolChoice_SpecificTool(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gemini-3-flash-preview",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "hi"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "json",
|
||||||
|
"description": "A JSON tool",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tool_choice": {"type": "tool", "name": "json"}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertClaudeRequestToGemini("gemini-3-flash-preview", inputJSON, false)
|
||||||
|
|
||||||
|
if got := gjson.GetBytes(output, "toolConfig.functionCallingConfig.mode").String(); got != "ANY" {
|
||||||
|
t.Fatalf("Expected toolConfig.functionCallingConfig.mode 'ANY', got '%s'", got)
|
||||||
|
}
|
||||||
|
allowed := gjson.GetBytes(output, "toolConfig.functionCallingConfig.allowedFunctionNames").Array()
|
||||||
|
if len(allowed) != 1 || allowed[0].String() != "json" {
|
||||||
|
t.Fatalf("Expected allowedFunctionNames ['json'], got %s", gjson.GetBytes(output, "toolConfig.functionCallingConfig.allowedFunctionNames").Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user