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

llm

package module
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 24 Imported by: 0

README

llm

模型客户端——多模型降级链/任务级预算/成本记账/分阶段模型路由/主备切换 —— oagentkit 模块矩阵 tier 2(集成层,依赖 tier0/tier1 + 重第三方地板)。

go get github.com/oagentkit/llm
包 职责
llm 模型客户端:多模型降级链、任务级预算、成本记账、分阶段路由、主备切换
wrap 调用包装与限流适配
llmtest 模型层契约测试套件

定位

tier 2 — 集成层,依赖 tier0/tier1 + 重第三方地板。

  • 本仓依赖的矩阵内仓:obs、textx。

外部依赖地板

  • github.com/cloudwego/eino
  • github.com/meguminnnnnnnnn/go-openai
  • github.com/cloudwego/eino-ext
  • golang.org/x/time

与 oekit 的关系

本矩阵(oagentkit,Agent/LLM 编排工具链)依赖 oekit(Open Easy Kit,Go 微服务工具链): oekit/pkg / oekit/concurrency / oekit/logx 等。oekit 是另一套独立矩阵,不是子品牌。

开发

go test ./... -race
go vet ./...
gofmt -l .

规范见 AGENTS.md。

License

MIT

  • 降级链的每一跳都必须记录原因(成本/限流/超时/内容策略),不得静默换模型。
  • 预算判定必须在调用前,不能事后扣减 —— 事后扣减等于没有预算。
  • panic 不能只转成 error。 RunSafe* 系列会把 panic 收进全局 PanicHandler (库不接 SetPanicHandler,默认 log.Printf)且返回的 panic: X 无哨兵类型, 无法挂回领域上下文。用 RunSafeErr 前先确认丢掉的结构化上下文可接受。

Documentation

Overview

Package llm LLM 客户端栈(eino BaseChatModel 之上),本包唯一 package doc。 文件族分工:

  • client/errors/config:客户端薄封装——生成失败指数退避+jitter 重试(429 退避 下限提高)、JSON 输出解析失败带错误回喂重试、token 记账(优先响应 usage, 缺省 CJK 感知估算(EstTokensCJK))、fitInput 输入自守恒、可选 token bucket 限速
  • provider/openai:Provider 抽象与多后端 + FallbackChain 降级链
  • resilient/failover:弹性客户端(降级矩阵/熔断/预算短路)+ 裸模型路径的 FailoverModel 装饰器
  • budget/cost/usage:预算、成本、完整用量采集三套记账
  • stage_router:按业务阶段路由模型

FailoverModel 模型链 failover 装饰器(eino BaseChatModel/ToolCallingChatModel 双形态)。

Resilient/FallbackChain 只覆盖 agentkit 自己的 Generator 客户端路径;ReAct 主路径 (ADK ChatModelAgent 直调模型)拿到的就是裸 BaseChatModel,没有降级链。本装饰器 填补这一层:按序尝试模型,前一个失败且调用方 ctx 仍存活时切下一个重放同一次请求。

刻意保持薄:不做熔断/限速/预算/同模型重试(那是 Resilient 的职责,二者可叠加—— Resilient.RawModelWithFailover() 即本装饰器包住整条 Provider 链)。切换决策对任何 错误恒真:429/5xx/网络类错误切换必然正确;401/403/上下文超限类确定性错误在模型 同端点时切了也白切,但异端点/异凭证时能救——误切代价仅一次模型调用。 调用方语义的失败(ctx 取消/超时)不切换,原样上抛。

从 bianque 生产装配提炼(v0.9.4);v0.10.9 由主备二元泛化为 N 模型链。

本文件族是 LLM Provider 抽象与多后端支持(Provider 接口 + FallbackChain 模型降级链)。包级总览见 client.go 的唯一 package doc。

流恢复原语(对标 ZCode streaming-recovery.ts + model-errors.ts):turn 级 流式请求的瞬态错误分类、空终态检测、上下文溢出识别与 busy 准入退避。 单请求重试骨架见 errors.go(ClassifyLLMError)与 retryafter.go;本文件补 「流断在哪、什么算瞬态、什么时候放弃」的 turn 级语义。

