docs(comments): 注释中文化 · internal/runtime/model + internal/services/backthink(54 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 只翻注释/文档字符串:`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
This commit is contained in:
AskaEth committed 2026-10-04 16:27:54 +08:00
1 parent f5cf117482
commit 3dc1232bc2
54 files changed
+1923 -1923

No files matched your search

+59 -59
View File
@@ -8,32 +8,32 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
)
// discardLogger returns a logger that drops every record but still carries the
// adapter's module source, so an adapter built without an injected logger can
// never emit an unsourced record.
// discardLogger 返回一个丢弃所有记录的日志器,但仍带有
// 适配器的模块来源,因此未注入日志器而构建的适配器
// 绝不会产生无来源记录。
func discardLogger(source string) *slog.Logger {
return logger.WithSource(slog.New(slog.NewTextHandler(io.Discard, nil)), source)
}
// Reasoning wire values shared by the OpenAI-compatible and Anthropic adapters
// (design/04 §3.2, checked against the provider documents on 2026-10-03).
// OpenAI 兼容与 Anthropic 适配器共用的推理线取值
// (design/04 §3.2,已于 2026-10-03 对照各提供商文档核对)。
const (
// thinkingTypeEnabled and thinkingTypeDisabled are the values of the
// {"thinking":{"type":...}} switch. DeepSeek's default is enabled, so omitting
// the field leaves thinking ON — "do not intervene" is not a way to switch it
// off; only an explicit disabled (or reasoning_effort "none") is.
// thinkingTypeEnabled 与 thinkingTypeDisabled 是
// {"thinking":{"type":...}} 开关的取值。DeepSeek 默认开启,所以省略
// 该字段会让思考保持开启——「不干预」不是关闭它的办法;
// 只有显式 disabled(或 reasoning_effort "none")才是。
thinkingTypeEnabled = "enabled"
thinkingTypeDisabled = "disabled"
// effortNone is the reasoning_effort value that switches thinking off. It is a
// switch, not a strength tier, so it must never be replaced by a tier: mapping
// it onto "low" would invert the caller's intent (thinking off → thinking on).
// effortNone 是关闭思考的 reasoning_effort 取值。它是
// 开关而非强度档位,所以绝不能被替换成某个档位:把它
// 映射到 "low" 会颠倒调用方的意图(关闭思考 → 开启思考)。
effortNone = "none"
)
// reasoningVendor identifies the vendor behind an OpenAI-compatible endpoint.
// The wire protocol is the same for all of them, but the accepted
// reasoning_effort values are not, so a value must be checked against the right
// whitelist instead of being forwarded blindly.
// reasoningVendor 标识 OpenAI 兼容端点背后的厂商。
// 它们的线协议相同,但可接受的
// reasoning_effort 取值不同,所以必须按正确的
// 白名单校验,而不是盲目转发。
type reasoningVendor string
const (
@@ -43,10 +43,10 @@ const (
vendorUnknown reasoningVendor = "unknown"
)
// reasoningEffortAliases is DeepSeek's documented compatibility mapping: the
// service accepts these aliases and applies the mapped tier. The mapping is
// applied on our side too, so the request body always carries the tier the
// provider documents as the effective one (design/04 §3.2 fact 1).
// reasoningEffortAliases 是 DeepSeek 记录的兼容映射:
// 服务接受这些别名并采用映射后的档位。我们在自己这一侧
// 也做同一映射,因此请求体总是携带提供商
// 记录为有效值的那一档(design/04 §3.2 事实 1)。
var reasoningEffortAliases = map[string]string{
"minimal": "low",
"medium": "high",
@@ -54,20 +54,20 @@ var reasoningEffortAliases = map[string]string{
"ultra": "max",
}
// reasoningEffortAllowed is the per-vendor set of accepted reasoning_effort
// values. A vendor absent from this table has no reasoning_effort support at all,
// which is reported rather than guessed.
// reasoningEffortAllowed 是按厂商划分的可接受 reasoning_effort
// 取值集合。不在本表里的厂商完全没有 reasoning_effort 支持,
// 这一点会被上报,而不是靠猜。
//
// There is deliberately no "round down to the nearest tier" fallback: an
// unsupported value is dropped and reported instead, because silently changing it
// can invert the caller's intent — the original bug mapped DeepSeek's "none"
// (thinking off) onto "low" (thinking on) exactly that way.
// 这里刻意没有「向下取到最近档位」的降级:不支持的取值
// 会被丢弃并上报,因为静默改写它可能会颠倒调用方的
// 意图——早先的 bug 正是这样把 DeepSeek 的 "none"(关闭思考)
// 映射到了 "low"(开启思考)。
var reasoningEffortAllowed = map[reasoningVendor]map[string]bool{
vendorOpenAI: tierSet("none", "minimal", "low", "medium", "high", "xhigh", "max"),
vendorDeepSeek: tierSet("none", "low", "high", "max"),
}
// tierSet builds a membership set from the given tiers.
// tierSet 由给定档位构建一个成员集合。
func tierSet(tiers ...string) map[string]bool {
set := make(map[string]bool, len(tiers))
for _, t := range tiers {
@@ -76,12 +76,12 @@ func tierSet(tiers ...string) map[string]bool {
return set
}
// reasoningVendorFor classifies the vendor of an OpenAI-compatible request from
// its model name, falling back to the base URL when the name does not identify
// one. An unrecognized pair yields vendorUnknown so the caller reports "not
// supported" instead of assuming OpenAI semantics. The Anthropic adapter uses the
// same classification to tell a DeepSeek /anthropic endpoint (which ignores
// budget_tokens) from the Anthropic endpoint.
// reasoningVendorFor 按模型名分类 OpenAI 兼容请求的厂商,
// 模型名无法识别出厂商时退回到 base URL。
// 无法识别的组合得到 vendorUnknown,好让调用方上报「不
// 支持」而不是假定 OpenAI 语义。Anthropic 适配器用
// 同一套分类来区分 DeepSeek 的 /anthropic 端点(它忽略
// budget_tokens)与 Anthropic 端点。
func reasoningVendorFor(model, baseURL string) reasoningVendor {
m := strings.ToLower(strings.TrimSpace(model))
switch {
@@ -105,7 +105,7 @@ func reasoningVendorFor(model, baseURL string) reasoningVendor {
return vendorUnknown
}
// thinkingSwitchValue maps an explicit on/off switch onto its wire value.
// thinkingSwitchValue 把显式开/关开关映射到它的线取值。
func thinkingSwitchValue(enable bool) string {
if enable {
return thinkingTypeEnabled
@@ -113,10 +113,10 @@ func thinkingSwitchValue(enable bool) string {
return thinkingTypeDisabled
}
// normalizeDeepSeekEffort maps an effort onto the tier DeepSeek applies and
// reports whether the value is one the service documents: the documented set
// (none/low/high/max) passes through, the compatibility aliases map to their
// official tier.
// normalizeDeepSeekEffort 把 effort 映射到 DeepSeek 实际采用的档位,
// 并报告该值是否为服务记录的取值:记录在案的集合
// (none/low/high/max)原样通过,兼容别名映射到其
// 官方档位。
func normalizeDeepSeekEffort(effort string) (string, bool) {
if tier, ok := reasoningEffortAliases[effort]; ok {
return tier, true
@@ -127,16 +127,16 @@ func normalizeDeepSeekEffort(effort string) (string, bool) {
return "", false
}
// resolveReasoningEffort validates effort against the vendor's whitelist and
// returns the value to send; "" drops the field. An unrecognized value is never
// turned into a different tier — it is dropped and reported with provider, field,
// value and model, because changing it can invert the caller's intent. A DeepSeek
// compatibility alias is normalized to the tier the service applies and reported.
// resolveReasoningEffort 针对厂商白名单校验 effort 并
// 返回要发送的值;"" 表示丢弃该字段。无法识别的值绝不会
// 被换成别的档位——它会被丢弃,并把 provider、field、
// value 与 model 一并上报,因为改动它会颠倒调用方的意图。DeepSeek
// 的兼容别名会被归一化为服务采用的档位并上报。
//
// vendor must be a vendor with a reasoning_effort whitelist (DeepSeek / OpenAI):
// the shapes that carry no such field (vLLM / DashScope) and the endpoints whose
// shape is unknown drop the effort themselves and report it, so their callers
// never reach this function (see reasoning_shape.go, 裁决 03.4.2).
// vendor 必须是带 reasoning_effort 白名单的厂商(DeepSeek / OpenAI):
// 不带该字段的形状(vLLM / DashScope)以及形状未知的
// 端点会自行丢弃 effort 并上报,因此它们的调用方
// 永远不会走到本函数(见 reasoning_shape.go,裁决 03.4.2)。
func resolveReasoningEffort(log *slog.Logger, vendor reasoningVendor, effort, model string) string {
effort = strings.TrimSpace(effort)
if effort == "" {
@@ -159,8 +159,8 @@ func resolveReasoningEffort(log *slog.Logger, vendor reasoningVendor, effort, mo
return ""
}
// toolChoiceForces reports whether a tool_choice directive obliges the model to
// call a tool ("required", Anthropic's "any", or a named tool). auto/none do not.
// toolChoiceForces 报告 tool_choice 指令是否强制模型
// 调用工具("required"、Anthropic 的 "any",或具名工具)。auto/none 不会。
func toolChoiceForces(toolChoice any) bool {
switch v := toolChoice.(type) {
case nil:
@@ -183,7 +183,7 @@ func toolChoiceForces(toolChoice any) bool {
return false
}
// forcedToolChoiceType reports whether a tool_choice "type" value forces a call.
// forcedToolChoiceType 报告 tool_choice 的 "type" 取值是否强制调用。
func forcedToolChoiceType(value any) bool {
t, _ := value.(string)
switch strings.ToLower(strings.TrimSpace(t)) {
@@ -193,14 +193,14 @@ func forcedToolChoiceType(value any) bool {
return false
}
// reportForcedToolChoiceWithThinking warns about the DeepSeek red line that a
// forced tool call — tool_choice "required"/"any"/a named tool — and thinking are
// not compatible: while thinking is on (its default) the API rejects the forcing
// directive with a 400, so the caller must switch thinking off explicitly first
// (design/04 §3.2 fact 5). It only reports: resolution must never switch thinking
// off on its own. On the OpenAI-compatible adapter the directive is not on the
// wire yet (M2), so the warning is prospective there — it still records that the
// requested combination can never be honoured.
// reportForcedToolChoiceWithThinking 就 DeepSeek 红线报警:
// 强制型工具调用——tool_choice "required"/"any"/具名工具——与思考
// 不兼容:思考开启(其默认)时 API 会用 400 拒绝该强制
// 指令,所以调用方必须先把思考显式关掉
// (design/04 §3.2 事实 5)。它只上报:解算过程绝不能自行
// 关掉思考。在 OpenAI 兼容适配器上该指令尚未上线
// (M2),所以那里只是前瞻性警告——它仍然记录了
// 所请求的组合永远无法被满足。
func reportForcedToolChoiceWithThinking(log *slog.Logger, provider, model string, toolChoice any, thinkingOn bool) {
if !thinkingOn || !toolChoiceForces(toolChoice) {
return