caller별 예외 대신 요청 의미와 protocol profile capability로 operation을 선택해 tools와 effort 조합을 보존한다. 지원하지 않는 effort는 가장 가까운 하위 등급으로만 내리고 상향 매핑은 거부한다.
148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
package openai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func validateThinkControl(req *chatCompletionRequest) error {
|
|
if req.ThinkingTokenBudget != nil && *req.ThinkingTokenBudget < 0 {
|
|
return fmt.Errorf("thinking_token_budget must be non-negative")
|
|
}
|
|
if req.ReasoningEffort != nil {
|
|
eff := *req.ReasoningEffort
|
|
if eff == "" {
|
|
return fmt.Errorf("reasoning_effort cannot be empty when present")
|
|
}
|
|
if eff != "none" && eff != "low" && eff != "medium" && eff != "high" && eff != "xhigh" && eff != "max" {
|
|
return fmt.Errorf("reasoning_effort must be one of none, low, medium, high, xhigh, or max")
|
|
}
|
|
}
|
|
if req.Think != nil && !*req.Think {
|
|
if req.ReasoningEffort != nil && *req.ReasoningEffort != "none" {
|
|
return fmt.Errorf("think=false conflicts with reasoning_effort=%s", *req.ReasoningEffort)
|
|
}
|
|
}
|
|
if req.ThinkingTokenBudget != nil {
|
|
if req.Think != nil && !*req.Think {
|
|
return fmt.Errorf("thinking_token_budget cannot be set when think=false")
|
|
}
|
|
if req.ReasoningEffort != nil && *req.ReasoningEffort == "none" {
|
|
return fmt.Errorf("thinking_token_budget cannot be set when reasoning_effort=none")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func applyModelCatalogGenerationPolicyToChat(req *chatCompletionRequest, entry config.ModelCatalogEntry, strictOutput bool, providerNativeThinking bool) {
|
|
if req == nil {
|
|
return
|
|
}
|
|
applyOutputTokenPolicy(&req.MaxTokens, req.MaxCompletionTokens, entry.DefaultMaxTokens, entry.MinMaxTokens)
|
|
if entry.DefaultThinkingTokenBudget > 0 && !providerNativeThinking && chatRequestAllowsDefaultThinkingBudget(*req) {
|
|
if req.ThinkingTokenBudget == nil {
|
|
req.ThinkingTokenBudget = intPtr(entry.DefaultThinkingTokenBudget)
|
|
}
|
|
if strictOutput && req.Think == nil {
|
|
req.Think = boolPtr(true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyModelCatalogGenerationPolicyToResponses(req *responsesRequest, entry config.ModelCatalogEntry) {
|
|
if req == nil {
|
|
return
|
|
}
|
|
applyOutputTokenPolicy(&req.MaxOutputTokens, nil, entry.DefaultMaxTokens, entry.MinMaxTokens)
|
|
}
|
|
|
|
func applyOutputTokenPolicy(primary **int, fallback *int, defaultTokens, minTokens int) {
|
|
if primary == nil {
|
|
return
|
|
}
|
|
current := 0
|
|
if *primary != nil {
|
|
current = **primary
|
|
} else if fallback != nil {
|
|
current = *fallback
|
|
}
|
|
|
|
next := current
|
|
if next == 0 && defaultTokens > 0 {
|
|
next = defaultTokens
|
|
}
|
|
if minTokens > 0 && (next == 0 || next < minTokens) {
|
|
next = minTokens
|
|
}
|
|
if next > 0 && next != current {
|
|
*primary = intPtr(next)
|
|
}
|
|
}
|
|
|
|
func chatRequestAllowsDefaultThinkingBudget(req chatCompletionRequest) bool {
|
|
if req.Think != nil && !*req.Think {
|
|
return false
|
|
}
|
|
if req.ReasoningEffort != nil && *req.ReasoningEffort == "none" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func chatRequestHasProviderNativeThinking(rawBody []byte) bool {
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(rawBody, &raw); err != nil {
|
|
return false
|
|
}
|
|
ctkRaw, ok := raw["chat_template_kwargs"]
|
|
if !ok {
|
|
return false
|
|
}
|
|
var ctk map[string]json.RawMessage
|
|
if err := json.Unmarshal(ctkRaw, &ctk); err != nil {
|
|
return false
|
|
}
|
|
_, hasEnableThinking := ctk["enable_thinking"]
|
|
_, hasBudget := ctk["thinking_token_budget"]
|
|
return hasEnableThinking || hasBudget
|
|
}
|
|
|
|
func intPtr(v int) *int {
|
|
return &v
|
|
}
|
|
|
|
func boolPtr(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func chatRunMetadata(runMeta map[string]string, req chatCompletionRequest, outputPolicy strictOutputPolicy) map[string]string {
|
|
if runMeta == nil {
|
|
runMeta = make(map[string]string)
|
|
}
|
|
runMeta["openai_model"] = req.Model
|
|
runMeta["openai_stream"] = fmt.Sprintf("%t", req.Stream)
|
|
runMeta["strict_output"] = fmt.Sprintf("%t", outputPolicy.Strict)
|
|
return runMeta
|
|
}
|
|
|
|
func (s *Server) resolveStrictOutput() bool {
|
|
return s.cfg.StrictOutput
|
|
}
|
|
|
|
func (s *Server) resolveStrictStreamBuffer() bool {
|
|
return s.cfg.StrictStreamBuffer
|
|
}
|
|
|
|
func (s *Server) resolveOutputPolicy(prompt string) strictOutputPolicy {
|
|
policy := strictOutputPolicy{
|
|
Strict: s.resolveStrictOutput(),
|
|
StreamBuffer: s.resolveStrictStreamBuffer(),
|
|
}
|
|
if !policy.Strict {
|
|
return policy
|
|
}
|
|
policy.XMLCompletionTool, policy.XMLResultTag = inferXMLCompletionContract(prompt)
|
|
policy.ContractInstruction = policy.XMLCompletionTool != ""
|
|
return policy
|
|
}
|