iop/apps/edge/internal/openai/chat_policy.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" {
return fmt.Errorf("reasoning_effort must be one of none, low, medium, or high")
}
}
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
}