StageRouter 分阶段模型路由:不同业务阶段(stage)用不同的 Generator—— 大窗口模型吃长输入(R1)、快模型跑判定(R3)、强模型保质量(R4)。

匹配语义:精确命中优先,其次最长前缀(注册 "R1" 可命中 "R1"/"R1a"); 未命中走缺省 Generator。装配示例:

sr := llm.NewStageRouter(defaultGen)      // 缺省 = 主链(含预算注入)
sr.Use("R1", r1Gen)                        // 前缀路由,命中 "R1"/"R1a"
sr.Use("qa", qaGen)                        // 精确路由
// 之后 sr.Generate(ctx, "R1a", ...) 走 r1Gen

预算:路由不改变预算语义——各 Generator 应由调用方完成预算注入 (BudgetInjector.WithBudget)后再注册;UsedTokens 聚合全部已注册链。

usage.go — LLM 用量采集(eino callbacks)——**全仓唯一的 callbacks 侧记账 出口**:拿得到 Cached/Reasoning tokens、FinishReason、Duration、Iteration。 归因经 Labels(map)透传,领域键由调用方决定。 (v0.10.12 前 obsx.Options.OnUsage 是并行的五数字出口,防重护栏跨包放在 obsx——现护栏归位于本包,obsx 回归纯 trace,ReAct/RawModel 旁路记账统一 走 NewUsageHandler 注入 callbacks。)

Index

Constants

View Source
const MaxServerRetryAfter = httpjson.MaxServerRetryAfter

MaxServerRetryAfter 服务端退避建议的可信上限:超过 5 分钟的建议不直接 采信(长时间限流应交由 failover/降级链处置,而非原地等)。

View Source
const MaxStreamRecoveryRetries = 10

MaxStreamRecoveryRetries 流断点恢复预算(对标 STREAM_RECOVERY_MAX_RETRIES: 跨过单请求重试边界后只恢复 1 次与用户预期差距过大,10 次是生产调过的上限)。

Variables

View Source
var BusyAdmissionDelays = []time.Duration{time.Second, 2 * time.Second}

BusyAdmissionDelays busy 准入重试的两档退避(对标 START_PLAN_BUSY_MAIN_TURN_ADMISSION_RETRY_DELAYS_MS:1s/2s)。

Functions

func BusyAdmissionDelay

func BusyAdmissionDelay(retry int) time.Duration

BusyAdmissionDelay 按 retry 序号(0 起)取准入退避;越界取最后一档。

func IsContextExceededError

func IsContextExceededError(err error) bool

IsContextExceededError 错误文案层识别上下文溢出(provider 报文形态)。

func IsContextExceededFinish

func IsContextExceededFinish(finishReason, rawFinishReason string) bool

IsContextExceededFinish finishReason 层识别上下文溢出。

func IsProviderBusyCode

func IsProviderBusyCode(code string) bool

IsProviderBusyCode provider 业务码是否「繁忙」类(准入级退避重试的判定; 耗尽后调用方应合成不可重试错误,别无限排队)。

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError 快速判定是否 429 类错误。

func IsTransientStreamError

func IsTransientStreamError(err error) bool

IsTransientStreamError 流式 turn 中的错误是否瞬态(值得从安全锚点重开流)。 与 ClassifyLLMError 的分工:后者面向单请求 HTTP 重试;本判定面向 turn 级 恢复——额外覆盖流空闲超时与原因码形态(错误链上任意一层的文案都参与匹配)。 调用方主动取消不算瞬态(与降级矩阵「ctx 取消 → 全链中止」一致):若判为 瞬态,取消后的恢复循环会空转 MaxStreamRecoveryRetries 轮、每轮立即再失败。

func IsTruncatedError

func IsTruncatedError(err error) bool

IsTruncatedError 快速判定是否输出截断错误。

func NewUsageHandler

func NewUsageHandler(onRecord Sink) callbacks.Handler

