Files
sentinel-home-ai/fam-edge/config/config.yaml
ericwyuan 9bba7b7e14 fix(chat): 流式问答中文乱码 + 问答模型链换成专用文字大模型
## 乱码修复
Gemini 流式接口 resp.iter_lines(decode_unicode=True) 没有显式设置
resp.encoding,requests 自己猜编码猜错了(Gemini 的 SSE 响应 Content-Type
不带 charset 参数),导致中文变成典型的 UTF-8 被当 Latin-1 解码的乱码。
fam-core 转发这一跳的 requests.iter_lines 调用有同样的坑,一并修了。
两处响应的 Content-Type 都补上 charset=utf-8,减少下游再犯同样错误的机会。
顺手用 latin-1/utf-8 往返把 NAS chat_history 里已经存了乱码的 3 条历史记录
恢复成正常文字。

## 问答模型链重新设计(跟视频分析完全独立)
用户要求问答不再用 flash/omni,优先用 NVIDIA 免费文字模型里上下文最大的几个,
不行再退 Gemini 非 flash 文字模型,最后本地 Ollama 兜底。

- base_adapter 新增 usage 字段(纯标记,不影响行为)
- qa.py 的 QAOrchestrator 只挑 role='text' 的适配器参与问答(按 config 出现
  顺序决定降级顺序),role='vision' 的视频分析适配器(gemini-flash-latest /
  nvidia omni)不再参与问答,两条链路彻底解耦
- 新增问答专用 NVIDIA 文字模型链(同一适配器内部 model_chain 降级):
  nemotron-3-ultra-550b-a55b(1M上下文/561B) -> nemotron-3-super-120b-a12b
  (1M上下文/124B) -> openai/gpt-oss-120b(131K上下文/117B)。三个都用真实
  API 调用验证过当前账号可访问;同时验证过 llama-3.1-70b-instruct 和
  nemotron-super-49b-v1.5 都收到"08/25/2026 起弃用"通知,排除;
  nemotron-ultra-253b/mistral-large-2/nemotron-4-340b/kimi-k2.6 在目录里
  能看到但实测调用返回 404"账号无权限",排除
- 新增问答专用 Gemini 非 flash 文字模型:gemini-pro-latest -> gemini-2.5-pro
  (1M 上下文,复用视频分析同一批多 key 轮换)
- /api/edge/chat/ask(/stream) 默认 max_tokens 从 1024 提到 3072:新链路
  优先用的都是"推理"模型,回答前会先吐一段思考过程,1024 经常在思考阶段
  就被截断,用户永远看不到真正答案

新增 test_qa.py::test_init_only_keeps_text_role_adapters_in_config_order
验证角色过滤+顺序正确。

本地检查过 Ollama 服务本身:systemd enabled+running(8h 正常运行,
qwen2.5:7b 已加载),并非没启动——用户观察到的现象另有原因。
2026-08-23 07:46:48 +08:00

