ericwyuan 795e8acc6c fix: 时区统一北京时区 — Edge(UTC机器)三处时间处理修复
背景: Oracle Edge 机器为 UTC 时区,NAS 与视频均为北京时间,
网页要求统一北京时区。排查结论: NAS 端(北京时间)与 UI(直读
MariaDB)均正确,问题集中在 Edge 端三处。

1. queue_manager: 7 处 datetime('now','localtime') 在 UTC 机器上
   写入 UTC 时间(比北京慢8h),全部改为 datetime('now','+8 hours')
2. preprocessor.compute_timestamps: event_start_time 缺失时 fallback
   datetime.now() 用了 Edge 本地时间(UTC),改为北京时间
   datetime.now(timezone(+8h));带时区的 ISO 输入统一转北京时间
3. nvidia_adapter._ts_to_seconds: 不支持生产格式
   'YYYY-MM-DD HH:MM:SS'(split后int抛ValueError全部返回-1),
   导致集锦视频永远为空、视频模式永远降级逐帧——上一轮引入的
   bug,测试用 HH:MM:SS 格式未暴露。现支持两种格式;drawtext
   标签与提示词同步为完整时间戳说明

数据修正: event 17(task 298 手动测试缺 event_start_time)的
frame_timestamp 全为 UTC,按视频文件名真实时间(04:34:10)重算;
Edge 队列存量时间戳 +8h。

验证: 单测三例通过(生产格式解析/fallback北京时间/ISO带时区转换);
task 41 新代码正确写入北京时间 17:36:52。
2026-08-20 17:41:59 +08:00

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-CoreNAS 端单进程Task-Scheduler / Dispatcher / Event-Receiver / Chat-Handler / Member-Manager / Video-Server 六个子模块
  • FAM-EdgeOracle 端单进程):接收视频上传 → FFmpeg 快速抽帧 → OpenCV 关键帧筛选 → 云端 VLM 视觉分析Gemini/NVIDIA NIM直出结构化 JSON → Edge 仅做格式化/校验 → 结果同步返回 全链路(本地模型不参与视频分析
  • FAM-UINAS 端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"落库,用户命名后批量回溯更新历史,后续分析直接用真名

1.2 不在首期范围(推迟 v1.1+

  • Nginx 静态服务Video-Server 用 Flask send_from_directory 替代,且推送模式下已不再必需)
  • 资源保护策略 BCPU/内存过载返回 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 + 融合 3min30min/360MB 视频实测 929s 已切换到云端 VLM 直出结构化 JSON无本地融合步骤吞吐瓶颈转移至云端调用延迟整体明显改善
Tailscale 端口不通 NAS tailscaled 以 userspace 模式运行(无 TUN 网卡Oracle 无法反向访问 NAS当前 NAS→Oracle 走公网 IP 推送模式已规避反向访问,但流量走公网
历史视频积压(已处理) 正式目录 /volume1/surveillance/Generic_ONVIF-001 原有约 285 个历史视频(~100GB已切回生产目录并 forward-only 处理:历史任务占位 FAILEDscheduler dedup 自动跳过,仅处理新增视频 已解决;生产目录已生效,新视频正常入链

规划方向(新框架)云端大模型负责视觉识别与结构化输出、本地大模型仅做智能问答兜底。按任务类型分工:

任务 执行方 模型 说明
视觉分析 + 结构化输出(看图识人/动作/衣着 → 直出 JSON 云端 Gemini → NVIDIA NIMfallback 降级) 云端 VLM 直接产出 global_summary / entities_json / frame_detailsEdge 仅做格式化校验后直存 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 Geminigemini-flash-latestAPI Key 已验证,支持多图单请求,视觉 + 问答均参与)
  • NVIDIA NIMmeta/llama-3.2-11b-vision-instructOpenAI 兼容 API云端 GPU 推理,单请求限 1 图故逐帧调用;视觉 + 问答均参与)
  • 本地 Ollamaqwen2.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:5000HTTP 上传视频。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/pushmultipart 视频,同步分析,结果随响应返回)          │
