Files
Cyrene_v2/internal/runtime/multimodal/vision/errors.go
T
AskaEth 94b882f4d9
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
perf(vision): 图片 URL 的「取回 + 内联」按内容哈希 memo(协同 H1:每回合重复付费/取回)
- 起因(Android 注册的成本项 H1):`resolveImages` 每回合都跑,而 `inlineImageSrc` 对 `data:` 短路、
  对 **URL 每回合重新取回并内联** ⇒ ① 同一张图反复网络 I/O;② 来源一旦失效则该回合解析失败,
  而 loop 保留原消息不中断回合 ⇒ **模型这轮静默看不到图**
- 修法:按**内容哈希**记忆「URL → data URI」结果(有界 + 并发安全,独立小文件承载);
  **失败不写缓存**(不把坏结果钉住,第二次仍可重试);命中即不再发起网络请求
- **来源失效不再静默**:取回失败产生可诊断信号(含主机与原因,**不含图片内容/凭据**),回合照旧继续
- 复核:vision 包 build/vet/测试绿(含 `-race`);断言覆盖「同一 URL 连续解析 → 上游只被取回 1 次且结果逐字节相同」
  「内容变化不被旧缓存命中」「失败不写缓存且第二次仍重试」「容量上限生效」,以及既有断言
  (非图片 `ErrNotImage` / SSRF 被拒 / 长度预检)不回归;变异反证已还原
2026-10-04 16:32:13 +08:00

65 lines
3.1 KiB
Go

package vision
import (
"errors"
"fmt"
)
// vision 包返回的哨兵错误。每个错误向外传递时都以 %w
// 包装,使调用方能按类别判断,同时保留原始原因。
var (
// ErrNoImage 在图像来源字符串为空时返回。
ErrNoImage = errors.New("vision: empty image source")
// ErrNotConfigured 在适配器未设置 endpoint/model 时返回。
ErrNotConfigured = errors.New("vision: adapter not configured")
// ErrUnsupportedSource 在图像来源无法处理时返回。
ErrUnsupportedSource = errors.New("vision: unsupported image source")
// ErrFetch 在 URL 图像无法下载时返回。
ErrFetch = errors.New("vision: cannot fetch image")
// ErrUpstream 在 vision/OCR 提供方请求失败时返回。
ErrUpstream = errors.New("vision: upstream request failed")
// ErrBadImage 在图像无法读取或解码时返回。
ErrBadImage = errors.New("vision: cannot read image")
// ErrNotImage 在来源声明的类型与真实字节都判不出图像时返回
// (例如把 HTML/PROXY 错误页当成图片送进模型)。
ErrNotImage = errors.New("vision: source is not an image")
// ErrBadArgs 在工具参数格式错误时由该工具返回。
ErrBadArgs = errors.New("vision: bad tool arguments")
// ErrBlockedAddress 在图像 URL 指向被拒的链路本地/云元数据地址时返回
// (见 guard.go)。错误只带主机与网段原因,不带 URL 或其中的凭据。
ErrBlockedAddress = errors.New("vision: image URL points at a blocked address")
// ErrImageTooLarge 在内联图像(base64 / data: URI)字符串超过上限时返回。
// 判定在解码与短路之前完成,错误信息只带上限与本次长度。
ErrImageTooLarge = errors.New("vision: inline image is too large")
)
// SourceFailure 描述一次图像来源取回失败。它只携带主机与底层原因:完整
// URL 可能带着查询串里的凭据、图像字节可能含隐私内容,两者都不进错误
// 文案。Host 取不到(URL 无法解析)时为空串。
//
// 它实现 Unwrap,因此底层哨兵(ErrFetch / ErrNotImage /
// ErrBlockedAddress / ErrImageTooLarge …)仍可用 errors.Is 判定;
// 调用方用 errors.As(*SourceFailure) 或结构断言 ImageSourceHost()
// 取出主机,把「来源失效」记成 WARN 而不是静默丢图(见 loop/vision.go)。
type SourceFailure struct {
// Host 是来源 URL 的主机(含端口),不含 userinfo、路径与查询串。
Host string
// Reason 是取回失败的底层原因,保留哨兵链供 errors.Is 判定。
Reason error
}
// Error 渲染主机与原因;Host 为空时只呈现原因。
func (e *SourceFailure) Error() string {
if e.Host == "" {
return e.Reason.Error()
}
return fmt.Sprintf("图像来源取回失败(主机 %s):%v", e.Host, e.Reason)
}
// Unwrap 暴露底层原因,使哨兵判定与具体错误类型照常可用。
func (e *SourceFailure) Unwrap() error { return e.Reason }
// ImageSourceHost 报告来源主机,供调用方按结构断言取用(loop 的解析点),
// 使 loop 无需依赖本包的取回实现。
func (e *SourceFailure) ImageSourceHost() string { return e.Host }