NewUsageHandler 构建 eino callbacks.Handler(非流式 OnEnd + 流式 OnEndWithStreamOutput——流式分支在后台 goroutine 排空副本流后回调, ⚠ onRecord 可能被多 goroutine 并发调用,须并发安全(第十轮审计钉约)。 OnEndWithStreamOutput——第八轮审计补齐:eino-ext 流式只触发后者,仅注册 OnEnd 的 handler 在开流(agentrun v0.10.32 起默认)下一条记录都不产生, 静默失明。流式分支消费框架给的副本流取末个非 nil usage,末帧即汇总帧)。 无 TokenUsage、无 RunInfo 的调用静默跳过;Labels 缺省不阻断采集。 防重:Client 自身配置了 OnUsage/Budget 时(Client 记账标记已在 ctx), 本 handler 对同一次物理调用自动跳过——两侧不会双倍记账,可同时启用。 ⚠ agentrun 场景:Config.OnUsage 与本 handler 记同一笔——**择一装配** (两者没有共享防重标记,同时挂会双计)。

func PriceOf

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

func RetryAfterFrom

func RetryAfterFrom(ctx context.Context) time.Duration

RetryAfterFrom 读取最近一次 429 的服务端建议(0 = 无)。

func RetryableLLMError

func RetryableLLMError(err error) bool

RetryableLLMError 判定错误是否值得重试。

func SelectRetryDelay

func SelectRetryDelay(local, hint time.Duration) time.Duration

SelectRetryDelay 退避取舍(钳制:建议 ≤5min 或 < 本地曲线才采信)。

func SuspiciousEmptyResult

func SuspiciousEmptyResult(finishReason string, responseLen, toolCallCount, usageTotal int) bool

SuspiciousEmptyResult 空终态检测:零文本 + 零工具调用 + 非 stop 终止 + 零用量 = 可疑空响应(各家 provider 都会偶发;按可重试错误处理而非当正常 完结吞掉)。usageTotal 为 provider 报告的总 token(≤0 视为零用量/未知)。

func WithCallCounter

func WithCallCounter(ctx context.Context) context.Context

WithCallCounter ctx 挂轮次计数器(每个 agent 运行作用域一个)。

func WithRetryAfterSink

func WithRetryAfterSink(ctx context.Context) context.Context

WithRetryAfterSink 在 ctx 注入 sink(重试环上游调用一次;传输层据此回写)。

func WithUsageLabels

func WithUsageLabels(ctx context.Context, labels map[string]string) context.Context

WithUsageLabels ctx 注入归因标签;不注入或无标签时 UsageRecord.Labels 为 nil。

Types

type Attempt

type Attempt struct {
	Provider string
	Model    string
	// Err error 类型(v0.11 起不再折损为 string):errors.Is/As 经
	// AttemptError.Unwrap 可直达 RequestError/context.Canceled 等——下游无须
	// 对聚合文案做文本分类(多模型聚合文本里 429 与 401 共存时文本分类结果
	// 取决于 marker 优先级,不可靠)。
	Err      error
	Duration time.Duration
}

Attempt 单模型尝试记录(AttemptError 的组成部分)。

type AttemptError

type AttemptError struct {
	Stage    string
	Attempts []Attempt
	// BreakerSkipped 被熔断跳过(未真正尝试)的模型数——仅入 Error 文案,
	// 区分"链上真失败 N 个"与"另 M 个熔断未试"。
	BreakerSkipped int
}

AttemptError 全部模型失败的聚合错误(含每个模型的失败原因链)。

func (*AttemptError) Error

func (e *AttemptError) Error() string

func (*AttemptError) Unwrap

func (e *AttemptError) Unwrap() []error

Unwrap 暴露各模型的原始错误链(Go 多错误 Unwrap 约定):errors.Is/As 穿透 聚合错误直达底层错误类型。

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 BudgetConfig

type BudgetConfig struct {
	ContextTokens int // 模型上下文窗口(token 数,输入+输出合计)
}

BudgetConfig 模型窗口派生配置("单一基准"哲学:窗口是唯一基准,全部上限按比例派生)。

func (BudgetConfig) MaxDiffChars

func (c BudgetConfig) MaxDiffChars() int

MaxDiffChars diff 总量上限(字符):窗口 35% 折算字符,min 10k。 chars/token 按 4 估算(中文按 2 保守,见 fitInput)。

func (BudgetConfig) MaxOutputTokens