│   ├─ AI-Orchestrator: 健康检查 → 抽帧 → 选帧 → 压缩 → 云端VLM视觉直出结构化JSON → 格式化校验(无本地融合) │
│   ├─ POST /api/edge/chat/ask问答编排: Gemini→NVIDIA→本地Ollama 兜底)            │
│   ▼                                                                               │
│  云端: Gemini / NVIDIA NIM (视觉直出结构化 + 问答)   本地: Ollama :11434 (qwen2.5:7b, 仅问答兜底) │
└───────────────────────────────────────────────────────────────────────────────────┘

2.3 主链路时序(推送模式)

  1. Scheduler 扫描到新视频(修改时间 > 60s 且大小稳定)→ 写 process_tasksPENDING
  2. Dispatcher 领取 PENDING 任务 → 状态置 PROCESSING → 读本地视频文件multipart POST 到 Edge /api/edge/video/pushpayload 含 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 帧差分析筛选关键帧 → 压缩(长边 ≤ 1024pxJPEG 质量 80
    • d. 云端视觉模型按 orchestrator.modefallback顺序降级Geminitimeout 30s→ NVIDIA NIMtimeout 20s首个直出结构化 JSON 成功的模型即采用,两云端全失败 → 任务 FAILED 走重试(绝不回退本地 Ollama本地模型不参与视频分析
    • e. format_cloud_result 格式化校验(无模型调用):字段归一化、补 source_providers=[provider] / compute_provider=[provider]、缺失 entities_jsonframe_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_events1 条聚合)+ 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.logdaemon 模式下 stdout 不可见)
  • 所有日志带 task_id 作为 trace_id各阶段耗时打 INFO

3. 模块设计

3.1 FAM-CoreNAS 端)

模块 文件 职责
Task-Scheduler scheduler/scheduler.py 60s 轮询视频目录(os.walk 递归,支持 AM/PM 子目录),video_path 去重,稳定文件建 PENDING 任务
Dispatcher dispatcher/dispatcher.py 30s 轮询 PENDINGmultipart 上传视频至 Edge push 端点;收响应后经 apply_success_event 落库;僵尸 PROCESSING 回收;退避重试
Event-Receiver event_receiver/event_receiver.py /api/core/callback/eventwebhook 兼容保留);核心逻辑抽为 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_detailsMariaDB 不支持 $[*] JSON 路径Python 层逐行更新)
Video-Server video_server/video_server.py /media/<path>?token=xxx 静态视频服务(推送模式下主链路不再使用,保留备用)
公共层 db_layer.py / config_loader.py / logger.py PyMySQL 连接unix_socketdatetime 空串归一化 NULL + NOT NULL 列兜底;文件日志

3.2 FAM-EdgeOracle 端)

模块 文件 职责
API-Gateway api_gateway/api_gateway.py POST /api/edge/video/pushmultipart 上传 + 同步分析 + 结果返回);POST /api/edge/video/analyze(旧拉取模式,兼容保留);POST /api/edge/chatOllama 代理);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.modefallback 顺序降级)调度,直出结构化 JSONformat_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/generatenum_predict 可配;role: text, usage: qa_fallback(仅智能问答兜底,不参与视觉分析、不参与融合)
└ GeminiAdapter model_adapters/gemini_adapter.py requests 直调 Google REST :generateContent多图单请求直出结构化 JSONrole: visionchat() 参与问答
└ NvidiaVisionAdapter model_adapters/nvidia_adapter.py 基于 openai SDKNIM 兼容 OpenAI API 规范),base_url=https://integrate.api.nvidia.com/v1api_key${NVIDIA_API_KEY} 展开;逐帧返回结构化单帧 JSON 并聚合为 frame_detailsNIM 限 1 图/请求);health_checkclient.models.list()role: visionchat() 参与问答
Storage-Cleaner storage_cleaner/ finally 删除临时视频与帧图片

