Files

211 lines
5.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package shared
import (
"encoding/json"
"testing"
"github.com/qianfree/team-api/relaykit/dto"
)
func TestMapOpenAIToolsToClaudeTools(t *testing.T) {
parameters := json.RawMessage(`{"type":"object","properties":{"location":{"type":"string"}}}`)
tools := []dto.Tool{
{
Type: "function",
Function: dto.FunctionDef{
Name: "get_weather",
Description: "Get current weather",
Parameters: parameters,
},
},
}
result := MapOpenAIToolsToClaudeTools(tools)
if len(result) != 1 {
t.Fatalf("Expected 1 tool, got %d", len(result))
}
if result[0].Name != "get_weather" {
t.Errorf("Expected name 'get_weather', got %q", result[0].Name)
}
if result[0].Description != "Get current weather" {
t.Errorf("Expected description 'Get current weather', got %q", result[0].Description)
}
}
func TestMapClaudeToolsToOpenAITools(t *testing.T) {
inputSchema := json.RawMessage(`{"type":"object","properties":{"location":{"type":"string"}}}`)
tools := []dto.ClaudeTool{
{
Name: "get_weather",
Description: "Get current weather",
InputSchema: inputSchema,
},
}
result := MapClaudeToolsToOpenAITools(tools)
if len(result) != 1 {
t.Fatalf("Expected 1 tool, got %d", len(result))
}
if result[0].Type != "function" {
t.Errorf("Expected type 'function', got %q", result[0].Type)
}
if result[0].Function.Name != "get_weather" {
t.Errorf("Expected name 'get_weather', got %q", result[0].Function.Name)
}
}
func TestMapClaudeToolCallsToOpenAI(t *testing.T) {
input := map[string]any{
"location": "San Francisco",
}
blocks := []dto.ClaudeContentBlock{
{
Type: "text",
Text: strPtr("Let me check the weather"),
},
{
Type: "tool_use",
ID: "call_123",
Name: "get_weather",
Input: input,
},
}
result := MapClaudeToolCallsToOpenAI(blocks)
if len(result) != 1 {
t.Fatalf("Expected 1 tool call, got %d", len(result))
}
if result[0].ID != "call_123" {
t.Errorf("Expected ID 'call_123', got %q", result[0].ID)
}
if result[0].Type != "function" {
t.Errorf("Expected type 'function', got %q", result[0].Type)
}
if result[0].Function.Name != "get_weather" {
t.Errorf("Expected name 'get_weather', got %q", result[0].Function.Name)
}
// 校验 arguments 为合法 JSON
var args map[string]any
if err := json.Unmarshal([]byte(result[0].Function.Arguments), &args); err != nil {
t.Errorf("Arguments are not valid JSON: %v", err)
}
if args["location"] != "San Francisco" {
t.Errorf("Expected location 'San Francisco', got %v", args["location"])
}
}
func TestMapOpenAIToolCallsToClaude(t *testing.T) {
toolCalls := []dto.ToolCall{
{
ID: "call_123",
Type: "function",
Function: dto.FunctionCall{
Name: "get_weather",
Arguments: `{"location":"San Francisco"}`,
},
},
}
result := MapOpenAIToolCallsToClaude(toolCalls)
if len(result) != 1 {
t.Fatalf("Expected 1 block, got %d", len(result))
}
if result[0].Type != "tool_use" {
t.Errorf("Expected type 'tool_use', got %q", result[0].Type)
}
if result[0].ID != "call_123" {
t.Errorf("Expected ID 'call_123', got %q", result[0].ID)
}
if result[0].Name != "get_weather" {
t.Errorf("Expected name 'get_weather', got %q", result[0].Name)
}
// 校验 input 已正确解析
input, ok := result[0].Input.(map[string]any)
if !ok {
t.Fatalf("Expected input to be map[string]any, got %T", result[0].Input)
}
if input["location"] != "San Francisco" {
t.Errorf("Expected location 'San Francisco', got %v", input["location"])
}
}
// 无参函数(OpenAI 允许省略 parameters)必须补出空对象 schema:
// Claude 的 custom tool 要求 input_schema 必填,缺失会被上游以 400 拒绝。
func TestMapOpenAIToolsToClaudeTools_NoParameters(t *testing.T) {
tools := []dto.Tool{
{
Type: "function",
Function: dto.FunctionDef{
Name: "get_current_time",
Description: "No arguments needed",
},
},
}
result := MapOpenAIToolsToClaudeTools(tools)
if len(result) != 1 {
t.Fatalf("Expected 1 tool, got %d", len(result))
}
if result[0].InputSchema == nil {
t.Fatal("InputSchema is nil, expected empty object schema")
}
// 序列化后必须带 input_schema 键(omitempty 不能把它抹掉)
data, err := json.Marshal(result[0])
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded map[string]any
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
schema, ok := decoded["input_schema"].(map[string]any)
if !ok {
t.Fatalf("input_schema missing or wrong type in %s", data)
}
if schema["type"] != "object" {
t.Errorf("input_schema.type = %v, want object", schema["type"])
}
if _, ok := schema["properties"].(map[string]any); !ok {
t.Errorf("input_schema.properties missing in %s", data)
}
}
// 非 function 类型的工具被过滤后返回空 slice,调用方据此判空。
func TestMapOpenAIToolsToClaudeTools_AllFiltered(t *testing.T) {
tools := []dto.Tool{
{Type: "custom", Function: dto.FunctionDef{Name: "freeform"}},
}
if result := MapOpenAIToolsToClaudeTools(tools); len(result) != 0 {
t.Errorf("Expected 0 tools, got %d", len(result))
}
}