From 0af541097ff0aeb1d32e5b1fa5df91073171e95c Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Thu, 20 Aug 2026 01:00:53 +0000 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) diff --git a/README.md b/README.md index e69de29..3e960ff 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,674 @@ + +# Sentinel Home AI — 家庭多模态智能监控系统 + +> 多模型容灾降级 + 交互式命名 + AI 对话的家庭监控系统。 +> 本文档为项目需求文档与 README 的整合版,按当前代码实际状态(v1.0 E2E 已打通)编写。 +> 最后更新:2026-08-20(云端多模型方案整合) + +--- + +## 1. 项目简介与设计目标 + +系统持续分析家庭监控摄像头(Synology Surveillance Station)录制的视频,抽取关键帧后调用视觉语言模型(VLM)分析画面中的人物、动作、衣着,落库为结构化事件;用户通过 Web UI 浏览事件、给"人物A/B/C"命名(批量回溯历史记录)、以及用自然语言向 AI 询问"汤圆今天干嘛了"。 + +### 1.1 首期范围(已基本完成) + +- **FAM-Core**(NAS 端单进程):Task-Scheduler / Dispatcher / Event-Receiver / Chat-Handler / Member-Manager / Video-Server 六个子模块 +- **FAM-Edge**(Oracle 端单进程):接收视频上传 → FFmpeg 快速抽帧 → OpenCV 关键帧筛选 → 云端 VLM 视觉分析(Gemini/NVIDIA NIM)→ 本地 Ollama 文本融合 → 结果同步返回 全链路 +- **FAM-UI**(NAS 端):Streamlit 直读 DB,事件列表 + 成员命名页 + AI 对话页 + 对话历史 +- **数据库六张表**:process_tasks / monitor_events / event_details / chat_history / family_members / daily_summaries(预留) +- **任务状态机**:PENDING → PROCESSING → SUCCESS/FAILED,含退避重试与僵尸任务回收 +- **AI 对话**:查 event_details 拼上下文 → 经 FAM-Edge 代理调 Oracle 本地 Ollama 纯文本 → 返回回答并写 chat_history +- **交互式成员命名**:VLM 按特征提取"人物A/B/C"落库,用户命名后批量回溯更新历史,后续分析直接用真名 + +### 1.2 不在首期范围(推迟 v1.1+) + +- Nginx 静态服务(Video-Server 用 Flask `send_from_directory` 替代,且推送模式下已不再必需) +- 资源保护策略 B(CPU/内存过载返回 503) +- 心跳监控(用整体超时 + 僵尸任务回收兜底) +- API 业务接口鉴权(`/api/edge/*` 和 `/api/core/*` 不加鉴权) +- Cron 兜底清理(`finally` 清理足够) +- daily_summaries 每日摘要(表已建,逻辑未实现) + +### 1.3 已知遗留问题(v1.1 优先解决) + +| 问题 | 现状 | 影响 | +|------|------|------| +| **llava-phi3 多图单请求失效(已替换)** | 原本地模型 llava-phi3 `analyze_frames` 多图单请求输出长度仅 3~4;**已替换为 qwen2.5:7b**(纯文本模型,专职融合与对话) | 历史遗留描述,新方案下视觉走云端,本地不参与视觉分析,该问题随之消除 | +| **吞吐不足** | 30s 视频全流程约 19 分钟(关键帧筛选 10min + VLM 5.5min + 融合 3min);30min/360MB 视频实测 929s | 生产视频每 30 分钟新增一个,处理速度勉强跟上但无余量;改用云端模型后该问题随之缓解 | +| **Tailscale 端口不通** | NAS tailscaled 以 userspace 模式运行(无 TUN 网卡),Oracle 无法反向访问 NAS;当前 NAS→Oracle 走公网 IP | 推送模式已规避反向访问,但流量走公网 | +| **历史视频积压** | 正式目录 `/volume1/surveillance/Generic_ONVIF-001` 有约 285 个历史视频(~100GB),288 个历史任务已标记 FAILED 避免全量上传 | 切回生产目录前需确认回补策略 | + +**规划方向**:**云端大模型负责视觉识别、甲骨文本地大模型负责文本问答**。按任务类型分工: + +| 任务 | 执行方 | 模型 | 说明 | +|------|--------|------|------| +| **视觉分析**(看图识人/动作/衣着) | 云端 | Gemini → NVIDIA NIM(fallback 降级) | 云端 GPU 推理,支持多图,质量与速度均优于本地 ARM | +| **文本融合**(多帧视觉结果 → 结构化 JSON) | 本地 | Ollama qwen2.5:7b | 纯文本能力,不涉及图片 | +| **AI 对话**("汤圆今天干嘛了") | 本地 | Ollama qwen2.5:7b | 纯文本问答 | + +- **Google Gemini**(`gemini-1.5-flash`,API Key 已验证,单帧 2~3s 且真正支持多图) +- **NVIDIA NIM**(`meta/llama-3.2-11b-vision-instruct`,OpenAI 兼容 API,云端 GPU 推理,响应快) +- **本地 Ollama**(qwen2.5:7b)**专职文本任务**(融合 + 对话),不再参与视觉分析;视觉链路两云端全失败 → 任务 FAILED 走重试 + +Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA NIM;记录每模型实际执行耗时与成功状态。`analyze_frames` 改为逐帧请求。 + +--- + +## 2. 系统架构 + +### 2.1 物理节点与部署 + +| 节点 | 角色 | 硬件 | IP | 服务与端口 | +|------|------|------|-----|-----------| +| Synology NAS | FAM-Core + FAM-UI + 数据库 | DS220+ (Geminilake), DSM 7 | 家庭局域网 192.168.50.64 / Tailscale 100.70.234.39 | FAM-Core :8000, FAM-UI :8501, MariaDB :3306, Surveillance Station | +| Oracle Cloud | FAM-Edge | Ampere A1 2C12G ARM64(无 GPU), Ubuntu 20.04 | 公网 129.146.203.203 / Tailscale 100.74.137.126 | FAM-Edge :5000, Ollama :11434(仅本地) | +| 家庭网络 | 用户入口 | 普通终端 | 192.168.50.0/24 | 浏览器访问 `http://192.168.50.64:8501` | + +**网络要点(推送模式)**: +- 服务间通信只有一条:**NAS → Oracle 公网 IP:5000**(HTTP 上传视频)。Edge 不需要反向访问 NAS(无 webhook、无视频拉取) +- Oracle 端 Ollama 端口 11434 不对外暴露,聊天请求经 FAM-Edge `/api/edge/chat` 代理转发 +- Tailscale 两节点已安装在线,但 NAS tailscaled 为 userspace 模式且防火墙端口不通,暂走公网 IP + +### 2.2 部署拓扑与数据流(推送模式) + +``` +┌─────────────────────────────── NAS (192.168.50.64) ──────────────────────────────┐ +│ │ +│ Surveillance Station ──► /volume1/surveillance/Generic_ONVIF-001/ │ +│ (YYYYMMDDAM / YYYYMMDDPM 两级目录,~30min/350MB) │ +│ │ │ +│ ▼ │ +│ FAM-Core (Flask :8000, gunicorn) │ +│ ├─ Task-Scheduler: 60s 轮询视频目录,稳定文件建 PENDING 任务 │ +│ ├─ Dispatcher: 30s 轮询,multipart 上传视频 ──────────┐ │ +│ │ (push_timeout=1800s,同步等待响应) │ │ +│ ├─ 僵尸回收: PROCESSING 超 push_timeout+120s 重置 PENDING │ +│ ├─ Event-Receiver: /api/core/callback/event(兼容保留) │ +│ ├─ Chat-Handler ── /api/edge/chat 代理 ──────────────┐ │ +│ ├─ Member-Manager / Video-Server(/media, token) │ │ +│ ▼ │ │ +│ MariaDB (sentinel_home_ai, 6 张表) │ │ +│ │ │ +│ FAM-UI (Streamlit :8501) 直读 DB │ │ +└─────────────────────────────────────────────────────────┼─────────────────────────┘ + │ HTTP (公网) + ▼ +┌──────────────────────── Oracle Cloud (129.146.203.203) ──────────────────────────┐ +│ FAM-Edge (Flask :5000, gunicorn --timeout 1800, 单 worker) │ +│ ├─ POST /api/edge/video/push(multipart 视频,同步分析,结果随响应返回) │ +│ ├─ AI-Orchestrator: 健康检查 → 抽帧 → 选帧 → 压缩 → 云端VLM视觉 → 本地Ollama文本融合 │ +│ ├─ POST /api/edge/chat(代理转发本地 Ollama 纯文本问答) │ +│ ▼ │ +│ 云端: Gemini / NVIDIA NIM (视觉) 本地: Ollama :11434 (qwen2.5:7b, 文本融合+对话) │ +└───────────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.3 主链路时序(推送模式) + +1. Scheduler 扫描到新视频(修改时间 > 60s 且大小稳定)→ 写 `process_tasks`(PENDING) +2. Dispatcher 领取 PENDING 任务 → 状态置 PROCESSING → 读本地视频文件,multipart POST 到 Edge `/api/edge/video/push`,payload 含 task_id / camera_name / event_start_time(文件 mtime)/ known_members_context +3. Edge 同步执行: + - a. 保存上传视频到临时目录(超时 60s) + - b. FFmpeg 快速 seek(`-ss -frames:v 1`)粗抽候选帧,帧数随视频时长自适应 + - c. OpenCV MSE 帧差分析筛选关键帧 → 压缩(长边 ≤ 1024px,JPEG 质量 80) + - d. 云端视觉模型按 `orchestrator.mode`(fallback)顺序降级:Gemini(timeout 15s)→ NVIDIA NIM(timeout 20s);首个返回非 None 结果即采用,两云端全失败 → 任务 FAILED 走重试(不回退本地 Ollama,本地仅负责文本) + - e. 本地 Ollama 文本融合(超时 300s,输入各帧视觉描述 + 时间戳 + known_members 上下文)→ JSON 结构化结果 + - f. `event_end_time` = event_start_time + 视频时长(Edge 推算) + - g. `finally` 清理临时文件 +4. Edge 把结果 JSON 直接作为 HTTP 响应返回(无 webhook) +5. Dispatcher 收到响应 → 调用 `apply_success_event()` 写 `monitor_events`(1 条聚合)+ `event_details`(每关键帧 1 条)+ upsert 未命名成员 → 任务置 SUCCESS;失败则退避重试(`min(60×(retry+1)×2, 600)`s,超 3 次 FAILED) + +**容错设计**: +- Dispatcher 僵尸回收:PROCESSING 状态超过 `push_timeout + 120s` 自动重置 PENDING(应对进程重启/Edge 重启导致 in-flight 请求丢失) +- fam-core 日志双写:stdout + `fam-core/logs/fam-core.log`(daemon 模式下 stdout 不可见) +- 所有日志带 `task_id` 作为 trace_id,各阶段耗时打 INFO + +--- + +## 3. 模块设计 + +### 3.1 FAM-Core(NAS 端) + +| 模块 | 文件 | 职责 | +|------|------|------| +| Task-Scheduler | `scheduler/scheduler.py` | 60s 轮询视频目录(`os.walk` 递归,支持 AM/PM 子目录),`video_path` 去重,稳定文件建 PENDING 任务 | +| Dispatcher | `dispatcher/dispatcher.py` | 30s 轮询 PENDING;multipart 上传视频至 Edge push 端点;收响应后经 `apply_success_event` 落库;僵尸 PROCESSING 回收;退避重试 | +| Event-Receiver | `event_receiver/event_receiver.py` | `/api/core/callback/event`(webhook 兼容保留);核心逻辑抽为 `apply_success_event(data)` 供 Dispatcher 推送模式复用;未命名 abstract_label 自动 upsert `family_members` | +| Chat-Handler | `chat_handler/chat_handler.py` | `/api/chat/ask` 查 event_details 拼上下文 → 经 Edge 代理调 Ollama → 写 chat_history;明细 > 50 条按小时聚合 | +| Member-Manager | `member_manager/member_manager.py` | `/api/member/unnamed` / `/api/member/name` / `/api/member/list`;命名后批量回溯 UPDATE event_details(MariaDB 不支持 `$[*]` JSON 路径,Python 层逐行更新) | +| Video-Server | `video_server/video_server.py` | `/media/?token=xxx` 静态视频服务(推送模式下主链路不再使用,保留备用) | +| 公共层 | `db_layer.py` / `config_loader.py` / `logger.py` | PyMySQL 连接(unix_socket);datetime 空串归一化 NULL + NOT NULL 列兜底;文件日志 | + +### 3.2 FAM-Edge(Oracle 端) + +| 模块 | 文件 | 职责 | +|------|------|------| +| API-Gateway | `api_gateway/api_gateway.py` | `POST /api/edge/video/push`(multipart 上传 + 同步分析 + 结果返回);`POST /api/edge/video/analyze`(旧拉取模式,兼容保留);`POST /api/edge/chat`(Ollama 代理);`GET /health`;单并发控制(处理中返回 429) | +| Video-Preprocessor | `video_preprocessor/preprocessor.py` | `save_upload` 保存上传视频;FFmpeg 快速 seek 粗抽候选帧(帧数自适应);OpenCV MSE 帧差筛选关键帧(首末帧必选);压缩;`compute_timestamps` 用 start+偏移算绝对时间戳;`video_duration` 供 event_end_time 推算 | +| AI-Orchestrator | `ai_orchestrator/orchestrator.py` | 模型健康检查 → 云端视觉适配器按 `orchestrator.mode`(fallback 顺序降级)调度 → 本地 Ollama 文本融合 → JSON 解析三层容错 + schema 校验;`process_push_task` 为推送模式入口(不触发 webhook);记录各模型实际执行耗时与成功状态到 `compute_provider` 数组 | +| Model-Adapters | `model_adapters/` | `BaseModelAdapter` 抽象基类(`__init__` / `health_check` / `analyze_frames` / `get_timeout` / 熔断器实例);`build_adapter` 工厂函数按 `provider` 字段分发实例化;视觉适配器(gemini/nvidia)参与视觉分析,文本适配器(ollama)专职融合与对话 | +| └ OllamaAdapter | `model_adapters/ollama_adapter.py` | requests 直调本地 REST `/api/chat`,`num_predict` 可配;**role: text**(文本融合 + AI 对话,不参与视觉分析) | +| └ GeminiAdapter | `model_adapters/gemini_adapter.py` | requests 直调 Google REST `:generateContent`,支持多图;**role: vision** | +| └ NvidiaVisionAdapter | `model_adapters/nvidia_adapter.py` | **基于 openai SDK**(NIM 兼容 OpenAI API 规范),`base_url=https://integrate.api.nvidia.com/v1`,`api_key` 从 `${NVIDIA_API_KEY}` 展开;`analyze_frames` 用 OpenAI 标准 `image_url`(Base64 内联)格式打包多张关键帧;`health_check` 调 `client.models.list()`;**role: vision** | +| Storage-Cleaner | `storage_cleaner/` | `finally` 删除临时视频与帧图片 | + +### 3.3 FAM-UI(NAS 端) + +Streamlit 应用(`fam-ui/src/app.py`),侧边栏切换页面: + +| 页面 | 功能 | +|------|------| +| 📊 事件列表 | 按日期筛选 + 分页(20 条/页)展示 monitor_events | +| 👤 成员命名 | 列出未命名人物 + 特征描述,输入真名后调 `/api/member/name` 批量回溯 | +| 💬 AI 对话 | 输入框 + 调 `/api/chat/ask`;按 queried_person 预设快捷提问 | +| 📜 对话历史 | chat_history 倒序展示 | +| 📈 统计图表 | compute_provider 占比(bar_chart) | + +--- + +## 4. 数据库设计 + +库名 `sentinel_home_ai`,MariaDB 10.11.11,utf8mb4。完整 DDL 见 `scripts/ddl.sql`。 + +### 4.1 表清单 + +| 表 | 用途 | 关键字段 | +|----|------|---------| +| `process_tasks` | 视频处理任务 | task_id, video_path, video_url, status(PENDING/PROCESSING/SUCCESS/FAILED), retry_count, max_retries, next_retry_at, error_message, failure_stage(ENUM) | +| `monitor_events` | 事件聚合(每任务 1 条) | event_id, task_id, event_start_time, event_end_time, camera_name, global_summary, entities_json(JSON), compute_provider(JSON 数组) | +| `event_details` | 每关键帧一条明细 | detail_id, event_id, task_id, frame_index, frame_timestamp, person, action, clothing, is_attention_event, source_providers(JSON) | +| `family_members` | 交互式命名 | member_id, abstract_label(如"人物A"), real_name(NULL=未命名), feature_description, first_seen_at, named_at, named_by | +| `chat_history` | AI 问答记录 | chat_id, user_question, ai_answer, context_summary, queried_date, queried_person | +| `daily_summaries` | 每日摘要(预留) | target_date, summary_text | + +### 4.2 表关系与命名回溯 + +``` +process_tasks (1) ─── (N) monitor_events (1) ─── (N) event_details +family_members 独立表: + - event_details.person 存 abstract_label(未命名)或 real_name(命名后) + - 命名后: UPDATE event_details SET person = real_name WHERE person = abstract_label + - monitor_events.entities_json 由 Python 层解析逐行更新(MariaDB 不支持 $[*] 路径) +chat_history 独立表 +``` + +### 4.3 compute_provider / source_providers + +- `monitor_events.compute_provider`:JSON 数组,记录本次任务实际成功调用的模型,如 `["ollama"]` 或 `["ollama","gemini"]` +- `event_details.source_providers`:该条明细被哪些模型识别到(可能少于 compute_provider) +- 多模型交叉验证:多模型一致 → 可信度高;仅单一模型描述 → source_providers 仅含该模型;冲突 → 多数派为准 + +### 4.4 兼容性注意 + +- MariaDB 10.11 严格模式:**空字符串不能插 DATETIME 列**(1292 错误)。`db_layer._dt_or_none` 将空串归一化 NULL;`event_end_time` NOT NULL 列按 end→start→NOW 兜底;`frame_timestamp` 空值兜底 NOW +- MariaDB 不支持 MySQL 的 `$[*]` JSON 通配路径与 `->` 操作符,JSON 字段在 Python 层处理 + +--- + +## 5. API 规范(实际实现) + +### 5.1 FAM-Edge(Oracle :5000) + +**POST /api/edge/video/push**(主链路,推送模式) + +- 请求:`multipart/form-data`,字段 `video`(文件) / `task_id` / `camera_name` / `event_start_time` / `known_members_context` +- 处理:同步执行完整分析流水线(可能耗时数分钟,gunicorn timeout 1800) +- 响应(200): + +```json +{ + "task_id": 289, + "status": "success", + "event_start_time": "2026-08-20 01:06:44", + "event_end_time": "2026-08-20 01:07:13", + "camera_name": "客厅", + "global_summary": "...", + "entities_json": [{"person": "汤圆", "action": "...", "clothing": "..."}], + "frame_details": [ + {"frame_index": 1, "frame_timestamp": "...", "person": "...", "action": "...", + "clothing": "...", "is_attention_event": false, "source_providers": ["ollama"]} + ], + "compute_provider": ["ollama"] +} +``` + +- 失败:`{"task_id": ..., "status": "failed", "failure_stage": "vlm_visual", "error_message": "..."}` +- 429:已有任务处理中(单并发);503:全部模型不健康 + +**POST /api/edge/video/analyze** — 旧拉取模式(Edge 拉 video_url + webhook 回调),兼容保留,主链路不再使用 +**POST /api/edge/chat** — Ollama 聊天代理(FAM-Core Chat-Handler 调用) +**GET /health** — 服务与模型健康状态(任务处理中可能无响应,单 worker 忙) + +### 5.2 FAM-Core(NAS :8000) + +| 端点 | 方法 | 说明 | +|------|------|------| +| `/health` | GET | 服务健康 | +| `/api/status` | GET | scheduler/dispatcher 运行状态 | +| `/api/core/callback/event` | POST | Edge 回调(webhook 兼容保留);推送模式下由 Dispatcher 内部调用 `apply_success_event` | +| `/api/chat/ask` | POST | 用户问答:`{"question","queried_person","queried_date"}` → `{"answer","context_summary","chat_id"}` | +| `/api/chat/history` | GET | 对话历史(`?date=` 或 `?person=&limit=`) | +| `/api/member/unnamed` | GET | 未命名人物列表(含特征描述、出现次数) | +| `/api/member/name` | POST | 命名:`{"abstract_label","real_name","named_by"}` → 批量回溯 event_details/entities_json,返回更新条数 | +| `/api/member/list` | GET | 全部成员 | +| `/media/?token=xxx` | GET | 视频静态服务(token 鉴权,推送模式下备用) | + +### 5.3 融合输出 JSON Schema + +三层容错解析:直接 `json.loads` → 提取 markdown fence ` ```json ... ``` ` → 贪婪匹配最大 `{...}`;再过 `validate_schema`(必填字段检查 + 脏数据清洗,`action` 由 AI 自由生成无枚举过滤,`is_attention_event` 由 AI 自行判断,`source_providers` 必须非空数组)。三层全失败 → 任务 FAILED 走重试。 + +--- + +## 6. 关键技术 + +### 6.1 视频预处理(自适应关键帧) + +- **粗抽候选帧**:FFmpeg 快速 seek(逐帧 `ffmpeg -ss -frames:v 1`),替代 fps 滤镜全解码(30min 视频从 180s+ 降到 33s,6-8x 提速) +- **帧数自适应**:候选帧 `clamp(时长分钟×2, 30, 120)`;关键帧上限 `clamp(时长/150s, 8, 30)`(30min→12 帧,60min→24 帧,封顶 30) +- **帧差筛选**:OpenCV MSE,首末帧必选,MSE > 阈值 500 的保留,不足 min_key_frames=5 补足 +- **压缩**:长边 > 1024px 才缩放,JPEG 质量 80 +- **异常兜底**:ffprobe 失败退化为 60s 间隔抽帧;帧差异常退化为等距 5 帧 + +### 6.2 Ollama 调用(ARM CPU 实测调优,专职文本任务) + +本地 Ollama(qwen2.5:7b,纯文本模型)**不参与视觉分析**,专职承担文本融合与 AI 对话两项纯文本任务。视觉分析全部由云端模型承担。以下参数作为文本任务的调优依据保留。 + +| 参数 | 值 | 依据 | +|------|-----|------| +| `OLLAMA_KEEP_ALIVE=-1` | 模型常驻内存 | 消除 55s 冷启动(常驻约 4.3GB,12GB 内存够用) | +| `num_predict=60` | 限制生成 token | ARM 约 5 tok/s,500 会单帧跑数分钟触发超时 | +| 视觉/模型 timeout | 600s | 实测 1024px 帧视觉编码 ~36s + 生成 ~12s/60token | +| 融合 timeout | 300s | — | +| gunicorn(Edge) | `--timeout 1800` | 同步分析模式,默认 30s 会杀 worker | +| push_timeout(NAS) | 1800s | 覆盖最坏情况(30min 视频实测 929s) | + +**已知问题**:原 llava-phi3 多图单请求基本失效(N 张图一次调用输出长度仅 3~4)。**已替换为 qwen2.5:7b**(纯文本模型,专职融合与对话,不涉及视觉多图问题)。 + +### 6.3 模型适配器架构 + +**抽象基类 `BaseModelAdapter`**:定义统一接口,每个模型实现自己的适配器。 + +| 方法 | 职责 | +|------|------| +| `__init__(provider_name, config)` | 读取 api_key / base_url / model_name / timeout / 熔断器配置 | +| `health_check() -> bool` | 轻量请求验证连通性与 Key 有效性 | +| `analyze_frames(frame_paths, frame_timestamps, known_members_context) -> Optional[str]` | 接收多张关键帧路径 + 时间戳 + 已知成员特征,返回 VLM 文本描述(失败返回 None) | +| `get_timeout() -> int` | 返回该模型单次调用超时阈值 | +| 熔断器实例 | 每个云端模型独立熔断器 | + +**模型清单由 `config.yaml` 的 `models` 数组动态决定**,新增模型 = 实现适配器 + 配置加一项,主流程不动。 + +**已实现 / 规划的适配器**(按 `role` 区分职责): + +| provider | 适配器类 | role | SDK / 协议 | 状态 | +|----------|---------|------|------------|------| +| `ollama` | `OllamaAdapter` | **text** | requests 直调 REST `/api/chat` | 已实现 | +| `gemini` | `GeminiAdapter` | **vision** | requests 直调 REST `:generateContent` | 待实现 | +| `nvidia` | `NvidiaVisionAdapter` | **vision** | **openai SDK**(NIM 兼容 OpenAI API 规范) | 待实现 | + +**role 语义**: +- `vision`:参与视觉分析阶段,按 fallback 顺序降级 +- `text`:参与文本融合与 AI 对话,不参与视觉分析 + +**NvidiaVisionAdapter 关键实现**(`fam_edge/adapters/nvidia_adapter.py`): + +- 基于 `openai` Python SDK,`base_url=https://integrate.api.nvidia.com/v1`,`api_key` 从 `${NVIDIA_API_KEY}` 展开 +- `health_check`:调 `client.models.list()` 轻量验证 Key +- `analyze_frames`:构建 OpenAI 标准 `content` 列表(1 个 text item + N 个 `image_url` item,图片以 `data:image/jpeg;base64,...` 格式内联),调 `chat.completions.create`,`temperature=0.2`、`max_tokens=1024` +- prompt 中文模板:要求按时间顺序分析人物数量/衣着/微动作/互动,重点标注异常高危行为 + +**Orchestrator 调度模式**(`config.yaml` → `orchestrator.mode`): + +| 模式 | 行为 | +|------|------| +| `fallback`(默认) | 顺序降级:按 models 数组顺序依次尝试,首个健康且返回非 None 的模型结果即采用;失败则降级到下一个;全部失败返回 503 | +| `ensemble` | 并行交叉验证:所有 enabled 模型并行调用,结果一致 → 可信度高;多数派为准;仅单一模型描述 → `source_providers` 仅含该模型 | + +**视觉分析降级链路(fallback 模式,仅云端 role=vision 模型)**: + +``` +Gemini (gemini-1.5-flash, role=vision) + │ 失败 / 熔断 OPEN / 超时 + ▼ +NVIDIA NIM (llama-3.2-11b-vision-instruct, role=vision) + │ 失败 / 熔断 OPEN / 超时 + ▼ +任务 FAILED(走重试,不回退本地 Ollama —— 本地仅负责文本) +``` + +**文本阶段链路**: + +``` +各帧视觉描述(来自云端成功 provider)+ 时间戳 + known_members 上下文 + ▼ +本地 Ollama (qwen2.5:7b, role=text) 文本融合 → JSON 结构化结果 + │ 失败 / 超时 + ▼ +任务 FAILED(走重试) +``` + +**熔断器策略**(按 provider 独立,仅云端模型启用): + +| provider | role | threshold | cooldown | enabled | +|----------|------|-----------|----------|---------| +| Gemini | vision | 3 次连续失败 | 600s | true | +| NVIDIA NIM | vision | 3 次连续失败 | 600s | true | +| Ollama | text | — | — | false(本地,不熔断) | + +**健康探测**:Ollama `GET /api/tags`;Gemini `GET /v1/models?key=...`;NVIDIA `client.models.list()`。视觉模型全部不健康返回 503。 + +**多模型标签兼容**:`compute_provider` 与 `event_details.source_providers` 的 JSON 数组值新增 `"nvidia"` 标签(与 `"ollama"` / `"gemini"` 并列);`validate_schema` 校验非空数组。 + +**执行可观测性**:Orchestrator 在每次调用记录各模型的 `provider / 耗时 / success` 三元组到日志,并落库到 `monitor_events.compute_provider` 数组。 + +### 6.4 任务可靠性 + +- 状态机 PENDING → PROCESSING → SUCCESS/FAILED,退避重试 `min(60×(retry+1)×2, 600)`s,max_retries=3 +- **僵尸任务回收**:PROCESSING 超过 `push_timeout + 120s` 自动重置 PENDING(应对 fam-core 重启 / Edge 重启 / in-flight 请求丢失) +- `failure_stage` ENUM:download / extract / vlm_visual / vlm_fusion / callback + +--- + +## 7. 性能基准(实测) + +### 7.1 单图推理(原 llava-phi3, Oracle ARM 2C12G — 已替换为 qwen2.5:7b) + +| 场景 | 总耗时 | +|------|--------| +| 冷启动(首次加载 2.9GB 模型) | 109s(已由 keep-alive 消除) | +| 预热 + num_predict=30 | **4.3s** | +| 1024px 真实帧 + num_predict=60 | ~48.6s(视觉编码 36s 固定成本 + ~5 tok/s) | + +### 7.2 真实视频全流程(30min / 360MB / 1080p H.264) + +| 步骤 | 耗时 | +|------|------| +| 视频上传 NAS→Oracle(公网,5.1 MB/s) | 70s | +| FFmpeg 快速 seek 抽 60 候选帧 | 33s | +| OpenCV 关键帧筛选(→12 帧) | 5s | +| 压缩 | 0.6s | +| Ollama 视觉分析(12 帧) | 820s(avg 68s/帧,0.9 tok/s) | +| **总计** | **929s(15.5min)** | + +### 7.3 E2E 验证(2026-08-20) + +task_id=289(30s 测试片段)全链路打通:推送 5.7MB → Edge 分析 1129s → 同步返回 → `monitor_events` event_id=1 + `event_details` 2 条落库,`event_end_time` 推算正确(start+29s)。 + +--- + +## 8. 部署说明 + +### 8.1 部署位置与启动命令 + +| 组件 | 节点 | 路径 | 启动 | +|------|------|------|------| +| FAM-Core | NAS | `/volume1/web/sentinel-home-ai/fam-core/` | `./venv/bin/gunicorn --chdir <路径> -w 1 -b 0.0.0.0:8000 --timeout 120 --daemon --pid /tmp/fam-core-gunicorn.pid src.fam_core.app:app` | +| FAM-UI | NAS | `/volume1/web/sentinel-home-ai/fam-ui/` | `./venv/bin/streamlit run src/app.py`(headless, :8501) | +| FAM-Edge | Oracle | `/opt/fam-edge/` | `venv/bin/gunicorn -w 1 -b 0.0.0.0:5000 --timeout 1800 src.fam_edge.app:app`(日志 `/tmp/fam-edge.log`) | +| Ollama | Oracle | systemd 托管 | 环境变量 `OLLAMA_KEEP_ALIVE=-1` | + +### 8.2 依赖 + +- **FAM-Core/UI(NAS, Python 3.10 venv)**:Flask, Gunicorn, **PyMySQL**(45KB 纯 Python 替代 19MB mysql-connector), PyYAML, requests;FAM-UI 另需 Streamlit + pandas +- **FAM-Edge(Oracle, Python 3.8+ venv)**:Flask, Gunicorn, requests, PyYAML, opencv-python, numpy, **openai**(NVIDIA NIM 兼容 OpenAI API 规范,复用同一 SDK);Gemini 用 requests 直调 REST(不依赖 google-generativeai SDK) +- **系统级**:FFmpeg(两端)、Ollama + qwen2.5:7b(Oracle)、MariaDB 10.11(NAS) + +### 8.3 配置文件要点 + +**fam-core/config/config.yaml**(NAS): + +```yaml +scheduler: + video_dir: "/volume1/web/sentinel-home-ai/e2e-test" # 当前指向 E2E 测试目录 + # 正式目录: /volume1/surveillance/Generic_ONVIF-001(YYYYMMDDAM/PM 两级子目录, + # os.walk 递归支持;切回前需处理 285 个历史视频积压,避免全量上传 ~100GB) +dispatcher: + edge_url: "http://129.146.203.203:5000/api/edge/video/push" + push_timeout: 1800 +chat_handler: + ollama_url: "http://129.146.203.203:5000/api/edge/chat" # 经 Edge 代理 +``` + +**fam-edge/config/config.yaml**(Oracle,多模型池配置): + +```yaml +# 调度模式配置: fallback(顺序降级) 或 ensemble(并行交叉验证) +orchestrator: + mode: "fallback" + overall_timeout: 600 + +# 关键帧配置 +video: + candidate_frames: 30 + min_key_frames: 5 + max_key_frames: 12 + mse_threshold: 500 + jpeg_quality: 80 + max_long_edge: 1024 + +# 多模型池配置(视觉: Gemini → NVIDIA NIM 降级;文本: 本地 Ollama) +models: + # 1. Google Gemini 1.5 Flash(视觉主) + - provider: "gemini" + role: "vision" # 视觉分析 + enabled: true + model_name: "gemini-1.5-flash" + api_key: "${GEMINI_API_KEY}" + timeout: 15 + circuit_breaker: + enabled: true + threshold: 3 + cooldown: 600 + + # 2. NVIDIA NIM 托管 API(视觉备) + - provider: "nvidia" + role: "vision" # 视觉分析 + enabled: true + model_name: "meta/llama-3.2-11b-vision-instruct" # 或 qwen/qwen2-vl-72b-instruct + base_url: "https://integrate.api.nvidia.com/v1" + api_key: "${NVIDIA_API_KEY}" + timeout: 20 + circuit_breaker: + enabled: true + threshold: 3 + cooldown: 600 + + # 3. 本地 Ollama(文本融合 + AI 对话,不参与视觉) + - provider: "ollama" + role: "text" # 文本融合 + AI 对话 + enabled: true + model_name: "qwen2.5:7b" + base_url: "http://localhost:11434" + timeout: 60 + circuit_breaker: + enabled: false +``` + +**环境变量**(Oracle 节点,写入 `~/.bashrc` 或 systemd 环境变量文件): + +```bash +export GEMINI_API_KEY="AQ.Ab8RN6I0l8hC7hLnNHRY6qOXdch5CTWczDNlS4c1XrneGHipUQ" +export NVIDIA_API_KEY="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" +``` + +**NVIDIA NIM 单图连通性验证**: + +```bash +pip3 install openai +python3 -c " +import os +from openai import OpenAI +client = OpenAI( + base_url='https://integrate.api.nvidia.com/v1', + api_key=os.environ['NVIDIA_API_KEY'] +) +response = client.chat.completions.create( + model='meta/llama-3.2-11b-vision-instruct', + messages=[{'role': 'user', 'content': 'Hello, are you ready?'}], + max_tokens=30 +) +print('NVIDIA NIM 连接成功:', response.choices[0].message.content) +" +``` + +### 8.4 运维注意事项 + +- NAS 部署目录**不是 git 仓库**(文件拷贝部署),同步代码用 stdin 管道:`ssh ... "cat > 远端路径" < 本地文件` +- NAS scp 子系统被禁用,同样用 stdin 管道传文件 +- 远端 kill gunicorn 时 pkill/pgrep 会匹配 SSH 自身命令行导致断连,用 `pgrep -f 'gunicorn -w [1]'` 字符类技巧或 PID 文件 +- fam-core 启动模块路径是 `src.fam_core.app:app`(不是 `fam_core.app:app`) +- Edge 单 worker 处理任务期间 `/health` 可能不响应,属正常 + +--- + +## 9. 快速开始 + +```bash +# 1. 初始化数据库(NAS) +python scripts/init_db.py + +# 2. 启动 FAM-Core(NAS) +cd fam-core && gunicorn -w 1 -b 0.0.0.0:8000 --timeout 120 src.fam_core.app:app + +# 3. 启动 FAM-Edge(Oracle) +cd fam-edge && gunicorn -w 1 -b 0.0.0.0:5000 --timeout 1800 src.fam_edge.app:app + +# 4. 启动 FAM-UI(NAS) +cd fam-ui && streamlit run src/app.py + +# 或使用脚本 +./scripts/start_core.sh && ./scripts/start_edge.sh && ./scripts/start_ui.sh +``` + +--- + +## 10. 服务器访问信息 + +### 10.1 Synology NAS(FAM-Core + FAM-UI + MariaDB) + +| 项目 | 值 | +|------|-----| +| IP | 192.168.50.64 | +| SSH 端口 | 2222(scp 禁用,用 stdin 管道传文件) | +| SSH 用户 / 密码 | ericwyuan / iLoveJava5 | +| 系统 | Synology DS220+ (Geminilake), DSM 7 | +| Tailscale IP | 100.70.234.39(userspace 模式,端口不通待修) | +| 登录命令 | `ssh -p 2222 ericwyuan@192.168.50.64` | + +### 10.2 MariaDB(NAS) + +| 项目 | 值 | +|------|-----| +| 版本 | MariaDB 10.11.11 | +| Socket | /run/mysqld/mysqld10.sock | +| Root 密码 | iLoveJava5! | +| 数据库名 | sentinel_home_ai | +| 连接 | `/usr/local/mariadb10/bin/mysql -S /run/mysqld/mysqld10.sock -u root -p`(非交互 PATH 下用 fam-core venv 的 PyMySQL + unix_socket 查询) | + +### 10.3 Oracle Cloud(FAM-Edge + Ollama + FFmpeg) + +| 项目 | 值 | +|------|-----| +| 公网 IP | 129.146.203.203 | +| SSH 用户 | ubuntu(密钥 `~/.ssh/oracle_sentinel`) | +| 系统 | aarch64 (Ampere A1 2C12G), Ubuntu 20.04 LTS | +| Tailscale | 100.74.137.126(已安装在线,与 NAS 端口不通) | +| 登录命令 | `ssh -i ~/.ssh/oracle_sentinel ubuntu@129.146.203.203` | + +### 10.4 Gitea 代码仓库 + +| 项目 | 值 | +|------|-----| +| URL | http://192.168.50.64:3000/ericwyuan/sentinel-home-ai | +| 账号 / 密码 | ericwyuan / iLoveJava5 | + +### 10.5 Gemini API(Google AI Studio) + +| 项目 | 值 | +|------|-----| +| API Key | AQ.Ab8RN6I0l8hC7hLnNHRY6qOXdch5CTWczDNlS4c1XrneGHipUQ | +| 模型 | `gemini-1.5-flash`(生产配置);`gemini-flash-latest`(curl 验证端点别名) | +| 端点 | https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent | +| 验证状态 | 2026-08-20 测试可用 | + +```bash +curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent" \ + -H 'Content-Type: application/json' \ + -H 'X-goog-api-key: AQ.Ab8RN6I0l8hC7hLnNHRY6qOXdch5CTWczDNlS4c1XrneGHipUQ' \ + -X POST \ + -d '{"contents":[{"parts":[{"text":"Explain how AI works in a few words"}]}]}' +``` + +### 10.6 NVIDIA NIM 托管 API + +| 项目 | 值 | +|------|-----| +| API Key | `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` | +| Base URL | `https://integrate.api.nvidia.com/v1` | +| 默认模型 | `meta/llama-3.2-11b-vision-instruct`(可替换 `qwen/qwen2-vl-72b-instruct`) | +| SDK | `openai` Python SDK(NIM 兼容 OpenAI API 规范,直接复用) | +| 验证状态 | 待测(2026-08-20 配置完成,单图连通性验证脚本见 8.3) | + +**环境变量**: + +```bash +export NVIDIA_API_KEY="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" +``` + +--- + +## 11. 提交规范(AI Agent 必读) + +**工作流程(强制)**: + +1. **开工前**:仓库根目录 `git pull --rebase` +2. **每完成一项验收子任务**:立即 `git add` + `git commit` + `git push`,一任务一 commit,不批量合并 +3. **遇到阻塞**:先 commit 可工作部分,message 加 `[WIP]` 前缀 +4. **修 Bug**:单独 commit,格式 `fix(模块): 问题简述` + +**commit message 格式**: + +- 任务:`[阶段X.Y子任务号] 子任务名称 - 完成内容简述` +- Bug:`fix(模块): 问题简述` +- 性能/文档:`perf(...)` / `docs(...)` + +**禁止**:不 commit 直接继续;一次 commit 多个子任务;`git push --force`;跳过 hooks(`--no-verify`)。 + +**自检清单**:任务对应哪一行验收标准?是否已 commit + push?message 是否合规?开工前是否 pull --rebase? + +--- + +## 12. 当前进度与 v1.1 计划 + +### 已完成(截至 2026-08-20) + +- 全部模块代码 + DDL + 部署脚本;NAS/Oracle 双端部署运行 +- 模型基准测试(原 llava-phi3 预热 4.3s PASS,**已替换为 qwen2.5:7b**);Ollama 常驻内存 +- 架构改为推送模式(NAS 上传整段视频 → Edge 同步分析 → 结果随响应返回) +- 关键帧自适应帧数 + FFmpeg 快速 seek(6-8x 提速) +- FAM-Core API 全端点测试通过;端到端聊天链路(Core→Edge→Ollama)验证 +- FAM-UI 部署(Streamlit 1.61.1) +- 真实视频性能基准(30min/360MB → 929s) +- **E2E 全链路打通**:task 289 → SUCCESS,monitor_events/event_details 落库正确 +- 可靠性加固:僵尸任务回收、文件日志、datetime 空值兜底、超时按实测调整 + +详细进度见 `PROGRESS.md`。 + +### v1.1 待办 + +| # | 任务 | 优先级 | +|---|------|--------| +| 1 | 接入云端视觉模型(Gemini `gemini-1.5-flash` + NVIDIA NIM `llama-3.2-11b-vision-instruct`),承担全部视觉分析;本地 Ollama 专职文本融合与 AI 对话(不再参与视觉) | 高 | +| 2 | 实现 `NvidiaVisionAdapter`(基于 openai SDK,Base64 多图内联,`role: vision`)并在 `build_adapter` 工厂注册 `provider: nvidia` | 高 | +| 3 | Orchestrator 视觉阶段按 `fallback` 降级 Gemini → NVIDIA NIM,全失败则任务 FAILED;文本阶段固定调本地 Ollama 融合;`compute_provider` / `source_providers` 兼容 `nvidia` 标签 | 高 | +| 4 | analyze_frames 改逐帧请求(云端模型支持多图,但逐帧更稳;同时解决历史 llava-phi3 多图失效问题,新方案本地 qwen2.5:7b 不参与视觉,该问题随之消除) | 高 | +| 5 | 切回生产视频目录(/volume1/surveillance/Generic_ONVIF-001),确认历史视频回补策略 | 高 | +| 6 | 单元测试(JSON parser / circuit breaker / schema 校验) | 中 | +| 7 | Tailscale 修复(NAS userspace 模式升级,流量不走公网) | 低 | +| 8 | daily_summaries 每日摘要 | 低 | + +--- + +文档结束 +