| package thinking |
|
|
| import ( |
| "strings" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" |
| "github.com/tidwall/gjson" |
| ) |
|
|
| |
| |
| |
| |
| func hasThinkTrigger(body []byte) bool { |
| if len(body) == 0 || !gjson.ValidBytes(body) { |
| return false |
| } |
| root := gjson.ParseBytes(body) |
| return scanThinkInNode(root, "") |
| } |
|
|
| func scanThinkInNode(node gjson.Result, key string) bool { |
| lk := strings.ToLower(strings.TrimSpace(key)) |
| switch node.Type { |
| case gjson.String: |
| if isPromptKey(lk) { |
| return containsThinkWord(node.String()) |
| } |
| return false |
| case gjson.JSON: |
| if node.IsArray() { |
| for _, item := range node.Array() { |
| if scanThinkInNode(item, key) { |
| return true |
| } |
| } |
| return false |
| } |
| if node.IsObject() { |
| found := false |
| node.ForEach(func(k, v gjson.Result) bool { |
| if scanThinkInNode(v, k.String()) { |
| found = true |
| return false |
| } |
| return true |
| }) |
| return found |
| } |
| } |
| return false |
| } |
|
|
| func isPromptKey(key string) bool { |
| switch key { |
| case "system", "content", "text", "instructions", "prompt": |
| return true |
| default: |
| return false |
| } |
| } |
|
|
| func containsThinkWord(text string) bool { |
| if text == "" { |
| return false |
| } |
| lower := strings.ToLower(text) |
| needle := "think" |
| start := 0 |
| for { |
| idx := strings.Index(lower[start:], needle) |
| if idx < 0 { |
| return false |
| } |
| idx += start |
| leftOK := idx == 0 || !isWordChar(rune(lower[idx-1])) |
| rightPos := idx + len(needle) |
| rightOK := rightPos >= len(lower) || !isWordChar(rune(lower[rightPos])) |
| if leftOK && rightOK { |
| return true |
| } |
| start = idx + len(needle) |
| if start >= len(lower) { |
| return false |
| } |
| } |
| } |
|
|
| func isWordChar(r rune) bool { |
| return (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' |
| } |
|
|
| func defaultThinkConfig(modelInfo *registry.ModelInfo) ThinkingConfig { |
| if modelInfo == nil || modelInfo.Thinking == nil { |
| return ThinkingConfig{} |
| } |
| support := modelInfo.Thinking |
|
|
| if support.DynamicAllowed { |
| return ThinkingConfig{Mode: ModeAuto, Budget: -1} |
| } |
|
|
| if len(support.Levels) > 0 { |
| for _, level := range support.Levels { |
| l := strings.ToLower(strings.TrimSpace(level)) |
| if l == string(LevelHigh) { |
| return ThinkingConfig{Mode: ModeLevel, Level: LevelHigh} |
| } |
| } |
| for _, level := range support.Levels { |
| l := strings.ToLower(strings.TrimSpace(level)) |
| switch l { |
| case string(LevelXHigh): |
| return ThinkingConfig{Mode: ModeLevel, Level: LevelXHigh} |
| case string(LevelMedium): |
| return ThinkingConfig{Mode: ModeLevel, Level: LevelMedium} |
| case string(LevelLow): |
| return ThinkingConfig{Mode: ModeLevel, Level: LevelLow} |
| case string(LevelMinimal): |
| return ThinkingConfig{Mode: ModeLevel, Level: LevelMinimal} |
| case string(LevelNone): |
| return ThinkingConfig{Mode: ModeNone, Budget: 0} |
| } |
| } |
| } |
|
|
| if support.Max > 0 { |
| return ThinkingConfig{Mode: ModeBudget, Budget: support.Max} |
| } |
| if support.ZeroAllowed { |
| return ThinkingConfig{Mode: ModeNone, Budget: 0} |
| } |
| if support.Min > 0 { |
| return ThinkingConfig{Mode: ModeBudget, Budget: support.Min} |
| } |
|
|
| return ThinkingConfig{} |
| } |
|
|