func (c BudgetConfig) MaxOutputTokens() int

MaxOutputTokens 单次生成上限:max(4096, 窗口 5%)。

func (BudgetConfig) ReqBodyMaxChars

func (c BudgetConfig) ReqBodyMaxChars() int

ReqBodyMaxChars 需求文本上限:窗口 6%。

func (BudgetConfig) ReqIssueMaxChars

func (c BudgetConfig) ReqIssueMaxChars() int

ReqIssueMaxChars issue 文本上限:窗口 1.5%。

func (BudgetConfig) TaskTokenBudget

func (c BudgetConfig) TaskTokenBudget() int

TaskTokenBudget 任务 token 预算:窗口 60%。

type BudgetHolder

type BudgetHolder interface {
	BoundBudget() TokenAccountant
}

BudgetHolder 可选接口:暴露 Generator 绑定的预算累计器。 聚合方(StageRouter.UsedTokens)按其指针身份去重——各链共享同一 Budget 时 逐链求和会按链数倍增。

type BudgetInjector

type BudgetInjector interface {
	WithBudget(b TokenAccountant) Generator
}

BudgetInjector 可选接口:支持运行时注入任务级预算(Dispatcher fan-out 场景)。 调用方通过类型断言检测;不实现则预算保持构造时的值。

type ChainLink struct {
	Model einomodel.BaseChatModel
	Name  string
}

ChainLink failover 链单链节:模型 + 观测名(结构化链节消平行切片错位—— names[i] 对不上 models[i] 是装配期静默事故)。

type Client

type Client struct {
	Model     model.BaseChatModel
	ModelName string

	// 模型窗口(token,输入+输出合计)与单次输出预留。
	ContextTokens   int
	MaxOutputTokens int

	// OnUsage 每次调用后的 token 记账回调(stage, promptTokens, completionTokens)。
	OnUsage func(stage string, prompt, completion int)
	// Budget 任务 token 预算累计器(可 nil)。
	Budget TokenAccountant
	// Limiter 全局 LLM 请求限速器(token bucket);nil = 不限速。
	Limiter *rate.Limiter

	MaxRetries int           // 最大尝试次数(含首次),默认 3
	BaseDelay  time.Duration // 指数退避基准,默认 2s
	MaxDelay   time.Duration // 退避上限,默认 30s
}

Client LLM 客户端(带重试、限速、预算、fitInput)。

func NewClient

func NewClient(m model.BaseChatModel, modelName string, budget TokenAccountant) *Client

NewClient 创建 LLM 客户端。

func (*Client) BoundBudget

func (c *Client) BoundBudget() TokenAccountant

BoundBudget 返回绑定的预算累计器(可 nil)。BudgetHolder 接口实现。

func (*Client) Generate

func (c *Client) Generate(ctx context.Context, stage string, msgs []*schema.Message) (*schema.Message, error)

Generate 带重试的普通生成(指数退避 + jitter)。 确定性失败(鉴权/权限/上下文超限)不重试;429 退避下限提高后值得等 (单客户端无备选可切)。stage 同时注入 ctx(obsx.WithStage)——eino callbacks handler 可读到业务阶段。

func (*Client) GenerateJSON

func (c *Client) GenerateJSON(ctx context.Context, stage string, msgs []*schema.Message, out any) error

GenerateJSON 生成并解析 JSON;解析失败把原始输出与错误回喂重试 1 次。 out 必须是 *T。截断在重试核心内已转为错误返回,走到这里的解析失败只会是 json.Unmarshal 错误——回喂提示只有"输出合法 JSON"一种。 刻意走 ExtractJSON+严格 Unmarshal 而非 jsonrepair.Unmarshal 宽容链:宽容修复 会改变输出内容,而本方法的失败路径要回喂重试,lastErr 须是纯解析错误。 宽容回收半损坏输出归 jsonrepair.Unmarshal(无重试回喂的场景用)。

func (*Client) RawModel

func (c *Client) RawModel() model.BaseChatModel

RawModel 返回底层 eino 模型(Generator 接口实现)。

func (*Client) UsedTokens

func (c *Client) UsedTokens() int

UsedTokens 返回累计消耗 token 数(无预算绑定时返回 0)。

