myown / internal /thinking /trigger.go
sshinmen's picture
chore: apply requested thinking logic changes
22728c9
Raw
History Blame Contribute Delete
3.3 kB
package thinking
import (
"strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/tidwall/gjson"
)
// hasThinkTrigger reports whether user prompt text contains the standalone word "think".
//
// It scans common prompt-bearing keys (system/content/text/instructions/prompt)
// and avoids triggering on unrelated fields such as model names.
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{}
}