Files
Cyrene_v2/internal/runtime/model/factory.go
T
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

161 lines
6.1 KiB
Go
Raw 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 (
"errors"
"fmt"
"log/slog"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
)
// 提供商感知工厂接受的提供商名。这是工厂目前理解的仅有的
// 两种协议;新增厂商意味着在这里加一个 case
// 再加一个配套适配器,而不必改动包内其它部分。
const (
// ProviderOpenAI 选择 OpenAI 兼容的 /chat/completions 协议。
ProviderOpenAI = "openai"
// ProviderAnthropic 选择 Anthropic Messages 的 /v1/messages 协议。
ProviderAnthropic = "anthropic"
)
// 配置的提供商名无法识别时,由提供商感知工厂返回
// ErrUnknownProvider。
var ErrUnknownProvider = errors.New("model: unknown provider")
// NewChatAdapter 按 cfg.Provider 构建单个提供商适配器。
// 提供商为空时为向后兼容默认用 openai。无法识别的
// 提供商返回 ErrUnknownProvider。
func NewChatAdapter(cfg config.LLMConfig) (Adapter, error) {
switch cfg.Provider {
case ProviderOpenAI, "":
return NewOpenAIChat(cfg), nil
case ProviderAnthropic:
return NewAnthropicChat(cfg), nil
default:
return nil, fmt.Errorf("model: %w: provider %q", ErrUnknownProvider, cfg.Provider)
}
}
// NewChatChain 为配置的提供商构建降级 Chain:先是主
// 模型,再是提供商已设置且不同的降级模型。每一步
// 都由所选协议的适配器服务。提供商为空
// 时默认用 openai。无法识别的提供商返回 ErrUnknownProvider。
func NewChatChain(cfg config.LLMConfig) (*Chain, error) {
return NewChatChainWithLog(cfg, nil)
}
// NewChatChainWithLog 是 NewChatChain,并为它构建的每个适配器
// 挂上日志器,好让提供商层面的推理决策(为容纳
// max_tokens 而抬高的思考预算、被降级或忽略的 reasoning_effort 值)
// 带着模块来源上报。nil 日志器则保留适配器的丢弃式默认值。
//
// 它也是唯一会追加可选本地第三级(local rung)的构建器
// (裁决 03.4.3,primary → fallback → local),因为那一级可能说
// 与生效组不同的协议,因此需要原始日志器来
// 构建自己的适配器。只有当本地级已启用且完全
// 配置好时才会追加;否则链保持两级形状,并
// 上报原因。
func NewChatChainWithLog(cfg config.LLMConfig, log *slog.Logger) (*Chain, error) {
var chain *Chain
var err error
switch cfg.Provider {
case ProviderOpenAI, "":
chain, err = newOpenAIChain(cfg, WithOpenAILogger(log))
case ProviderAnthropic:
chain, err = newAnthropicChain(cfg, WithAnthropicLogger(log))
default:
return nil, fmt.Errorf("model: %w: provider %q", ErrUnknownProvider, cfg.Provider)
}
if err != nil {
return nil, err
}
if step, ok := localChainStep(cfg, log); ok {
chain.steps = append(chain.steps, step)
}
return chain, nil
}
// localChainStep 把本地第三级降级构造成一个链步骤。
// 当该级关闭、未配置或其提供商未知时,返回 ok=false
// (若该级被要求但配置不全,则附一条点出缺失字段的 WARN)
// ——一个半配置的步骤只会失败,那还不如没有
// 它。
func localChainStep(cfg config.LLMConfig, log *slog.Logger) (Step, bool) {
local := cfg.Local
// Configured() 本身已要求 Enabled,所以该级默认关闭,
// 被禁用的级永远不会成为链步骤。只有当该级确实被
// 要求、但配置不全时才发出该警告。
if !local.Configured() {
if local.Enabled {
warn(log, "local model rung enabled but not configured; skipping it",
"base_url_set", strings.TrimSpace(local.BaseURL) != "",
"model_set", strings.TrimSpace(local.Model) != "")
}
return Step{}, false
}
localCfg := config.LLMConfig{
Provider: local.Provider,
BaseURL: local.BaseURL,
APIKey: local.APIKey,
Model: local.Model,
}
switch local.Provider {
case ProviderAnthropic:
return Step{Model: local.Model, Adapter: NewAnthropicChat(localCfg, WithAnthropicLogger(log))}, true
case ProviderOpenAI, "":
return Step{Model: local.Model, Adapter: NewOpenAIChat(localCfg, WithOpenAILogger(log))}, true
default:
warn(log, "local model rung skipped: unknown provider",
"provider", local.Provider, "field", "LLM_LOCAL_PROVIDER")
return Step{}, false
}
}
// newOpenAIChain 由 OpenAI 兼容配置块构建降级链。
// 每一步都是针对该端点的 OpenAIChatAdapter。
func newOpenAIChain(cfg config.LLMConfig, opts ...OpenAIOption) (*Chain, error) {
if cfg.BaseURL == "" || cfg.Model == "" {
return nil, ErrNoModel
}
steps := []Step{{Model: cfg.Model, Adapter: NewOpenAIChat(cfg, opts...)}}
if cfg.Fallback != "" && cfg.Fallback != cfg.Model {
steps = append(steps, Step{Model: cfg.Fallback, Adapter: NewOpenAIChat(cfg, opts...)})
}
return NewChain(steps...), nil
}
// newAnthropicChain 由 Anthropic 配置块构建降级链。每一步
// 都是针对该 /v1/messages 端点的 AnthropicChatAdapter。
func newAnthropicChain(cfg config.LLMConfig, opts ...AnthropicOption) (*Chain, error) {
if cfg.AnthropicBaseURL == "" || cfg.AnthropicModel == "" {
return nil, ErrNoModel
}
steps := []Step{{Model: cfg.AnthropicModel, Adapter: NewAnthropicChat(cfg, opts...)}}
if cfg.AnthropicFallback != "" && cfg.AnthropicFallback != cfg.AnthropicModel {
steps = append(steps, Step{Model: cfg.AnthropicFallback, Adapter: NewAnthropicChat(cfg, opts...)})
}
return NewChain(steps...), nil
}
// DefaultModelForProvider 返回配置提供商的主模型名:
// "openai" 取扁平配置里的 OpenAI Model,"anthropic" 取 Anthropic 模型。
// 调用方(如 cmd/cyrene)用它播种 Router 的默认值。
func DefaultModelForProvider(cfg config.LLMConfig) string {
if cfg.Provider == ProviderAnthropic {
return cfg.AnthropicModel
}
return cfg.Model
}
// warn 通过注入的日志器上报,降级到一个仍带有模块来源的丢弃式
// 日志器,因此绝不会产生无来源日志
// (项目铁律 §10)。它之所以存在,是因为链可能在没有日志器
// 的情况下构建(NewChatChain),而被跳过的级仍须上报。
func warn(log *slog.Logger, msg string, args ...any) {
if log == nil {
log = discardLogger("model")
}
log.Warn(msg, args...)
}