func (*Client) WithBudget

func (c *Client) WithBudget(b TokenAccountant) Generator

WithBudget Client 的预算注入:浅拷贝(Client 只含无锁值字段,Budget 指针共享是有意为之)。

type CostRecord

type CostRecord struct {
	Model            string // 模型名
	Stage            string // 阶段(R1/R2/R3/R4/R5/plugin:xxx)
	PromptTokens     int
	CompletionTokens int
	CostUSD          float64 // 本次成本(美元)
}

CostRecord 单次调用成本记录。

type CostTracker

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

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

func NewCostTracker

func NewCostTracker() *CostTracker

NewCostTracker 创建成本追踪器。

func (*CostTracker) Record

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

Record 记录一次调用。

func (*CostTracker) Records

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

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

func (*CostTracker) Summary

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

Summary 按模型汇总。

type FailoverModel

type FailoverModel struct {

	// OnFailover 切换观测回调(from/to 模型名 + reason=失败模型错误;nil=静默)。
	OnFailover func(from, to, reason string)
	// contains filtered or unexported fields
}

FailoverModel 模型链装饰器(构建后只读;OnFailover 构建期设置)。

func NewFailoverModel

func NewFailoverModel(links ...ChainLink) *FailoverModel

NewFailoverModel 模型链按序 failover 装饰(至少一节;主备二元传两节即可)。 nil 模型会在被尝试到时报"failover 链含 nil 模型"错误并继续下一个。

func (*FailoverModel) Generate

func (m *FailoverModel) Generate(ctx context.Context, input []*schema.Message, opts ...einomodel.Option) (*schema.Message, error)

Generate 按链序尝试;失败且 ctx 存活(非调用方取消/超时)时切下一个。 全部失败上抛末个模型的错误——此前各模型的失败经 OnFailover 回调可见。

func (*FailoverModel) Stream

Stream 与 Generate 同一降级语义(首块前失败才可切换;已在流中失败无法换模型重放)。

func (*FailoverModel) WithTools

WithTools 工具绑定转发:链上各模型分别派生带工具的实例后重新包装(不可变派生, 并发安全语义与 eino ToolCallingChatModel 契约一致)。

type FallbackChain

type FallbackChain struct {
	Providers []Provider
}

FallbackChain 模型降级链:按优先级尝试多个 Provider。

func NewFallbackChain

func NewFallbackChain(providers ...Provider) *FallbackChain

NewFallbackChain 创建降级链。

func (*FallbackChain) Fallback

func (fc *FallbackChain) Fallback() []Provider

Fallback 返回备选 Provider 列表(不含主 Provider)。

func (*FallbackChain) Primary

func (fc *FallbackChain) Primary() Provider

Primary 返回主 Provider(优先级最高)。

type Generator

type Generator interface {
	Generate(ctx context.Context, stage string, msgs []*schema.Message) (*schema.Message, error)
	GenerateJSON(ctx context.Context, stage string, msgs []*schema.Message, out any) error
	// UsedTokens 返回该客户端累计消耗的 token 数(无预算绑定时返回 0)。
	UsedTokens() int
	// RawModel 返回底层 eino 模型(供 ReAct agent 等需要裸模型的场景;
	// Resilient 返回主模型——降级仅覆盖 Generate/GenerateJSON 路径)。
	RawModel() model.BaseChatModel
}

Generator LLM 生成接口:*Client 与 *Resilient 都满足,调用方无感切换。

type ModelSummary

type ModelSummary struct {
	Model                 string
	TotalPromptTokens     int
	TotalCompletionTokens int
	TotalCostUSD          float64
	CallCount             int
}

ModelSummary 单模型汇总。

func (ModelSummary) TotalTokens

func (s ModelSummary) TotalTokens() int

TotalTokens 总 token 消耗。

type OpenAIProvider

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

OpenAIProvider OpenAI 兼容 LLM 提供者。 支持 OpenAI / 百炼 / DeepSeek / 任何 OpenAI 兼容端点。

func NewOpenAIProvider

func NewOpenAIProvider(ctx context.Context, cfg OpenAIProviderConfig) (*OpenAIProvider, error)

