Files
descrybe/apps/api/internal/processing/openai_chat_params.go
T

98 lines
3.2 KiB
Go
Raw Normal View History

2026-08-23 12:40:59 +02:00
package processing
import (
"strings"
)
// openAIChatUsesMaxCompletionTokens reports models that reject legacy max_tokens
// on Chat Completions (o-series + GPT-5 family including GPT-5.6).
// See https://developers.openai.com/api/reference/resources/chat
func openAIChatUsesMaxCompletionTokens(model string) bool {
return openAIReasoningChatModel(model)
}
// openAIChatOmitsCustomTemperature reports models that reject non-default
// temperature (only the API default of 1 is accepted, if temperature is sent).
func openAIChatOmitsCustomTemperature(model string) bool {
return openAIReasoningChatModel(model)
}
// openAIChatSupportsReasoningEffort reports Chat Completions models that accept
// top-level reasoning_effort (Responses API uses reasoning.effort instead).
func openAIChatSupportsReasoningEffort(model string) bool {
return openAIReasoningChatModel(model)
}
// openAIReasoningChatModel matches OpenAI reasoning / GPT-5(+).5/5.6 chat models
// and common snapshot aliases (e.g. gpt-5.6-luna-2026-…).
func openAIReasoningChatModel(model string) bool {
m := strings.ToLower(strings.TrimSpace(model))
if m == "" {
return false
}
// Strip provider prefixes used by OpenRouter-style gateways.
if i := strings.LastIndex(m, "/"); i >= 0 {
m = m[i+1:]
}
switch {
case strings.HasPrefix(m, "o1"),
strings.HasPrefix(m, "o3"),
strings.HasPrefix(m, "o4"),
strings.HasPrefix(m, "gpt-5"),
strings.HasPrefix(m, "chatgpt-o"):
return true
default:
return false
}
}
// defaultReasoningEffort picks Chat Completions reasoning_effort when unset.
// Long formula-HTML enhance uses "low" so max_completion_tokens prefers visible
// HTML over deep hidden reasoning. Tiny probes use "none" for speed/cost.
func defaultReasoningEffort(model string, maxTokens int, explicit string) string {
explicit = strings.ToLower(strings.TrimSpace(explicit))
if explicit != "" {
return explicit
}
if !openAIChatSupportsReasoningEffort(model) {
return ""
}
if maxTokens <= 0 || maxTokens < 512 {
return "none"
}
return "low"
}
// chatCompletionBody is the Chat Completions JSON body. Temperature is a pointer
// so reasoning models can omit it entirely (omitempty).
type chatCompletionBody struct {
Model string `json:"model"`
Messages []chatMessage `json:"messages"`
Temperature *float64 `json:"temperature,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
}
// buildChatCompletionBody maps CompleteOptions onto the correct Chat Completions
// fields for classic vs GPT-5 / o-series models.
func buildChatCompletionBody(model string, messages []chatMessage, temperature float64, maxTokens int, reasoningEffort string) chatCompletionBody {
body := chatCompletionBody{
Model: model,
Messages: messages,
}
if openAIChatUsesMaxCompletionTokens(model) {
if maxTokens > 0 {
body.MaxCompletionTokens = maxTokens
}
} else if maxTokens > 0 {
body.MaxTokens = maxTokens
}
if !openAIChatOmitsCustomTemperature(model) {
t := temperature
body.Temperature = &t
}
body.ReasoningEffort = defaultReasoningEffort(model, maxTokens, reasoningEffort)
return body
}