refactor(thinking): use bracket tags for thinking meta

Align thinking suffix handling on a single bracket-style marker.

NormalizeThinkingModel strips a terminal `[value]` segment from
model identifiers and turns it into either a thinking budget (for
numeric values) or a reasoning effort hint (for strings). Emission
of `ThinkingIncludeThoughtsMetadataKey` is removed.

Executor helpers and the example config are updated so their
comments reference the new `[value]` suffix format instead of the
legacy dash variants.

BREAKING CHANGE: dash-based thinking suffixes (`-thinking`,
`-thinking-N`, `-reasoning`, `-nothinking`) are no longer parsed
for thinking metadata; only `[value]` annotations are recognized.
This commit is contained in:
hkfires
2025-12-11 18:17:28 +08:00
parent 6285459c08
commit facfe7c518
3 changed files with 41 additions and 87 deletions

View File

@@ -100,7 +100,7 @@ ws-auth: false
# excluded-models:
# - "claude-opus-4-5-20251101" # exclude specific models (exact match)
# - "claude-3-*" # wildcard matching prefix (e.g. claude-3-7-sonnet-20250219)
# - "*-think" # wildcard matching suffix (e.g. claude-opus-4-5-thinking)
# - "*-thinking" # wildcard matching suffix (e.g. claude-opus-4-5-thinking)
# - "*haiku*" # wildcard matching substring (e.g. claude-3-5-haiku-20241022)
# OpenAI compatibility providers

View File

@@ -11,7 +11,7 @@ import (
"github.com/tidwall/sjson"
)
// applyThinkingMetadata applies thinking config from model suffix metadata (e.g., -reasoning, -thinking-N)
// applyThinkingMetadata applies thinking config from model suffix metadata (e.g., [high], [8192])
// for standard Gemini format payloads. It normalizes the budget when the model supports thinking.
func applyThinkingMetadata(payload []byte, metadata map[string]any, model string) []byte {
budgetOverride, includeOverride, ok := util.ResolveThinkingConfigFromMetadata(model, metadata)
@@ -28,7 +28,7 @@ func applyThinkingMetadata(payload []byte, metadata map[string]any, model string
return util.ApplyGeminiThinkingConfig(payload, budgetOverride, includeOverride)
}
// applyThinkingMetadataCLI applies thinking config from model suffix metadata (e.g., -reasoning, -thinking-N)
// applyThinkingMetadataCLI applies thinking config from model suffix metadata (e.g., [high], [8192])
// for Gemini CLI format payloads (nested under "request"). It normalizes the budget when the model supports thinking.
func applyThinkingMetadataCLI(payload []byte, metadata map[string]any, model string) []byte {
budgetOverride, includeOverride, ok := util.ResolveThinkingConfigFromMetadata(model, metadata)

View File

@@ -14,100 +14,57 @@ const (
)
// NormalizeThinkingModel parses dynamic thinking suffixes on model names and returns
// the normalized base model with extracted metadata. Supported patterns:
// - "-thinking-<number>" extracts a numeric budget
// - "-thinking-<level>" extracts a reasoning effort level (minimal/low/medium/high/xhigh/auto/none)
// - "-thinking" maps to a default reasoning effort of "medium"
// - "-reasoning" maps to dynamic budget (-1) and include_thoughts=true
// - "-nothinking" maps to budget=0 and include_thoughts=false
// the normalized base model with extracted metadata. Supported pattern:
// - "[<value>]" where value can be:
// - A numeric budget (e.g., "[8192]", "[16384]")
// - A reasoning effort level (e.g., "[high]", "[medium]", "[low]")
//
// Examples:
// - "claude-sonnet-4-5-20250929[16384]" → budget=16384
// - "gpt-5.1[high]" → reasoning_effort="high"
// - "gemini-2.5-pro[32768]" → budget=32768
//
// Note: Empty brackets "[]" are not supported and will be ignored.
func NormalizeThinkingModel(modelName string) (string, map[string]any) {
if modelName == "" {
return modelName, nil
}
lower := strings.ToLower(modelName)
baseModel := modelName
var (
budgetOverride *int
includeThoughts *bool
reasoningEffort *string
matched bool
)
switch {
case strings.HasSuffix(lower, "-nothinking"):
baseModel = modelName[:len(modelName)-len("-nothinking")]
budget := 0
include := false
budgetOverride = &budget
includeThoughts = &include
matched = true
case strings.HasSuffix(lower, "-reasoning"):
baseModel = modelName[:len(modelName)-len("-reasoning")]
budget := -1
include := true
budgetOverride = &budget
includeThoughts = &include
matched = true
default:
if idx := strings.LastIndex(lower, "-thinking-"); idx != -1 {
// Skip stripping if the original model is a registered thinking model.
// This prevents "-thinking-2507" in "qwen3-235b-a22b-thinking-2507" from being parsed.
if ModelSupportsThinking(modelName) {
break
// Match "[value]" pattern at the end of the model name
if idx := strings.LastIndex(modelName, "["); idx != -1 {
if !strings.HasSuffix(modelName, "]") {
// Incomplete bracket, ignore
return baseModel, nil
}
value := modelName[idx+len("-thinking-"):]
if value != "" {
if parsed, ok := parseIntPrefix(value); ok {
value := modelName[idx+1 : len(modelName)-1] // Extract content between [ and ]
if value == "" {
// Empty brackets not supported
return baseModel, nil
}
candidateBase := modelName[:idx]
if ModelUsesThinkingLevels(candidateBase) {
baseModel = candidateBase
// Numeric suffix on level-aware models should still surface as reasoning effort metadata.
raw := strings.ToLower(strings.TrimSpace(value))
if raw != "" {
reasoningEffort = &raw
}
matched = true
} else {
// Auto-detect: pure numeric → budget, string → reasoning effort level
if parsed, ok := parseIntPrefix(value); ok {
// Numeric value: treat as thinking budget
baseModel = candidateBase
budgetOverride = &parsed
matched = true
}
} else {
baseModel = modelName[:idx]
if normalized, ok := NormalizeReasoningEffortLevel(baseModel, value); ok {
reasoningEffort = &normalized
matched = true
} else if !ModelUsesThinkingLevels(baseModel) {
// Keep unknown effort tokens so callers can honor user intent even without normalization.
raw := strings.ToLower(strings.TrimSpace(value))
if raw != "" {
reasoningEffort = &raw
matched = true
} else {
baseModel = modelName
}
} else {
raw := strings.ToLower(strings.TrimSpace(value))
if raw != "" {
reasoningEffort = &raw
matched = true
} else {
baseModel = modelName
}
}
}
}
} else if strings.HasSuffix(lower, "-thinking") {
candidateBase := modelName[:len(modelName)-len("-thinking")]
// Only strip the suffix if the original model is NOT a registered thinking model.
// This prevents stripping "-thinking" from models like "kimi-k2-thinking" where
// the suffix is part of the model's actual name.
if !ModelSupportsThinking(modelName) {
// String value: treat as reasoning effort level
baseModel = candidateBase
effort := "medium"
reasoningEffort = &effort
raw := strings.ToLower(strings.TrimSpace(value))
if raw != "" {
reasoningEffort = &raw
matched = true
}
}
@@ -123,9 +80,6 @@ func NormalizeThinkingModel(modelName string) (string, map[string]any) {
if budgetOverride != nil {
metadata[ThinkingBudgetMetadataKey] = *budgetOverride
}
if includeThoughts != nil {
metadata[ThinkingIncludeThoughtsMetadataKey] = *includeThoughts
}
if reasoningEffort != nil {
metadata[ReasoningEffortMetadataKey] = *reasoningEffort
}