3.3 FAM-UINAS 端)

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_aiMariaDB 10.11.11utf8mb4。完整 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_providerJSON 数组,记录本次任务实际成功调用(视觉分析)的云端模型,如 ["gemini"]["nvidia"];本地 Ollama 不参与视频分析,不会出现在该字段
  • event_details.source_providers:该条明细被哪些模型识别到(可能少于 compute_provider
  • 多模型交叉验证:多模型一致 → 可信度高;仅单一模型描述 → source_providers 仅含该模型;冲突 → 多数派为准

4.4 兼容性注意

  • MariaDB 10.11 严格模式:空字符串不能插 DATETIME 列1292 错误)。db_layer._dt_or_none 将空串归一化 NULLevent_end_time NOT NULL 列按 end→start→NOW 兜底;frame_timestamp 空值兜底 NOW
  • MariaDB 不支持 MySQL 的 $[*] JSON 通配路径与 -> 操作符JSON 字段在 Python 层处理

5. API 规范(实际实现)

5.1 FAM-EdgeOracle :5000

POST /api/edge/video/push(主链路,推送模式)

  • 请求:multipart/form-data,字段 video(文件) / task_id / camera_name / event_start_time / known_members_context
  • 处理同步执行完整分析流水线可能耗时数分钟gunicorn timeout 1800
  • 响应200
{
  "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"]}
  ],
  "compute_provider": ["gemini"]
}
  • 失败:{"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/ask — 智能问答编排FAM-Core Chat-Handler 调用):请求 {"prompt"} → 响应 {"answer","provider"};内部按 Gemini → NVIDIA → 本地 Ollama 顺序,仅两云端都失败才用本地兜底 POST /api/edge/chat — Ollama 直连代理(兼容旧调用,保留) GET /health — 服务与模型健康状态(任务处理中可能无响应,单 worker 忙)

5.2 FAM-CoreNAS :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/<path>?token=xxx GET 视频静态服务token 鉴权,推送模式下备用)

5.3 云端结构化输出 JSON Schema

云端 VLM 直接产出结构化 JSON经两道处理入库

  1. 适配器内三层容错解析json_parser.parse_vlm_json):直接 json.loads → 提取 markdown fence ```json ... ``` → 贪婪匹配最大 {...};失败抛 VLMOutputInvalidError
  2. format_cloud_result 归一化/校验(无模型调用):frame_details 必须为非空列表并做字段类型归一化;source_providers 缺失时补为 [provider]compute_provider 置为本次成功 providerentities_json 缺失时由 frame_details 按人物去重推导;global_summary 缺失时格式化拼接生成

任一步骤失败 → 任务 FAILED 走重试。action 由 AI 自由生成无枚举过滤,is_attention_event 由 AI 自行判断。


6. 关键技术

6.1 视频预处理(自适应关键帧)

  • 粗抽候选帧FFmpeg 快速 seek逐帧 ffmpeg -ss <ts> -frames:v 1),替代 fps 滤镜全解码30min 视频从 180s+ 降到 33s6-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

本地 Ollamaqwen2.5:7b纯文本模型不参与视觉分析、不参与云端结果融合。它只在智能问答场景下、且 Gemini 与 NVIDIA 两云端模型都失败时才被启用作为兜底。视觉分析与结构化输出全部由云端模型承担。以下参数作为问答任务的调优依据保留。

参数 依据
OLLAMA_KEEP_ALIVE=-1 模型常驻内存 消除 55s 冷启动(常驻约 4.3GB12GB 内存够用)
num_predict=512 限制生成 token ARM 约 5 tok/s过长生成会拖慢问答响应
视觉/模型 timeout 600s 实测 1024px 帧视觉编码 ~36s + 生成 ~12s/60token问答链路改用 config 中各模型 timeout
gunicornEdge --timeout 1800 同步分析模式,默认 30s 会杀 worker
push_timeoutNAS 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.yamlmodels 数组动态决定,新增模型 = 实现适配器 + 配置加一项,主流程不动。

已实现 / 规划的适配器(按 role 区分职责):

