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 被拒 / 长度预检)不回归;变异反证已还原
This commit is contained in:
1 parent
b65fe05635
commit
94b882f4d9
5 files changed
+682
-8
No files matched your search
@@ -135,21 +135,42 @@ func fetchURL(ctx context.Context, rawURL string, client *http.Client) (string,
|
||||
return mime, data, nil
|
||||
}
|
||||
|
||||
// inlinedImage 是一次「取回 + 内联」的产物。MIME 与 Raw 只为按内容哈希
|
||||
// 缓存而返回(见 cache.go 的 imageCache),绝不写进日志。
|
||||
type inlinedImage struct {
|
||||
// URI 是可供模型直接消费的 data: URI。
|
||||
URI string
|
||||
// MIME 是归一化后的图像 MIME;data: 输入(纯短路路径)时为空。
|
||||
MIME string
|
||||
// Raw 是内联前的原始字节;data: 输入(纯短路路径)时为 nil。
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
// inlineImageSrc 把图像来源字符串取回并内联为 data: URI,供主模型
|
||||
// 直接消费。已是 data: URI 的原样返回(客户端现有的 base64 路径因此
|
||||
// 逐字节不变),但**短路之前**也要过长度预检:这条路径不解码,不预检
|
||||
// 就等于把体积上限完全交给入口层。URL 取回复用 Resolve→fetchURL 的既有
|
||||
// 守卫(协议白名单 + 体积上限 + 地址守卫),并加取回超时。
|
||||
func inlineImageSrc(ctx context.Context, src string) (string, error) {
|
||||
img, err := inlineImage(ctx, src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return img.URI, nil
|
||||
}
|
||||
|
||||
// inlineImage 与 inlineImageSrc 同一条路径,但额外返回内联前的 MIME 与
|
||||
// 原始字节,供 cache.go 按内容哈希 memo 化(同一 URL 不再每回合重复取回)。
|
||||
func inlineImage(ctx context.Context, src string) (inlinedImage, error) {
|
||||
if strings.HasPrefix(src, "data:") {
|
||||
if err := checkInlineImageLength(src); err != nil {
|
||||
return "", err
|
||||
return inlinedImage{}, err
|
||||
}
|
||||
return src, nil
|
||||
return inlinedImage{URI: src}, nil
|
||||
}
|
||||
ref, err := ParseImageRef(src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return inlinedImage{}, err
|
||||
}
|
||||
if ref.Source == SourceURL {
|
||||
var cancel context.CancelFunc
|
||||
@@ -158,9 +179,9 @@ func inlineImageSrc(ctx context.Context, src string) (string, error) {
|
||||
}
|
||||
mime, data, err := Resolve(ctx, ref, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return inlinedImage{}, err
|
||||
}
|
||||
return DataURI(mime, data), nil
|
||||
return inlinedImage{URI: DataURI(mime, data), MIME: mime, Raw: data}, nil
|
||||
}
|
||||
|
||||
// splitDataURI 解析 "data:<mime>;base64,<payload>" 前缀。src 不是
|
||||
|
||||
Reference in new issue
Block a user