244 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FAM-Edge 配置文件 (Oracle 端) - 新架构 v2
#
# 新架构2026-08-21 重构):
# 1. 不再切片/抽帧:整视频直传云端 VLMGemini 用 Files APINVIDIA 用整视频 video_url
# 2. 视频来源rclone 从 Google 硬盘实时同步到本地 local_dir监听目录处理新视频
# 3. Oracle 自建 SQLite 库存储所有视频摘要/事件/人物,并对外提供同步接口供 NAS 拉取
# 4. 独立 person_service 汇总全量人物 -> LLM 合并为规范人物表 -> 回灌视频提示
# 5. NAS 仅作管理后台,每 30 分钟从甲骨文拉增量镜像到本地 MariaDB
# Oracle 端 HTTP 服务
server:
host: "0.0.0.0"
port: 5000
# Google 硬盘同步rclone 负责同步落地,本段仅描述监听行为)
gdrive_sync:
enabled: true
local_dir: "/opt/fam-edge/gdrive_videos" # rclone 同步落地目录video_processing 监听此目录)
watch_interval_sec: 30 # 监听新视频的轮询间隔
camera_name: "客厅" # 摄像头名称(注入视频提示)
# 文件名解析开始时间:监控文件名含时间戳时使用(如 2026-08-21_081500.mp4
parse_start_from_filename: true
# Oracle 本地库(视频摘要/事件/人物)
oracle_db:
path: "/opt/fam-edge/data/oracle.db"
# NAS 拉取同步接口鉴权 token与 NAS oracle_sync.token 一致,走 .env不明文入库
sync_api:
token: "${ORACLE_SYNC_TOKEN}"
# 人物识别服务2026-08-22 停用):已被闭集识别 person_identifier 取代
# (汤圆/媳妇走性别年龄规则免费识别,爷爷/爸爸走视觉大模型比对参考图,见
# person_identifier 配置块)。原 LLM 合并逻辑不可靠(用户原话"现在的识别全是
# 错的"),继续跑只会用不可靠的猜测持续覆盖 people 表的 canonical_name跟新
# 系统的结果打架——停用,不删除代码(关键冲突检测逻辑 _features_conflict 仍
# 保留供参考/未来复用)。
person_service:
enabled: false
schedule_interval_sec: 1800
model: "gemini"
# 视频处理
video_processing:
max_concurrent: 1 # 消费者线程数(串行处理,避免云端并发超额)
timeout: 900 # 兜底单视频分析超时
timeout_multiplier: 2 # 模型消费超时倍数:在 models[i].timeout 原值上 ×2大视频上传+分析耗时)
max_retries: 10 # 单视频失败最大重试次数(配额/过载等瞬时故障给足重试机会)
retry_interval_sec: 3600 # 失败重试最小间隔:距上次失败 ≥1h 才重新入队,等配额恢复
file_validate: true # 登记入队前用 ffprobe 校验文件可解码;失败标记 invalid 不入队
stable_window_sec: 60 # 文件 mtime 稳定窗口写入中rclone 同步未完成)的文件跳过本轮
# 降级顺序:先 gemini 整视频,失败再 nvidia 整视频;两者都失败 -> 标记 failed
vision_order: ["gemini", "nvidia"]
# 运动事件2026-08-22 运动事件驱动架构):
# - NAS 端 fam-core MotionNotifier 轮询 SS 事件后POST 推送到甲骨文
# /api/ss/motion落库 ss_motion_eventsstart_time/duration 为 Unix epoch
# 并定期用空 events 调接口当心跳,证明推送链路存活。
# - video_processor 不再整段分析:整段素材按 ss_motion_events 中【已结束】的
# 运动事件分割成运动片段motion_segment 配置),只分析运动片段。
dsm_motion_prefilter:
max_heartbeat_age_sec: 900 # 心跳超过 15 分钟没更新 -> 判定推送链路可能已死
# 运动片段分割运动事件驱动架构2026-08-22 新增):
# 整段素材视频rclone 同步落地)按 ss_motion_events 分割成运动片段再分析。
# 只分割已结束事件start_time+duration 落在当前时刻附近),进行中的事件
# 等结束后的下一轮再分割;素材保持 pending 直到窗口内事件全部结束。
motion_segment:
clips_dir: "/opt/fam-edge/motion_clips" # 分割产物目录
keep_audio: true # 保留音频pcm_alaw -> aac 64k 转码false 则 -an 去音频
min_duration_sec: 1 # 短于该时长的事件不分割
unfinished_grace_sec: 10 # start_time+duration 距当前 ≤ 该秒视为"已结束"容差
# 闭集人物识别2026-08-22 新增,家里固定 4 人:爷爷/爸爸/媳妇/汤圆):
# 原来靠大模型自己编的"人物A/B/C"临时 uid + 文字特征描述跨视频合并,验证下来
# 不可靠(用户原话"现在的识别全是错的")。人脸向量方案也验证过,家庭监控这种
# 大广角/远距离画面下同人内部相似度经常比不同人还低,此路不通。
# 现在改成:汤圆(幼儿/儿童特征)/媳妇(唯一成年女性) 直接用 Gemini 已产出的
# 性别/年龄字段判断,免费且验证下来接近 100% 准;爷爷/爸爸(两个成年男性,纯
# 外观规则/人脸向量都区分不开) 改用视觉大模型"看参考图比对"——NVIDIA 实测
# 6/6 全对且配额与主分析链路完全独立设为优先Gemini flash-lite(7/8) 兜底。
# 参考图目录结构:{ref_dir}/爷爷/*.jpg、{ref_dir}/爸爸/*.jpg已用确认过身份的
# 历史截图种好,见 PROGRESS.md 记录)。
person_identifier:
enabled: true
ref_dir: "/opt/fam-edge/data/person_refs"
max_ref_per_person: 6
# 连续两次分类调用之间的最小间隔(不分 provider 统一限速):正常处理新片段时
# 调用本来就稀疏,这个主要是给历史数据批量回填用的,避免短时间内密集调用打爆配额
min_call_interval_sec: 2
nvidia:
api_key: "${NVIDIA_API_KEY}"
model_name: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning"
fallback_models: [] # 目前只验证过这一个能用的 NVIDIA 视觉模型,先留好扩展位
timeout: 60
max_retries: 3 # 每个模型对瞬时故障429/5xx/超时)最多重试几次(含首次)
retry_backoff_sec: 3 # 重试前基础等待秒数指数退避3s -> 6s -> 12s
gemini:
api_key: "${GEMINI_API_KEY}"
extra_api_keys: ["${GEMINI_API_KEY_2}", "${GEMINI_API_KEY_3}", "${GEMINI_API_KEY_4}"]
model_name: "gemini-flash-lite-latest"
timeout: 60
max_retries: 2
retry_backoff_sec: 3
# 智能问答降级链与视频分析独立Gemini -> NVIDIA -> 本地 Ollama
models:
- provider: "gemini"
role: "vision"
enabled: true
model_name: "gemini-flash-latest" # 主模型(每日免费配额 20 请求,按模型独立)
fallback_models:
- "gemini-flash-lite-latest"
api_key: "${GEMINI_API_KEY}"
# 多 Key 轮换(各自独立 Google Cloud 项目,配额互不影响):主 key 配额用尽时
# 依次尝试这些 key每个 key 都会重新走一遍模型 fallback 链。key 本身放 .env
# 这里只放环境变量名,不直接写密钥。
extra_api_keys:
- "${GEMINI_API_KEY_2}"
- "${GEMINI_API_KEY_3}"
- "${GEMINI_API_KEY_4}"
# 每个 key 对应的 Google Cloud 项目名(顺序对应上面 api_key + extra_api_keys
# 用于模型统计页展示是哪个项目在跑,不填则退回 "key1/key2/..."
key_labels:
- "智能摄像头-1"
- "智能摄像头-2"
- "智能摄像头-3"
- "智能摄像头-4"
timeout: 600
# 问答专用超时(跟上面视频分析的 timeout 分开):用户在等交互式回答,一个
# key/模型卡住不该等 10 分钟,超时要短,快速降级到下一个 key/模型/provider
chat_timeout: 20
# 模型级独立超时(最终值,不参与编排层 ×2 放大)
# gemini-flash-lite 实测 ~22-34s按用户要求放宽至 8 分钟480s避免大视频/排队时过早切断
model_timeouts:
"gemini-flash-lite-latest": 480
circuit_breaker:
enabled: true
threshold: 5
cooldown: 300
- provider: "nvidia"
role: "vision"
enabled: true
# 模型可用性实测记录2026-08-21用真实短视频逐个探测 chat.completions 接口):
# omni本行 model_namevideo_url 只认 base64 data URINVCF asset_id 引用
# 方式对它直接 500"Only base64 data URLs are supported for now")——本适配器
# 已改为 base64 内嵌整段视频,见 max_base64_mb。确认可用真实返回结构化 JSON
# nemotron-nano-12b-v2-vl走 asset_id 引用需要 NVCF-ASSET-DIR/
# NVCF-FUNCTION-ASSET-IDS 请求头,这两个头的值由 NVCF 服务端按内部路径生成,
# 客户端传什么都 400 "Invalid NVCF-ASSET-DIR",标准 OpenAI 兼容调用打不通,已移除。
# meta/llama-3.2-11b-vision-instruct明确不支持视频输入
# "At most 0 video(s) may be provided"),只能单图,已移除。
# base64 方案受请求体大小限制(实测约 25MB 上限),真实监控视频压缩后通常在
# 20MB 上下,超过 max_base64_mb 直接跳过(不做注定失败的慢速编码),不是本地
# 故意限制过窄——这是当前唯一能打通的 NVIDIA 视频理解路径。
model_name: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning"
fallback_models: []
base_url: "https://integrate.api.nvidia.com/v1"
api_key: "${NVIDIA_API_KEY}"
timeout: 600
chat_timeout: 20 # 问答专用超时,跟视频分析的 timeout 分开
max_base64_mb: 20 # 超过此大小直接跳过 NVIDIA不做注定失败的编码+上传
switch_interval_sec: 5 # 模型切换间隔:一个失败后等待再试下一个(未来加模型时用)
model_timeouts: # 模型级独立超时(最终值,不参与 ×2
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": 300
circuit_breaker:
enabled: true
threshold: 5
cooldown: 300
# 问答专用 NVIDIA 文字模型链2026-08-23 新增,跟上面视频分析用的 omni 模型
# 完全独立):用户要求问答不用 flash/omni优先找 NVIDIA 免费文字模型里上下文
# 最大的几个。实测(2026-08-23)在当前账号可用、非 deprecated 的候选里:
# nemotron-3-ultra-550b-a55b: 1M 上下文561B最强free endpoint 已验证可调
# nemotron-3-super-120b-a12b: 1M 上下文124B同为 Nemotron-3 系列备份
# openai/gpt-oss-120b: 131K 上下文117B不同厂商备份Nemotron 系列整体
# 出问题时的多样性兜底)
# 淘汰原因记录meta/llama-3.1-70b-instruct、nvidia/llama-3.3-nemotron-
# super-49b-v1.5 均已收到 "will be deprecated on 08/25/2026" 通知,不用;
# nvidia/llama-3.1-nemotron-ultra-253b-v1、mistralai/mistral-large-2-instruct、
# nvidia/nemotron-4-340b-instruct、moonshotai/kimi-k2.6 在 /v1/models 目录
# 里能看到,但实测调用 chat.completions 返回 404 "Not found for account"
# (免费层没有这些模型的调用权限,文档列出不代表能调)。
- provider: "nvidia"
role: "text"
usage: "qa_primary"
enabled: true
model_name: "nvidia/nemotron-3-ultra-550b-a55b"
fallback_models:
- "nvidia/nemotron-3-super-120b-a12b"
- "openai/gpt-oss-120b"
base_url: "https://integrate.api.nvidia.com/v1"
api_key: "${NVIDIA_API_KEY}"
timeout: 600
# 这几个都是"推理"模型,回答前会先输出一段思考过程再给最终答案,比普通模型
# 更费 token/更慢,问答超时给宽松一点(不是简单文字模型那种 20s 就该出结果)
chat_timeout: 45
switch_interval_sec: 3
circuit_breaker:
enabled: true
threshold: 5
cooldown: 300
# 问答专用 Gemini 非 flash 文字模型2026-08-23 新增):用户明确要求问答不用
# flash这里走 gemini-pro-latest1M 上下文,跟 gemini-flash-latest 同样的
# "-latest" 别名习惯,自动跟最新 pro 版本),失败再退 gemini-2.5-pro。
# key 复用视频分析同一批(各自独立 Google Cloud 项目,配额互不影响)。
- provider: "gemini"
role: "text"
usage: "qa_primary"
enabled: true
model_name: "gemini-pro-latest"
fallback_models:
- "gemini-2.5-pro"
api_key: "${GEMINI_API_KEY}"
extra_api_keys:
- "${GEMINI_API_KEY_2}"
- "${GEMINI_API_KEY_3}"
- "${GEMINI_API_KEY_4}"
key_labels:
- "智能摄像头-1"
- "智能摄像头-2"
- "智能摄像头-3"
- "智能摄像头-4"
timeout: 60
chat_timeout: 30
circuit_breaker:
enabled: true
threshold: 5
cooldown: 300
# 本地模型:纯文本 qwen2.5:7b仅参与智能问答兜底
- provider: "ollama"
role: "text"
usage: "qa_fallback"
enabled: true
model_name: "qwen2.5:7b"
base_url: "http://localhost:11434"
timeout: 120
num_predict: 512
circuit_breaker:
enabled: false