package model import ( "log/slog" "strings" ) // Anthropic 扩展思考协议的边界(design/04 §3.2)。 const ( // anthropicMinThinkingBudget 是 Messages API 在 thinking.type=enabled 时 // 接受的最小 budget_tokens;低于它会被 400 拒绝, // 因此更低但非零的预算会被抬高并上报,而不是照发。 anthropicMinThinkingBudget = 1024 // anthropicDefaultThinkingBudget 在开启思考但未给 // 显式预算时使用。 anthropicDefaultThinkingBudget = 4096 // anthropicThinkingBodyReserve 是在思考预算之上留给正文的空间: // max_tokens 由思考与可见答案共享,所以 // 预算绝不允许把它全部吃光(design/04 §3.2 红线: // "预算与正文不得互相吃光"). anthropicThinkingBodyReserve = 2048 ) // deepSeekAnthropicEndpoint 报告一次 Anthropic 协议请求是否 // 真的由 DeepSeek 的 /anthropic 端点服务。先检查 base URL, // 因为是端点——而不是模型名——决定 budget_tokens 是否 // 被尊重,而 DeepSeek 端点常常配着一个非 DeepSeek 的 // 模型名(该服务会把未知名称静默映射到 deepseek-flash)。 func deepSeekAnthropicEndpoint(model, baseURL string) bool { if strings.Contains(strings.ToLower(baseURL), "deepseek") { return true } return reasoningVendorFor(model, baseURL) == vendorDeepSeek } // anthropicThinkingFor 解出扩展思考指令以及它所需的 // max_tokens。它返回(可能已被抬高的)max_tokens 与 // 线格式块;块为 nil 表示「不发送思考」(无指令,或 // 显式关闭)。由于 max_tokens 由思考与可见答案共享, // 返回的预算总会为正文留下 anthropicThinkingBodyReserve, // 而预算会吃光答案的请求则改为抬高 max_tokens——该 // 调整会被记录,因此绝不是一次静默改写。 // // 这条规则只适用于 Anthropic 端点。DeepSeek 的 /anthropic 端点 // 会忽略 budget_tokens,所以它绝不能走这里(design/04 §3.2 事实 6)。 func anthropicThinkingFor(th *Thinking, maxTokens int, log *slog.Logger) (int, *anthropicThinking) { if th == nil || (th.Enable != nil && !*th.Enable) { return maxTokens, nil } budget := th.BudgetTokens if budget == 0 { budget = anthropicDefaultThinkingBudget } if budget < anthropicMinThinkingBudget { log.Warn("thinking budget raised to the protocol minimum", "provider", "anthropic", "field", "thinking.budget_tokens", "budget_tokens", th.BudgetTokens, "sent", anthropicMinThinkingBudget) budget = anthropicMinThinkingBudget } if need := budget + anthropicThinkingBodyReserve; maxTokens < need { log.Warn("max_tokens raised to leave room for thinking plus the answer", "provider", "anthropic", "budget_tokens", budget, "max_tokens_requested", maxTokens, "max_tokens_sent", need, "body_reserve", anthropicThinkingBodyReserve) maxTokens = need } return maxTokens, &anthropicThinking{Type: anthropicThinkingTypeEnabled, BudgetTokens: budget} } // anthropicDeepSeekReasoning 解出 DeepSeek /anthropic 请求的 // 推理旋钮。与 Anthropic 端点的两点差异决定了它:该 // 端点忽略 budget_tokens,所以 max_tokens 保持不动(抬高它 // 会错误反映提供商实际采用的预算),而强度由 // output_config.effort 控制,它没有 "none" 取值——在那里「关闭」 // 意图由 thinking 开关自身承载(design/04 §3.2 事实 2 与 6)。 func anthropicDeepSeekReasoning(log *slog.Logger, model string, th *Thinking, effort string) (*anthropicThinking, *anthropicOutputConfig) { var block *anthropicThinking if th != nil { if th.BudgetTokens > 0 { log.Warn("thinking.budget_tokens ignored by the DeepSeek /anthropic endpoint: strength is output_config.effort, so max_tokens was not raised for the budget", "provider", "deepseek", "field", "thinking.budget_tokens", "budget_tokens", th.BudgetTokens, "model", model) } switch { case th.Enable != nil: block = &anthropicThinking{Type: thinkingSwitchValue(*th.Enable), BudgetTokens: th.BudgetTokens} case th.BudgetTokens > 0: block = &anthropicThinking{Type: thinkingTypeEnabled, BudgetTokens: th.BudgetTokens} } } effort = strings.TrimSpace(effort) if effort == "" { return block, nil } if effort == effortNone { if block == nil || block.Type == thinkingTypeEnabled { log.Warn(`reasoning_effort none mapped to thinking.type=disabled: the DeepSeek /anthropic endpoint has no output_config.effort "none"`, "provider", "deepseek", "field", "reasoning_effort", "value", effort, "model", model) } return &anthropicThinking{Type: thinkingTypeDisabled}, nil } if block != nil && block.Type == thinkingTypeDisabled { log.Warn("output_config.effort dropped: thinking is explicitly disabled", "provider", "deepseek", "field", "reasoning_effort", "value", effort, "model", model) return block, nil } tier, ok := normalizeDeepSeekEffort(effort) if !ok { log.Warn("reasoning_effort dropped: unrecognized value", "provider", "deepseek", "field", "reasoning_effort", "value", effort, "model", model) return block, nil } if tier != effort { log.Warn("reasoning_effort alias normalized to the tier DeepSeek applies", "provider", "deepseek", "field", "reasoning_effort", "value", effort, "sent", tier, "model", model) } return block, &anthropicOutputConfig{Effort: tier} } // anthropicThinkingOn 报告一个已解出的 DeepSeek /anthropic 请求的 // 实际思考状态:省略开关会保留提供商默认值,而 // DeepSeek 的默认是开启。 func anthropicThinkingOn(th *anthropicThinking) bool { return th == nil || th.Type != thinkingTypeDisabled }