Files
AskaEth 3dc1232bc2
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/model + internal/services/backthink(54 文件,纯替换)
- 只翻注释/文档字符串:`go/scanner` 剥注释后按 token 序列+literal 逐 token 比对,**54/54 文件 SAME**;
  改动文件总行数与 HEAD 漂移 = 0(严格 1:1,无合并/拆行)
- 裁决编号与 design 节号逐字保留(03.4.1/03.4.2/03.4.3/14.4.1/14.4.5/07.4.2-4、design/04 §3.1/§3.2 等);
  `chat_template_kwargs` / `enable_thinking` / `reasoning_effort` / `thinking:{type}` 字段名与形状说明未动
- 存疑译法(已登记):`persona` 在叙事语境用「角色」、标识符语境保留 `persona`(仓库既有模块本身混用);
  `rung`→「级」;`provider` 存在「提供商/提供者」两种落笔(可按需一键统一)
- 复核:build/vet 绿 + 两包测试绿;gofmt 干净;残留英文注释 0 条
- 既有存量(未修,只报告):`model/anthropic_test.go` 612 行 >500;`applyAnthropicEvent` 115 行 >80
2026-10-04 16:27:54 +08:00

127 lines
5.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}