refactor(embedding): 删掉生产不可达的冷启动 WARN 及其 logger 缝
review round2 的 p2:`api.go` 那句「一次性冷启动放宽被花掉」的 WARN 在生产里 **不可能被人看见** —— 本包默认丢弃日志器,`WithLogger` 全树零调用者,生产走 `embedding.New(cfg)` 不带任何选项。而它的注释写着「这是唯一能审计一次性放宽的 地方」,与实际不符(本项目规则:注释不得说与实现不符的话)。 - 删该 WARN(失败并非静默:四个调用方本来就把错误原文写进各自的 WARN, 路径见 review 召回 / 整理召回 / 向量索引写入 / 检索)。 - 连带删掉 `WithLogger` 与 `log` 字段:WARN 是 `e.log` 的唯一使用者,留着它会 变成一个**空转的公开选项 + 又一次与实现不符的注释**(与上一轮删 `WithRequestTimeout` 同一条理由:不留没人用且未测的缝)。 - `callTimeout` 不再返回 `relaxed`(它只为那条 WARN 存在)。 - `NewAPI` 的注释改为把「本包不记日志、失败沿错误返回」写成**实话**。 行为零变化(生产本就是丢弃日志器),只删死代码与不实注释。
This commit is contained in:
1 parent
6c30ca540a
commit
95f517d61d
1 file changed
+10
-32
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -94,7 +93,6 @@ var ErrNoAPIURL = errors.New("embedding: api url is empty")
|
|||||||
type APIEmbedder struct {
|
type APIEmbedder struct {
|
||||||
cfg config.EmbeddingConfig
|
cfg config.EmbeddingConfig
|
||||||
client *http.Client
|
client *http.Client
|
||||||
log *slog.Logger
|
|
||||||
// warmupLeft 是**一次性**冷启动放宽的开关:为真时下一个请求用 warmupTimeout,
|
// warmupLeft 是**一次性**冷启动放宽的开关:为真时下一个请求用 warmupTimeout,
|
||||||
// 取走即置假(CompareAndSwap ⇒ 并发下也只有一个请求能拿到)。
|
// 取走即置假(CompareAndSwap ⇒ 并发下也只有一个请求能拿到)。
|
||||||
// 它**不是**「未成功就一直放宽」—— 失败也照样花掉这一次,因此服务挂死时
|
// 它**不是**「未成功就一直放宽」—— 失败也照样花掉这一次,因此服务挂死时
|
||||||
@@ -127,24 +125,17 @@ func WithRoundTripper(rt http.RoundTripper) APIOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLogger sets the logger used for embed failures. A nil logger is ignored
|
|
||||||
// and the default discard logger is kept.
|
|
||||||
func WithLogger(l *slog.Logger) APIOption {
|
|
||||||
return func(e *APIEmbedder) {
|
|
||||||
if l != nil {
|
|
||||||
e.log = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAPI builds an APIEmbedder from cfg using the default HTTP client.
|
// NewAPI builds an APIEmbedder from cfg using the default HTTP client.
|
||||||
// 单次超时不可由选项更换:它是 budget.go 那条推演的输入,批次上限与它同源,
|
//
|
||||||
// 因此**只允许改常量**(改了之后批次上限自动跟着改,测试的编译期/常量断言立刻复核)。
|
// 本包**不记日志**(没有任何 logger 缝):失败一律沿错误返回,由四个调用方
|
||||||
|
// (审查召回 / 整理召回 / 向量索引写入 / 检索)各自带 source 记账 —— 它们本来
|
||||||
|
// 就会把错误原文写进各自的 WARN,因此这里再记一条只会是重复的第二份。
|
||||||
|
// 单次超时也不可由选项更换:它是 budget.go 那条推演的输入,批次上限与它同源,
|
||||||
|
// 因此**只允许改常量**(改了之后批次上限自动跟着改,测试的常量断言立刻复核)。
|
||||||
func NewAPI(cfg config.EmbeddingConfig, opts ...APIOption) *APIEmbedder {
|
func NewAPI(cfg config.EmbeddingConfig, opts ...APIOption) *APIEmbedder {
|
||||||
e := &APIEmbedder{
|
e := &APIEmbedder{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
client: defaultHTTPClient(),
|
client: defaultHTTPClient(),
|
||||||
log: slog.New(slog.NewTextHandler(io.Discard, nil)),
|
|
||||||
}
|
}
|
||||||
// 冷启动放宽是一个**一次性**许可:见 APIEmbedder.warmupLeft。
|
// 冷启动放宽是一个**一次性**许可:见 APIEmbedder.warmupLeft。
|
||||||
e.warmupLeft.Store(true)
|
e.warmupLeft.Store(true)
|
||||||
@@ -200,21 +191,18 @@ func (e *APIEmbedder) Embed(ctx context.Context, texts []string) ([]Vector, erro
|
|||||||
// (成功、失败都算花掉)。因此:
|
// (成功、失败都算花掉)。因此:
|
||||||
// - 本进程的第一个请求最多等 warmupTimeout(冷启动),
|
// - 本进程的第一个请求最多等 warmupTimeout(冷启动),
|
||||||
// - 从第二个请求起一律 DefaultRequestTimeout —— 服务挂死时不会每次都慢。
|
// - 从第二个请求起一律 DefaultRequestTimeout —— 服务挂死时不会每次都慢。
|
||||||
//
|
func (e *APIEmbedder) callTimeout() time.Duration {
|
||||||
// 返回值 relaxed 表示这一次是不是拿到了放宽,供失败时留痕。
|
|
||||||
func (e *APIEmbedder) callTimeout() (timeout time.Duration, relaxed bool) {
|
|
||||||
if e.warmupLeft.CompareAndSwap(true, false) {
|
if e.warmupLeft.CompareAndSwap(true, false) {
|
||||||
return warmupTimeout, true
|
return warmupTimeout
|
||||||
}
|
}
|
||||||
return DefaultRequestTimeout, false
|
return DefaultRequestTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// embedChunk 发**一次** /embeddings 请求。chunk 必须已经落在 batchLimitsFor 推出来
|
// embedChunk 发**一次** /embeddings 请求。chunk 必须已经落在 batchLimitsFor 推出来
|
||||||
// 的边界内(Embed 负责切),因此它的耗时上界不超过单次超时的一半。
|
// 的边界内(Embed 负责切),因此它的耗时上界不超过单次超时的一半。
|
||||||
func (e *APIEmbedder) embedChunk(ctx context.Context, texts []string) ([]Vector, error) {
|
func (e *APIEmbedder) embedChunk(ctx context.Context, texts []string) ([]Vector, error) {
|
||||||
// 先取超时(取走即花掉那次放宽),再建 ctx:放宽许可与这次请求一一对应。
|
// 先取超时(取走即花掉那次放宽),再建 ctx:放宽许可与这次请求一一对应。
|
||||||
timeout, relaxed := e.callTimeout()
|
callCtx, cancel := context.WithTimeout(ctx, e.callTimeout())
|
||||||
callCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := e.newEmbeddingRequest(callCtx, texts)
|
req, err := e.newEmbeddingRequest(callCtx, texts)
|
||||||
@@ -224,16 +212,6 @@ func (e *APIEmbedder) embedChunk(ctx context.Context, texts []string) ([]Vector,
|
|||||||
|
|
||||||
resp, err := e.client.Do(req)
|
resp, err := e.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 冷启动放宽被花掉且没成功时留一条 WARN:它是「本进程第一次请求」这个
|
|
||||||
// 判断唯一能被事后复核的地方(此后一切失败都按常态超时快速失败)。
|
|
||||||
// 只记两个预算与错误(错误文本里已带端点,不另记 URL,避免重复落一条
|
|
||||||
// 可能含 userinfo 的地址)。
|
|
||||||
if relaxed {
|
|
||||||
e.log.Warn("embedding: one-shot cold-start allowance spent without a successful response",
|
|
||||||
"warmup_timeout", warmupTimeout.String(),
|
|
||||||
"steady_timeout", DefaultRequestTimeout.String(),
|
|
||||||
"error", err)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("embedding: request: %w", err)
|
return nil, fmt.Errorf("embedding: request: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|||||||
Reference in new issue
Block a user