Help us shape the future of Go! Take the 2026 Go Developer Survey

port

package
v0.18.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package port 是 oagentkit/llm 的**窄接口层**——本仓不依赖 eino 的那一半。

为什么需要它

本仓全部对外能力原先都长在 eino 的类型上:

func NewClient(m model.BaseChatModel, modelName string, budget TokenAccountant) *Client
func (c *Client) Generate(ctx, stage, msgs []*schema.Message) (*schema.Message, error)

非 eino 生态的宿主想接一个**单方法**窄接口(如 `Complete(ctx, req LLMRequest) (string, error)`),就只能自己重写一遍: 拼请求体、解析响应、判错误码、算超时、处理 JSON 模式。代价不只是代码量, 还有**边界易错**——HTTP 状态码判错就会静默把失败当成功。

本包把这些能力收在一处:**降级链 / 任务级预算 / 成本记账**在窄接口形态下 同样可用,且与 eino 原生路径**共用同一份实现**(见下)。

依赖面

本包**不引入 eino**(用 `go list -deps ./port` 可验证)。它只依赖: 标准库、oekit/concurrency(熔断)、oagentkit/textx(token 估算)。

怎么选:窄接口 vs eino 原生

  • 宿主已在 eino 生态(要 ToolCall / 多模态 / 流式 / 结构化输出到 struct): 直接用根包 llm.NewClient / llm.NewResilient(能力更全,含 JSON 模式、 分阶段路由、context 窗口 fitInput)。
  • 宿主不在 eino 生态(只要「一句话进、一句话出」,或不想把 eino 带进依赖图): 用本包。宿主自己实现 Completer(拼 HTTP 请求),本包负责降级、预算、成本。

两条路径的**预算与成本口径完全一致**:本包与根包共用同一份 Budget / CostTracker / PriceTable 实现(见 budget.go / cost.go / pricing.go 的说明),不会出现两套算账。

pricing.go — 模型定价:内建价表 + 归一化查价 + 调用方覆盖。

为什么价格要进 llm 层

此前 `llm` 有完整的成本追踪能力(CostTracker/CostRecord),但**价格不在这一层**: `CostTracker.Record` 要求调用方每次传入单价。于是每个下游都得自己维护一张 「模型名 → 价格」的表(通常落在应用配置里,键是模型名精确匹配),而:

  1. 漏配 = 成本**静默记 0**——0 成本在账单上与「免费模型」不可区分;
  2. 路由后缀对不上(`mimo-v2.5-pro-20250101`、部署名前缀)→ 静默漏配;
  3. 换模型、改价目表要改每个下游的配置。

本文件把价格收进框架层:内建价表随包维护,价目表变动时升版本即可,下游自动受益。

⚠️ 价目表是**参考价**,不是权威报价

  • 采集时间见 builtinPricing 的注释;**厂商随时调价,本表必然滞后于厂商公告**。
  • 同一模型在不同渠道(官方 / 代理 / 云托管)价格不同,缓存命中、批量、 区域定价也另算。本表只反映官方 list price。
  • 契约价、代理加价、缓存折扣一律用 NewPriceTable 的 overrides 覆盖。

单位

**所有单价一律是「美元 / 每 100 万 token」**(Price.InputPerM / OutputPerM)。 本仓历史上并存过「每 1K token」(Provider.CostPer1KTokens、CostTracker.Record 的 costPer1K 参数)——两套单位并存是**一个数量级的错账**来源,故新 API 全部 收敛到每 1M;旧入口保留但标记 Deprecated。

Index

Constants

View Source
const (
	// DefaultBreakerTrip 模型熔断阈值(连续失败次数)。
	DefaultBreakerTrip = 3
	// DefaultBreakerCooldown 模型熔断冷却期。
	DefaultBreakerCooldown = 5 * time.Minute
)

