- loop 解析点用结构断言取 SourceFailure 的主机(errors.As 穿透 %w 包装), 不导入 vision 包、不引入方向性依赖;拿不到来源时留空串而不编造。 - 新增 TestImageSourceHost 四例(nil/普通错误/包装后来源/空主机), 变异验证:把返回值改成 "" 该用例必红(已复跑确认)。
88 lines
3.1 KiB
Go
88 lines
3.1 KiB
Go
package loop
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
|
)
|
|
|
|
// ImageResolver 是循环通向多模态视觉层的接缝(design/06
|
|
// §0)。它在 Run 开始时对每条携带图像内容部件的消息调用一次,
|
|
// 并可以改写该消息,使主模型能对图像进行推理。具体
|
|
// resolver 位于 vision 包,它通过 vision Router 决定
|
|
// 是保留图像给支持视觉的主模型,还是折叠为
|
|
// vision/OCR 工具产出的文字描述(「先
|
|
// 看图再对话」)。为 nil 时图像内容保持原样,直接流向
|
|
// 支持视觉的主模型。
|
|
type ImageResolver interface {
|
|
// Resolve 改写 msg,其中至少携带一个图像内容部件。它
|
|
// 返回(可能已改写的)消息。出错时保留原始
|
|
// 消息,使该 turn 仍能继续。
|
|
Resolve(ctx context.Context, msg model.Message) (model.Message, error)
|
|
}
|
|
|
|
// ImageResolveFunc 把一个函数适配为 ImageResolver 接口。
|
|
type ImageResolveFunc func(ctx context.Context, msg model.Message) (model.Message, error)
|
|
|
|
// Resolve 实现 ImageResolver。
|
|
func (f ImageResolveFunc) Resolve(ctx context.Context, msg model.Message) (model.Message, error) {
|
|
return f(ctx, msg)
|
|
}
|
|
|
|
// resolveImages 用注入的 resolver 改写 messages 中每条
|
|
// 携带图像的消息。未配置 resolver、没有图像、或某条消息
|
|
// 解析失败(失败会被记录日志并保留原消息,使该 turn 仍能
|
|
// 运行)时,它原样返回输入切片。任一消息发生变化时
|
|
// 返回一个新切片。
|
|
func (l *Loop) resolveImages(ctx context.Context, messages []model.Message) []model.Message {
|
|
if l.imageResolver == nil {
|
|
return messages
|
|
}
|
|
out := make([]model.Message, len(messages))
|
|
changed := false
|
|
for i, m := range messages {
|
|
if !messageHasImage(m) {
|
|
out[i] = m
|
|
continue
|
|
}
|
|
resolved, err := l.imageResolver.Resolve(ctx, m)
|
|
if err != nil {
|
|
// 图像解析失败(尤其:URL 来源已失效)在这里留下可诊断信号 ——
|
|
// 原消息照旧保留、回合继续,但不再静默:host 是来源 URL 的主机,
|
|
// 不含路径、查询串与凭据(由 SourceFailure 保证)。
|
|
l.log.Warn("image resolve", "error", err, "host", imageSourceHost(err))
|
|
out[i] = m
|
|
continue
|
|
}
|
|
out[i] = resolved
|
|
changed = true
|
|
}
|
|
if !changed {
|
|
return messages
|
|
}
|
|
return out
|
|
}
|
|
|
|
// imageSourceHost 从图像解析失败错误里取出来源主机(vision 的取回失败
|
|
// 错误以结构断言暴露它),使 WARN 能定位到具体来源。错误不带这个信息
|
|
// (解析失败、非图像、无解析器等)时返回空串。用结构断言而不是导入
|
|
// vision 包:loop 只需知道「有个东西能报主机」,不需要依赖取回实现。
|
|
func imageSourceHost(err error) string {
|
|
var withHost interface{ ImageSourceHost() string }
|
|
if errors.As(err, &withHost) {
|
|
return withHost.ImageSourceHost()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// messageHasImage 报告 msg 是否有任何内容部件是图像部件。
|
|
func messageHasImage(m model.Message) bool {
|
|
for _, part := range m.Content {
|
|
if part.Type == model.ContentTypeImage {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|