[架构重构] 移除本地Ollama融合,云端直出JSON直存DB,Q&A三模型降级

1. 视频摘要链路:云端VLM直出结构化JSON → Edge format_cloud_result格式化校验 → 直存NAS DB(移除run_text_fusion本地融合)
2. 智能问答链路:Gemini→NVIDIA→Ollama降级,新增chat()纯文本问答方法
3. 适配器重构:base/gemini/nvidia/ollama adapter新增chat();gemini多图单请求结构化JSON;nvidia逐帧调用聚合
4. 端点变更:/api/edge/chat → /api/edge/chat/ask,调orchestrator.run_qa()
5. chat_handler改经Edge Q&A编排,不再直连Ollama
6. 配置更新:ollama_url → qa_url,Ollama role注释改为Q&A兜底
7. README同步更新架构描述、拓扑图、时序图、模块表
This commit is contained in:
ericwyuan
2026-08-20 10:21:09 +08:00
parent 486eee4feb
commit babf5b09a9
10 changed files with 503 additions and 267 deletions

View File

@@ -14,11 +14,11 @@
### 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-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 代理调 Oracle 本地 Ollama 纯文本 → 返回回答并写 chat_history
- **AI 对话**:查 event_details 拼上下文 → 经 FAM-Edge 问答编排Gemini → NVIDIA → 本地 Ollama 兜底)生成回答 → 返回并写 chat_history
- **交互式成员命名**VLM 按特征提取"人物A/B/C"落库,用户命名后批量回溯更新历史,后续分析直接用真名
### 1.2 不在首期范围(推迟 v1.1+
@@ -39,19 +39,19 @@
| **Tailscale 端口不通** | NAS tailscaled 以 userspace 模式运行(无 TUN 网卡Oracle 无法反向访问 NAS当前 NAS→Oracle 走公网 IP | 推送模式已规避反向访问,但流量走公网 |
| **历史视频积压** | 正式目录 `/volume1/surveillance/Generic_ONVIF-001` 有约 285 个历史视频(~100GB288 个历史任务已标记 FAILED 避免全量上传 | 切回生产目录前需确认回补策略 |
**规划方向****云端大模型负责视觉识别、甲骨文本地大模型负责文本问答**。按任务类型分工:
**规划方向(新框架)****云端大模型负责视觉识别与结构化输出、本地大模型仅做智能问答兜底**。按任务类型分工:
| 任务 | 执行方 | 模型 | 说明 |
|------|--------|------|------|
| **视觉分析**(看图识人/动作/衣着) | 云端 | Gemini → NVIDIA NIMfallback 降级) | 云端 GPU 推理,支持多图,质量与速度均优于本地 ARM |
| **文本融合**多帧视觉结果 → 结构化 JSON | 本地 | Ollama qwen2.5:7b | 纯文本能力,不涉及图片 |
| **AI 对话**"汤圆今天干嘛了" | 本地 | Ollama qwen2.5:7b | 纯文本问答 |
| **视觉分析 + 结构化输出**(看图识人/动作/衣着 → 直出 JSON | 云端 | Gemini → NVIDIA NIMfallback 降级) | 云端 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-1.5-flash`API Key 已验证,单帧 2~3s 且真正支持多图)
- **NVIDIA NIM**`meta/llama-3.2-11b-vision-instruct`OpenAI 兼容 API云端 GPU 推理,响应快
- **本地 Ollama**qwen2.5:7b**专职文本任务**(融合 + 对话),不再参与视觉分析;视觉链路两云端全失败 → 任务 FAILED 走重试
- **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记录每模型实际执行耗时与成功状态。`analyze_frames` 改为逐帧请求
Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA NIM云端模型直出结构化 JSON 后由 `format_cloud_result` 格式化。问答阶段按 `gemini → nvidia → ollama` 顺序,仅末位本地模型作兜底
---
@@ -97,10 +97,10 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA N
┌──────────────────────── Oracle Cloud (129.146.203.203) ──────────────────────────┐
│ FAM-Edge (Flask :5000, gunicorn --timeout 1800, 单 worker) │
│ ├─ POST /api/edge/video/pushmultipart 视频,同步分析,结果随响应返回) │
│ ├─ AI-Orchestrator: 健康检查 → 抽帧 → 选帧 → 压缩 → 云端VLM视觉 → 本地Ollama文本融合 │
│ ├─ POST /api/edge/chat(代理转发本地 Ollama 纯文本问答)
│ ├─ AI-Orchestrator: 健康检查 → 抽帧 → 选帧 → 压缩 → 云端VLM视觉直出结构化JSON → 格式化校验(无本地融合)
│ ├─ POST /api/edge/chat/ask问答编排: Gemini→NVIDIA→本地Ollama 兜底)
│ ▼ │
│ 云端: Gemini / NVIDIA NIM (视觉) 本地: Ollama :11434 (qwen2.5:7b, 文本融合+对话) │
│ 云端: Gemini / NVIDIA NIM (视觉直出结构化 + 问答) 本地: Ollama :11434 (qwen2.5:7b, 仅问答兜底) │
└───────────────────────────────────────────────────────────────────────────────────┘
```
@@ -112,8 +112,8 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA N
- a. 保存上传视频到临时目录(超时 60s
- b. FFmpeg 快速 seek`-ss <ts> -frames:v 1`)粗抽候选帧,帧数随视频时长自适应
- c. OpenCV MSE 帧差分析筛选关键帧 → 压缩(长边 ≤ 1024pxJPEG 质量 80
- d. 云端视觉模型按 `orchestrator.mode`fallback顺序降级Geminitimeout 15s→ NVIDIA NIMtimeout 20s首个返回非 None 结果即采用,两云端全失败 → 任务 FAILED 走重试(不回退本地 Ollama本地仅负责文本
- e. 本地 Ollama 文本融合(超时 300s输入各帧视觉描述 + 时间戳 + known_members 上下文)→ JSON 结构化结果
- d. 云端视觉模型按 `orchestrator.mode`fallback顺序降级Geminitimeout 30s→ NVIDIA NIMtimeout 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
@@ -135,7 +135,7 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA N
| 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/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 条按小时聚合 |
| 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 列兜底;文件日志 |
@@ -146,11 +146,11 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA N
|------|------|------|
| 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** |
| 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` 删除临时视频与帧图片 |
### 3.3 FAM-UINAS 端)
@@ -195,7 +195,7 @@ chat_history 独立表
### 4.3 compute_provider / source_providers
- `monitor_events.compute_provider`JSON 数组,记录本次任务实际成功调用模型,如 `["ollama"]``["ollama","gemini"]`
- `monitor_events.compute_provider`JSON 数组,记录本次任务实际成功调用**视觉分析**)的云端模型,如 `["gemini"]``["nvidia"]`;本地 Ollama 不参与视频分析,不会出现在该字段
- `event_details.source_providers`:该条明细被哪些模型识别到(可能少于 compute_provider
- 多模型交叉验证:多模型一致 → 可信度高;仅单一模型描述 → source_providers 仅含该模型;冲突 → 多数派为准
@@ -227,9 +227,9 @@ chat_history 独立表
"entities_json": [{"person": "汤圆", "action": "...", "clothing": "..."}],
"frame_details": [
{"frame_index": 1, "frame_timestamp": "...", "person": "...", "action": "...",
"clothing": "...", "is_attention_event": false, "source_providers": ["ollama"]}
"clothing": "...", "is_attention_event": false, "source_providers": ["gemini"]}
],
"compute_provider": ["ollama"]
"compute_provider": ["gemini"]
}
```
@@ -237,7 +237,8 @@ chat_history 独立表
- 429已有任务处理中单并发503全部模型不健康
**POST /api/edge/video/analyze** — 旧拉取模式Edge 拉 video_url + webhook 回调),兼容保留,主链路不再使用
**POST /api/edge/chat**Ollama 聊天代理FAM-Core Chat-Handler 调用)
**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
@@ -270,9 +271,9 @@ chat_history 独立表
- **压缩**:长边 > 1024px 才缩放JPEG 质量 80
- **异常兜底**ffprobe 失败退化为 60s 间隔抽帧;帧差异常退化为等距 5 帧
### 6.2 Ollama 调用ARM CPU 实测调优,专职文本任务
### 6.2 本地 Ollama仅智能问答兜底ARM CPU
本地 Ollamaqwen2.5:7b纯文本模型**不参与视觉分析**,专职承担文本融合与 AI 对话两项纯文本任务。视觉分析全部由云端模型承担。以下参数作为文本任务的调优依据保留。
本地 Ollamaqwen2.5:7b纯文本模型**不参与视觉分析、不参与云端结果融合**。它只在**智能问答**场景下、且 Gemini 与 NVIDIA 两云端模型都失败时才被启用作为兜底。视觉分析与结构化输出全部由云端模型承担。以下参数作为问答任务的调优依据保留。
| 参数 | 值 | 依据 |
|------|-----|------|
@@ -308,8 +309,8 @@ chat_history 独立表
| `nvidia` | `NvidiaVisionAdapter` | **vision** | **openai SDK**NIM 兼容 OpenAI API 规范) | 待实现 |
**role 语义**
- `vision`:参与视觉分析阶段,按 fallback 顺序降级
- `text`:参与文本融合与 AI 对话,不参与视觉分析
- `vision`:参与视觉分析阶段,按 fallback 顺序降级,直出结构化 JSON
- `text`参与智能问答(`usage: qa_fallback`),且为 Gemini/NVIDIA 都失败时的兜底,不参与视觉分析、不参与云端结果融合
**NvidiaVisionAdapter 关键实现**`fam_edge/adapters/nvidia_adapter.py`
@@ -328,24 +329,39 @@ chat_history 独立表
**视觉分析降级链路fallback 模式,仅云端 role=vision 模型)**
```
Gemini (gemini-1.5-flash, role=vision)
Gemini (gemini-flash-latest, role=vision) —— 多图单请求直出结构化 JSON
│ 失败 / 熔断 OPEN / 超时
NVIDIA NIM (llama-3.2-11b-vision-instruct, role=vision)
NVIDIA NIM (llama-3.2-11b-vision-instruct, role=vision) —— 逐帧结构化聚合
│ 失败 / 熔断 OPEN / 超时
任务 FAILED走重试不回退本地 Ollama —— 本地仅负责文本
任务 FAILED走重试不回退本地 Ollama —— 本地仅负责问答兜底
```
**文本阶段链路**
**格式化阶段(无模型调用,仅数据转换)**
```
各帧视觉描述(来自云端成功 provider+ 时间戳 + known_members 上下文
云端成功 provider 直出的结构化 JSONframe_details / 可选 global_summary / entities_json
本地 Ollama (qwen2.5:7b, role=text) 文本融合 → JSON 结构化结果
│ 失败 / 超时
format_cloud_result字段归一化 → 补 source_providers=[provider] / compute_provider=[provider]
→ 缺失 entities_json 由 frame_details 推导 → 缺失 global_summary 则事实拼接 → schema 校验
任务 FAILED走重试
合法入库结构,随响应返回 NAS 直接落库(本地模型不介入
```
**智能问答降级链路chat 场景)**
```
Gemini (role=vision, 也参与问答)
│ 失败 / 熔断 OPEN / 超时
NVIDIA NIM (role=vision, 也参与问答)
│ 失败 / 熔断 OPEN / 超时
本地 Ollama (qwen2.5:7b, role=text, usage=qa_fallback) —— 仅当两云端都失败才启用
│ 失败
返回"所有模型均不可用"
```
**熔断器策略**(按 provider 独立,仅云端模型启用):