758 lines
44 KiB
Markdown
758 lines
44 KiB
Markdown
|
||
# Sentinel Home AI — 家庭多模态智能监控系统
|
||
|
||
> 多模型容灾降级 + 交互式命名 + AI 对话的家庭监控系统。
|
||
> 本文档为项目需求文档与 README 的整合版,按当前代码实际状态(v1.0 E2E 已打通)编写。
|
||
> 最后更新:2026-08-22(运动事件驱动架构:整段素材分割运动片段再分析)
|
||
|
||
---
|
||
|
||
## 1. 项目简介与设计目标
|
||
|
||
系统持续分析家庭监控摄像头(Synology Surveillance Station)录制的视频,抽取关键帧后调用视觉语言模型(VLM)分析画面中的人物、动作、衣着,落库为结构化事件;用户通过 Web UI 浏览事件、给"人物A/B/C"命名(批量回溯历史记录)、以及用自然语言向 AI 询问"汤圆今天干嘛了"。
|
||
|
||
### 1.1 首期范围(已基本完成)
|
||
|
||
> **新架构 v3(2026-08-22 重构:运动事件驱动)**:NAS 不再处理视频,仅作管理后台 + 运动事件推送;视频分析全部上云(Oracle),且**不再分析整段视频**——整段素材按 SS 运动事件**分割成运动片段**后只分析片段。
|
||
|
||
- **FAM-Core**(NAS 端单进程):**Oracle-Sync**(每 30 分钟拉增量镜像)+ **MotionNotifier**(轮询 SS 运动事件推送 Oracle)+ **Chat-Handler** + **Member-Manager**,CPU 占用极低
|
||
- **FAM-Edge**(Oracle 端单进程):rclone 实时同步 Google 硬盘视频(**整段素材**)→ 按 `ss_motion_events` 运动事件(start_time/duration)**ffmpeg 分割运动片段** → **只把运动片段送云端 VLM**(Gemini 用 Files API / NVIDIA 整视频 `video_url`)→ 结构化 JSON 落本地 SQLite → 对外提供 `/api/oracle/sync` 增量拉取接口(**本地模型不参与视频分析**)
|
||
- **FAM-UI**(NAS 端):Vue3 SPA 读本地同步镜像(sync_videos / sync_events / sync_people),事件时间轴 + 人物管理 + AI 对话 + 对话历史 + 统计
|
||
- **数据库**:Oracle 侧 SQLite(videos/events/people/ss_motion_events/sync_cursor);NAS 侧 MariaDB 镜像(sync_videos / sync_events / sync_people / sync_cursor)+ chat_history
|
||
- **数据流向**:Google 硬盘 ──rclone──► 甲骨文整段素材 ──按运动事件分割片段──► 片段云端分析 ──► Oracle SQLite ──每 30 分钟 NAS 拉取──► NAS MariaDB 镜像 ──► FAM-UI;运动事件由 NAS 轮询 SS 推送 Oracle(NAS → Oracle 单向)
|
||
- **AI 对话**:查 sync_events 拼上下文 → 经 FAM-Edge 问答编排(Gemini → NVIDIA → 本地 Ollama 兜底)生成回答 → 返回并写 chat_history
|
||
- **人物命名**:用户命名/合并某 label → 回推 Oracle `/api/oracle/people/correct`(manual 优先)→ 下一周期同步回 NAS;Oracle 独立 person_service 汇总全量人物 → LLM 合并为规范名 → 回灌视频提示
|
||
|
||
### 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**(纯文本模型,专职问答兜底) | 历史遗留描述,新方案下视觉走云端,本地不参与视觉分析,该问题随之消除 |
|
||
| **吞吐不足** | 历史本地模型全流程约 19 分钟(关键帧筛选 10min + VLM 5.5min + 融合 3min);30min/360MB 视频实测 929s | 已切换到云端 VLM 直出结构化 JSON(无本地融合步骤),吞吐瓶颈转移至云端调用延迟,整体明显改善 |
|
||
| **Tailscale 端口不通** | NAS tailscaled 以 userspace 模式运行(无 TUN 网卡),Oracle 无法反向访问 NAS;当前 NAS→Oracle 走公网 IP | 推送模式已规避反向访问,但流量走公网 |
|
||
| **历史视频积压(已处理)** | 正式目录 `/volume1/surveillance/Generic_ONVIF-001` 原有约 285 个历史视频(~100GB),已切回生产目录并 forward-only 处理:历史任务占位 FAILED,scheduler dedup 自动跳过,仅处理新增视频 | 已解决;生产目录已生效,新视频正常入链 |
|
||
|
||
**规划方向(新框架)**:**云端大模型负责视觉识别与结构化输出、本地大模型仅做智能问答兜底**。按任务类型分工:
|
||
|
||
| 任务 | 执行方 | 模型 | 说明 |
|
||
|------|--------|------|------|
|
||
| **视觉分析 + 结构化输出**(看图识人/动作/衣着 → 直出 JSON) | 云端 | Gemini → NVIDIA NIM(fallback 降级) | 云端 VLM 直接产出 `global_summary` / `entities_json` / `frame_details`,Edge 仅做**格式化校验**后直存 NAS,**本地模型不介入** |
|
||
| **结果格式化**(云端 JSON → 入库 schema) | Edge 进程 | 无模型调用 | `format_cloud_result`:字段归一化、补 `source_providers`/`compute_provider`、推导 `entities`、缺失 `global_summary` 时事实拼接;纯数据转换,非 LLM 二次汇总 |
|
||
| **AI 对话**("汤圆今天干嘛了") | 云端优先 + 本地兜底 | Gemini → NVIDIA NIM → 本地 Ollama | 两云端任一成功即用;**仅当 Gemini 与 NVIDIA 都失败**才回退本地 Ollama qwen2.5:7b |
|
||
|
||
- **Google Gemini**(`gemini-flash-latest`,API Key 已验证,支持多图单请求,视觉 + 问答均参与)
|
||
- **NVIDIA NIM**(`meta/llama-3.2-11b-vision-instruct`,OpenAI 兼容 API,云端 GPU 推理,单请求限 1 图故逐帧调用;视觉 + 问答均参与)
|
||
- **本地 Ollama**(qwen2.5:7b)**仅参与智能问答,且仅作兜底**:视觉链路两云端全失败 → 任务 FAILED 走重试,**绝不回退本地模型做视觉/融合**
|
||
|
||
Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA NIM;云端模型直出结构化 JSON 后由 `format_cloud_result` 格式化。问答阶段按 `gemini → nvidia → ollama` 顺序,仅末位本地模型作兜底。
|
||
|
||
---
|
||
|
||
## 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 上传视频 ② `POST /api/ss/motion` 推送运动侦测事件)。Edge 不需要反向访问 NAS(无视频拉取)
|
||
- **Surveillance Station → NAS 本机 Webhook**:摄像头动作经 SS「行動規則/Webhook」实时 `POST :8000/api/ss/webhook`(同机 127.0.0.1,无需外网),由 FAM-Core 映射后推送 Oracle。这是 NAS 入站,非 Edge 反向访问
|
||
- Oracle 端 Ollama 端口 11434 不对外暴露,聊天请求经 FAM-Edge `/api/edge/chat` 代理转发
|
||
- Tailscale 两节点已安装在线,但 NAS tailscaled 为 userspace 模式且防火墙端口不通,暂走公网 IP
|
||
|
||
### 2.2 部署拓扑与数据流(新架构 v2:Oracle 分析 + NAS 镜像)
|
||
|
||
```
|
||
┌──────────── Google 硬盘 ────────────┐
|
||
│ oraclenas@...gserviceaccount.com │
|
||
│ (Cloud Sync 落盘目录) │
|
||
└──────────────┬───────────────────────┘
|
||
│ rclone 定时同步(systemd timer)
|
||
▼
|
||
┌──────────────────────── Oracle Cloud (129.146.203.203) ────────────────────────┐
|
||
│ FAM-Edge (Flask :5000) │
|
||
│ ├─ Video-Queue: 30s 轮询 /opt/fam-edge/gdrive_videos 登记整段素材 │
|
||
│ ├─ Video-Processor: 素材按 ss_motion_events 分割运动片段 → 只分析片段 │
|
||
│ │ (ffmpeg -c:v copy -c:a aac) → Gemini → 失败 NVIDIA → 再失败 FAILED │
|
||
│ ├─ Person-Service: 汇总人物 → LLM 合并规范名 → 回灌视频提示 │
|
||
│ ├─ OracleDB (SQLite): videos / events / people / ss_motion_events │
|
||
│ └─ API: /api/oracle/sync (增量拉取) · /api/ss/motion (运动事件) · │
|
||
│ /api/oracle/people/correct (命名校正) · /api/edge/chat/ask (问答) │
|
||
└───────────────────────────────┬───────────────────────────────────────────────┘
|
||
│ HTTP GET /api/oracle/sync?since=&token= (每 30 分钟)
|
||
▼
|
||
┌─────────────────────────────── NAS (192.168.50.64) ────────────────────────────┐
|
||
│ FAM-Core (Flask :8000) │
|
||
│ ├─ Oracle-Sync: 唯一后台线程,拉增量写 MariaDB 镜像 + 维护 sync_cursor │
|
||
│ ├─ Chat-Handler: /api/chat/ask(查 sync_events 拼上下文 → 走 Oracle 编排) │
|
||
│ ├─ Member-Manager: /api/member/name|merge(回推 Oracle + 即时拉回) │
|
||
│ ▼ │
|
||
│ MariaDB (sentinel_home_ai): sync_videos / sync_events / sync_people / │
|
||
│ sync_cursor / chat_history │
|
||
│ FAM-UI (Streamlit :8501) 读同步镜像 │
|
||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2.2.1 运动监测链路(v3,NAS 轮询驱动)
|
||
|
||
摄像头动作事件由 **NAS 端 MotionNotifier 轮询 Surveillance Station 事件列表**获取(简单稳定,事件不遗漏;Webhook 端点在代码中保留为可选低延迟补充):
|
||
|
||
Surveillance Station (NAS 本机)
|
||
│ SYNO.SurveillanceStation.EventCenter.Event method=List
|
||
│ camera_ids=2, event_types=10, start_time/end_time(下划线风格)
|
||
▼
|
||
NAS FAM-Core MotionNotifier(motion_notifier.py,每 60s)
|
||
│ ├─ 增量游标(MariaDB sync_cursor.motion_last_event_id,DB 续用/重启补推)
|
||
│ ├─ 推送失败批次不前进游标(下轮重试,不丢事件)
|
||
│ └─ 定期空 events 心跳(证明推送链路存活)
|
||
│ HTTP POST /api/ss/motion?token=ORACLE_SYNC_TOKEN(真实 event_id/start_time/duration)
|
||
▼
|
||
Oracle FAM-Edge :5000 /api/ss/motion (api_gateway.py)
|
||
│ → OracleDB.ss_motion_events(event_id UNIQUE,自动去重;start_time/duration 为 Unix epoch)
|
||
▼
|
||
Oracle video_processor:整段素材按运动事件【分割运动片段】→ 只分析片段(见 2.3)
|
||
|
||
> 设计要点:甲骨文**不反向访问** NAS;只分割**已结束**事件(SS 事件 duration 在动作进行中为 0、结束才回填真实时长)。
|
||
> 失败-open:心跳超过 `max_heartbeat_age_sec`(900s)未更新时预过滤/分割 fail-open,不误判"无运动"。
|
||
|
||
**网络要点(新架构)**:
|
||
- NAS → Oracle 出站共两条:① `GET /api/oracle/sync`(拉取增量)+ `POST /api/oracle/people/correct`(命名回推);② `POST /api/ss/motion`(运动侦测事件推送)。均走 Oracle 公网 IP:5000,token 鉴权
|
||
- Oracle Ollama :11434 不对外暴露,问答经 FAM-Edge `/api/edge/chat/ask` 代理
|
||
- Tailscale 两节点在线但 NAS 无法反向访问 Oracle,故全部走 NAS 主动出站拉取模式
|
||
|
||
### 2.3 主链路时序(新架构 v3:运动事件驱动)
|
||
|
||
1. Google 硬盘新视频(整段素材)→ rclone 定时同步到 Oracle `/opt/fam-edge/gdrive_videos`
|
||
2. Video-Queue 轮询发现新文件 → 登记到 Oracle `videos`(pending)
|
||
3. Video-Processor 处理素材:按文件名解析开始时间 → 查窗口内 `ss_motion_events` **已结束**运动事件 → ffmpeg 分割运动片段(`-c:v copy -c:a aac` 保留音频)→ 片段登记 `videos`(pending,`motion_event_id` 关联)并入队;素材标记"已分割 N 段"(仍有未结束事件则保持 pending 下轮再分割)
|
||
4. Video-Processor 处理运动片段:**只分析片段**(不分析整段)→ Gemini(或 NVIDIA 兜底)直出 `{global_summary, events[], people_mentioned[]}` → 写 Oracle `videos` + `events` + `people`
|
||
5. Person-Service 每 30 分钟汇总全量人物 → LLM 合并为规范名 → 更新 `people.canonical_name` → 生成 `known_members_context` 回灌后续片段提示
|
||
6. NAS Oracle-Sync 每 30 分钟 `GET /api/oracle/sync?since=<cursor>` → upsert 到本地 `sync_*` 镜像表 → 推进 `sync_cursor`
|
||
7. FAM-UI 读本地镜像展示(时间轴/人物/统计字段契约不变);用户命名 → `POST /api/oracle/people/correct` 回推 Oracle,下一周期同步生效
|
||
|
||
**运动监测支流(NAS 轮询驱动)**:
|
||
- MotionNotifier 每 60s 轮询 SS `EventCenter.Event.List`(camera_ids=2, event_types=10)→ 增量(游标)推送 `POST /api/ss/motion` 落库 `ss_motion_events`(真实 event_id/start_time/duration)
|
||
- 整段素材处理时按 `ss_motion_events` 分割运动片段(只分割已结束事件;duration=0 的进行中事件等结束后的下轮);片段分析结果的时间点(`events.ts`)为绝对时间,前端时间轴与帧图(`/api/proxy/frame`,绝对 ts − event_start_time 偏移取帧)天然兼容
|
||
|
||
**容错设计**:
|
||
- Oracle 单视频串行(`max_concurrent=1`)避免多视频抢占云端配额
|
||
- 运动片段分析失败(两云端均不可用)标记 `failed`,下一周期 cursor 仍包含它会被重试
|
||
- 素材分割幂等:按 `motion_event_id` 去重,片段文件已存在则跳过分割
|
||
- NAS 同步失败仅记日志,下一周期(30 分钟)自动重试,不阻塞 UI
|
||
- fam-core 日志双写:stdout + `fam-core/logs/fam-core.log`
|
||
|
||
---
|
||
|
||
## 3. 模块设计
|
||
|
||
### 3.1 FAM-Core(NAS 端)
|
||
|
||
> NAS 不再处理视频,仅作管理后台。唯一常驻后台线程是 Oracle-Sync。
|
||
|
||
| 模块 | 文件 | 职责 |
|
||
|------|------|------|
|
||
| Oracle-Sync | `oracle_sync/oracle_sync.py` | 唯一后台线程:每 30 分钟 `GET /api/oracle/sync?since=<cursor>&token=` 拉增量 → upsert 到 `sync_videos`/`sync_events`/`sync_people` → 推进 `sync_cursor`;`push_name_correct()` 回推命名校正;`trigger_now()` 立即同步 |
|
||
| Chat-Handler | `chat_handler/chat_handler.py` | `/api/chat/ask` 查 `sync_events` 拼上下文 → 经 Oracle `/api/edge/chat/ask` 问答编排(Gemini→NVIDIA→本地 Ollama 兜底)→ 写 chat_history |
|
||
| Member-Manager | `member_manager/member_manager.py` | `/api/member/unnamed` / `/api/member/list` / `/api/member/name` / `/api/member/merge`;命名/合并回推 Oracle 并即时拉回本地镜像 |
|
||
| MotionNotifier | `motion_notifier/motion_notifier.py` | 运动事件映射/发送服务:**轮询已关闭**(`poll_enabled=false`);启动时一次性拉取 SS 摄像头「名→id」映射并以 config 兜底;提供 `build_event_from_webhook()` 将 SS Webhook 字段归一化为事件 |
|
||
| Motion-Webhook | `motion_bp.py` | `POST /api/ss/webhook` 接收 SS Webhook(兼容 JSON/表单/单条/数组)→ 映射 → 推送 Oracle;`GET /api/ss/status` 查询状态 |
|
||
| 公共层 | `db_layer.py` / `config_loader.py` / `logger.py` | PyMySQL 连接(unix_socket);同步镜像 CRUD;文件日志 |
|
||
|
||
> 已删除:Task-Scheduler / Dispatcher / Poller / Event-Receiver / Video-Server(视频上传、切片、抽帧、关键帧落盘等职责全部迁移至 Oracle 端,NAS CPU 占用大幅降低)。
|
||
|
||
### 3.2 FAM-Edge(Oracle 端)
|
||
|
||
> 整视频分析,不切片、不抽帧、不依赖 OpenCV 人脸。
|
||
|
||
| 模块 | 文件 | 职责 |
|
||
|------|------|------|
|
||
| API-Gateway | `api_gateway/api_gateway.py` | `GET /api/oracle/sync`(增量拉取,since+token 校验);`POST /api/oracle/people/correct`(命名校正);`POST /api/edge/chat/ask`(问答编排);`POST /api/ss/motion`(运动事件接收,token 校验,落库 `ss_motion_events`);`GET /health` |
|
||
| Video-Queue | `video_queue.py` | 生产-消费队列:生产者 30s 轮询 rclone 同步落地目录登记整段素材入队(含重启恢复);消费者(`max_concurrent` 个线程)取队列调 Video-Processor;模型超时 = 原配置 ×`timeout_multiplier`;失败重试上限 `max_retries` |
|
||
| Video-Processor | `video_processor.py` | **素材→分割/片段→分析**双分支:素材按 `ss_motion_events` 已结束运动事件 ffmpeg 分割运动片段(`-c:v copy -c:a aac`,幂等去重),片段只送云端 VLM 分析;按 `vision_order` 调适配器;首个成功即落库 Oracle `videos`+`events`+`people`;全失败标 `failed` |
|
||
| Person-Service | `person_service.py` | 汇总全量人物 → LLM 合并为规范名 → `set_canonical`;生成 `known_members_context` 回灌视频提示;manual 命名优先不被覆盖 |
|
||
| OracleDB | `oracle_db.py` | SQLite:videos / events / people / sync_cursor / ss_motion_events;`get_sync_delta(since)` 增量导出;`record_motion_events` / `has_motion_in_range_local`(运动事件幂等落库与窗口查询) |
|
||
| Model-Adapters | `model_adapters/` | `BaseModelAdapter.analyze_video(video_path, known_members_context, event_start_time)`;Gemini(Files API 整视频)/ NVIDIA(整视频 `video_url`,`num_frames=128`)/ Ollama(纯文本,不参与视频) |
|
||
| QA-Orchestrator | `qa.py` | 遍历所有适配器 `chat()`,Gemini→NVIDIA→Ollama 三级降级(仅问答) |
|
||
|
||
### 3.3 FAM-UI(NAS 端)
|
||
|
||
Streamlit 应用(`fam-ui/src/app.py`),侧边栏切换页面(均读本地同步镜像):
|
||
|
||
| 页面 | 功能 |
|
||
|------|------|
|
||
| 🕒 事件时间轴 | 视频会话列表(按处理后时间倒序)+ 选中会话的事件时间线(时间点 + 描述 + 人物/关注徽章,无帧图) |
|
||
| 💬 AI 对话 | 输入框 + 调 `/api/chat/ask`;按 queried_person 预设快捷提问 |
|
||
| 📝 对话历史 | chat_history 倒序展示 |
|
||
| 👤 人物管理 | 按规范名/标签聚合,命名/合并(回推 Oracle);不再展示帧照片 |
|
||
| 📈 统计图表 | 模型来源占比 / 关注事件 / 同步状态 |
|
||
|
||
---
|
||
|
||
## 4. 数据库设计
|
||
|
||
库名 `sentinel_home_ai`,MariaDB 10.11.11,utf8mb4。完整 DDL 见 `scripts/ddl.sql`。
|
||
|
||
### 4.1 表清单
|
||
|
||
> 新架构 v2:Oracle 侧用 SQLite(`videos`/`events`/`people`/`sync_cursor`),NAS 侧 MariaDB 仅保留 **同步镜像表 + 问答历史**。`process_tasks`/`monitor_events`/`event_details`/`family_members` 等旧表已不再写入(保留历史数据,未删除)。
|
||
|
||
**Oracle(SQLite,`oracle_db.py`)**
|
||
|
||
| 表 | 用途 | 关键字段 |
|
||
|----|------|---------|
|
||
| `videos` | 视频会话(每视频 1 行) | id, filename(UNIQUE), camera_name, status, summary_json, events_json, people_json, compute_provider, event_start_time, updated_at |
|
||
| `events` | 视频内时间点事件 | id, video_id, ts, description, person_list_json, is_attention_event |
|
||
| `people` | 规范人物(Oracle 维护) | id, label(UNIQUE), canonical_name, appearances, source(llm/manual) |
|
||
| `sync_cursor` | 同步游标 | key, value(上次 server_time) |
|
||
|
||
**NAS(MariaDB,同步镜像,`scripts/ddl.sql`)**
|
||
|
||
| 表 | 用途 | 关键字段 |
|
||
|----|------|---------|
|
||
| `sync_videos` | 视频会话镜像(对齐 Oracle videos) | id, filename, camera_name, status, summary_json, events_json, people_json, compute_provider, processed_at |
|
||
| `sync_events` | 事件镜像(对齐 Oracle events) | id, video_id, ts, description, person_list_json, is_attention_event |
|
||
| `sync_people` | 人物镜像(对齐 Oracle people) | id, label, canonical_name, appearances, source |
|
||
| `sync_cursor` | 同步游标 | key='last_since', value |
|
||
| `chat_history` | AI 问答记录 | chat_id, user_question, ai_answer, context_summary, queried_date, queried_person |
|
||
|
||
### 4.2 表关系
|
||
|
||
```
|
||
Oracle: videos (1) ─── (N) events people 独立(label/canonical_name)
|
||
NAS 镜像: sync_videos (1) ─── (N) sync_events sync_people 独立
|
||
chat_history 独立表(问答上下文摘要留存)
|
||
```
|
||
|
||
命名回溯:用户命名某 `label` → `POST /api/oracle/people/correct` 设 `canonical_name`(manual 优先)→ 下一周期同步回 NAS `sync_people`;Oracle `person_service` 用规范名回灌视频提示,后续事件 `person_list_json` 直接带真名。
|
||
|
||
### 4.3 compute_provider
|
||
|
||
- `sync_videos.compute_provider`:字符串,记录该视频实际成功调用的视觉模型(`gemini` / `nvidia`);本地 Ollama 不参与视频分析,不会出现在该字段
|
||
- 问答链路(Gemini→NVIDIA→Ollama 兜底)的 provider 体现在 `/api/edge/chat/ask` 响应的 `provider` 字段
|
||
|
||
### 4.4 兼容性注意
|
||
|
||
- MariaDB 10.11 严格模式:**空字符串不能插 DATETIME 列**(1292 错误)。同步表时间字段统一用 `VARCHAR(32)` 文本存储 Oracle 的 ISO 字符串,规避类型转换问题
|
||
- MariaDB 不支持 MySQL 的 `$[*]` JSON 通配路径,人物统计按 `person_list_json LIKE '%name%'` 字符串匹配在 Python 层完成
|
||
|
||
---
|
||
|
||
## 5. API 规范(实际实现)
|
||
|
||
### 5.1 FAM-Edge(Oracle :5000)
|
||
|
||
**GET /api/oracle/sync**(NAS 每 30 分钟拉增量,新架构主接口)
|
||
|
||
- 请求:`?since=<ISO 文本>&token=<ORACLE_SYNC_TOKEN>`(`since` 为空拉全量)
|
||
- 响应(200):
|
||
|
||
```json
|
||
{
|
||
"videos": [
|
||
{"id": 1, "filename": "2026-08-21_081500.mp4", "camera_name": "客厅",
|
||
"status": "done", "summary_json": "...", "events_json": "[...]",
|
||
"people_json": "[...]", "compute_provider": "gemini",
|
||
"event_start_time": "2026-08-21 08:15:00", "updated_at": "2026-08-21 08:40:12"}
|
||
],
|
||
"events": [
|
||
{"id": 10, "video_id": 1, "ts": "00:01:23", "description": "汤圆在客厅玩耍",
|
||
"person_list_json": "[\"汤圆\"]", "is_attention_event": 0, "updated_at": "2026-08-21 08:40:12"}
|
||
],
|
||
"people": [
|
||
{"id": 1, "label": "人物A", "canonical_name": "汤圆", "source": "manual",
|
||
"appearances": 12, "updated_at": "2026-08-21 08:41:00"}
|
||
],
|
||
"server_time": "2026-08-21 08:41:30"
|
||
}
|
||
```
|
||
|
||
- 401:token 校验失败
|
||
|
||
**POST /api/oracle/people/correct** — 命名校正回推:`{"label":"人物A","canonical_name":"汤圆","token":...}`(manual 优先,不被 LLM 覆盖)→ `{"status":"ok"}`
|
||
**POST /api/edge/chat/ask** — 智能问答编排(FAM-Core Chat-Handler 调用):请求 `{"prompt","max_tokens"}` → 响应 `{"answer","provider"}`;内部按 Gemini → NVIDIA → 本地 Ollama 顺序,仅两云端都失败才用本地兜底
|
||
**GET /health** — 服务状态(含已处理视频数)
|
||
|
||
### 5.2 FAM-Core(NAS :8000)
|
||
|
||
| 端点 | 方法 | 说明 |
|
||
|------|------|------|
|
||
| `/health` | GET | 服务健康 |
|
||
| `/api/status` | GET | Oracle-Sync 同步状态(running / last_sync_at / last_error / cursor / last_count) |
|
||
| `/api/chat/ask` | POST | 用户问答:`{"question","queried_person","queried_date"}` → `{"answer","context_summary","chat_id"}`(上下文来自 sync_events) |
|
||
| `/api/chat/history` | GET | 对话历史(`?date=` 或 `?person=&limit=`) |
|
||
| `/api/member/unnamed` | GET | 未命名人物列表(label / 出现次数 / 首见时间) |
|
||
| `/api/member/list` | GET | 全部人物(label + canonical_name + 是否命名) |
|
||
| `/api/member/name` | POST | 命名:`{"label","canonical_name"}` → 回推 Oracle 并即时拉回本地镜像 |
|
||
| `/api/member/merge` | POST | 合并:`{"source","target"}` → 将 source 并入 target 身份(统一 canonical_name) |
|
||
| `/api/ss/webhook` | POST | 接收 Surveillance Station Webhook(参数 `event_time`/`device_name`/`event_name`/`server_name`/`thumbnail_url`);映射后推送 Oracle `/api/ss/motion` |
|
||
| `/api/ss/status` | GET | 运动监测状态:`poll_enabled` / `camera_loaded` / `camera_map` / `pushed_total` / `last_error` |
|
||
|
||
> 已删除:`/api/core/callback/event`、`/media/<path>`(视频处理职责已迁移至 Oracle)。
|
||
|
||
### 5.3 云端结构化输出 JSON Schema
|
||
|
||
整视频直传云端 VLM,模型直接产出结构化 JSON(`analyze_video` 返回):
|
||
|
||
```json
|
||
{
|
||
"global_summary": "客厅监控摘要……",
|
||
"events": [
|
||
{"timestamp": "00:01:23", "description": "汤圆在客厅玩耍",
|
||
"people": ["汤圆"], "is_attention_event": false}
|
||
],
|
||
"people_mentioned": ["汤圆"]
|
||
}
|
||
```
|
||
|
||
- Gemini 用 Files API 上传整视频后 `generateContent`;NVIDIA 用整视频 `video_url` + `num_frames=128`(模型内部自行采样帧),均不切片、不抽帧、不依赖 OpenCV
|
||
- `person_service` 汇总全量 `people_mentioned` → LLM 合并为规范名 → 生成 `known_members_context` 回灌后续视频提示,使模型用真名指代
|
||
- `action` / 描述由 AI 自由生成无枚举过滤,`is_attention_event` 由 AI 自行判断
|
||
|
||
---
|
||
|
||
## 6. 关键技术
|
||
|
||
### 6.1 视频预处理(自适应关键帧)
|
||
|
||
- **粗抽候选帧**:FFmpeg 快速 seek(逐帧 `ffmpeg -ss <ts> -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,纯文本模型)**不参与视觉分析、不参与云端结果融合**。它只在**智能问答**场景下、且 Gemini 与 NVIDIA 两云端模型都失败时才被启用作为兜底。视觉分析与结构化输出全部由云端模型承担。以下参数作为问答任务的调优依据保留。
|
||
|
||
| 参数 | 值 | 依据 |
|
||
|------|-----|------|
|
||
| `OLLAMA_KEEP_ALIVE=-1` | 模型常驻内存 | 消除 55s 冷启动(常驻约 4.3GB,12GB 内存够用) |
|
||
| `num_predict=512` | 限制生成 token | ARM 约 5 tok/s,过长生成会拖慢问答响应 |
|
||
| 视觉/模型 timeout | 600s | 实测 1024px 帧视觉编码 ~36s + 生成 ~12s/60token(问答链路改用 config 中各模型 timeout) |
|
||
| gunicorn(Edge) | `--timeout 1800` | 同步分析模式,默认 30s 会杀 worker |
|
||
| push_timeout(NAS) | 1800s | 覆盖最坏情况(30min 视频实测 929s) |
|
||
|
||
**已知问题**:原 llava-phi3 多图单请求基本失效(N 张图一次调用输出长度仅 3~4)。**已替换为 qwen2.5:7b**(纯文本模型,专职问答兜底,不涉及视觉多图问题;视频分析已全部由云端 VLM 承担)。
|
||
|
||
### 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 顺序降级,直出结构化 JSON
|
||
- `text`:仅参与智能问答(`usage: qa_fallback`),且为 Gemini/NVIDIA 都失败时的兜底,不参与视觉分析、不参与云端结果融合
|
||
|
||
**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-flash-latest, role=vision) —— 多图单请求直出结构化 JSON
|
||
│ 失败 / 熔断 OPEN / 超时
|
||
▼
|
||
NVIDIA NIM (llama-3.2-11b-vision-instruct, role=vision) —— 逐帧结构化聚合
|
||
│ 失败 / 熔断 OPEN / 超时
|
||
▼
|
||
任务 FAILED(走重试,绝不回退本地 Ollama —— 本地仅负责问答兜底)
|
||
```
|
||
|
||
**格式化阶段(无模型调用,仅数据转换)**:
|
||
|
||
```
|
||
云端成功 provider 直出的结构化 JSON(frame_details / 可选 global_summary / entities_json)
|
||
▼
|
||
format_cloud_result:字段归一化 → 补 source_providers=[provider] / compute_provider=[provider]
|
||
→ 缺失 entities_json 由 frame_details 推导 → 缺失 global_summary 则事实拼接 → schema 校验
|
||
▼
|
||
合法入库结构,随响应返回 NAS 直接落库(本地模型不介入)
|
||
```
|
||
|
||
**智能问答降级链路(chat 场景)**:
|
||
|
||
```
|
||
Gemini (role=vision, 也参与问答)
|
||
│ 失败 / 熔断 OPEN / 超时
|
||
▼
|
||
NVIDIA NIM (role=vision, 也参与问答)
|
||
│ 失败 / 熔断 OPEN / 超时
|
||
▼
|
||
本地 Ollama (qwen2.5:7b, role=text, usage=qa_fallback) —— 仅当两云端都失败才启用
|
||
│ 失败
|
||
▼
|
||
返回"所有模型均不可用"
|
||
```
|
||
|
||
**熔断器策略**(按 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,新架构 v2 —— 仅同步 + 问答):
|
||
|
||
```yaml
|
||
server:
|
||
port: 8000
|
||
database: # MariaDB(unix_socket 优先)
|
||
unix_socket: "/run/mysqld/mysqld10.sock"
|
||
oracle_sync: # 唯一后台线程配置
|
||
base_url: "http://129.146.203.203:5000"
|
||
token: "${ORACLE_SYNC_TOKEN}" # 与 Oracle 端 sync_api.token 一致
|
||
interval_sec: 1800 # 每 30 分钟拉一次增量
|
||
timeout: 120
|
||
chat_handler:
|
||
qa_url: "http://129.146.203.203:5000/api/edge/chat/ask" # 问答统一走 Oracle 编排
|
||
timeout: 120
|
||
```
|
||
|
||
**fam-edge/config/config.yaml**(Oracle,整视频分析 + 同步 + 人物服务):
|
||
|
||
```yaml
|
||
server:
|
||
port: 5000
|
||
gdrive_sync: # rclone 同步落地目录监听
|
||
enabled: true
|
||
local_dir: "/opt/fam-edge/gdrive_videos"
|
||
watch_interval_sec: 30
|
||
camera_name: "客厅"
|
||
parse_start_from_filename: true
|
||
oracle_db:
|
||
path: "/opt/fam-edge/data/oracle.db"
|
||
sync_api:
|
||
token: "${ORACLE_SYNC_TOKEN}" # NAS 拉取鉴权(与 NAS oracle_sync.token 一致)
|
||
person_service:
|
||
schedule_interval_sec: 1800 # 每 30 分钟重新汇总人物
|
||
model: "gemini"
|
||
video_processing:
|
||
max_concurrent: 1 # 单视频串行,避免抢占云端配额
|
||
timeout: 900
|
||
vision_order: ["gemini", "nvidia"]
|
||
models:
|
||
- provider: "gemini"
|
||
role: "vision"
|
||
model_name: "gemini-flash-latest"
|
||
api_key: "${GEMINI_API_KEY}"
|
||
timeout: 600
|
||
- provider: "nvidia"
|
||
role: "vision"
|
||
model_name: "nvidia/nemotron-nano-12b-v2-vl" # 整视频 video_url 输入(内部采样帧)
|
||
base_url: "https://integrate.api.nvidia.com/v1"
|
||
api_key: "${NVIDIA_API_KEY}"
|
||
timeout: 600
|
||
- provider: "ollama"
|
||
role: "text"
|
||
usage: "qa_fallback" # 仅智能问答兜底,不参与视频
|
||
model_name: "qwen2.5:7b"
|
||
``` api_key: "${NVIDIA_API_KEY}"
|
||
timeout: 20
|
||
circuit_breaker:
|
||
enabled: true
|
||
threshold: 3
|
||
cooldown: 600
|
||
|
||
# 3. 本地 Ollama(纯文本,仅 Q&A 兜底,不参与视觉分析、不参与云端结果融合)
|
||
- provider: "ollama"
|
||
role: "text" # 仅问答兜底
|
||
usage: "qa_fallback" # Gemini/NVIDIA 都失败时才启用
|
||
enabled: true
|
||
model_name: "qwen2.5:7b"
|
||
base_url: "http://localhost:11434"
|
||
timeout: 120
|
||
num_predict: 512
|
||
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-flash-latest`(生产配置,v1beta 下 `gemini-1.5-flash` 会 404,故用别名) |
|
||
| 端点 | 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 部署验证:`/api/edge/chat/ask` 实测 provider=nvidia,Gemini 超时后 NVIDIA 兜底成功;单图连通性验证脚本见 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 全端点测试通过
|
||
- FAM-UI 部署(Streamlit 1.61.1)
|
||
- 真实视频性能基准(30min/360MB → 929s)
|
||
- **E2E 全链路打通**:task 289 → SUCCESS,monitor_events/event_details 落库正确
|
||
- 可靠性加固:僵尸任务回收、文件日志、datetime 空值兜底、超时按实测调整
|
||
- **架构重构(移除本地融合,云端直出直存)**:
|
||
- 视频分析链路:云端 VLM(Gemini `gemini-flash-latest` / NVIDIA NIM `llama-3.2-11b-vision-instruct`)直出结构化 JSON → Edge `format_cloud_result` 格式化/校验(无模型调用)→ 直存 NAS DB;本地 Ollama 不再参与视频摘要与融合(`run_text_fusion` 已移除)
|
||
- 智能问答链路:新增 `chat()` 方法,`run_qa` 按 Gemini → NVIDIA → 本地 Ollama 降级编排;端点 `/api/edge/chat/ask`;NAS Chat-Handler 经 Edge 编排(`qa_url`),不再直连 Ollama
|
||
- 生产目录已切回 `/volume1/surveillance/Generic_ONVIF-001`,285 历史视频 forward-only 占位跳过
|
||
- **双端部署验证通过**(2026-08-20):Oracle Edge 7 文件部署 + 重启,`/api/edge/chat/ask` 实测 provider=nvidia(Gemini 超时→NVIDIA 兜底成功);NAS chat_handler + config 手术式更新 + HUP 重载,插入临时上下文实测 NAS→Edge 编排链路 43s 返回并落库(测试数据已清理);Edge 日志确认 task 294(360MB)以新架构处理中、三模型健康检查全通过
|
||
|
||
详细进度见 `PROGRESS.md`。
|
||
|
||
### v1.1 待办
|
||
|
||
| # | 任务 | 优先级 |
|
||
|---|------|--------|
|
||
| 1 | 单元测试(JSON parser / circuit breaker / schema 校验) | 中 |
|
||
| 2 | Tailscale 修复(NAS userspace 模式升级,流量不走公网) | 低 |
|
||
| 3 | daily_summaries 每日摘要 | 低 |
|
||
|
||
---
|
||
|
||
文档结束
|
||
|