mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
feat(remotedevice): screensee 落盘化,回路径而非自动送模型
## 为什么
原行为把截图的 base64 直接送去视觉模型自动描述。三个问题:
1. **上下文成本**:96KB 截图 ≈ 13 万 token 的内联 base64
2. **源不可控**:生产实证 AUTO 把大图路由到不支持视觉的源,模型回
「没有收到截图」——而我们无法区分「图没到」与「模型看不了」
3. **agent 无法决策**:不知道图存不存在,也就不知道该 describe 还是该 ocr
## 现在
截图落盘 → 回 {file,mime,size,sha256,width,height} →
agent 显式调 describe_image(path=…)/ocr_image(path=…)。
需要哪个能力由它自己判断,provider 参数也可控。
工具描述同步改写,明说「本工具不会自动描述任何内容,两步缺一不可」——
否则模型会以为拿到 file 就等于看到了画面。
## 实现
- `Registry.SaveInlineMedia(name, payload, mimeHint)`:
解 data URL / 裸 base64 → 落盘 → 回可核对的元信息
- 文件名用**内容摘要**后缀(`<name>_<sha16>.<ext>`):
截屏/录像会被反复抓取,用 reqID 命名会把同一张图存成多份,
用固定名则不同内容互相覆盖 —— 摘要两个问题都避免
- `decodeInlineMedia` / `imageDimensions`:JPEG SOFn 与 PNG IHDR
解尺寸,不解码全图;认不出的格式**省略字段而非填 0**
- 落盘失败当**工具失败**上抛,不静默退回内联:
图已在手上却拿不到路径,agent 既没有描述也没有可查证产物
- 旧路径保留在 `auto_describe=true`(调试用)
顺带把 cmd_data_end 的媒体落盘逻辑也换成同一个方法(原来内联了
一份 MkdirAll+WriteFile 的副本)。
## 判据
新增 inline_media_test.go,六个判据经四个变异自证会红:
- 变异 A:写入截半的字节但元信息照填 → 红(只查「文件存在」会放过)
- 变异 B:尺寸写死 0 占位 → 红(元信息「看起来有」但不可信)
- 变异 C:sha256 换成假的 → 红
- 变异 D:未配置 mediaDir 时静默降级 → 红
★ 变异 C 第一次跑出「无输出」,查下来是**编译失败不是被杀**
(`sum`/`hex` 变成未使用)。这正是本项目记过的坑:
编译失败 ≠ 被杀。改写成能编译的注入后确认被杀。
## 验证
- go build ./... / go vet / go test ./internal/... 全绿
- go test -race remotedevice + multimodal 无数据竞争
- 旧判据按新契约更新时保留原意:
TestScreenseE2E_SuccessReturnsDescription 现在同时验两条路径
(默认不出 description 且不调 handler;auto_describe 才出)
- 部署后:39 插件加载、0 panic/fatal、plugin=multimodal
- graph.db integrity ok,blocks 2787 / relations 980 未动
- 快照 /home/newqqagent/memory/pre-screensee-persist-20261006-091536/
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -298,8 +298,15 @@ func TestWSBinaryMediaToFile(t *testing.T) {
|
||||
if _, hasB64 := res["data_base64"]; hasB64 {
|
||||
t.Fatal("data_base64 should be absent in file mode")
|
||||
}
|
||||
if want := filepath.Join(mediaDir, "req-file-1.mp4"); fp != want {
|
||||
t.Fatalf("file path = %s, want %s", fp, want)
|
||||
// ★ 2026-10-06:文件名含内容摘要(SaveInlineMedia)。
|
||||
// 截屏/录像这类会被反复抓取的媒体,用 reqID 命名会把同内容存成多份、
|
||||
// 不同内容却可能覆盖 —— 摘要后缀两<E7BC80><E4B8A4><EFBFBD>都避免。
|
||||
if filepath.Dir(fp) != mediaDir {
|
||||
t.Fatalf("file 应落在媒体目录 %s,实际 %s", mediaDir, fp)
|
||||
}
|
||||
base := filepath.Base(fp)
|
||||
if !strings.HasPrefix(base, "req-file-1_") || !strings.HasSuffix(base, ".mp4") {
|
||||
t.Fatalf("文件名应为 req-file-1_<digest>.mp4,实际 %s", base)
|
||||
}
|
||||
got, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
@ -402,6 +409,7 @@ func TestPushDataOfflineDevice(t *testing.T) {
|
||||
|
||||
func TestScreenseeEndToEnd(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
reg.SetMediaDir(t.TempDir())
|
||||
token := "test-token-see"
|
||||
reg.SetAcceptToken(func(provided string) bool { return provided == token })
|
||||
|
||||
@ -449,7 +457,10 @@ func TestScreenseeEndToEnd(t *testing.T) {
|
||||
}()
|
||||
|
||||
// agent 调用 screensee
|
||||
res, err := dev.Execute("screensee", map[string]interface{}{"device_id": "see-dev"})
|
||||
// ★ 2026-10-06:默认走落盘 + 回路径;旧的自<E79A84><E887AA><EFBFBD>描述路径用 auto_describe=true。
|
||||
res, err := dev.Execute("screensee", map[string]interface{}{
|
||||
"device_id": "see-dev", "auto_describe": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("screensee: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user