熔断默认值。放在 llm 而非直接引用上游常量:上游 breaker 未导出默认值(它的缺省是 「自适应丢弃」,没有 trip/cooldown 概念),而这里的三个值是本包对外承诺的缺省, 属于 ResilientConfig 的 API 契约,不随上游变化。

Variables

View Source
var ErrBudgetExhausted = errors.New("port: 任务 token 预算已耗尽")

ErrBudgetExhausted 任务 token 预算已耗尽(调用前拦下)。

View Source
var ErrCircuitOpen = errors.New("port: 熔断打开")

ErrCircuitOpen 熔断打开:该环节暂不可用,已切向下一环。

View Source
var ErrEmptyChain = errors.New("port: 降级链为空")

ErrEmptyChain 链上没有任何环节。

View Source
var ErrPriceUnknown = errors.New("llm: 模型价格未知(价表未收录该模型)")

ErrPriceUnknown 价表里查不到该模型。

用 LookupPriceStrict 拿这个哨兵;errors.Is 可判定。

Functions

func ClassifyStatus

func ClassifyStatus(err error) bool

ClassifyStatus 按 HTTP 状态码判定是否值得重试: 429(限速)与 5xx(服务端故障)值得;其余 4xx(请求本身有问题,重试无益)不值得。

它同时是 Classifier——直接传给 WithClassifier 即可。

func LookupPrice

func LookupPrice(model string) (Price, PriceLookup)

LookupPrice 用内建价表查价。

func PriceOf deprecated

func PriceOf(pricing map[string]Price, model string, prompt, completion int) float64

PriceOf 按模型名匹配定价表估算单次调用成本(USD;未配价返回 0,不阻塞记账)。

Deprecated: 未配价时返回 0,与「免费模型」不可区分——这正是本仓要根除的静默失真。 改用 PriceTable.Lookup / LookupPrice:查不到时是「价格未知」而非 0。

另注意其「最长前缀回落」会把同主干异价 SKU 静默定价错(`gpt-4o-mini` 命中 `gpt-4o`),新查价 API 不再提供该行为。

Types

type Breakers

type Breakers struct {
	// contains filtered or unexported fields
}

Breakers 是按模型名分桶的熔断器集合。

状态机全部委托 github.com/oekit/concurrency/breaker 的 GenBreaker:显式 closed/open/half-open 三态、放行与回报的代绑定(陈旧代整体作废)、冷却期与探测 时限可独立配置、计数型触发与速率型并联——任一命中即切断。