NewOpenAIProvider 创建 OpenAI 兼容提供者。

func (*OpenAIProvider) AttemptTimeout

func (p *OpenAIProvider) AttemptTimeout() time.Duration

AttemptTimeout 单次尝试超时(Resilient 降级链按 Provider 独立限时)。

func (*OpenAIProvider) ContextTokens

func (p *OpenAIProvider) ContextTokens() int

func (*OpenAIProvider) CostPer1KTokens

func (p *OpenAIProvider) CostPer1KTokens() (float64, float64)

func (*OpenAIProvider) MaxOutputTokens

func (p *OpenAIProvider) MaxOutputTokens() int

func (*OpenAIProvider) Model

func (p *OpenAIProvider) Model() model.BaseChatModel

func (*OpenAIProvider) ModelName

func (p *OpenAIProvider) ModelName() string

func (*OpenAIProvider) Name

func (p *OpenAIProvider) Name() string

type OpenAIProviderConfig

type OpenAIProviderConfig struct {
	BaseURL         string
	APIKey          string
	Model           string
	ContextTokens   int           // 模型上下文窗口(默认 128k)
	MaxOutputTokens int           // 单次输出上限(默认从窗口派生)
	Timeout         time.Duration // 请求超时(默认 60s)
	// CaptureRetryAfter 捕获 429 响应的 Retry-After 头(批次四十五,对标 ZCode
	// runner-retry 的服务端退避建议):经 ctx sink 透出给重试环的退避取舍。
	// 需要 http.Client 注入(Timeout 语义由注入的 Client 自带保留)。
	CaptureRetryAfter   bool
	CostPer1KPrompt     float64 // 每 1000 prompt token 成本(美元)
	CostPer1KCompletion float64 // 每 1000 completion token 成本(美元)
}

OpenAIProviderConfig OpenAI 提供者配置。

type Price

type Price struct {
	InputPerM  float64
	OutputPerM float64
}

Price 单模型定价(USD / 1M tokens)。

func PriceOfMatched

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

PriceOf 按模型名匹配定价表估算单次调用成本(USD;未配价返回 0,不阻塞记账)。 精确匹配优先,未命中回落最长前缀(模型名带版本/日期后缀时定价键可只写主干)。 宿主只需提供 map[模型名]Price;来自 bianque scheduler/usage.go 生产路径(v0.9.5 沉淀)。 PriceOfMatched PriceOf 的可观测形态:返回命中的定价键与是否经前缀回落。 前缀回落对「同主干异价 SKU」(gpt-4o 前缀命中 gpt-4o-mini)会静默误定价 ——宿主可在装配期对回落命中做校验/上报(第十轮审计)。

type Provider

type Provider interface {
	// Name 提供者名称(如 "openai", "anthropic", "ollama")。
	Name() string
	// Model 返回 eino ChatModel 实例。
	Model() model.BaseChatModel
	// ModelName 返回模型标识(如 "gpt-4o", "deepseek-v4")。
	ModelName() string
	// ContextTokens 返回模型上下文窗口大小(token 数)。
	ContextTokens() int
	// MaxOutputTokens 返回单次生成输出上限。
	MaxOutputTokens() int
	// CostPer1KTokens 返回每 1000 token 的成本(美元)。
	// 用于成本追踪,返回 0 表示不计费。
	CostPer1KTokens() (prompt, completion float64)
}

Provider LLM 提供者接口:抽象模型创建与配置。

type Resilient

type Resilient struct {

	// Budget 任务 token 预算(nil = 不限)。耗尽时全链短路。
	Budget TokenAccountant
	// Limiter 全局限速器(所有模型共享;nil = 不限速)。
	Limiter *rate.Limiter
	// Tracker 成本追踪器(nil = 不追踪)。记账跟随实际执行的模型。
	Tracker *CostTracker
	// OnUsage token 使用回调(model = 实际执行的模型)。
	OnUsage func(model, stage string, prompt, completion int)
	// OnFallback 降级事件回调(from = 失败模型,to = 切换到的模型)。
	OnFallback func(from, to, stage, reason string)
	// contains filtered or unexported fields
}

Resilient 弹性 LLM 客户端。