provider 适配器类 role SDK / 协议 状态
ollama OllamaAdapter text requests 直调 REST /api/chat 已实现
gemini GeminiAdapter vision requests 直调 REST :generateContent 待实现
nvidia NvidiaVisionAdapter vision openai SDKNIM 兼容 OpenAI API 规范) 待实现

role 语义

  • vision:参与视觉分析阶段,按 fallback 顺序降级,直出结构化 JSON
  • text:仅参与智能问答(usage: qa_fallback),且为 Gemini/NVIDIA 都失败时的兜底,不参与视觉分析、不参与云端结果融合

NvidiaVisionAdapter 关键实现fam_edge/adapters/nvidia_adapter.py

  • 基于 openai Python SDKbase_url=https://integrate.api.nvidia.com/v1api_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.createtemperature=0.2max_tokens=1024
  • prompt 中文模板:要求按时间顺序分析人物数量/衣着/微动作/互动,重点标注异常高危行为

Orchestrator 调度模式config.yamlorchestrator.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 直出的结构化 JSONframe_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/tagsGemini GET /v1/models?key=...NVIDIA client.models.list()。视觉模型全部不健康返回 503。

多模型标签兼容compute_providerevent_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)smax_retries=3
  • 僵尸任务回收PROCESSING 超过 push_timeout + 120s 自动重置 PENDING应对 fam-core 重启 / Edge 重启 / in-flight 请求丢失)
  • failure_stage ENUMdownload / 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 帧) 820savg 68s/帧0.9 tok/s
总计 929s15.5min

7.3 E2E 验证2026-08-20

task_id=28930s 测试片段)全链路打通:推送 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.pyheadless, :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/UINAS, Python 3.10 venvFlask, Gunicorn, PyMySQL45KB 纯 Python 替代 19MB mysql-connector, PyYAML, requestsFAM-UI 另需 Streamlit + pandas
  • FAM-EdgeOracle, Python 3.8+ venvFlask, Gunicorn, requests, PyYAML, opencv-python, numpy, openaiNVIDIA NIM 兼容 OpenAI API 规范,复用同一 SDKGemini 用 requests 直调 REST不依赖 google-generativeai SDK
  • 系统级FFmpeg两端、Ollama + qwen2.5:7bOracle、MariaDB 10.11NAS

8.3 配置文件要点

fam-core/config/config.yamlNAS生产值

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
chat_handler:
  qa_url: "http://129.146.203.203:5000/api/edge/chat/ask"   # 问答统一走 Edge 编排Gemini→NVIDIA→Ollama
  timeout: 120

fam-edge/config/config.yamlOracle多模型池配置新架构

# 编排调度模式: fallback(顺序降级, 默认) | ensemble(并行交叉验证)
orchestrator:
  mode: "fallback"
  overall_timeout: 600

# 多模型池配置(新框架:本地大模型不参与视频分析,仅智能问答兜底)
# 视频分析链路: 云端 VLM 直出结构化 JSON → Edge format_cloud_result 格式化/校验 → 直存 NAS DB无本地融合
# 智能问答链路: Gemini → NVIDIA → 本地 Ollama仅两云端都失败才启用本地兜底
models:
  # 1. Google Gemini视觉主 + 参与问答)
  - provider: "gemini"
    role: "vision"              # 视觉分析 + 问答vision role 也参与 chat
    enabled: true
    model_name: "gemini-flash-latest"   # v1beta 下 gemini-1.5-flash 会 404
    api_key: "${GEMINI_API_KEY}"
    timeout: 30
    circuit_breaker:
      enabled: true
      threshold: 3
      cooldown: 600

  # 2. NVIDIA NIM 托管 API视觉备 + 参与问答)
  - provider: "nvidia"
    role: "vision"              # 视觉分析 + 问答vision role 也参与 chat
    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纯文本仅 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 环境变量文件):

export GEMINI_API_KEY="AQ.Ab8RN6I0l8hC7hLnNHRY6qOXdch5CTWczDNlS4c1XrneGHipUQ"
export NVIDIA_API_KEY="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"

