Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
3878a5a4
Unverified
Commit
3878a5a4
authored
Mar 19, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 19, 2026
Browse files
Merge pull request #1164 from GuangYiDing/fix/normalize-tool-parameters-schema
fix: Anthropic tool schema 转换时补充缺失的 properties 字段
parents
525cdb88
e443a6a1
Changes
2
Show whitespace changes
Inline
Side-by-side
backend/internal/pkg/apicompat/anthropic_responses_test.go
View file @
3878a5a4
...
...
@@ -1008,3 +1008,114 @@ func TestAnthropicToResponses_ImageEmptyMediaType(t *testing.T) {
// Should default to image/png when media_type is empty.
assert
.
Equal
(
t
,
"data:image/png;base64,iVBOR"
,
parts
[
0
]
.
ImageURL
)
}
// ---------------------------------------------------------------------------
// normalizeToolParameters tests
// ---------------------------------------------------------------------------
func
TestNormalizeToolParameters
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
input
json
.
RawMessage
expected
string
}{
{
name
:
"nil input"
,
input
:
nil
,
expected
:
`{"type":"object","properties":{}}`
,
},
{
name
:
"empty input"
,
input
:
json
.
RawMessage
(
``
),
expected
:
`{"type":"object","properties":{}}`
,
},
{
name
:
"null input"
,
input
:
json
.
RawMessage
(
`null`
),
expected
:
`{"type":"object","properties":{}}`
,
},
{
name
:
"object without properties"
,
input
:
json
.
RawMessage
(
`{"type":"object"}`
),
expected
:
`{"type":"object","properties":{}}`
,
},
{
name
:
"object with properties"
,
input
:
json
.
RawMessage
(
`{"type":"object","properties":{"city":{"type":"string"}}}`
),
expected
:
`{"type":"object","properties":{"city":{"type":"string"}}}`
,
},
{
name
:
"non-object type"
,
input
:
json
.
RawMessage
(
`{"type":"string"}`
),
expected
:
`{"type":"string"}`
,
},
{
name
:
"object with additional fields preserved"
,
input
:
json
.
RawMessage
(
`{"type":"object","required":["name"]}`
),
expected
:
`{"type":"object","required":["name"],"properties":{}}`
,
},
{
name
:
"invalid JSON passthrough"
,
input
:
json
.
RawMessage
(
`not json`
),
expected
:
`not json`
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
result
:=
normalizeToolParameters
(
tt
.
input
)
if
tt
.
name
==
"invalid JSON passthrough"
{
assert
.
Equal
(
t
,
tt
.
expected
,
string
(
result
))
}
else
{
assert
.
JSONEq
(
t
,
tt
.
expected
,
string
(
result
))
}
})
}
}
func
TestAnthropicToResponses_ToolWithoutProperties
(
t
*
testing
.
T
)
{
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
Messages
:
[]
AnthropicMessage
{
{
Role
:
"user"
,
Content
:
json
.
RawMessage
(
`"Hello"`
)},
},
Tools
:
[]
AnthropicTool
{
{
Name
:
"mcp__pencil__get_style_guide_tags"
,
Description
:
"Get style tags"
,
InputSchema
:
json
.
RawMessage
(
`{"type":"object"}`
)},
},
}
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
resp
.
Tools
,
1
)
assert
.
Equal
(
t
,
"function"
,
resp
.
Tools
[
0
]
.
Type
)
assert
.
Equal
(
t
,
"mcp__pencil__get_style_guide_tags"
,
resp
.
Tools
[
0
]
.
Name
)
// Parameters must have "properties" field after normalization.
var
params
map
[
string
]
json
.
RawMessage
require
.
NoError
(
t
,
json
.
Unmarshal
(
resp
.
Tools
[
0
]
.
Parameters
,
&
params
))
assert
.
Contains
(
t
,
params
,
"properties"
)
}
func
TestAnthropicToResponses_ToolWithNilSchema
(
t
*
testing
.
T
)
{
req
:=
&
AnthropicRequest
{
Model
:
"gpt-5.2"
,
MaxTokens
:
1024
,
Messages
:
[]
AnthropicMessage
{
{
Role
:
"user"
,
Content
:
json
.
RawMessage
(
`"Hello"`
)},
},
Tools
:
[]
AnthropicTool
{
{
Name
:
"simple_tool"
,
Description
:
"A tool"
},
},
}
resp
,
err
:=
AnthropicToResponses
(
req
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
resp
.
Tools
,
1
)
var
params
map
[
string
]
json
.
RawMessage
require
.
NoError
(
t
,
json
.
Unmarshal
(
resp
.
Tools
[
0
]
.
Parameters
,
&
params
))
assert
.
JSONEq
(
t
,
`"object"`
,
string
(
params
[
"type"
]))
assert
.
JSONEq
(
t
,
`{}`
,
string
(
params
[
"properties"
]))
}
backend/internal/pkg/apicompat/anthropic_to_responses.go
View file @
3878a5a4
...
...
@@ -409,8 +409,41 @@ func convertAnthropicToolsToResponses(tools []AnthropicTool) []ResponsesTool {
Type
:
"function"
,
Name
:
t
.
Name
,
Description
:
t
.
Description
,
Parameters
:
t
.
InputSchema
,
Parameters
:
normalizeToolParameters
(
t
.
InputSchema
)
,
})
}
return
out
}
// normalizeToolParameters ensures the tool parameter schema is valid for
// OpenAI's Responses API, which requires "properties" on object schemas.
//
// - nil/empty → {"type":"object","properties":{}}
// - type=object without properties → adds "properties": {}
// - otherwise → returned unchanged
func
normalizeToolParameters
(
schema
json
.
RawMessage
)
json
.
RawMessage
{
if
len
(
schema
)
==
0
||
string
(
schema
)
==
"null"
{
return
json
.
RawMessage
(
`{"type":"object","properties":{}}`
)
}
var
m
map
[
string
]
json
.
RawMessage
if
err
:=
json
.
Unmarshal
(
schema
,
&
m
);
err
!=
nil
{
return
schema
}
typ
:=
m
[
"type"
]
if
string
(
typ
)
!=
`"object"`
{
return
schema
}
if
_
,
ok
:=
m
[
"properties"
];
ok
{
return
schema
}
m
[
"properties"
]
=
json
.
RawMessage
(
`{}`
)
out
,
err
:=
json
.
Marshal
(
m
)
if
err
!=
nil
{
return
schema
}
return
out
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment