@@ -1318,6 +1318,273 @@ func TestFinishReasonTranslation(t *testing.T) {
13181318 }
13191319}
13201320
1321+ // TestToolParameterDereferencing tests the JSON schema dereferencing functionality
1322+ // for tool parameters when translating from OpenAI to GCP Anthropic.
1323+ func TestToolParameterDereferencing (t * testing.T ) {
1324+ tests := []struct {
1325+ name string
1326+ openAIReq * openai.ChatCompletionRequest
1327+ expectedTools []anthropic.ToolUnionParam
1328+ expectedToolChoice anthropic.ToolChoiceUnionParam
1329+ expectErr bool
1330+ expectedErrMsg string
1331+ }{
1332+ {
1333+ name : "tool with complex nested $ref - successful dereferencing" ,
1334+ openAIReq : & openai.ChatCompletionRequest {
1335+ Tools : []openai.Tool {
1336+ {
1337+ Type : "function" ,
1338+ Function : & openai.FunctionDefinition {
1339+ Name : "complex_tool" ,
1340+ Description : "Tool with complex nested references" ,
1341+ Parameters : map [string ]any {
1342+ "type" : "object" ,
1343+ "$defs" : map [string ]any {
1344+ "BaseType" : map [string ]any {
1345+ "type" : "object" ,
1346+ "properties" : map [string ]any {
1347+ "id" : map [string ]any {
1348+ "type" : "string" ,
1349+ },
1350+ "required" : []any {"id" },
1351+ },
1352+ },
1353+ "NestedType" : map [string ]any {
1354+ "allOf" : []any {
1355+ map [string ]any {"$ref" : "#/$defs/BaseType" },
1356+ map [string ]any {
1357+ "properties" : map [string ]any {
1358+ "name" : map [string ]any {
1359+ "type" : "string" ,
1360+ },
1361+ },
1362+ },
1363+ },
1364+ },
1365+ },
1366+ "properties" : map [string ]any {
1367+ "nested" : map [string ]any {
1368+ "$ref" : "#/$defs/NestedType" ,
1369+ },
1370+ },
1371+ },
1372+ },
1373+ },
1374+ },
1375+ },
1376+ expectedTools : []anthropic.ToolUnionParam {
1377+ {
1378+ OfTool : & anthropic.ToolParam {
1379+ Name : "complex_tool" ,
1380+ Description : anthropic .String ("Tool with complex nested references" ),
1381+ InputSchema : anthropic.ToolInputSchemaParam {
1382+ Type : "object" ,
1383+ Properties : map [string ]any {
1384+ "nested" : map [string ]any {
1385+ "allOf" : []any {
1386+ map [string ]any {
1387+ "type" : "object" ,
1388+ "properties" : map [string ]any {
1389+ "id" : map [string ]any {
1390+ "type" : "string" ,
1391+ },
1392+ "required" : []any {"id" },
1393+ },
1394+ },
1395+ map [string ]any {
1396+ "properties" : map [string ]any {
1397+ "name" : map [string ]any {
1398+ "type" : "string" ,
1399+ },
1400+ },
1401+ },
1402+ },
1403+ },
1404+ },
1405+ },
1406+ },
1407+ },
1408+ },
1409+ },
1410+ {
1411+ name : "tool with invalid $ref - dereferencing error" ,
1412+ openAIReq : & openai.ChatCompletionRequest {
1413+ Tools : []openai.Tool {
1414+ {
1415+ Type : "function" ,
1416+ Function : & openai.FunctionDefinition {
1417+ Name : "invalid_ref_tool" ,
1418+ Description : "Tool with invalid reference" ,
1419+ Parameters : map [string ]any {
1420+ "type" : "object" ,
1421+ "properties" : map [string ]any {
1422+ "location" : map [string ]any {
1423+ "$ref" : "#/$defs/NonExistent" ,
1424+ },
1425+ },
1426+ },
1427+ },
1428+ },
1429+ },
1430+ },
1431+ expectErr : true ,
1432+ expectedErrMsg : "failed to dereference tool parameters" ,
1433+ },
1434+ {
1435+ name : "tool with circular $ref - dereferencing error" ,
1436+ openAIReq : & openai.ChatCompletionRequest {
1437+ Tools : []openai.Tool {
1438+ {
1439+ Type : "function" ,
1440+ Function : & openai.FunctionDefinition {
1441+ Name : "circular_ref_tool" ,
1442+ Description : "Tool with circular reference" ,
1443+ Parameters : map [string ]any {
1444+ "type" : "object" ,
1445+ "$defs" : map [string ]any {
1446+ "A" : map [string ]any {
1447+ "type" : "object" ,
1448+ "properties" : map [string ]any {
1449+ "b" : map [string ]any {
1450+ "$ref" : "#/$defs/B" ,
1451+ },
1452+ },
1453+ },
1454+ "B" : map [string ]any {
1455+ "type" : "object" ,
1456+ "properties" : map [string ]any {
1457+ "a" : map [string ]any {
1458+ "$ref" : "#/$defs/A" ,
1459+ },
1460+ },
1461+ },
1462+ },
1463+ "properties" : map [string ]any {
1464+ "circular" : map [string ]any {
1465+ "$ref" : "#/$defs/A" ,
1466+ },
1467+ },
1468+ },
1469+ },
1470+ },
1471+ },
1472+ },
1473+ expectErr : true ,
1474+ expectedErrMsg : "failed to dereference tool parameters" ,
1475+ },
1476+ {
1477+ name : "tool without $ref - no dereferencing needed" ,
1478+ openAIReq : & openai.ChatCompletionRequest {
1479+ Tools : []openai.Tool {
1480+ {
1481+ Type : "function" ,
1482+ Function : & openai.FunctionDefinition {
1483+ Name : "simple_tool" ,
1484+ Description : "Simple tool without references" ,
1485+ Parameters : map [string ]any {
1486+ "type" : "object" ,
1487+ "properties" : map [string ]any {
1488+ "location" : map [string ]any {
1489+ "type" : "string" ,
1490+ },
1491+ },
1492+ "required" : []any {"location" },
1493+ },
1494+ },
1495+ },
1496+ },
1497+ },
1498+ expectedTools : []anthropic.ToolUnionParam {
1499+ {
1500+ OfTool : & anthropic.ToolParam {
1501+ Name : "simple_tool" ,
1502+ Description : anthropic .String ("Simple tool without references" ),
1503+ InputSchema : anthropic.ToolInputSchemaParam {
1504+ Type : "object" ,
1505+ Properties : map [string ]any {
1506+ "location" : map [string ]any {
1507+ "type" : "string" ,
1508+ },
1509+ },
1510+ Required : []string {"location" },
1511+ },
1512+ },
1513+ },
1514+ },
1515+ },
1516+ {
1517+ name : "tool parameter dereferencing returns non-map type - casting error" ,
1518+ openAIReq : & openai.ChatCompletionRequest {
1519+ Tools : []openai.Tool {
1520+ {
1521+ Type : "function" ,
1522+ Function : & openai.FunctionDefinition {
1523+ Name : "problematic_tool" ,
1524+ Description : "Tool with parameters that can't be properly dereferenced to map" ,
1525+ // This creates a scenario where jsonSchemaDereference might return a non-map type
1526+ // though this is a contrived example since normally the function should return map[string]any
1527+ Parameters : map [string ]any {
1528+ "$ref" : "#/$defs/StringType" , // This would resolve to a string, not a map
1529+ "$defs" : map [string ]any {
1530+ "StringType" : "not-a-map" , // This would cause the casting to fail
1531+ },
1532+ },
1533+ },
1534+ },
1535+ },
1536+ },
1537+ expectErr : true ,
1538+ expectedErrMsg : "failed to cast dereferenced tool parameters" ,
1539+ },
1540+ }
1541+
1542+ for _ , tt := range tests {
1543+ t .Run (tt .name , func (t * testing.T ) {
1544+ tools , toolChoice , err := translateOpenAItoAnthropicTools (tt .openAIReq .Tools , tt .openAIReq .ToolChoice , tt .openAIReq .ParallelToolCalls )
1545+
1546+ if tt .expectErr {
1547+ require .Error (t , err )
1548+ if tt .expectedErrMsg != "" {
1549+ require .Contains (t , err .Error (), tt .expectedErrMsg )
1550+ }
1551+ return
1552+ }
1553+
1554+ require .NoError (t , err )
1555+
1556+ if tt .openAIReq .Tools != nil {
1557+ require .NotNil (t , tools )
1558+ require .Len (t , tools , len (tt .expectedTools ))
1559+
1560+ for i , expectedTool := range tt .expectedTools {
1561+ actualTool := tools [i ]
1562+ require .Equal (t , expectedTool .GetName (), actualTool .GetName ())
1563+ require .Equal (t , expectedTool .GetType (), actualTool .GetType ())
1564+ require .Equal (t , expectedTool .GetDescription (), actualTool .GetDescription ())
1565+
1566+ expectedSchema := expectedTool .GetInputSchema ()
1567+ actualSchema := actualTool .GetInputSchema ()
1568+
1569+ require .Equal (t , expectedSchema .Type , actualSchema .Type )
1570+ require .Equal (t , expectedSchema .Required , actualSchema .Required )
1571+
1572+ // For properties, we'll do a deep comparison to verify dereferencing worked
1573+ if expectedSchema .Properties != nil {
1574+ require .NotNil (t , actualSchema .Properties )
1575+ require .Equal (t , expectedSchema .Properties , actualSchema .Properties )
1576+ }
1577+ }
1578+ }
1579+
1580+ if tt .openAIReq .ToolChoice != nil {
1581+ require .NotNil (t , toolChoice )
1582+ require .Equal (t , * tt .expectedToolChoice .GetType (), * toolChoice .GetType ())
1583+ }
1584+ })
1585+ }
1586+ }
1587+
13211588// TestContentTranslationCoverage adds specific coverage for the openAIToAnthropicContent helper.
13221589func TestContentTranslationCoverage (t * testing.T ) {
13231590 tests := []struct {
0 commit comments