fix: NVIDIA single-frame JSON parsing + Gemini timeout/circuit breaker tuning

NVIDIA fix:
- Replace parse_vlm_json (requires full schema: global_summary/entities_json/
  frame_details/compute_provider) with lightweight _parse_single_frame_json
  that only extracts per-frame fields (person/action/clothing/etc)
- Root cause: NVIDIA adapter does per-frame analysis returning single-frame
  JSON, but parse_vlm_json rejected it for missing full-schema fields
- Verified: task 297 → 6/6 frames parsed successfully, first SUCCESS

Gemini + circuit breaker tuning:
- Gemini timeout: 30s → 90s (multi-image vision analysis needs more time)
- NVIDIA timeout: 20s → 30s (per-frame API call)
- Circuit breaker threshold: 3 → 5 (less aggressive tripping)
- Circuit breaker cooldown: 600s → 300s (faster recovery)
This commit is contained in:
ericwyuan
2026-08-20 11:02:26 +08:00
parent 2e43afb6b2
commit 853cb21542
2 changed files with 34 additions and 11 deletions

View File

@@ -54,11 +54,11 @@ models:
enabled: true enabled: true
model_name: "gemini-flash-latest" # v1beta 下 gemini-1.5-flash 会 404 model_name: "gemini-flash-latest" # v1beta 下 gemini-1.5-flash 会 404
api_key: "${GEMINI_API_KEY}" api_key: "${GEMINI_API_KEY}"
timeout: 30 timeout: 90
circuit_breaker: circuit_breaker:
enabled: true enabled: true
threshold: 3 threshold: 5
cooldown: 600 cooldown: 300
- provider: "nvidia" - provider: "nvidia"
role: "vision" role: "vision"
@@ -66,11 +66,11 @@ models:
model_name: "meta/llama-3.2-11b-vision-instruct" model_name: "meta/llama-3.2-11b-vision-instruct"
base_url: "https://integrate.api.nvidia.com/v1" base_url: "https://integrate.api.nvidia.com/v1"
api_key: "${NVIDIA_API_KEY}" api_key: "${NVIDIA_API_KEY}"
timeout: 20 timeout: 30
circuit_breaker: circuit_breaker:
enabled: true enabled: true
threshold: 3 threshold: 5
cooldown: 600 cooldown: 300
# 本地模型:纯文本 qwen2.5:7b仅参与智能问答作为 Gemini/NVIDIA 都失败时的兜底 # 本地模型:纯文本 qwen2.5:7b仅参与智能问答作为 Gemini/NVIDIA 都失败时的兜底
- provider: "ollama" - provider: "ollama"

View File

@@ -10,12 +10,13 @@ SDK: openai (NIM 兼容 OpenAI API 规范)
""" """
import os import os
import base64 import base64
import json
import re
from typing import Dict, List, Optional from typing import Dict, List, Optional
from .base_adapter import BaseModelAdapter from .base_adapter import BaseModelAdapter
from .circuit_breaker import CircuitBreaker from .circuit_breaker import CircuitBreaker
from ..logger import setup_logger from ..logger import setup_logger
from ..ai_orchestrator.json_parser import parse_vlm_json, VLMOutputInvalidError
logger = setup_logger('fam-edge.nvidia_adapter') logger = setup_logger('fam-edge.nvidia_adapter')
@@ -98,6 +99,30 @@ class NvidiaVisionAdapter(BaseModelAdapter):
# NVIDIA 单帧无法跨帧综合 global_summary交由 Edge format_cloud_result 格式化生成 # NVIDIA 单帧无法跨帧综合 global_summary交由 Edge format_cloud_result 格式化生成
return {"frame_details": frame_details} return {"frame_details": frame_details}
def _parse_single_frame_json(self, content: str) -> Optional[dict]:
"""轻量解析单帧 JSON不要求全 schema仅提取字段"""
content = content.strip()
# 直接解析
try:
return json.loads(content)
except json.JSONDecodeError:
pass
# 提取 markdown fence
fence = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', content, re.DOTALL)
if fence:
try:
return json.loads(fence.group(1))
except json.JSONDecodeError:
pass
# 贪婪匹配最大 {...}
brace = re.search(r'\{.*\}', content, re.DOTALL)
if brace:
try:
return json.loads(brace.group(0))
except json.JSONDecodeError:
pass
return None
def _analyze_one_structured(self, path: str, ts: str, idx: int, def _analyze_one_structured(self, path: str, ts: str, idx: int,
known_members: str) -> Optional[Dict]: known_members: str) -> Optional[Dict]:
try: try:
@@ -122,12 +147,10 @@ class NvidiaVisionAdapter(BaseModelAdapter):
content = resp.choices[0].message.content content = resp.choices[0].message.content
if not content: if not content:
return None return None
try: data = self._parse_single_frame_json(content)
data = parse_vlm_json(content) if not data:
except VLMOutputInvalidError:
logger.warning(f"NVIDIA 单帧 JSON 解析失败: {content[:120]}") logger.warning(f"NVIDIA 单帧 JSON 解析失败: {content[:120]}")
return None return None
# 组装统一字段
return { return {
"frame_index": idx, "frame_index": idx,
"frame_timestamp": str(data.get("frame_timestamp", ts)), "frame_timestamp": str(data.get("frame_timestamp", ts)),