mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Merge pull request #4082 from zuelu/agent/fix-codex-additional-tools-chat-bridge
fix(apicompat): 支持 Codex additional_tools 到 Chat Completions 桥接
This commit is contained in:
@@ -35,8 +35,12 @@ func ResponsesToChatCompletionsRequest(req *ResponsesRequest) (*ChatCompletionsR
|
||||
if req.Reasoning != nil {
|
||||
out.ReasoningEffort = req.Reasoning.Effort
|
||||
}
|
||||
if len(req.Tools) > 0 {
|
||||
tools, err := responsesToolsToChatTools(req.Tools)
|
||||
effectiveTools, err := EffectiveResponsesTools(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(effectiveTools) > 0 {
|
||||
tools, err := responsesToolsToChatTools(effectiveTools)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,6 +67,44 @@ func ResponsesToChatCompletionsRequest(req *ResponsesRequest) (*ChatCompletionsR
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// EffectiveResponsesTools returns every client-executable tool declared by a
|
||||
// Responses request. Newer Codex clients place their runtime tools in an
|
||||
// input item shaped as {"type":"additional_tools","tools":[...]} instead of
|
||||
// the top-level tools field. Chat-only upstreams must receive both forms.
|
||||
func EffectiveResponsesTools(req *ResponsesRequest) ([]ResponsesTool, error) {
|
||||
if req == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tools := append([]ResponsesTool(nil), req.Tools...)
|
||||
inputRaw := bytesTrimSpace(req.Input)
|
||||
if len(inputRaw) == 0 || string(inputRaw) == "null" || inputRaw[0] != '[' {
|
||||
return tools, nil
|
||||
}
|
||||
|
||||
var items []json.RawMessage
|
||||
if err := json.Unmarshal(inputRaw, &items); err != nil {
|
||||
return nil, fmt.Errorf("parse responses input for additional tools: %w", err)
|
||||
}
|
||||
for _, raw := range items {
|
||||
raw = bytesTrimSpace(raw)
|
||||
if len(raw) == 0 || raw[0] != '{' {
|
||||
continue
|
||||
}
|
||||
var item struct {
|
||||
Type string `json:"type"`
|
||||
Tools []ResponsesTool `json:"tools"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &item); err != nil {
|
||||
return nil, fmt.Errorf("parse responses additional tools item: %w", err)
|
||||
}
|
||||
if item.Type == "additional_tools" {
|
||||
tools = append(tools, item.Tools...)
|
||||
}
|
||||
}
|
||||
return tools, nil
|
||||
}
|
||||
|
||||
// CustomToolNames 收集 Responses 请求中 custom/freeform 工具的名字。chat 桥回程时
|
||||
// 需要据此把模型对这些工具的调用还原为 custom_tool_call 项(codex 只按该类型路由)。
|
||||
func CustomToolNames(tools []ResponsesTool) map[string]bool {
|
||||
|
||||
@@ -34,6 +34,51 @@ func TestResponsesToChatCompletionsRequest_CustomToolBecomesFunctionTool(t *test
|
||||
assert.Equal(t, "wait", out.Tools[1].Function.Name)
|
||||
}
|
||||
|
||||
func TestResponsesToChatCompletionsRequest_AdditionalToolsItem(t *testing.T) {
|
||||
req := &ResponsesRequest{
|
||||
Model: "gpt-test",
|
||||
Input: json.RawMessage(`[
|
||||
{"type":"additional_tools","role":"developer","tools":[
|
||||
{"type":"custom","name":"exec","description":"Run PowerShell","format":{"type":"text"}},
|
||||
{"type":"function","name":"wait","parameters":{"type":"object","properties":{}}},
|
||||
{"type":"namespace","name":"collaboration","tools":[
|
||||
{"type":"function","name":"send_message","parameters":{"type":"object","properties":{}}}
|
||||
]}
|
||||
]},
|
||||
{"type":"message","role":"user","content":[{"type":"input_text","text":"run Get-Location"}]}
|
||||
]`),
|
||||
ToolChoice: json.RawMessage(`"auto"`),
|
||||
}
|
||||
|
||||
effective, err := EffectiveResponsesTools(req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, effective, 3)
|
||||
assert.True(t, CustomToolNames(effective)["exec"])
|
||||
assert.Equal(t, NamespacedToolName{Namespace: "collaboration", Name: "send_message"}, NamespaceToolNames(effective)["collaboration__send_message"])
|
||||
|
||||
out, err := ResponsesToChatCompletionsRequest(req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out.Tools, 3)
|
||||
assert.Equal(t, "exec", out.Tools[0].Function.Name)
|
||||
assert.Equal(t, "wait", out.Tools[1].Function.Name)
|
||||
assert.Equal(t, "collaboration__send_message", out.Tools[2].Function.Name)
|
||||
assert.JSONEq(t, `"auto"`, string(out.ToolChoice))
|
||||
|
||||
require.Len(t, out.Messages, 1, "additional_tools must not become a chat message")
|
||||
assert.Equal(t, "user", out.Messages[0].Role)
|
||||
}
|
||||
|
||||
func TestEffectiveResponsesTools_SkipsStringInputItems(t *testing.T) {
|
||||
req := &ResponsesRequest{
|
||||
Input: json.RawMessage(`["plain input",{"type":"additional_tools","tools":[{"type":"custom","name":"exec"}]}]`),
|
||||
}
|
||||
|
||||
tools, err := EffectiveResponsesTools(req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tools, 1)
|
||||
assert.Equal(t, "exec", tools[0].Name)
|
||||
}
|
||||
|
||||
func TestResponsesToChatCompletionsRequest_DropsToolChoiceWhenNoConvertibleTools(t *testing.T) {
|
||||
req := &ResponsesRequest{
|
||||
Model: "glm-5.2",
|
||||
|
||||
@@ -43,9 +43,14 @@ func (s *OpenAIGatewayService) forwardResponsesViaRawChatCompletions(
|
||||
// custom_tool_call 项,先记下名字集合;tool_search 工具同理,回程还原为
|
||||
// tool_search_call 项;namespace 子工具(如 MCP 工具)摊平转发,回程按映射还原
|
||||
// 为带 namespace 字段的 function_call 项。
|
||||
customTools := apicompat.CustomToolNames(responsesReq.Tools)
|
||||
toolSearch := apicompat.HasToolSearchTool(responsesReq.Tools)
|
||||
namespaceTools := apicompat.NamespaceToolNames(responsesReq.Tools)
|
||||
effectiveTools, err := apicompat.EffectiveResponsesTools(&responsesReq)
|
||||
if err != nil {
|
||||
writeOpenAIResponsesFallbackError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
|
||||
return nil, fmt.Errorf("resolve responses tools: %w", err)
|
||||
}
|
||||
customTools := apicompat.CustomToolNames(effectiveTools)
|
||||
toolSearch := apicompat.HasToolSearchTool(effectiveTools)
|
||||
namespaceTools := apicompat.NamespaceToolNames(effectiveTools)
|
||||
|
||||
chatReq, err := apicompat.ResponsesToChatCompletionsRequest(&responsesReq)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user