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` 要求调用方每次传入单价。于是每个下游都得自己维护一张 「模型名 → 价格」的表(通常落在应用配置里,键是模型名精确匹配),而:
- 漏配 = 成本**静默记 0**——0 成本在账单上与「免费模型」不可区分;
- 路由后缀对不上(`mimo-v2.5-pro-20250101`、部署名前缀)→ 静默漏配;
- 换模型、改价目表要改每个下游的配置。
本文件把价格收进框架层:内建价表随包维护,价目表变动时升版本即可,下游自动受益。
⚠️ 价目表是**参考价**,不是权威报价 ¶
- 采集时间见 builtinPricing 的注释;**厂商随时调价,本表必然滞后于厂商公告**。
- 同一模型在不同渠道(官方 / 代理 / 云托管)价格不同,缓存命中、批量、 区域定价也另算。本表只反映官方 list price。
- 契约价、代理加价、缓存折扣一律用 NewPriceTable 的 overrides 覆盖。
单位 ¶
**所有单价一律是「美元 / 每 100 万 token」**(Price.InputPerM / OutputPerM)。 本仓历史上并存过「每 1K token」(Provider.CostPer1KTokens、CostTracker.Record 的 costPer1K 参数)——两套单位并存是**一个数量级的错账**来源,故新 API 全部 收敛到每 1M;旧入口保留但标记 Deprecated。
Index ¶
- Constants
- Variables
- func ClassifyStatus(err error) bool
- func LookupPrice(model string) (Price, PriceLookup)
- func PriceOf(pricing map[string]Price, model string, prompt, completion int) float64deprecated
- type Breakers
- func (b *Breakers) Abandon(name string, gen uint64)
- func (b *Breakers) Allow(ctx context.Context, name string) (gen uint64, ok bool)
- func (b *Breakers) Failure(ctx context.Context, name string, gen uint64)
- func (b *Breakers) Get(name string) breaker.GenBreaker
- func (b *Breakers) Success(name string, gen uint64)
- type Budget
- type Chain
- type ChainOption
- func WithBackoff(base, max time.Duration) ChainOption
- func WithBreaker(b *Breakers) ChainOption
- func WithBudget(b TokenAccountant) ChainOption
- func WithClassifier(f Classifier) ChainOption
- func WithCompleter(name string, c Completer) ChainOption
- func WithRetries(n int) ChainOption
- func WithTokenCounter(f TokenCounter) ChainOption
- func WithTracker(t *CostTracker) ChainOption
- type Classifier
- type Completer
- type CostRecord
- type CostTracker
- func (ct *CostTracker) Record(model, stage string, promptTokens, completionTokens int, costPer1K [2]float64)deprecated
- func (ct *CostTracker) RecordAuto(model, stage string, promptTokens, completionTokens int)
- func (ct *CostTracker) RecordAutoCached(model, stage string, promptTokens, cachedTokens, completionTokens int)
- func (ct *CostTracker) RecordPriced(model, stage string, promptTokens, completionTokens int, price Price)
- func (ct *CostTracker) RecordPricedCached(model, stage string, promptTokens, cachedTokens, completionTokens int, ...)
- func (ct *CostTracker) RecordUnpriced(model, stage string, promptTokens, completionTokens int)
- func (ct *CostTracker) RecordUnpricedCached(model, stage string, promptTokens, cachedTokens, completionTokens int)
- func (ct *CostTracker) Records() []CostRecord
- func (ct *CostTracker) Summary() map[string]ModelSummary
- func (ct *CostTracker) Unpriced() []CostRecord
- func (ct *CostTracker) UnpricedCount() int
- type Entry
- type MatchKind
- type ModelSummary
- type Price
- type PriceLookup
- type PriceStatus
- type PriceTable
- type Request
- type StatusError
- type TokenAccountant
- type TokenCounter
- type Usage
Constants ¶
const ( // DefaultBreakerTrip 模型熔断阈值(连续失败次数)。 DefaultBreakerTrip = 3 // DefaultBreakerCooldown 模型熔断冷却期。 DefaultBreakerCooldown = 5 * time.Minute )
熔断默认值。放在 llm 而非直接引用上游常量:上游 breaker 未导出默认值(它的缺省是 「自适应丢弃」,没有 trip/cooldown 概念),而这里的三个值是本包对外承诺的缺省, 属于 ResilientConfig 的 API 契约,不随上游变化。
Variables ¶
var ErrBudgetExhausted = errors.New("port: 任务 token 预算已耗尽")
ErrBudgetExhausted 任务 token 预算已耗尽(调用前拦下)。
var ErrCircuitOpen = errors.New("port: 熔断打开")
ErrCircuitOpen 熔断打开:该环节暂不可用,已切向下一环。
var ErrEmptyChain = errors.New("port: 降级链为空")
ErrEmptyChain 链上没有任何环节。
var ErrPriceUnknown = errors.New("llm: 模型价格未知(价表未收录该模型)")
ErrPriceUnknown 价表里查不到该模型。
用 LookupPriceStrict 拿这个哨兵;errors.Is 可判定。
Functions ¶
func ClassifyStatus ¶
ClassifyStatus 按 HTTP 状态码判定是否值得重试: 429(限速)与 5xx(服务端故障)值得;其余 4xx(请求本身有问题,重试无益)不值得。
它同时是 Classifier——直接传给 WithClassifier 即可。
func PriceOf
deprecated
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 ¶
abandon 放弃一次放行:探针失联(或用户主动中止)的确定性回收——腾出探测位, 不计失败统计。走 Failure 会让「连续 N 次用户取消」把模型误熔断。
func (*Breakers) Allow ¶
allow 判定放行并返回本次放行的代。ok=false 表示熔断拒绝。
ctx 已取消时不放行也不消耗统计——与回报侧的 ctx 门对称:用户主动中止不该把模型 推向熔断(TestResilientUserCancelDoesNotTripBreaker 钉的就是这条)。
type Budget ¶
type Budget struct {
// contains filtered or unexported fields
}
Budget 任务级 token 预算累计器(TokenAccountant 的标准实现)。 并发安全。
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
Chain 窄接口形态的降级链。
装配后**只读**,可并发使用。
func NewChain ¶
func NewChain(opts ...ChainOption) *Chain
NewChain 创建窄接口降级链。
链上环节的**先后顺序 = WithCompleter 的调用顺序**(先注册者优先)。 不带任何 WithCompleter 时链为空,Complete 返回 ErrEmptyChain。
type ChainOption ¶
type ChainOption func(*Chain)
ChainOption 链装配选项。
func WithBackoff ¶
func WithBackoff(base, max time.Duration) ChainOption
WithBackoff 重试退避:base 起倍,max 封顶。默认 2s / 30s。
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 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 ¶
Classifier 错误分类:返回该错误是否值得重试(true)或应立刻切下一个(false)。
默认口径:除 ctx 取消/超时外都算可重试。宿主应按自己的错误形态覆盖它—— 本包提供 ClassifyStatus(按 HTTP 状态码)覆盖最常见的 OpenAI 兼容场景, 免得每个宿主各写一遍、各判错一遍。
type Completer ¶
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 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 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 前提是字节稳定);命中率异常低 = 提示词/工具表前缀在抖动,值得查。
type Price ¶
Price 单价(**美元 / 每 100 万 token**)。
输入、输出各一档。缓存命中价、分档价不在本结构内——那些是**按厂商规则**算的, 硬塞进一个 flat 单价只会算错;需要时由调用方覆盖。
func LookupPriceStrict ¶
LookupPriceStrict 查价,查不到返回 ErrPriceUnknown。
给「必须硬失败」的路径用(例如按量计费的出账链路:宁可报错也不能按 0 记账)。 非硬失败路径用 LookupPrice / CostTracker.RecordAuto——那里会**标记未定价**而不是丢弃用量。
func PriceOfMatched
deprecated
PriceOfMatched PriceOf 的可观测形态:返回命中的定价键与是否经前缀回落。
前缀回落对「同主干异价 SKU」(gpt-4o 前缀命中 gpt-4o-mini)会静默误定价 ——宿主可在装配期对回落命中做校验/上报(第十轮审计)。
Deprecated: 同 PriceOf——未配价返回 0 不可区分「免费」,前缀回落会误定价。 改用 PriceTable.Lookup(返回 PriceLookup,命中方式可见且不做前缀回落)。
type PriceLookup ¶
type PriceLookup struct {
// Key 这一项价的**原始键**(未命中为空串)。
//
// 内建项 = 基础形态模型名;覆盖项 = 调用方给的键(原样保留,可能带部署前缀 /
// 日期后缀)。它就是**查回同一份价**用的键:Lookup(Key) 必然拿到同一个价。
Key string
// Kind 命中方式。
Kind MatchKind
}
PriceLookup 查价结果:命中的键 + 命中方式。
命中方式对外可见,是为了让调用方能审计「这个价格是怎么来的」—— 归一化命中与精确命中在**同族异价 SKU** 上的后果不同(见 PriceTable.Lookup)。
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`)。此时**只有一个生效**,按下列优先级(高 → 低):
- **基础形态**的键优先——`gpt-4o` 优先于 `openai/gpt-4o`、`gpt-4o-2024-08-06`;
- 同为派生形态时,**更长的键**优先(更具体);
- 再同则**字典序小**的键优先(兜底,保证跨进程、跨 map 迭代顺序都稳定)。
落选的键**整族不生效**(不是只在自己那条查询上落选)——一个基座只有一个价。 生效的是哪个键,查价结果的 PriceLookup.Key 会原样报出来。
func (*PriceTable) Lookup ¶
func (t *PriceTable) Lookup(model string) (Price, PriceLookup)
Lookup 查价。
归一化规则(按优先级) ¶
- 原名精确命中(MatchExact);
- 去掉**部署前缀**(取最后一个 '/' 之后)后精确命中,如 `openai/gpt-4o` → `gpt-4o`;
- 去掉**日期后缀**后精确命中,如 `mimo-v2.5-pro-20250101` → `mimo-v2.5-pro`、 `gpt-4o-2024-08-06` → `gpt-4o`;
- 上面组合都试过仍不中 → **未命中**(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 ¶
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 ¶
TokenCounter 精确 token 计量口。
默认口径是**估算**(textutil.EstTokensCJK,与 eino 路径的 estimateTokens 同源)。 宿主若能从 provider 回传 usage,注入本口即可换成精确值—— 链在 Completer 返回后**紧接着**调用它,所以宿主在同一调用内暂存 usage 是安全的。