mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 01:18:08 +00:00
此前三段式拆分后 ONNX 路径从未从 Go 侧跑通:embedder_onnx_test.go 仍引用 分段前的 API(e.renderInput、TextTower.onnx、旧目录),go vet -tags onnxruntime 直接编译失败。导出脚本只在 /tmp 且硬编码本机路径、从第三个目录拷贝固定形状的 Vision.onnx,完全不可复现。音频会被视觉塔编码,静默往统一空间灌入错误坐标。 本提交补齐这些缺口: 一、可复现导出脚本(scripts/export_qwen3vl_embedding_onnx.py) - 自动拉取模型(HuggingFace 优先,失败回落 ModelScope,支持 HF_ENDPOINT 镜像); - 导出 TokenEmbedding + Transformer + Vision 三段图,图文共用同一 token embedding、28 层 Transformer、last-token 池化与 fingerprint; - 双重自检(不可省):分段 PyTorch vs 完整模型 + 导出后的 ONNX vs 完整模型, cos < 0.999999 即非零退出——「能加载」不等于「算得对」; - 默认把 L2 归一化后的冻结参考向量写入产物目录(qwen_reference.json)—— Go 测试据此做逐维冻结回归,且「该目录是哪次导出的」从文件本身可追溯; - --verify-only 校验既有产物不重新导出,可用来确认线上在用的图没坏。 关键实测结论(已写入 docs/zh/multimodal-space.md 与长期记忆): 原生多帧视频不可行——Qwen3-VL 视觉塔把 grid_thw 当 Python 值消费 (grid_thw.tolist()),legacy tracer 固化为常量,导出后图中根本没有 grid_thw 输入,换帧数调用直接 Invalid input name: grid_thw。故视觉塔固定 (1,48,48), 视频由上层抽帧后逐帧按图像编码(同模型/同维度/同 fingerprint),音频明确 unsupported。 二、模态边界(vector.ErrModalityUnsupported) - 新增 vector.ErrModalityUnsupported:表示「该模态不在本统一空间的原生覆盖 范围内」,与普通错误语义不同——调用方应把它当「永远不会有向量」而非 「本次失败、下次重试」; - qwen.EmbedImageDense 按 mime 拒绝 audio/* 与 video/*:此前它会拿视觉塔 去解音频字节,往统一空间灌入语义错误的坐标且静默; - reembedStaleMedia 对 ErrModalityUnsupported 不计失败、不重试、不用别的 模型向量顶替(TestReembedStaleMedia_SkipsUnsupportedWithoutFaking 守住)。 三、Go ONNX 测试首次完整通过 - 重写 embedder_onnx_test.go:修复编译 + 文本冻结回归 + 图像冻结回归 + 两条阴性对照(不同输入必须不同、图像与文本必须不同)+ 不支持模态断言; - 参考值从产物目录的 qwen_reference.json 读取(不在测试里硬编码浮点); - 用线上部署产物实测全部通过(text cos=0.999999940, image cos=0.999999762)。 四、.gitignore 修复 - /scripts/ 此前被列在「运行时产物」下,但它是作者维护的工具目录 (模型导出、侧车、部署校验),deploy/systemd/embed-sidecar.service 直接 引用 scripts/embed_sidecar.py,忽略它会让那份 unit 在别人的机器上指向 不存在的文件。改为只忽略 __pycache__。 五、文档(docs/zh/multimodal-space.md) - 获取/启用/产物契约/模态边界/验证/资源成本/与现有部署产物的等价性。 验证:go build ./...、go vet ./...、go vet -tags onnxruntime ./...、 go test -short 全部通过;ONNX 标签测试对线上部署产物全部通过。
106 lines
4.7 KiB
Bash
Executable File
106 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# HomeAgent 部署验证脚本:检查 healthcheck 残留 / 记忆去重 / archived 残留 / 嵌入规格
|
||
# 用法: verify_deploy.sh [data_dir] 默认 /home/newqqagent
|
||
set -u
|
||
DATA_DIR="${1:-/home/newqqagent}"
|
||
PASS=0; FAIL=0; SKIP=0
|
||
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
|
||
bad() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
|
||
skip() { echo "[SKIP] $1"; SKIP=$((SKIP+1)); }
|
||
|
||
echo "== HomeAgent 部署验证 =="
|
||
echo "数据目录: $DATA_DIR"
|
||
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||
echo "模式: 只读检测,不修改/删除任何数据"
|
||
echo
|
||
|
||
# ---- 0. 数据目录可用性 ----
|
||
if [ ! -d "$DATA_DIR" ]; then
|
||
echo "[FATAL] 数据目录不存在: $DATA_DIR"
|
||
exit 2
|
||
fi
|
||
|
||
# ---- 1. healthcheck 残留检查 ----
|
||
echo "--- 1. healthcheck 残留 ---"
|
||
hc_files=$(find "$DATA_DIR/knowledge" "$DATA_DIR/memory" -maxdepth 2 \
|
||
\( -name '*_hc_*' -o -name 'gotest' -o -name 'luatest' \) 2>/dev/null)
|
||
if [ -z "$hc_files" ]; then
|
||
ok "无 _hc_*/gotest/luatest 残留"
|
||
else
|
||
echo " 发现残留:"
|
||
echo "$hc_files" | sed 's/^/ /'
|
||
bad "存在 healthcheck 残留(上列,需人工确认清理)"
|
||
fi
|
||
|
||
# ---- 2. relations 去重检查 ----
|
||
echo "--- 2. relations 去重 ---"
|
||
GRAPH_DB="$DATA_DIR/memory/graph.db"
|
||
if [ -f "$GRAPH_DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
||
dup=$(sqlite3 "$GRAPH_DB" "SELECT count(*) - count(DISTINCT source_id||'|'||target_id||'|'||relation_type||'|'||COALESCE(session_id,'')) FROM relations;" 2>/dev/null)
|
||
total=$(sqlite3 "$GRAPH_DB" "SELECT count(*) FROM relations;" 2>/dev/null)
|
||
if [ -n "$dup" ] && [ "$dup" -le 0 ] 2>/dev/null; then
|
||
ok "relations 去重良好 (共 ${total:-0} 条, 重复 ${dup:-0})"
|
||
else
|
||
bad "relations 存在重复: 共 ${total:-?} 条, 重复 ${dup:-?}"
|
||
fi
|
||
# UNIQUE 索引存在性:查 relations 表 DDL 是否含 UNIQUE 约束
|
||
# (注意:UNIQUE 约束创建的 sqlite_autoindex_* 索引其 sql 字段为 NULL,
|
||
# 用 LIKE 查索引 sql 会漏报——须查表 DDL)
|
||
ddl=$(sqlite3 "$GRAPH_DB" "SELECT sql FROM sqlite_master WHERE type='table' AND name='relations';" 2>/dev/null)
|
||
if echo "$ddl" | grep -q 'UNIQUE'; then
|
||
# 进一步确认唯一约束覆盖的列
|
||
auto_idx=$(sqlite3 "$GRAPH_DB" "SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'sqlite_autoindex_relations%' AND tbl_name='relations';" 2>/dev/null | head -1)
|
||
if [ -n "$auto_idx" ]; then
|
||
cols=$(sqlite3 "$GRAPH_DB" "SELECT group_concat(name,',') FROM pragma_index_info('$auto_idx');" 2>/dev/null)
|
||
echo " UNIQUE 自动索引: $auto_idx, 覆盖列: ${cols:-?}"
|
||
fi
|
||
ok "relations 复合 UNIQUE 约束存在"
|
||
else
|
||
bad "relations 复合 UNIQUE 约束缺失(迁移未生效?)"
|
||
fi
|
||
else
|
||
skip "graph.db 不存在或 sqlite3 不可用 ($GRAPH_DB)"
|
||
fi
|
||
|
||
# ---- 3. archived 模板垃圾检查 ----
|
||
echo "--- 3. archived 残留 ---"
|
||
if [ -f "$GRAPH_DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
||
# 兜底检测:relation_type 直接为 context_archived,或 target_id 含 context_archived
|
||
arch=$(sqlite3 "$GRAPH_DB" "SELECT count(*) FROM relations WHERE relation_type='context_archived' OR relation_type='来源' AND target_id LIKE '%context_archived%';" 2>/dev/null)
|
||
if [ -n "$arch" ] && [ "$arch" -eq 0 ] 2>/dev/null; then
|
||
ok "无 context_archived 模板垃圾"
|
||
else
|
||
bad "context_archived 模板垃圾: ${arch:-?} 条"
|
||
fi
|
||
else
|
||
skip "graph.db 不存在或 sqlite3 不可用"
|
||
fi
|
||
|
||
# ---- 4. 嵌入规格检查 ----
|
||
echo "--- 4. 嵌入规格 ---"
|
||
CONFIG_DB="$DATA_DIR/config.db"
|
||
if [ -f "$CONFIG_DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
||
emb=$(sqlite3 "$CONFIG_DB" "SELECT value FROM config WHERE key='core.agent.embedding_model_path';" 2>/dev/null)
|
||
echo " embedding_model_path = ${emb:-(未配置)}"
|
||
if [ -n "$emb" ]; then
|
||
# 模型路径含 #topN 则裁剪生效;否则全量加载(300 维 ≈ 1.5G/模型)
|
||
if [[ "$emb" == *"#top"* ]]; then
|
||
ok "嵌入模型已启用 #topN 裁剪(内存预算受控)"
|
||
else
|
||
# 逗号分隔的模型个数估算内存
|
||
n=$(echo "$emb" | awk -F',' '{print NF}')
|
||
est=$(( n * 1500 ))
|
||
echo " 全量加载 ${n} 个模型,估算常驻 ≈ ${est}MB(未裁剪)"
|
||
bad "嵌入模型未启用 #topN 裁剪(内存预算偏高)"
|
||
fi
|
||
else
|
||
skip "embedding_model_path 未配置(可能是默认值)"
|
||
fi
|
||
else
|
||
skip "config.db 不存在或 sqlite3 不可用 ($CONFIG_DB)"
|
||
fi
|
||
|
||
# ---- 汇总 ----
|
||
echo
|
||
echo "== 汇总: PASS=$PASS FAIL=$FAIL SKIP=$SKIP =="
|
||
[ "$FAIL" -eq 0 ] && exit 0 || exit 1 |