Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions dto/openai_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ type ResponsesOutputContent struct {
Annotations []interface{} `json:"annotations"`
}

type ResponsesReasoningSummaryPart struct {
Type string `json:"type"`
Text string `json:"text"`
}

const (
BuildInToolWebSearchPreview = "web_search_preview"
BuildInToolFileSearch = "file_search"
Expand All @@ -374,8 +379,11 @@ type ResponsesStreamResponse struct {
Item *ResponsesOutput `json:"item,omitempty"`
// - response.function_call_arguments.delta
// - response.function_call_arguments.done
OutputIndex *int `json:"output_index,omitempty"`
ItemID string `json:"item_id,omitempty"`
OutputIndex *int `json:"output_index,omitempty"`
ContentIndex *int `json:"content_index,omitempty"`
SummaryIndex *int `json:"summary_index,omitempty"`
ItemID string `json:"item_id,omitempty"`
Part *ResponsesReasoningSummaryPart `json:"part,omitempty"`
}

// GetOpenAIError 从动态错误类型中提取OpenAIError结构
Expand Down
3 changes: 3 additions & 0 deletions relay/channel/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,9 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo
}
request.Model = originModel
}
if info != nil && request.Reasoning != nil && request.Reasoning.Effort != "" {
info.ReasoningEffort = request.Reasoning.Effort
}
return request, nil
}

Expand Down
112 changes: 112 additions & 0 deletions relay/channel/openai/chat_via_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ import (
"github.com/gin-gonic/gin"
)

func responsesStreamIndexKey(itemID string, idx *int) string {
if itemID == "" {
return ""
}
if idx == nil {
return itemID
}
return fmt.Sprintf("%s:%d", itemID, *idx)
}

func stringDeltaFromPrefix(prev string, next string) string {
if next == "" {
return ""
}
if prev != "" && strings.HasPrefix(next, prev) {
return next[len(prev):]
}
return next
}

func OaiResponsesToChatHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
if resp == nil || resp.Body == nil {
return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
Expand Down Expand Up @@ -86,6 +106,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
toolCallArgsByID := make(map[string]string)
toolCallNameSent := make(map[string]bool)
toolCallCanonicalIDByItemID := make(map[string]string)
//reasoningSummaryTextByKey := make(map[string]string)

sendStartIfNeeded := func() bool {
if sentStart {
Expand All @@ -99,6 +120,66 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
return true
}

//sendReasoningDelta := func(delta string) bool {
// if delta == "" {
// return true
// }
// if !sendStartIfNeeded() {
// return false
// }
//
// usageText.WriteString(delta)
// chunk := &dto.ChatCompletionsStreamResponse{
// Id: responseId,
// Object: "chat.completion.chunk",
// Created: createAt,
// Model: model,
// Choices: []dto.ChatCompletionsStreamResponseChoice{
// {
// Index: 0,
// Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
// ReasoningContent: &delta,
// },
// },
// },
// }
// if err := helper.ObjectData(c, chunk); err != nil {
// streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
// return false
// }
// return true
//}

sendReasoningSummaryDelta := func(delta string) bool {
if delta == "" {
return true
}
if !sendStartIfNeeded() {
return false
}

usageText.WriteString(delta)
chunk := &dto.ChatCompletionsStreamResponse{
Id: responseId,
Object: "chat.completion.chunk",
Created: createAt,
Model: model,
Choices: []dto.ChatCompletionsStreamResponseChoice{
{
Index: 0,
Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
ReasoningContent: &delta,
},
},
},
}
if err := helper.ObjectData(c, chunk); err != nil {
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
return false
}
return true
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

sendToolCallDelta := func(callID string, name string, argsDelta string) bool {
if callID == "" {
return true
Expand Down Expand Up @@ -188,6 +269,37 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
}
}

//case "response.reasoning_text.delta":
//if !sendReasoningDelta(streamResp.Delta) {
// return false
//}

//case "response.reasoning_text.done":

case "response.reasoning_summary_text.delta":
if !sendReasoningSummaryDelta(streamResp.Delta) {
return false
}

case "response.reasoning_summary_text.done":

//case "response.reasoning_summary_part.added", "response.reasoning_summary_part.done":
// key := responsesStreamIndexKey(strings.TrimSpace(streamResp.ItemID), streamResp.SummaryIndex)
// if key == "" || streamResp.Part == nil {
// break
// }
// // Only handle summary text parts, ignore other part types.
// if streamResp.Part.Type != "" && streamResp.Part.Type != "summary_text" {
// break
// }
// prev := reasoningSummaryTextByKey[key]
// next := streamResp.Part.Text
// delta := stringDeltaFromPrefix(prev, next)
// reasoningSummaryTextByKey[key] = next
// if !sendReasoningSummaryDelta(delta) {
// return false
// }

case "response.output_text.delta":
if !sendStartIfNeeded() {
return false
Expand Down
5 changes: 3 additions & 2 deletions service/openaicompat/chat_to_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,10 @@ func ChatCompletionsRequestToResponsesRequest(req *dto.GeneralOpenAIRequest) (*d
Metadata: req.Metadata,
}

if req.ReasoningEffort != "" && req.ReasoningEffort != "none" {
if req.ReasoningEffort != "" {
out.Reasoning = &dto.Reasoning{
Effort: req.ReasoningEffort,
Effort: req.ReasoningEffort,
Summary: "detailed",
}
Comment on lines +349 to 353

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

OpenAI Responses API reasoning.effort reasoning.summary valid values constraints

💡 Result:

reasoning.effort (Responses API)

Type: string (in reasoning: { effort: ... })
Supported values (overall): none, minimal, low, medium, high, xhigh [1]

Key constraints / model limits:

  • reasoning is for gpt-5 and o-series reasoning models (not all models). [1]
  • gpt-5.1: default none; supports only none, low, medium, high. [1]
  • Models before gpt-5.1: default medium; do not support none. [1]
  • gpt-5-pro: defaults to and only supports high. [1]
  • gpt-5.2-pro: supports only medium, high, xhigh. [4][5]
  • xhigh is supported for models after gpt-5.1-codex-max. [1]

reasoning.summary (Responses API)

Type: string (in reasoning: { summary: ... })
Valid values: auto, concise, detailed [1]

Constraints:

  • concise is supported for computer-use-preview models and reasoning models after gpt-5. [1]
  • reasoning.generate_summary is deprecated; use reasoning.summary instead. [1]

🏁 Script executed:

cat -n service/openaicompat/chat_to_responses.go | sed -n '340,360p'

Repository: QuantumNous/new-api

Length of output: 577


🏁 Script executed:

# Search for ReasoningEffort validation and usage
rg "ReasoningEffort" service/openaicompat/ -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 487


🏁 Script executed:

# Check if there's any validation of effort values in this file or related files
rg "none|minimal|low|medium|high|xhigh" service/openaicompat/chat_to_responses.go -i

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Look at the function signature and docstring
head -n 20 service/openaicompat/chat_to_responses.go

Repository: QuantumNous/new-api

Length of output: 420


🏁 Script executed:

# Check the dto.Reasoning struct to understand its design
rg "type Reasoning struct" . -A 10

Repository: QuantumNous/new-api

Length of output: 611


🏁 Script executed:

# Look for test cases related to reasoning effort
fd "test" service/openaicompat/ --type f | xargs rg -l "ReasoningEffort|Reasoning" 2>/dev/null | head -5

Repository: QuantumNous/new-api

Length of output: 220


🏁 Script executed:

# Check test file for reasoning-related tests
cat service/openaicompat/chat_to_responses_test.go 2>/dev/null | head -100

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Look at responses_to_chat.go to understand the reverse flow
rg "Reasoning|ReasoningEffort" service/openaicompat/responses_to_chat.go -B 3 -A 3

Repository: QuantumNous/new-api

Length of output: 406


Guard against setting Reasoning.Summary when effort disables reasoning.
When req.ReasoningEffort == "none", the code still sets Summary: "detailed", which contradicts the OpenAI Responses API semantics—effort: "none" disables reasoning output, so requesting a summary is meaningless and may fail validation.

Update the condition to exclude "none":

Suggested fix
-	if req.ReasoningEffort != "" {
+	if req.ReasoningEffort != "" && strings.ToLower(req.ReasoningEffort) != "none" {
 		out.Reasoning = &dto.Reasoning{
 			Effort:  req.ReasoningEffort,
 			Summary: "detailed",
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if req.ReasoningEffort != "" {
out.Reasoning = &dto.Reasoning{
Effort: req.ReasoningEffort,
Effort: req.ReasoningEffort,
Summary: "detailed",
}
if req.ReasoningEffort != "" && strings.ToLower(req.ReasoningEffort) != "none" {
out.Reasoning = &dto.Reasoning{
Effort: req.ReasoningEffort,
Summary: "detailed",
}
}
🤖 Prompt for AI Agents
In `@service/openaicompat/chat_to_responses.go` around lines 349 - 353, The
current block in chat_to_responses.go sets out.Reasoning =
&dto.Reasoning{Effort: req.ReasoningEffort, Summary: "detailed"} whenever
req.ReasoningEffort != "", which incorrectly requests a summary even when effort
== "none"; change the guard so you only set Summary (or create out.Reasoning
with Summary) when req.ReasoningEffort is non-empty and not equal to "none"
(e.g., check req.ReasoningEffort != "" && req.ReasoningEffort != "none"),
ensuring dto.Reasoning.Summary is omitted when effort disables reasoning.

}

Expand Down