NVIDIA NIM 单图连通性验证

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. 快速开始

# 1. 初始化数据库NAS
python scripts/init_db.py

# 2. 启动 FAM-CoreNAS
cd fam-core && gunicorn -w 1 -b 0.0.0.0:8000 --timeout 120 src.fam_core.app:app

# 3. 启动 FAM-EdgeOracle
cd fam-edge && gunicorn -w 1 -b 0.0.0.0:5000 --timeout 1800 src.fam_edge.app:app

# 4. 启动 FAM-UINAS
cd fam-ui && streamlit run src/app.py

# 或使用脚本
./scripts/start_core.sh && ./scripts/start_edge.sh && ./scripts/start_ui.sh

10. 服务器访问信息

10.1 Synology NASFAM-Core + FAM-UI + MariaDB

项目
IP 192.168.50.64
SSH 端口 2222scp 禁用,用 stdin 管道传文件)
SSH 用户 / 密码 ericwyuan / iLoveJava5
系统 Synology DS220+ (Geminilake), DSM 7
Tailscale IP 100.70.234.39userspace 模式,端口不通待修)
登录命令 ssh -p 2222 ericwyuan@192.168.50.64

10.2 MariaDBNAS

项目
版本 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 CloudFAM-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 APIGoogle 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 测试可用
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 SDKNIM 兼容 OpenAI API 规范,直接复用)
验证状态 已验证2026-08-20 部署验证:/api/edge/chat/ask 实测 provider=nvidiaGemini 超时后 NVIDIA 兜底成功;单图连通性验证脚本见 8.3

环境变量

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子任务号] 子任务名称 - 完成内容简述
  • Bugfix(模块): 问题简述
  • 性能/文档:perf(...) / docs(...)

禁止:不 commit 直接继续;一次 commit 多个子任务;git push --force;跳过 hooks--no-verify)。

自检清单:任务对应哪一行验收标准?是否已 commit + pushmessage 是否合规?开工前是否 pull --rebase


12. 当前进度与 v1.1 计划

已完成(截至 2026-08-20

  • 全部模块代码 + DDL + 部署脚本NAS/Oracle 双端部署运行
  • 模型基准测试(原 llava-phi3 预热 4.3s PASS已替换为 qwen2.5:7bOllama 常驻内存
  • 架构改为推送模式NAS 上传整段视频 → Edge 同步分析 → 结果随响应返回)
  • 关键帧自适应帧数 + FFmpeg 快速 seek6-8x 提速)
  • FAM-Core API 全端点测试通过
  • FAM-UI 部署Streamlit 1.61.1
  • 真实视频性能基准30min/360MB → 929s
  • E2E 全链路打通task 289 → SUCCESSmonitor_events/event_details 落库正确
  • 可靠性加固僵尸任务回收、文件日志、datetime 空值兜底、超时按实测调整
  • 架构重构(移除本地融合,云端直出直存)
    • 视频分析链路:云端 VLMGemini 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/askNAS Chat-Handler 经 Edge 编排(qa_url),不再直连 Ollama
    • 生产目录已切回 /volume1/surveillance/Generic_ONVIF-001285 历史视频 forward-only 占位跳过
  • 双端部署验证通过2026-08-20Oracle Edge 7 文件部署 + 重启,/api/edge/chat/ask 实测 provider=nvidiaGemini 超时→NVIDIA 兜底成功NAS chat_handler + config 手术式更新 + HUP 重载,插入临时上下文实测 NAS→Edge 编排链路 43s 返回并落库测试数据已清理Edge 日志确认 task 294360MB以新架构处理中、三模型健康检查全通过

详细进度见 PROGRESS.md

v1.1 待办

# 任务 优先级
1 单元测试JSON parser / circuit breaker / schema 校验)
2 Tailscale 修复NAS userspace 模式升级,流量不走公网)
3 daily_summaries 每日摘要

文档结束

Description
No description provided
Readme 3.2 MiB
Languages
Python 86%
Vue 11%
JavaScript 1.4%
Shell 1.2%
CSS 0.3%