[阶段3] FAM-Core 瘦身为管理后台+Oracle-Sync 同步引擎,FAM-UI 改读同步镜像 - 删除 scheduler/dispatcher/poller/event_receiver/video_server,新增 oracle_sync 每30分钟拉增量写 sync_* 镜像表;member_manager/chat_handler 改走同步数据;UI 移除帧图改为事件时间线;DDL 新增 sync_videos/events/people/cursor
This commit is contained in:
341
README.md
341
README.md
@@ -13,13 +13,15 @@
|
||||
|
||||
### 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)直出结构化 JSON → Edge 仅做格式化/校验 → 结果同步返回 全链路(**本地模型不参与视频分析**)
|
||||
- **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 问答编排(Gemini → NVIDIA → 本地 Ollama 兜底)生成回答 → 返回并写 chat_history
|
||||
- **交互式成员命名**:VLM 按特征提取"人物A/B/C"落库,用户命名后批量回溯更新历史,后续分析直接用真名
|
||||
> **新架构 v2(2026-08-21 重构)**:NAS 不再处理视频,仅作管理后台;视频分析全部上云(Oracle)。
|
||||
|
||||
- **FAM-Core**(NAS 端单进程):仅 **Oracle-Sync**(每 30 分钟拉增量镜像)+ **Chat-Handler** + **Member-Manager** 三个子模块,CPU 占用极低
|
||||
- **FAM-Edge**(Oracle 端单进程):rclone 实时同步 Google 硬盘视频 → 监听目录 → **整视频直传云端 VLM**(Gemini 用 Files API / NVIDIA 用整视频 `video_url`,不切片不抽帧)→ 结构化 JSON 落本地 SQLite → 对外提供 `/api/oracle/sync` 增量拉取接口(**本地模型不参与视频分析**)
|
||||
- **FAM-UI**(NAS 端):Streamlit 读本地同步镜像(sync_videos / sync_events / sync_people),事件时间轴 + 人物管理 + AI 对话 + 对话历史 + 统计
|
||||
- **数据库**:Oracle 侧 SQLite(videos/events/people/sync_cursor);NAS 侧 MariaDB 镜像(sync_videos / sync_events / sync_people / sync_cursor)+ chat_history
|
||||
- **数据流向**:Google 硬盘 ──rclone──► 甲骨文本地 ──整视频分析──► Oracle SQLite ──每 30 分钟 NAS 拉取──► NAS MariaDB 镜像 ──► FAM-UI
|
||||
- **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+)
|
||||
|
||||
@@ -70,59 +72,58 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N
|
||||
- Oracle 端 Ollama 端口 11434 不对外暴露,聊天请求经 FAM-Edge `/api/edge/chat` 代理转发
|
||||
- Tailscale 两节点已安装在线,但 NAS tailscaled 为 userspace 模式且防火墙端口不通,暂走公网 IP
|
||||
|
||||
### 2.2 部署拓扑与数据流(推送模式)
|
||||
### 2.2 部署拓扑与数据流(新架构 v2:Oracle 分析 + NAS 镜像)
|
||||
|
||||
```
|
||||
┌─────────────────────────────── 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视觉直出结构化JSON → 格式化校验(无本地融合) │
|
||||
│ ├─ POST /api/edge/chat/ask(问答编排: Gemini→NVIDIA→本地Ollama 兜底) │
|
||||
│ ▼ │
|
||||
│ 云端: Gemini / NVIDIA NIM (视觉直出结构化 + 问答) 本地: Ollama :11434 (qwen2.5:7b, 仅问答兜底) │
|
||||
└───────────────────────────────────────────────────────────────────────────────────┘
|
||||
┌──────────── Google 硬盘 ────────────┐
|
||||
│ oraclenas@...gserviceaccount.com │
|
||||
│ (Cloud Sync 落盘目录) │
|
||||
└──────────────┬───────────────────────┘
|
||||
│ rclone 定时同步(systemd timer)
|
||||
▼
|
||||
┌──────────────────────── Oracle Cloud (129.146.203.203) ────────────────────────┐
|
||||
│ FAM-Edge (Flask :5000) │
|
||||
│ ├─ Watch-Processor: 30s 轮询 /opt/fam-edge/gdrive_videos,新视频串行处理 │
|
||||
│ ├─ Video-Processor: 整视频直传云端 VLM(不切片不抽帧) │
|
||||
│ │ Gemini(Files API) → 失败 NVIDIA(整视频 video_url) → 再失败 FAILED │
|
||||
│ ├─ Person-Service: 汇总人物 → LLM 合并规范名 → 回灌视频提示 │
|
||||
│ ├─ OracleDB (SQLite): videos / events / people / sync_cursor │
|
||||
│ └─ API: /api/oracle/sync (增量拉取) · /api/oracle/people/correct (命名校正) · │
|
||||
│ /api/edge/chat/ask (问答编排 Gemini→NVIDIA→Ollama) │
|
||||
└───────────────────────────────┬───────────────────────────────────────────────┘
|
||||
│ 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.3 主链路时序(推送模式)
|
||||
**网络要点(新架构)**:
|
||||
- NAS → Oracle 仅一条出站 HTTPS/HTTP:`GET /api/oracle/sync`(拉取)与 `POST /api/oracle/people/correct`(命名回推),均走 Oracle 公网 IP:5000,token 鉴权
|
||||
- Oracle Ollama :11434 不对外暴露,问答经 FAM-Edge `/api/edge/chat/ask` 代理
|
||||
- Tailscale 两节点在线但 NAS 无法反向访问 Oracle,故全部走 NAS 主动出站拉取模式
|
||||
|
||||
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 <ts> -frames:v 1`)粗抽候选帧,帧数随视频时长自适应
|
||||
- c. OpenCV MSE 帧差分析筛选关键帧 → 压缩(长边 ≤ 1024px,JPEG 质量 80)
|
||||
- d. 云端视觉模型按 `orchestrator.mode`(fallback)顺序降级:Gemini(timeout 30s)→ NVIDIA NIM(timeout 20s);首个**直出结构化 JSON** 成功的模型即采用,两云端全失败 → 任务 FAILED 走重试(绝不回退本地 Ollama,本地模型不参与视频分析)
|
||||
- e. `format_cloud_result` 格式化校验(无模型调用):字段归一化、补 `source_providers=[provider]` / `compute_provider=[provider]`、缺失 `entities_json` 由 `frame_details` 推导、缺失 `global_summary` 时事实拼接 → 合法入库 schema
|
||||
- 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)
|
||||
### 2.3 主链路时序(新架构 v2)
|
||||
|
||||
1. Google 硬盘新视频 → rclone 定时同步到 Oracle `/opt/fam-edge/gdrive_videos`
|
||||
2. Watch-Processor 轮询发现新文件 → 登记到 Oracle `videos`(pending)
|
||||
3. Video-Processor 串行处理:整视频上传 Gemini Files API(或 NVIDIA 整视频 `video_url`)→ 模型直出 `{global_summary, events[], people_mentioned[]}` → 写 Oracle `videos` + `events` + `people`
|
||||
4. Person-Service 每 30 分钟汇总全量人物 → LLM 合并为规范名 → 更新 `people.canonical_name` → 生成 `known_members_context` 回灌后续视频提示
|
||||
5. NAS Oracle-Sync 每 30 分钟 `GET /api/oracle/sync?since=<cursor>` → upsert 到本地 `sync_*` 镜像表 → 推进 `sync_cursor`
|
||||
6. FAM-UI 读本地镜像展示;用户命名 → `POST /api/oracle/people/correct` 回推 Oracle,下一周期同步生效
|
||||
|
||||
**容错设计**:
|
||||
- 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
|
||||
- Oracle 单视频串行(`max_concurrent=1`)避免多视频抢占云端配额
|
||||
- 视频分析失败(两云端均不可用)标记 `failed`,下一周期 cursor 仍包含它会被重试
|
||||
- NAS 同步失败仅记日志,下一周期(30 分钟)自动重试,不阻塞 UI
|
||||
- fam-core 日志双写:stdout + `fam-core/logs/fam-core.log`
|
||||
|
||||
---
|
||||
|
||||
@@ -130,40 +131,42 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N
|
||||
|
||||
### 3.1 FAM-Core(NAS 端)
|
||||
|
||||
> NAS 不再处理视频,仅作管理后台。唯一常驻后台线程是 Oracle-Sync。
|
||||
|
||||
| 模块 | 文件 | 职责 |
|
||||
|------|------|------|
|
||||
| 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 `/api/edge/chat/ask` 问答编排(Gemini→NVIDIA→本地 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/<path>?token=xxx` 静态视频服务(推送模式下主链路不再使用,保留备用) |
|
||||
| 公共层 | `db_layer.py` / `config_loader.py` / `logger.py` | PyMySQL 连接(unix_socket);datetime 空串归一化 NULL + NOT NULL 列兜底;文件日志 |
|
||||
| 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 并即时拉回本地镜像 |
|
||||
| 公共层 | `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` | `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 顺序降级)调度,**直出结构化 JSON** → `format_cloud_result` 格式化校验(无本地融合)→ JSON schema 校验;`run_qa` 实现问答编排(Gemini→NVIDIA→本地 Ollama 兜底);`process_push_task` 为推送模式入口(不触发 webhook);记录各模型实际执行耗时与成功状态到 `compute_provider` 数组 |
|
||||
| Model-Adapters | `model_adapters/` | `BaseModelAdapter` 抽象基类(`__init__` / `health_check` / `analyze_frames` / `chat` / `get_timeout` / 熔断器实例);`build_adapter` 工厂函数按 `provider` 字段分发实例化;视觉适配器(gemini/nvidia)直出结构化 JSON,文本适配器(ollama)仅智能问答兜底 |
|
||||
| └ OllamaAdapter | `model_adapters/ollama_adapter.py` | requests 直调本地 REST `/api/generate`,`num_predict` 可配;**role: text, usage: qa_fallback**(仅智能问答兜底,不参与视觉分析、不参与融合) |
|
||||
| └ GeminiAdapter | `model_adapters/gemini_adapter.py` | requests 直调 Google REST `:generateContent`,**多图单请求直出结构化 JSON**;**role: vision**;`chat()` 参与问答 |
|
||||
| └ NvidiaVisionAdapter | `model_adapters/nvidia_adapter.py` | **基于 openai SDK**(NIM 兼容 OpenAI API 规范),`base_url=https://integrate.api.nvidia.com/v1`,`api_key` 从 `${NVIDIA_API_KEY}` 展开;**逐帧返回结构化单帧 JSON 并聚合为 frame_details**(NIM 限 1 图/请求);`health_check` 调 `client.models.list()`;**role: vision**;`chat()` 参与问答 |
|
||||
| Storage-Cleaner | `storage_cleaner/` | `finally` 删除临时视频与帧图片 |
|
||||
| API-Gateway | `api_gateway/api_gateway.py` | `GET /api/oracle/sync`(增量拉取,since+token 校验);`POST /api/oracle/people/correct`(命名校正);`POST /api/edge/chat/ask`(问答编排);`GET /health` |
|
||||
| Watch-Processor | `watch_processor.py` | 30s 轮询 rclone 同步落地目录,登记新视频,串行触发 Video-Processor |
|
||||
| Video-Processor | `video_processor.py` | 按 `vision_order` 调适配器 `analyze_video`(整视频);首个成功即落库 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;`get_sync_delta(since)` 增量导出 |
|
||||
| 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`),侧边栏切换页面:
|
||||
Streamlit 应用(`fam-ui/src/app.py`),侧边栏切换页面(均读本地同步镜像):
|
||||
|
||||
| 页面 | 功能 |
|
||||
|------|------|
|
||||
| 📊 事件列表 | 按日期筛选 + 分页(20 条/页)展示 monitor_events |
|
||||
| 👤 成员命名 | 列出未命名人物 + 特征描述,输入真名后调 `/api/member/name` 批量回溯 |
|
||||
| 🕒 事件时间轴 | 视频会话列表(按处理后时间倒序)+ 选中会话的事件时间线(时间点 + 描述 + 人物/关注徽章,无帧图) |
|
||||
| 💬 AI 对话 | 输入框 + 调 `/api/chat/ask`;按 queried_person 预设快捷提问 |
|
||||
| 📜 对话历史 | chat_history 倒序展示 |
|
||||
| 📈 统计图表 | compute_provider 占比(bar_chart) |
|
||||
| 📝 对话历史 | chat_history 倒序展示 |
|
||||
| 👤 人物管理 | 按规范名/标签聚合,命名/合并(回推 Oracle);不再展示帧照片 |
|
||||
| 📈 统计图表 | 模型来源占比 / 关注事件 / 同步状态 |
|
||||
|
||||
---
|
||||
|
||||
@@ -173,36 +176,46 @@ Streamlit 应用(`fam-ui/src/app.py`),侧边栏切换页面:
|
||||
|
||||
### 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`)**
|
||||
|
||||
| 表 | 用途 | 关键字段 |
|
||||
|----|------|---------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `daily_summaries` | 每日摘要(预留) | target_date, summary_text |
|
||||
|
||||
### 4.2 表关系与命名回溯
|
||||
### 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 独立表
|
||||
Oracle: videos (1) ─── (N) events people 独立(label/canonical_name)
|
||||
NAS 镜像: sync_videos (1) ─── (N) sync_events sync_people 独立
|
||||
chat_history 独立表(问答上下文摘要留存)
|
||||
```
|
||||
|
||||
### 4.3 compute_provider / source_providers
|
||||
命名回溯:用户命名某 `label` → `POST /api/oracle/people/correct` 设 `canonical_name`(manual 优先)→ 下一周期同步回 NAS `sync_people`;Oracle `person_service` 用规范名回灌视频提示,后续事件 `person_list_json` 直接带真名。
|
||||
|
||||
- `monitor_events.compute_provider`:JSON 数组,记录本次任务实际成功调用(**视觉分析**)的云端模型,如 `["gemini"]` 或 `["nvidia"]`;本地 Ollama 不参与视频分析,不会出现在该字段
|
||||
- `event_details.source_providers`:该条明细被哪些模型识别到(可能少于 compute_provider)
|
||||
- 多模型交叉验证:多模型一致 → 可信度高;仅单一模型描述 → source_providers 仅含该模型;冲突 → 多数派为准
|
||||
### 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 错误)。`db_layer._dt_or_none` 将空串归一化 NULL;`event_end_time` NOT NULL 列按 end→start→NOW 兜底;`frame_timestamp` 空值兜底 NOW
|
||||
- MariaDB 不支持 MySQL 的 `$[*]` JSON 通配路径与 `->` 操作符,JSON 字段在 Python 层处理
|
||||
- MariaDB 10.11 严格模式:**空字符串不能插 DATETIME 列**(1292 错误)。同步表时间字段统一用 `VARCHAR(32)` 文本存储 Oracle 的 ISO 字符串,规避类型转换问题
|
||||
- MariaDB 不支持 MySQL 的 `$[*]` JSON 通配路径,人物统计按 `person_list_json LIKE '%name%'` 字符串匹配在 Python 层完成
|
||||
|
||||
---
|
||||
|
||||
@@ -210,59 +223,70 @@ chat_history 独立表
|
||||
|
||||
### 5.1 FAM-Edge(Oracle :5000)
|
||||
|
||||
**POST /api/edge/video/push**(主链路,推送模式)
|
||||
**GET /api/oracle/sync**(NAS 每 30 分钟拉增量,新架构主接口)
|
||||
|
||||
- 请求:`multipart/form-data`,字段 `video`(文件) / `task_id` / `camera_name` / `event_start_time` / `known_members_context`
|
||||
- 处理:同步执行完整分析流水线(可能耗时数分钟,gunicorn timeout 1800)
|
||||
- 请求:`?since=<ISO 文本>&token=<ORACLE_SYNC_TOKEN>`(`since` 为空拉全量)
|
||||
- 响应(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": ["gemini"]}
|
||||
"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"}
|
||||
],
|
||||
"compute_provider": ["gemini"]
|
||||
"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"
|
||||
}
|
||||
```
|
||||
|
||||
- 失败:`{"task_id": ..., "status": "failed", "failure_stage": "vlm_visual", "error_message": "..."}`
|
||||
- 429:已有任务处理中(单并发);503:全部模型不健康
|
||||
- 401:token 校验失败
|
||||
|
||||
**POST /api/edge/video/analyze** — 旧拉取模式(Edge 拉 video_url + webhook 回调),兼容保留,主链路不再使用
|
||||
**POST /api/edge/chat/ask** — 智能问答编排(FAM-Core Chat-Handler 调用):请求 `{"prompt"}` → 响应 `{"answer","provider"}`;内部按 Gemini → NVIDIA → 本地 Ollama 顺序,仅两云端都失败才用本地兜底
|
||||
**POST /api/edge/chat** — Ollama 直连代理(兼容旧调用,保留)
|
||||
**GET /health** — 服务与模型健康状态(任务处理中可能无响应,单 worker 忙)
|
||||
**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 | 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/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 | 未命名人物列表(含特征描述、出现次数) |
|
||||
| `/api/member/name` | POST | 命名:`{"abstract_label","real_name","named_by"}` → 批量回溯 event_details/entities_json,返回更新条数 |
|
||||
| `/api/member/list` | GET | 全部成员 |
|
||||
| `/media/<path>?token=xxx` | GET | 视频静态服务(token 鉴权,推送模式下备用) |
|
||||
| `/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/core/callback/event`、`/media/<path>`(视频处理职责已迁移至 Oracle)。
|
||||
|
||||
### 5.3 云端结构化输出 JSON Schema
|
||||
|
||||
云端 VLM 直接产出结构化 JSON,经两道处理入库:
|
||||
整视频直传云端 VLM,模型直接产出结构化 JSON(`analyze_video` 返回):
|
||||
|
||||
1. **适配器内三层容错解析**(`json_parser.parse_vlm_json`):直接 `json.loads` → 提取 markdown fence ` ```json ... ``` ` → 贪婪匹配最大 `{...}`;失败抛 `VLMOutputInvalidError`
|
||||
2. **`format_cloud_result` 归一化/校验**(无模型调用):`frame_details` 必须为非空列表并做字段类型归一化;`source_providers` 缺失时补为 `[provider]`;`compute_provider` 置为本次成功 provider;`entities_json` 缺失时由 `frame_details` 按人物去重推导;`global_summary` 缺失时格式化拼接生成
|
||||
```json
|
||||
{
|
||||
"global_summary": "客厅监控摘要……",
|
||||
"events": [
|
||||
{"timestamp": "00:01:23", "description": "汤圆在客厅玩耍",
|
||||
"people": ["汤圆"], "is_attention_event": false}
|
||||
],
|
||||
"people_mentioned": ["汤圆"]
|
||||
}
|
||||
```
|
||||
|
||||
任一步骤失败 → 任务 FAILED 走重试。`action` 由 AI 自由生成无枚举过滤,`is_attention_event` 由 AI 自行判断。
|
||||
- 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 自行判断
|
||||
|
||||
---
|
||||
|
||||
@@ -436,51 +460,62 @@ task_id=289(30s 测试片段)全链路打通:推送 5.7MB → Edge 分析
|
||||
|
||||
### 8.3 配置文件要点
|
||||
|
||||
**fam-core/config/config.yaml**(NAS,生产值):
|
||||
**fam-core/config/config.yaml**(NAS,新架构 v2 —— 仅同步 + 问答):
|
||||
|
||||
```yaml
|
||||
scheduler:
|
||||
video_dir: "/volume1/surveillance/Generic_ONVIF-001" # 生产目录(YYYYMMDDAM/PM 两级子目录)
|
||||
# 285 个历史视频由占位 FAILED 任务占用路径,scheduler dedup 自动跳过(forward-only 模式)
|
||||
dispatcher:
|
||||
edge_url: "http://129.146.203.203:5000/api/edge/video/push"
|
||||
push_timeout: 1800
|
||||
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" # 问答统一走 Edge 编排(Gemini→NVIDIA→Ollama)
|
||||
qa_url: "http://129.146.203.203:5000/api/edge/chat/ask" # 问答统一走 Oracle 编排
|
||||
timeout: 120
|
||||
```
|
||||
|
||||
**fam-edge/config/config.yaml**(Oracle,多模型池配置,新架构):
|
||||
**fam-edge/config/config.yaml**(Oracle,整视频分析 + 同步 + 人物服务):
|
||||
|
||||
```yaml
|
||||
# 编排调度模式: fallback(顺序降级, 默认) | ensemble(并行交叉验证)
|
||||
orchestrator:
|
||||
mode: "fallback"
|
||||
overall_timeout: 600
|
||||
|
||||
# 多模型池配置(新框架:本地大模型不参与视频分析,仅智能问答兜底)
|
||||
# 视频分析链路: 云端 VLM 直出结构化 JSON → Edge format_cloud_result 格式化/校验 → 直存 NAS DB(无本地融合)
|
||||
# 智能问答链路: Gemini → NVIDIA → 本地 Ollama(仅两云端都失败才启用本地兜底)
|
||||
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:
|
||||
# 1. Google Gemini(视觉主 + 参与问答)
|
||||
- provider: "gemini"
|
||||
role: "vision" # 视觉分析 + 问答(vision role 也参与 chat)
|
||||
enabled: true
|
||||
model_name: "gemini-flash-latest" # v1beta 下 gemini-1.5-flash 会 404
|
||||
role: "vision"
|
||||
model_name: "gemini-flash-latest"
|
||||
api_key: "${GEMINI_API_KEY}"
|
||||
timeout: 30
|
||||
circuit_breaker:
|
||||
enabled: true
|
||||
threshold: 3
|
||||
cooldown: 600
|
||||
|
||||
# 2. NVIDIA NIM 托管 API(视觉备 + 参与问答)
|
||||
timeout: 600
|
||||
- provider: "nvidia"
|
||||
role: "vision" # 视觉分析 + 问答(vision role 也参与 chat)
|
||||
enabled: true
|
||||
model_name: "meta/llama-3.2-11b-vision-instruct" # 或 qwen/qwen2-vl-72b-instruct
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user