PlanOpticon
fix(diagram): handle Ollama llava returning dicts for relationships llava:latest returns relationship objects like {"source": "A", "destination": "B"} instead of strings like "A -> B: relationship". This caused Pydantic validation to fail on DiagramResult, crashing the entire pipeline. - Normalize relationship entries: convert dicts to "src -> dst: label" strings - Wrap DiagramResult construction in try/except, fall back to screengrab on failure
Commit
54cb35343d57909d075d7d8be8925e4c87d3265b6b234647f2a67ca2f34f0806
Parent
ab8aa49d396db8b…
1 file changed
+37
-11
| --- video_processor/analyzers/diagram_analyzer.py | ||
| +++ video_processor/analyzers/diagram_analyzer.py | ||
| @@ -194,21 +194,47 @@ | ||
| 194 | 194 | try: |
| 195 | 195 | diagram_type = DiagramType(dtype) |
| 196 | 196 | except ValueError: |
| 197 | 197 | diagram_type = DiagramType.unknown |
| 198 | 198 | |
| 199 | - dr = DiagramResult( | |
| 200 | - frame_index=i, | |
| 201 | - diagram_type=diagram_type, | |
| 202 | - confidence=confidence, | |
| 203 | - description=analysis.get("description"), | |
| 204 | - text_content=analysis.get("text_content"), | |
| 205 | - elements=analysis.get("elements") or [], | |
| 206 | - relationships=analysis.get("relationships") or [], | |
| 207 | - mermaid=analysis.get("mermaid"), | |
| 208 | - chart_data=analysis.get("chart_data"), | |
| 209 | - ) | |
| 199 | + # Normalize relationships: llava sometimes returns dicts instead of strings | |
| 200 | + raw_rels = analysis.get("relationships") or [] | |
| 201 | + relationships = [] | |
| 202 | + for rel in raw_rels: | |
| 203 | + if isinstance(rel, str): | |
| 204 | + relationships.append(rel) | |
| 205 | + elif isinstance(rel, dict): | |
| 206 | + src = rel.get("source", rel.get("from", "?")) | |
| 207 | + dst = rel.get("destination", rel.get("to", "?")) | |
| 208 | + label = rel.get("label", rel.get("relationship", "")) | |
| 209 | + relationships.append( | |
| 210 | + f"{src} -> {dst}: {label}" if label else f"{src} -> {dst}" | |
| 211 | + ) | |
| 212 | + else: | |
| 213 | + relationships.append(str(rel)) | |
| 214 | + | |
| 215 | + try: | |
| 216 | + dr = DiagramResult( | |
| 217 | + frame_index=i, | |
| 218 | + diagram_type=diagram_type, | |
| 219 | + confidence=confidence, | |
| 220 | + description=analysis.get("description"), | |
| 221 | + text_content=analysis.get("text_content"), | |
| 222 | + elements=analysis.get("elements") or [], | |
| 223 | + relationships=relationships, | |
| 224 | + mermaid=analysis.get("mermaid"), | |
| 225 | + chart_data=analysis.get("chart_data"), | |
| 226 | + ) | |
| 227 | + except Exception as e: | |
| 228 | + logger.warning( | |
| 229 | + f"DiagramResult validation failed for frame {i}: {e}, " | |
| 230 | + "falling back to screengrab" | |
| 231 | + ) | |
| 232 | + capture = self._save_screengrab(fp, i, capture_idx, captures_dir, confidence) | |
| 233 | + captures.append(capture) | |
| 234 | + capture_idx += 1 | |
| 235 | + continue | |
| 210 | 236 | |
| 211 | 237 | # Save outputs (story 3.4) |
| 212 | 238 | if diagrams_dir: |
| 213 | 239 | diagrams_dir.mkdir(parents=True, exist_ok=True) |
| 214 | 240 | prefix = f"diagram_{diagram_idx}" |
| 215 | 241 |
| --- video_processor/analyzers/diagram_analyzer.py | |
| +++ video_processor/analyzers/diagram_analyzer.py | |
| @@ -194,21 +194,47 @@ | |
| 194 | try: |
| 195 | diagram_type = DiagramType(dtype) |
| 196 | except ValueError: |
| 197 | diagram_type = DiagramType.unknown |
| 198 | |
| 199 | dr = DiagramResult( |
| 200 | frame_index=i, |
| 201 | diagram_type=diagram_type, |
| 202 | confidence=confidence, |
| 203 | description=analysis.get("description"), |
| 204 | text_content=analysis.get("text_content"), |
| 205 | elements=analysis.get("elements") or [], |
| 206 | relationships=analysis.get("relationships") or [], |
| 207 | mermaid=analysis.get("mermaid"), |
| 208 | chart_data=analysis.get("chart_data"), |
| 209 | ) |
| 210 | |
| 211 | # Save outputs (story 3.4) |
| 212 | if diagrams_dir: |
| 213 | diagrams_dir.mkdir(parents=True, exist_ok=True) |
| 214 | prefix = f"diagram_{diagram_idx}" |
| 215 |
| --- video_processor/analyzers/diagram_analyzer.py | |
| +++ video_processor/analyzers/diagram_analyzer.py | |
| @@ -194,21 +194,47 @@ | |
| 194 | try: |
| 195 | diagram_type = DiagramType(dtype) |
| 196 | except ValueError: |
| 197 | diagram_type = DiagramType.unknown |
| 198 | |
| 199 | # Normalize relationships: llava sometimes returns dicts instead of strings |
| 200 | raw_rels = analysis.get("relationships") or [] |
| 201 | relationships = [] |
| 202 | for rel in raw_rels: |
| 203 | if isinstance(rel, str): |
| 204 | relationships.append(rel) |
| 205 | elif isinstance(rel, dict): |
| 206 | src = rel.get("source", rel.get("from", "?")) |
| 207 | dst = rel.get("destination", rel.get("to", "?")) |
| 208 | label = rel.get("label", rel.get("relationship", "")) |
| 209 | relationships.append( |
| 210 | f"{src} -> {dst}: {label}" if label else f"{src} -> {dst}" |
| 211 | ) |
| 212 | else: |
| 213 | relationships.append(str(rel)) |
| 214 | |
| 215 | try: |
| 216 | dr = DiagramResult( |
| 217 | frame_index=i, |
| 218 | diagram_type=diagram_type, |
| 219 | confidence=confidence, |
| 220 | description=analysis.get("description"), |
| 221 | text_content=analysis.get("text_content"), |
| 222 | elements=analysis.get("elements") or [], |
| 223 | relationships=relationships, |
| 224 | mermaid=analysis.get("mermaid"), |
| 225 | chart_data=analysis.get("chart_data"), |
| 226 | ) |
| 227 | except Exception as e: |
| 228 | logger.warning( |
| 229 | f"DiagramResult validation failed for frame {i}: {e}, " |
| 230 | "falling back to screengrab" |
| 231 | ) |
| 232 | capture = self._save_screengrab(fp, i, capture_idx, captures_dir, confidence) |
| 233 | captures.append(capture) |
| 234 | capture_idx += 1 |
| 235 | continue |
| 236 | |
| 237 | # Save outputs (story 3.4) |
| 238 | if diagrams_dir: |
| 239 | diagrams_dir.mkdir(parents=True, exist_ok=True) |
| 240 | prefix = f"diagram_{diagram_idx}" |
| 241 |