本层不做任何熔断语义兜底。v0.13.0 之前上游计数型只切状态、切不了放行决策 (见 oekit/concurrency#25),当时本文件带一个本地准入否决计数器;v0.13.0 起 上游已修(同一探针 5/5 轮均在第 N+1 次放行返回 ErrCircuitOpen),那段兜底已删除。

为什么按模型名分桶:降级链要按模型独立统计——一个 breaker 覆盖多个模型名会把互相 独立的故障率混成一路,一个模型的连挂会误伤另一个。

func NewBreakers

func NewBreakers(opts ...breaker.BreakerOption) *Breakers

func (*Breakers) Abandon

func (b *Breakers) Abandon(name string, gen uint64)

abandon 放弃一次放行:探针失联(或用户主动中止)的确定性回收——腾出探测位, 不计失败统计。走 Failure 会让「连续 N 次用户取消」把模型误熔断。

func (*Breakers) Allow

func (b *Breakers) Allow(ctx context.Context, name string) (gen uint64, ok bool)

allow 判定放行并返回本次放行的代。ok=false 表示熔断拒绝。

ctx 已取消时不放行也不消耗统计——与回报侧的 ctx 门对称:用户主动中止不该把模型 推向熔断(TestResilientUserCancelDoesNotTripBreaker 钉的就是这条)。

func (*Breakers) Failure

func (b *Breakers) Failure(ctx context.Context, name string, gen uint64)

failure 回报失败。ctx 已取消时不消耗统计。

func (*Breakers) Get

func (b *Breakers) Get(name string) breaker.GenBreaker

get 取(或首次创建)某模型名的熔断器。

func (*Breakers) Success

func (b *Breakers) Success(name string, gen uint64)

success 回报成功:陈旧代作废。

type Budget

type Budget struct {
	// contains filtered or unexported fields
}

Budget 任务级 token 预算累计器(TokenAccountant 的标准实现)。 并发安全。

func NewBudget

func NewBudget(limit int) *Budget

NewBudget 创建预算累计器。

func (*Budget) Add

func (b *Budget) Add(n int)

Add 累计消耗。

func (*Budget) Limit

func (b *Budget) Limit() int

Limit 预算上限。

func (*Budget) Remaining

func (b *Budget) Remaining() int

Remaining 剩余额度(不低于 0)。

func (*Budget) Used

func (b *Budget) Used() int

Used 已消耗。

type Chain

type Chain struct {
	// contains filtered or unexported fields
}

Chain 窄接口形态的降级链。

装配后**只读**,可并发使用。

func NewChain

func NewChain(opts ...ChainOption) *Chain

NewChain 创建窄接口降级链。

链上环节的**先后顺序 = WithCompleter 的调用顺序**(先注册者优先)。 不带任何 WithCompleter 时链为空,Complete 返回 ErrEmptyChain。

func (*Chain) Complete

func (c *Chain) Complete(ctx context.Context, req Request) (string, error)

Complete 走降级链完成一次调用。

顺序:**预算前置判定 → 逐环节尝试(每环节内部可重试)→ 失败切下一环节**。

成功时:预算累计消耗、成本按价表记账(价表未收录的模型记为「未定价」,不记 0)。 失败时:返回最后一个错误;ctx 取消/超时时**原样上抛**(用户主动中止不该 把模型推向熔断,也不该被当成模型故障)。

type ChainOption

type ChainOption func(*Chain)

ChainOption 链装配选项。

func WithBackoff

func WithBackoff(base, max time.Duration) ChainOption

WithBackoff 重试退避:base 起倍,max 封顶。默认 2s / 30s。

func WithBreaker

func WithBreaker(b *Breakers) ChainOption

WithBreaker 绑定熔断器(按环节名分桶)。

func WithBudget

func WithBudget(b TokenAccountant) ChainOption

WithBudget 绑定任务级预算:预算耗尽时**在调用前**拦下(事后扣减等于没有预算)。

func WithClassifier

func WithClassifier(f Classifier) ChainOption

WithClassifier 覆盖错误分类(默认:除 ctx 取消/超时外都算可重试)。

func WithCompleter

func WithCompleter(name string, c Completer) ChainOption

WithCompleter 往链上加一环(name 用于熔断分桶与成本记账的模型名)。

func WithRetries

func WithRetries(n int) ChainOption

WithRetries 每个环节的重试次数(不含首次),默认 1(即不重试)。

func WithTokenCounter

func WithTokenCounter(f TokenCounter) ChainOption

WithTokenCounter 覆盖 token 计量口径(默认按 textutil.EstTokensCJK 估算)。

func WithTracker

func WithTracker(t *CostTracker) ChainOption

WithTracker 绑定成本追踪器:每次调用按价表计价并记账。 价表里查不到的模型记为「未定价」(CostRecord.PriceStatus = PriceUnpriced), **不记 0**——0 成本与「免费模型」不可区分。

type Classifier

type Classifier func(err error) bool

Classifier 错误分类:返回该错误是否值得重试(true)或应立刻切下一个(false)。

默认口径:除 ctx 取消/超时外都算可重试。宿主应按自己的错误形态覆盖它—— 本包提供 ClassifyStatus(按 HTTP 状态码)覆盖最常见的 OpenAI 兼容场景, 免得每个宿主各写一遍、各判错一遍。

type Completer

type Completer interface {
	Complete(ctx context.Context, req Request) (string, error)
}

Completer 窄接口:**一个方法**完成一次调用。

名字与下游一致(agent-memory 的 ports.LLM: Complete(ctx, req) (string, error)), 宿主实现它即可接进降级链。本身不引任何 eino 类型。

type CostRecord

type CostRecord struct {
	Model            string // 模型名
	Stage            string // 阶段(R1/R2/R3/R4/R5/plugin:xxx)
	PromptTokens     int
	CompletionTokens int
	// CachedTokens 命中前缀缓存的 prompt token(⊆ PromptTokens;DeepSeek/MiMo 等
	// 端点的 PromptTokenDetails.CachedTokens)。只作观测——不改 CostUSD 计价口径
	//(缓存折扣是厂商规则、由调用方在 Price 层覆盖,见 Price 文档);缓存命中率
	// = ΣCachedTokens / ΣPromptTokens,从 Summary/ModelSummary 派生。零值 = 未知。
	CachedTokens int
	CostUSD      float64 // 本次成本(美元)

	// PriceStatus 计价状态:PricePriced / PriceUnpriced。
	//
	// ⚠️ PriceUnpriced 时 CostUSD 是 **0**,但那表示「**价格未知**」而不是
	// 「免费」——真实成本无从得知。判读汇总口径时必须同时看
	// ModelSummary.UnpricedCalls:TotalCostUSD==0 且 UnpricedCalls>0
	// ⇒ 成本口径不完整,不是「这个模型不要钱」。
	PriceStatus PriceStatus
	// PriceKey 命中的价表键(PricePriced 时非空;PriceUnpriced 时为空)。
	PriceKey string
	// PriceMatched 命中方式(exact / normalized;未命中为 none)。
	PriceMatched MatchKind
}

CostRecord 单次调用成本记录。

type CostTracker

type CostTracker struct {
	// contains filtered or unexported fields
}

CostTracker token 成本追踪器。 按模型+阶段统计 token 消耗和成本。 环形缓冲:满窗后 Record O(1) 覆盖最旧,不再每笔整体搬移。

价格从哪来

三种记录入口,价格来源不同,**但「价格未知」在任何一条路径上都不会被记成 0**:

  • RecordAuto:按价表自动查(内建价 + 调用方覆盖)——**推荐**。
  • RecordPriced:调用方显式给单价(企业折扣价、代理加价率)。
  • Record:调用方每次传单价(**已废弃**,单位是每 1K token,与 Price 的每 1M 差一个数量级)。

查不到价格时记录仍会写入(用量数据不丢),但会被标记为 PriceUnpriced, 可经 Unpriced() / UnpricedCount() 查出来——**0 成本与「价格未知」必须可区分**。

func NewCostTracker

func NewCostTracker() *CostTracker

NewCostTracker 创建成本追踪器(用内建价表)。

func NewCostTrackerWithTable

func NewCostTrackerWithTable(table *PriceTable) *CostTracker

NewCostTrackerWithTable 创建成本追踪器(指定价表)。

价表是**只读**的:可构造一份带覆盖的表传进来 (NewPriceTable(overrides)),也可在运行期用 PriceTable.WithOverride 造新表 再换一台 tracker。

func (*CostTracker) Record deprecated

func (ct *CostTracker) Record(model, stage string, promptTokens, completionTokens int, costPer1K [2]float64)

Record 记录一次调用(**调用方自带单价,每 1000 token**)。

Deprecated: 单位是「每 1K token」,而 Price/RecordPriced 是「每 1M token」—— 同一仓里并存两套单位是一个数量级的错账来源。改用 RecordPriced(显式单价, 单位写在类型上)或 RecordAuto(按价表自动计价)。

func (*CostTracker) RecordAuto

func (ct *CostTracker) RecordAuto(model, stage string, promptTokens, completionTokens int)

RecordAuto 按价表自动计价并记录一次调用。

**查不到价格不报错,但也绝不静默记 0**:记录照写(用量数据不丢), 状态标为 PriceUnpriced,调用方可用 Unpriced() / UnpricedCount() 查出来, 汇总里也会在 ModelSummary.UnpricedCalls 上体现。

需要「查不到就硬失败」的路径用 LookupPriceStrict。

func (*CostTracker) RecordAutoCached added in v0.17.0

func (ct *CostTracker) RecordAutoCached(model, stage string, promptTokens, cachedTokens, completionTokens int)

RecordAutoCached 按价表自动计价并记录一次调用(带前缀缓存命中量)。 cachedTokens 语义同 RecordPricedCached:只作观测入库,不改计价口径。

func (*CostTracker) RecordPriced

func (ct *CostTracker) RecordPriced(model, stage string, promptTokens, completionTokens int, price Price)

RecordPriced 记录一次调用,单价由调用方显式给出(**美元 / 每 1M token**)。

适用:企业折扣价、代理转发加价率、人民币计价合同的折算价—— 这些都**不属于**内建价表能覆盖的范围。

func (*CostTracker) RecordPricedCached added in v0.17.0

func (ct *CostTracker) RecordPricedCached(model, stage string, promptTokens, cachedTokens, completionTokens int, price Price)

RecordPricedCached 记录一次调用(带前缀缓存命中量)。

cachedTokens 是命中前缀缓存的 prompt token(⊆ promptTokens;DeepSeek/MiMo 等端点的 PromptTokenDetails.CachedTokens)。**缓存折扣不在本层算**——Price 文档已明「缓存 命中价按厂商规则、由调用方覆盖」,故计价仍用 price.CostOf(promptTokens, completion) 的同一口径;cachedTokens 只作观测入库(CostRecord.CachedTokens),供缓存命中率派生。 需要缓存折扣成本的调用方,在 Price 层按厂商规则折算后传入(或自行摊薄 InputPerM)。

func (*CostTracker) RecordUnpriced

func (ct *CostTracker) RecordUnpriced(model, stage string, promptTokens, completionTokens int)

RecordUnpriced 记录一次调用,并显式标记为**价格未知**。

与 RecordAuto 查不到价时走的同一条路:CostUSD 是 0,但那表示「未知」不是 「免费」——记录上的 PriceStatus=PriceUnpriced 与汇总里的 UnpricedCalls 就是它的标记,运维据此告警,而不是对着恒为 0 的看板误判模型免费。

func (*CostTracker) RecordUnpricedCached added in v0.17.0

func (ct *CostTracker) RecordUnpricedCached(model, stage string, promptTokens, cachedTokens, completionTokens int)

RecordUnpricedCached 记录一次调用(价格未知,带前缀缓存命中量)。 cachedTokens 只作观测入库——价格未知不影响缓存命中率可算。

func (*CostTracker) Records

func (ct *CostTracker) Records() []CostRecord

Records 返回所有记录的副本(时间序:最旧在前)。

func (*CostTracker) Summary

func (ct *CostTracker) Summary() map[string]ModelSummary

Summary 按模型汇总。

func (*CostTracker) Unpriced

func (ct *CostTracker) Unpriced() []CostRecord

Unpriced 返回**价格未知**的记录(时间序:最旧在前)。

这是「未知价不静默」的可观测出口:账面上 CostUSD 是 0,但这些调用 根本没计价——运维能据此告警,而不是对着一个恒为 0 的看板以为模型免费。

func (*CostTracker) UnpricedCount

func (ct *CostTracker) UnpricedCount() int

UnpricedCount 返回价格未知的记录数。

type Entry

type Entry struct {
	// Name 环节名:熔断分桶键 + 成本记录里的模型名。
	Name string
	// Completer 窄接口实现。
	Completer Completer
}

Entry 链上的一环:名字 + 窄接口实现。

type MatchKind

type MatchKind string

MatchKind 查价命中方式。

const (
	// MatchNone 未命中(价格未知)。
	MatchNone MatchKind = "none"
	// MatchExact 原名精确命中。
	MatchExact MatchKind = "exact"
	// MatchNormalized 归一化后命中(去部署前缀 / 去日期后缀)。
	MatchNormalized MatchKind = "normalized"
)

type ModelSummary

type ModelSummary struct {
	Model                 string
	TotalPromptTokens     int
	TotalCompletionTokens int
	// TotalCachedTokens 命中前缀缓存的 prompt token 累计(⊆ TotalPromptTokens)。
	TotalCachedTokens int
	TotalCostUSD      float64
	CallCount         int
	// PricedCalls 已计价的调用数。
	PricedCalls int
	// UnpricedCalls **价格未知**的调用数——这些调用的成本没进 TotalCostUSD。
	//
	// 判读口径:TotalCostUSD==0 且 UnpricedCalls>0 ⇒ 成本口径不完整
	//(不是「这个模型免费」);TotalCostUSD==0 且 UnpricedCalls==0 ⇒ 确实免费/零价。
	UnpricedCalls int
}

ModelSummary 单模型汇总。

func (ModelSummary) CacheHitRatio added in v0.17.0

func (s ModelSummary) CacheHitRatio() float64

CacheHitRatio 前缀缓存命中率 = TotalCachedTokens / TotalPromptTokens。 无 prompt(或零命中)返回 0。命中率高 = 上下文前缀稳定(provider context cache 前提是字节稳定);命中率异常低 = 提示词/工具表前缀在抖动,值得查。

func (ModelSummary) TotalTokens

func (s ModelSummary) TotalTokens() int

TotalTokens 总 token 消耗。

type Price

type Price struct {
	InputPerM  float64
	OutputPerM float64
}

Price 单价(**美元 / 每 100 万 token**)。

输入、输出各一档。缓存命中价、分档价不在本结构内——那些是**按厂商规则**算的, 硬塞进一个 flat 单价只会算错;需要时由调用方覆盖。

func LookupPriceStrict

func LookupPriceStrict(model string) (Price, error)

LookupPriceStrict 查价,查不到返回 ErrPriceUnknown。

给「必须硬失败」的路径用(例如按量计费的出账链路:宁可报错也不能按 0 记账)。 非硬失败路径用 LookupPrice / CostTracker.RecordAuto——那里会**标记未定价**而不是丢弃用量。

func PriceOfMatched deprecated

func PriceOfMatched(pricing map[string]Price, model string) (Price, string, bool)

PriceOfMatched PriceOf 的可观测形态:返回命中的定价键与是否经前缀回落。

前缀回落对「同主干异价 SKU」(gpt-4o 前缀命中 gpt-4o-mini)会静默误定价 ——宿主可在装配期对回落命中做校验/上报(第十轮审计)。

Deprecated: 同 PriceOf——未配价返回 0 不可区分「免费」,前缀回落会误定价。 改用 PriceTable.Lookup(返回 PriceLookup,命中方式可见且不做前缀回落)。

func (Price) CostOf

func (p Price) CostOf(promptTokens, completionTokens int) float64

CostOf 按本单价算一次调用的成本(美元)。

关系式:cost = promptTokens/1e6×InputPerM + completionTokens/1e6×OutputPerM。

type PriceLookup

type PriceLookup struct {
	// Key 这一项价的**原始键**(未命中为空串)。
	//
	// 内建项 = 基础形态模型名;覆盖项 = 调用方给的键(原样保留,可能带部署前缀 /
	// 日期后缀)。它就是**查回同一份价**用的键:Lookup(Key) 必然拿到同一个价。
	Key string
	// Kind 命中方式。
	Kind MatchKind
}

PriceLookup 查价结果:命中的键 + 命中方式。

命中方式对外可见,是为了让调用方能审计「这个价格是怎么来的」—— 归一化命中与精确命中在**同族异价 SKU** 上的后果不同(见 PriceTable.Lookup)。

func (PriceLookup) Found

func (l PriceLookup) Found() bool

Found 是否查到了价。

type PriceStatus

type PriceStatus string

PriceStatus 计价状态。

const (
	// PricePriced 已按单价算出成本。
	PricePriced PriceStatus = "priced"
	// PriceUnpriced **价格未知**:价表里查不到这个模型。
	//
	// 它**不等于「成本为 0」**——CostRecord.CostUSD 此时是 0,但那只是占位,
	// 真实成本未知。两者必须可区分:把「未知」记成 0,是本仓要根除的静默失真。
	PriceUnpriced PriceStatus = "unpriced"
)

type PriceTable

type PriceTable struct {
	// contains filtered or unexported fields
}

PriceTable 价表:内建价 + 调用方覆盖。**构造后只读**,可并发使用。

表内按**基座形态**(基础模型名,无部署前缀 / 日期后缀)存键——内建价如此, 调用方覆盖也先折算成基座形态再入库(见 NewPriceTable)。于是「查价」与 「存价」用的是同一套规则:调用方配的价不会再被内建参考价悄悄顶掉。

func DefaultPriceTable

func DefaultPriceTable() *PriceTable

DefaultPriceTable 返回内建价表(无覆盖)。

返回的是**共享只读实例**,不要修改它;要改价请用 NewPriceTable 或 WithOverride 造一份新的。

func NewPriceTable

func NewPriceTable(overrides map[string]Price) *PriceTable

NewPriceTable 造一份价表:内建价 + overrides 覆盖。

覆盖键与内建表走**同一套归一化规则**

overrides 的键先折算成**基座形态**(去掉部署前缀 + 日期后缀,见 baseForm) 再入库——与内建表的键形态一致。所以覆盖是**双向**的:

  • 覆盖 `mimo-v2.5-pro` 能盖住 `mimo-v2.5-pro-20250101` 这类变体;
  • 覆盖 `mimo-v2.5-pro-20250101` 同样能盖住 `mimo-v2.5-pro`。

覆盖优先于内建价。

同一基座上的多个覆盖键:一条确定的优先级规则

两个覆盖键折算后可能落到**同一个基座**(例如同时给 `gpt-4o` 与 `openai/gpt-4o`)。此时**只有一个生效**,按下列优先级(高 → 低):

  1. **基础形态**的键优先——`gpt-4o` 优先于 `openai/gpt-4o`、`gpt-4o-2024-08-06`;
  2. 同为派生形态时,**更长的键**优先(更具体);
  3. 再同则**字典序小**的键优先(兜底,保证跨进程、跨 map 迭代顺序都稳定)。

落选的键**整族不生效**(不是只在自己那条查询上落选)——一个基座只有一个价。 生效的是哪个键,查价结果的 PriceLookup.Key 会原样报出来。

func (*PriceTable) Lookup

func (t *PriceTable) Lookup(model string) (Price, PriceLookup)

Lookup 查价。

归一化规则(按优先级)

  1. 原名精确命中(MatchExact);
  2. 去掉**部署前缀**(取最后一个 '/' 之后)后精确命中,如 `openai/gpt-4o` → `gpt-4o`;
  3. 去掉**日期后缀**后精确命中,如 `mimo-v2.5-pro-20250101` → `mimo-v2.5-pro`、 `gpt-4o-2024-08-06` → `gpt-4o`;
  4. 上面组合都试过仍不中 → **未命中**(MatchNone,价格未知)。

覆盖键与内建键同一套规则

价表里的键都是**基座形态**(内建如此,覆盖键入库前也折算过,见 NewPriceTable), 所以查 `mimo-v2.5-pro` 与查 `mimo-v2.5-pro-20250101` 落到同一项—— 调用方显式配的单价不会再被内建参考价悄悄顶掉。

MatchExact / MatchNormalized 的判据

PriceLookup.Key 报的是**这一项价的原始键**。原始键与查询名**完全相同** ⇒ MatchExact;否则(任一侧需要去部署前缀 / 日期后缀才对上)⇒ MatchNormalized。

为什么不做「最长前缀回落」

旧版 PriceOf/PriceOfMatched 会做最长前缀回落,那会把**同主干异价 SKU** 静默 定价错:表里有 `gpt-4o` 时,`gpt-4o-mini`(便宜一半)会按 gpt-4o 计费。 「静默算错」比「显式未知」更危险——所以新查价不做前缀回落;需要的模型请显式 收录进表里(旧 API 仍保留该行为,见 PriceOf 的 Deprecated 说明)。

func (*PriceTable) Models

func (t *PriceTable) Models() []string

Models 返回表里的全部模型名(字典序)。

覆盖项返回的是**调用方给的原始键**(未折算的形态),与 Lookup 的 PriceLookup.Key 一致——查这些键都能查到同一份价。

func (*PriceTable) WithOverride

func (t *PriceTable) WithOverride(model string, price Price) *PriceTable

WithOverride 返回一份新价表:当前表 + 该覆盖(不动原表,原表可能正被并发读)。

覆盖键与 NewPriceTable 的 overrides 走**同一套归一化规则**(折算成基座形态 再入库),同一基座上的优先级规则也相同(见 NewPriceTable 的文档)。

type Request

type Request struct {
	// Prompt 用户输入。
	Prompt string
	// System 系统提示(可选)。
	System string
	// JSON 要求模型输出 JSON。**只是意图透传**:本层不解析 JSON
	// (eino 路径的 GenerateJSON 才是「解析进 struct」),宿主按需自行反序列化。
	JSON bool
	// Stage 阶段标签(预算/成本按阶段归集;与 eino 路径的 stage 语义一致)。
	Stage string
	// MaxTokens 输出上限(0 = 不限)。
	MaxTokens int
}

Request 一次窄接口调用的请求。

刻意做窄:只保留「非 eino 宿主也需要」的字段——宿主自己拼请求体, 本包不越界替宿主决定 prompt 模板或协议细节。

type StatusError

type StatusError struct {
	// Status HTTP 状态码(0 = 非 HTTP 错误)。
	Status int
	// Err 底层错误。
	Err error
}

StatusError 宿主 transport 的错误形态:带 HTTP 状态码。

存在的意义:把「HTTP 状态码 → 要不要重试」这个**最容易判错**的判定 收进本包一次,宿主不必各写一遍(判错的后果是静默把失败当成功)。

func (*StatusError) Error

func (e *StatusError) Error() string

func (*StatusError) Unwrap

func (e *StatusError) Unwrap() error

Unwrap 支持 errors.Is / errors.As 穿透到底层错误。

type TokenAccountant

type TokenAccountant interface {
	// Add 累计消耗。
	Add(n int)
	// Used 已消耗。
	Used() int
	// Remaining 剩余额度(不低于 0)。
	Remaining() int
}

TokenAccountant token 记账接口(调用方可接预算累计器或 Prometheus)。

原先定义在根包 client.go 里——而根包因为 client.go 依赖 eino 而「整包不可用」, 非 eino 生态的宿主连「记一笔 token」都做不到。下沉到这里后,根包用类型别名 保持 API 不变(llm.TokenAccountant == port.TokenAccountant)。

type TokenCounter

type TokenCounter func(req Request, resp string) Usage

TokenCounter 精确 token 计量口。

默认口径是**估算**(textutil.EstTokensCJK,与 eino 路径的 estimateTokens 同源)。 宿主若能从 provider 回传 usage,注入本口即可换成精确值—— 链在 Completer 返回后**紧接着**调用它,所以宿主在同一调用内暂存 usage 是安全的。

type Usage

type Usage struct {
	PromptTokens     int
	CompletionTokens int
}

Usage 一次调用的 token 用量。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL