Documentation
¶
Overview ¶
Package openai 实现 OpenAI Chat Completions API 的 ModelProvider.
支持所有 OpenAI 官方模型(GPT-4o,o1,o3 系列等), 通过 Config.ModelOverrides 可注入厂商新发布但我们尚未维护的模型.
内部复用 internal/wire/openai_compat.go 处理 SSE 流式协议, 本包只负责配置工厂和维护静态模型表.
Index ¶
- type Config
- type Provider
- func (p *Provider) CheckHealth(ctx context.Context) error
- func (p *Provider) ExtractVision(ctx context.Context, req *flyto.VisionRequest) (*flyto.VisionResponse, error)
- func (p *Provider) Models(ctx context.Context) ([]flyto.ModelInfo, error)
- func (p *Provider) Name() string
- func (p *Provider) Stream(ctx context.Context, req *flyto.Request) (<-chan flyto.Event, error)
- func (p *Provider) Transcribe(ctx context.Context, req *flyto.TranscriptionRequest) (*flyto.TranscriptionResponse, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// APIKey 是 OpenAI API 密钥(sk-... 格式).
APIKey string
// BaseURL 覆盖 API 端点(默认 https://api.openai.com).
// 用于 Azure OpenAI,兼容 OpenAI 的私有化端点等.
BaseURL string
// HTTPClient 注入自定义 HTTP 客户端.
// nil = 使用默认 http.Client (带 Timeout 字段配置的 ResponseHeaderTimeout).
// 非 nil 时 consumer 完全接管超时责任, 下面的 Timeout 字段被忽略.
HTTPClient *http.Client
// Timeout 限制"从请求发出到收到响应首字节"的时间.
//
// 通过 http.Transport.ResponseHeaderTimeout 实现, **不影响** SSE 流式响应的后续
// body 读取 - 长流式回复 (2-5 分钟) 可以正常读完.这是 LLM provider 的正确超时
// 语义: 捕捉服务端死等, 放行长流式输出.
//
// 精妙之处(CLEVER): 不要误用 http.Client.Timeout - 那会把 SSE 流砍死.
// 详见 internal/wire/openai.go defaultResponseHeaderTimeout 注释.
//
// 0 = 使用 defaultTimeout (60s, 适合 OpenAI 云端).
// 仅当 HTTPClient 为 nil 时生效; 提供自定义 HTTPClient 时此字段被忽略.
Timeout time.Duration
// ModelOverrides 覆盖或扩展静态模型表.
// 厂商发布新模型时,无需等待我们更新代码即可使用.
ModelOverrides []flyto.ModelInfo
// LiveDiscovery 为 true 时, Models() 改打 {BaseURL}/v1/models 现场拉取
// 当前加载的模型, 而非返回静态 GPT 表. 给自托管 OpenAI 兼容端点 (fmlx /
// oMLX) 用 — 这类端点没有静态目录, 真目录只能现场发现 (ADR-0018 第二档).
// 默认 false = 现状静态行为 (真 OpenAI 零回归: api.openai.com 的
// /v1/models 返 embedding/audio/tts 一堆无 pricing/context 的垃圾, 故
// 真 OpenAI 不 opt-in).
//
// When true, Models() GETs {BaseURL}/v1/models to live-discover the
// currently loaded models instead of returning the static GPT table.
// For self-hosted OpenAI-compatible endpoints (fmlx / oMLX) that have
// no static catalog -- their real catalog can only be discovered live
// (ADR-0018 tier 2). Defaults to false = current static behavior (zero
// regression for real OpenAI: api.openai.com /v1/models returns
// embedding/audio/tts junk with no pricing/context, so real OpenAI
// does not opt in).
LiveDiscovery bool
// HealthCheckPath, when non-empty (e.g. "/health"), enables the
// flyto.HealthChecker capability: CheckHealth GETs {BaseURL}{path}
// (no auth) and expensive single-shot calls (Transcribe) pre-flight it
// to fail fast when the backend is offline. For self-hosted backends
// (oMLX on a Mac) that are NOT always online; real OpenAI leaves it
// empty (no such endpoint) and CheckHealth degrades to a no-op nil.
//
// HealthCheckPath 非空时 (如 "/health") 启用 flyto.HealthChecker 能力:
// CheckHealth GET {BaseURL}{path} (免鉴权), 昂贵单次调用 (Transcribe)
// 会先探活, 后端离线时快速失败. 给**不常在线**的自托管后端 (Mac 上的
// oMLX) 用; 真 OpenAI 留空 (无此端点), CheckHealth 退化为 no-op nil.
HealthCheckPath string
}
Config 是 OpenAI provider 的配置.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider 是 OpenAI ModelProvider 实现.
func (*Provider) CheckHealth ¶
CheckHealth implements flyto.HealthChecker. No-op nil when Config.HealthCheckPath is empty (no probe available != unhealthy). A reachable endpoint must answer 2xx AND, when the body carries a "status" field, report a healthy value -- {"status":"degraded"} with HTTP 200 is still a failure (fail-loud, ADR-0006). Both "healthy" (single-backend oMLX) and "ok" (oMLX multi-backend router since 2026-07) count as healthy: the router's top-level status is {"status":"ok","backends":{...}}, and rejecting it silently forced every transcription onto the cloud fallback (prod incident 2026-07-17..19).
CheckHealth 实现 flyto.HealthChecker. Config.HealthCheckPath 空时 no-op nil (无探测可用 != 不健康). 可达端点须 2xx **且** body 带 "status" 字段时报健康值 -- HTTP 200 + {"status":"degraded"} 仍算失败 (fail-loud, ADR-0006). "healthy" (单后端 oMLX) 与 "ok" (2026-07 起的 oMLX 多后端 router, 顶层 {"status":"ok","backends":{...}}) 都算健康 -- 拒掉 "ok" 曾把所有转写 静默逼上云端降级 (生产事故 2026-07-17..19).
func (*Provider) ExtractVision ¶
func (p *Provider) ExtractVision(ctx context.Context, req *flyto.VisionRequest) (*flyto.VisionResponse, error)
ExtractVision implements flyto.VisionProvider. Single non-streaming HTTP POST; blocks until the full response body is read. Model is REQUIRED (the OpenAI wire has no endpoint-locked model like minimax). Errors propagate from transport failures, non-2xx HTTP, an error envelope, or empty content (caller cannot parse "" so we fail loudly, surfacing a reasoning snippet to diagnose a clipped / reasoning-only response).
ExtractVision 实现 flyto.VisionProvider. 单次非流式 POST, 阻塞至读完 body. Model **必填** (OpenAI 线协议无 minimax 那种端点锁模型). 错误来源: 传输失败 / 非 2xx / error 外壳 / 空 content (调用方解析 "" 没意义, fail-loud, 附 reasoning 片段诊断被切断 / 只有推理的响应).
func (*Provider) Models ¶
Models 返回 OpenAI 可用模型列表.
历史包袱(LEGACY): OpenAI 有 /v1/models 端点,但返回的模型列表包含 embedding,audio,image 等非 chat 模型,且不包含定价和上下文窗口信息. 我们维护静态表覆盖主要 chat 模型,通过 ModelOverrides 补充新模型. 未来改进:调用 /v1/models 后与静态表合并,补充定价信息.
现场发现 (LiveDiscovery, ADR-0018 第二档): 自托管 OpenAI 兼容端点 (fmlx / oMLX) 没有静态目录, Config.LiveDiscovery=true 时改打 {BaseURL}/v1/models 现场拉真目录 (复用 lmstudio 已验证的 OpenAICompatClient.FetchOpenAIModels). 默认 false 保留真 OpenAI 的静态路径不变.
Live discovery (LiveDiscovery, ADR-0018 tier 2): self-hosted OpenAI-compatible endpoints (fmlx / oMLX) have no static catalog. When Config.LiveDiscovery=true, Models() GETs {BaseURL}/v1/models to fetch the real catalog live (reusing the lmstudio-proven OpenAICompatClient.FetchOpenAIModels). Defaults to false, keeping real OpenAI's static path unchanged.
func (*Provider) Transcribe ¶
func (p *Provider) Transcribe(ctx context.Context, req *flyto.TranscriptionRequest) (*flyto.TranscriptionResponse, error)
Transcribe implements flyto.TranscriptionProvider. Single multipart POST; blocks until the full response body is read -- for long recordings the server transcribes before answering, so callers must budget the context deadline in minutes, not seconds. Model is REQUIRED (per-request selection, mirrors vision). For response_format json / verbose_json (or empty) the body is parsed into the structured response; for text / srt / vtt the raw body lands in Text.
Transcribe 实现 flyto.TranscriptionProvider. 单次 multipart POST, 阻塞至读完 body -- 长录音服务端转完才应答, 调用方的 context deadline 要按分钟级预算. Model **必填** (per-request 选, 对齐 vision). response_format 为 json / verbose_json (或空) 时解析结构化响应; text / srt / vtt 时原始 body 放 Text.