与 *Client 方法签名一致(满足 Generator),可作 drop-in 替换。 额外能力:多模型降级、按模型熔断、预算短路、每模型窗口自适应 fitInput。

func NewResilient

func NewResilient(chain *FallbackChain, cfg ResilientConfig) (*Resilient, error)

NewResilient 创建弹性客户端。空 Provider 列表报错。

func (*Resilient) BoundBudget

func (r *Resilient) BoundBudget() TokenAccountant

BoundBudget 返回绑定的预算累计器(可 nil)。BudgetHolder 接口实现。

func (*Resilient) Generate

func (r *Resilient) Generate(ctx context.Context, stage string, msgs []*schema.Message) (*schema.Message, error)

Generate 弹性生成:按链序尝试各 Provider,全部失败返回 *AttemptError。

func (*Resilient) GenerateJSON

func (r *Resilient) GenerateJSON(ctx context.Context, stage string, msgs []*schema.Message, out any) error

GenerateJSON 弹性 JSON 生成:每模型内部含解析失败回喂重试,仍败切换下一模型。

func (*Resilient) LastModel

func (r *Resilient) LastModel() string

LastModel 返回最近一次成功调用使用的模型名(无成功调用返回空)。

func (*Resilient) PrimaryModel

func (r *Resilient) PrimaryModel() model.BaseChatModel

PrimaryModel 返回主模型的 BaseChatModel(供 eino agent 工具表使用)。

func (*Resilient) RawModel

func (r *Resilient) RawModel() model.BaseChatModel

RawModel Generator 接口实现:等价 RawModelWithFailover()——裸模型路径同样获得 链式降级(v0.10.9 起不再返回未装饰的主模型;要主模型裸实例用 PrimaryModel)。 ⚠ 此路径不受 Budget/Tracker/熔断约束(failover 薄切换刻意不做记账——ReAct 主路径依赖此形态时预算可被烧穿、Tracker 对该路径消耗记零;需要硬上限时勿把 RawModel 交给不受限消费方,或消费方自行在回调侧记账)。

func (*Resilient) RawModelWithFailover

func (r *Resilient) RawModelWithFailover() model.BaseChatModel

RawModelWithFailover 把整条 Provider 链包装成一个带 failover 的 BaseChatModel: ReAct 等裸模型路径按链序降级,不再静默丢弃降级链(此前 RawModel 返回未装饰的 主模型——链/熔断只覆盖 Generate/GenerateJSON,两条路径两套命运)。语义与 FailoverModel 一致:薄切换(无熔断/限速/预算/同模型重试),全败上抛末个模型错误, 切换经 OnFallback 观测。

func (*Resilient) String

func (r *Resilient) String() string

String 返回降级链描述(如 "openai(gpt-4o) → openai(deepseek-v4)")。

func (*Resilient) UsedTokens

func (r *Resilient) UsedTokens() int

UsedTokens Generator 接口实现:返回预算累计消耗。

func (*Resilient) WithBudget

func (r *Resilient) WithBudget(b TokenAccountant) Generator

WithBudget Resilient 的预算注入:显式构造新实例(Resilient 含互斥字段不可浅拷贝)。 chain/breakers 共享原实例(熔断状态跨任务全局),Budget/Tracker 指向任务级。

type ResilientConfig

type ResilientConfig struct {
	// RetriesPerModel 每模型重试次数(不含首次),默认 2。
	RetriesPerModel int
	// BreakerTrip 模型熔断阈值(连续失败次数),默认 3。
	BreakerTrip int
	// BreakerCooldown 模型熔断冷却期,默认 5 分钟。
	BreakerCooldown time.Duration // BaseDelay 重试退避基准,默认 2s(测试可调小)。
	BaseDelay       time.Duration
	// MaxDelay 重试退避上限,默认 30s。
	MaxDelay time.Duration
	// BreakerProbeTimeout 熔断半开探测的单次时限。缺省按「单模型一次完整尝试」
	// 推导:(RetriesPerModel+1) 次尝试 × (MaxDelay + 90s 预算)。该值必须覆盖
	// tryOneProvider 的最长在途时间——否则 Breakers 会把超时探测判为失联并
	// 放行并发新探测,半开「单探测隔离」失效(v0.10.28 审计修复)。
	BreakerProbeTimeout time.Duration
}

ResilientConfig 弹性客户端配置。

type RetryHint

type RetryHint struct {
	Retryable   bool // 是否值得重试
	IsRateLimit bool // 是否 429/rate limit
	IsTruncated bool // 是否输出截断
}

RetryHint 错误分类结果。

func ClassifyLLMError

func ClassifyLLMError(err error) RetryHint

ClassifyLLMError 判定是否值得重试及错误类型。 分类优先级:结构化 HTTP 状态码(go-openai RequestError)→ 文本 marker。 确定性失败(鉴权/参数错/模型不存在/上下文超限等 4xx)不重试——重试纯浪费 钱和延迟;429 可重试 + 退避下限提高;5xx/网络错误默认可重试。

type Sink

type Sink func(UsageRecord)

Sink 用量出口。

type StageRouter

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

StageRouter 按 stage 分发的 Generator(Generator 接口实现,可无感嵌入现有装配)。

func NewStageRouter

func NewStageRouter(def Generator) *StageRouter

NewStageRouter 创建阶段路由器(def = 未命中阶段的缺省 Generator)。 def 为 nil panic——构造期配置错误尽早暴露(与 toolprior.Table.Add 同纪律), 运行期才炸的 nil 难排查。

func (*StageRouter) Generate

func (r *StageRouter) Generate(ctx context.Context, stage string, msgs []*schema.Message) (*schema.Message, error)

func (*StageRouter) GenerateJSON

func (r *StageRouter) GenerateJSON(ctx context.Context, stage string, msgs []*schema.Message, out any) error

func (*StageRouter) RawModel

func (r *StageRouter) RawModel() model.BaseChatModel

RawModel 缺省链的底层模型(StageRouter 本身不做 ReAct——ReAct 需要哪个模型 由调用方显式指定)。

func (*StageRouter) Use

func (r *StageRouter) Use(stage string, gen Generator)

Use 注册阶段路由。stage 为精确名("qa")或前缀("R1" 命中 "R1a"); 重名注册覆盖旧路由。

func (*StageRouter) UsedTokens

func (r *StageRouter) UsedTokens() int

UsedTokens 全部链(缺省 + 已注册)的预算累计和——任务预算口径下各链共享 同一 Budget 时即任务总消耗。暴露 BudgetHolder 的链按预算指针身份去重, 避免共享预算被按链数倍增;未暴露的链退回逐链求和。

type TokenAccountant

type TokenAccountant interface {
	Add(n int)
	Used() int
	Remaining() int
}

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

type UsageCallCounter

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

UsageCallCounter 单作用域 LLM 调用轮次计数(原子递增)。

type UsageRecord

type UsageRecord struct {
	Model            string
	PromptTokens     int
	CachedTokens     int
	CompletionTokens int
	ReasoningTokens  int
	TotalTokens      int
	DurationMS       int64
	FinishReason     string
	Iteration        int64             // 本作用域第几次 LLM 调用(1 起;无计数器为 0)
	Labels           map[string]string // 归因标签(session/case/step/agent 等,调用方自定)
}

UsageRecord 单次 LLM 调用的用量记录。

Directories

Path Synopsis
Package llmtest eino ChatModel/Provider 的脚本化测试桩——llm/reflection/router 等包的测试共用(此前四份手写桩已漂移出两种耗尽语义与多种记录口径)。
Package llmtest eino ChatModel/Provider 的脚本化测试桩——llm/reflection/router 等包的测试共用(此前四份手写桩已漂移出两种耗尽语义与多种记录口径)。
输出截断续写(对标 ZCode turn-output-token-continuation):finish reason 为 length/max_tokens 且消息无工具调用时,保留部分输出并以「直接续写」指令再调, 内容顺序拼接,最多 maxContinuations 轮。
输出截断续写(对标 ZCode turn-output-token-continuation):finish reason 为 length/max_tokens 且消息无工具调用时,保留部分输出并以「直接续写」指令再调, 内容顺序拼接,最多 maxContinuations 轮。

Jump to

Keyboard shortcuts

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