Skip to content

Commit 799f9cc

Browse files
author
Ovtcharov
committed
fix(rag): don't let a broken native dep crash every agent import
A broken native dependency under the optional RAG stack — most commonly torchcodec/FFmpeg pulled in transitively by sentence-transformers, but also an arch-mismatched faiss build — raises RuntimeError/OSError at import, not ImportError. The guard in rag/sdk.py only caught ImportError, so the exception escaped and killed the entire import chain: importing gaia.agents.chat (or any agent that transitively imports RAG) died at module load even when RAG was never used, taking `gaia chat` and friends down with it. Broaden the sentence-transformers and faiss guards to treat any import failure as "not installed", and capture the failure reason so the loud, deferred error in RAGSDK._check_dependencies() distinguishes "not installed" (reinstall) from "installed but broken" (fix the underlying native dep, e.g. FFmpeg) instead of misdirecting the user.
1 parent f1533ee commit 799f9cc

2 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/gaia/rag/sdk.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,26 @@
3030
except ImportError:
3131
PdfReader = None
3232

33+
# Not just ImportError: a broken native dependency (e.g. torchcodec/FFmpeg
34+
# pulled in by sentence-transformers, or an arch-mismatched faiss build) raises
35+
# RuntimeError/OSError at import. Treat that the same as "not installed" so a
36+
# bad install can't crash every module that transitively imports RAG; the loud,
37+
# actionable error is deferred to RAGSDK._check_dependencies() at point of use.
38+
# Capture the reason so that error can tell "not installed" from "installed but
39+
# broken" instead of misdirecting the user to reinstall a package they have.
40+
_SENTENCE_TRANSFORMERS_IMPORT_ERROR = None
3341
try:
3442
from sentence_transformers import SentenceTransformer
35-
except ImportError:
43+
except Exception as _e: # pylint: disable=broad-except
3644
SentenceTransformer = None
45+
_SENTENCE_TRANSFORMERS_IMPORT_ERROR = _e
3746

47+
_FAISS_IMPORT_ERROR = None
3848
try:
3949
import faiss
40-
except ImportError:
50+
except Exception as _e: # pylint: disable=broad-except
4151
faiss = None
52+
_FAISS_IMPORT_ERROR = _e
4253

4354
from gaia.chat.sdk import AgentConfig, AgentSDK
4455
from gaia.logger import get_logger
@@ -225,6 +236,23 @@ def _check_dependencies(self):
225236
f"Or install packages directly:\n"
226237
f" uv pip install {' '.join(missing)}\n"
227238
)
239+
# A package that is installed but failed to import (broken native
240+
# deps) needs a different fix than a missing one — name the cause.
241+
broken = []
242+
if SentenceTransformer is None and _SENTENCE_TRANSFORMERS_IMPORT_ERROR:
243+
broken.append(
244+
f" sentence-transformers: {_SENTENCE_TRANSFORMERS_IMPORT_ERROR}"
245+
)
246+
if faiss is None and _FAISS_IMPORT_ERROR:
247+
broken.append(f" faiss: {_FAISS_IMPORT_ERROR}")
248+
if broken:
249+
error_msg += (
250+
"\nThe package(s) below are installed but failed to load — "
251+
"reinstalling won't help until the underlying error is fixed "
252+
"(e.g. a missing FFmpeg for torchcodec):\n"
253+
+ "\n".join(broken)
254+
+ "\n"
255+
)
228256
raise ImportError(error_msg)
229257

230258
def _safe_open(self, file_path: str, mode="rb"):
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright(C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.
2+
# SPDX-License-Identifier: MIT
3+
4+
"""Unit tests for RAG dependency-import guards.
5+
6+
A broken native dependency (e.g. torchcodec/FFmpeg under sentence-transformers,
7+
or an arch-mismatched faiss build) raises ``RuntimeError``/``OSError`` at import
8+
rather than ``ImportError``. The guard in ``gaia.rag.sdk`` must treat that the
9+
same as "not installed" so it cannot crash every module that transitively
10+
imports RAG, while still surfacing a loud, actionable error at point of use.
11+
"""
12+
13+
import importlib
14+
from unittest.mock import patch
15+
16+
import pytest
17+
18+
# Importing the module must NOT raise even when an optional native dep is broken
19+
# in the environment — that is the regression this guard protects against.
20+
sdk = importlib.import_module("gaia.rag.sdk")
21+
22+
23+
def _bare_sdk():
24+
"""An RAGSDK instance without running __init__ (it only needs the method)."""
25+
return sdk.RAGSDK.__new__(sdk.RAGSDK)
26+
27+
28+
def test_module_imports_without_optional_deps():
29+
"""The module is importable regardless of optional-dependency health."""
30+
assert hasattr(sdk, "RAGSDK")
31+
assert hasattr(sdk, "_SENTENCE_TRANSFORMERS_IMPORT_ERROR")
32+
assert hasattr(sdk, "_FAISS_IMPORT_ERROR")
33+
34+
35+
def test_broken_install_reports_actionable_cause():
36+
"""An installed-but-broken dep surfaces the captured cause, not just 'install it'."""
37+
cause = RuntimeError("Could not load libtorchcodec (FFmpeg not found)")
38+
with (
39+
patch.object(sdk, "SentenceTransformer", None),
40+
patch.object(sdk, "_SENTENCE_TRANSFORMERS_IMPORT_ERROR", cause),
41+
):
42+
with pytest.raises(ImportError) as excinfo:
43+
_bare_sdk()._check_dependencies()
44+
msg = str(excinfo.value)
45+
assert "installed but failed to load" in msg
46+
assert "libtorchcodec" in msg # the captured cause is named
47+
48+
49+
def test_genuinely_missing_dep_omits_broken_section():
50+
"""A simply-missing dep gets install instructions, not the broken-load hint."""
51+
with (
52+
patch.object(sdk, "SentenceTransformer", None),
53+
patch.object(sdk, "_SENTENCE_TRANSFORMERS_IMPORT_ERROR", None),
54+
):
55+
with pytest.raises(ImportError) as excinfo:
56+
_bare_sdk()._check_dependencies()
57+
msg = str(excinfo.value)
58+
assert "sentence-transformers" in msg
59+
assert "installed but failed to load" not in msg

0 commit comments

Comments
 (0)