Skip to content

Commit 7c8a106

Browse files
authored
tests: add unit tests for Nvidia integration (#3162)
1 parent a987fe2 commit 7c8a106

9 files changed

Lines changed: 205 additions & 5 deletions

File tree

integrations/nvidia/src/haystack_integrations/components/embedders/nvidia/document_embedder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def run(self, documents: list[Document]) -> dict[str, list[Document] | dict[str,
253253
if not self._initialized:
254254
self.warm_up()
255255

256-
elif not isinstance(documents, list) or (documents and not isinstance(documents[0], Document)):
256+
if not isinstance(documents, list) or (documents and not isinstance(documents[0], Document)):
257257
msg = (
258258
"NvidiaDocumentEmbedder expects a list of Documents as input."
259259
"In case you want to embed a string, please use the NvidiaTextEmbedder."

integrations/nvidia/src/haystack_integrations/components/embedders/nvidia/text_embedder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,13 @@ def run(self, text: str) -> dict[str, list[float] | dict[str, Any]]:
205205
if not self._initialized:
206206
self.warm_up()
207207

208-
elif not isinstance(text, str):
208+
if not isinstance(text, str):
209209
msg = (
210210
"NvidiaTextEmbedder expects a string as an input."
211211
"In case you want to embed a list of Documents, please use the NvidiaDocumentEmbedder."
212212
)
213213
raise TypeError(msg)
214-
elif not text:
214+
if not text:
215215
msg = "Cannot embed an empty string."
216216
raise ValueError(msg)
217217

integrations/nvidia/src/haystack_integrations/components/generators/nvidia/generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def warm_up(self) -> None:
132132

133133
if not self.is_hosted and not self._model:
134134
if self.backend.model:
135-
self.model = self.backend.model
135+
self._model = self.backend.model
136136
else:
137137
self.default_model()
138138

integrations/nvidia/tests/test_document_embedder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ def test_run_wrong_input_format(self):
354354
with pytest.raises(TypeError, match="NvidiaDocumentEmbedder expects a list of Documents as input"):
355355
embedder.run(documents=list_integers_input)
356356

357+
def test_run_validates_input_without_prior_warm_up(self):
358+
api_key = Secret.from_token("fake-api-key")
359+
embedder = NvidiaDocumentEmbedder("nvidia/nv-embedqa-e5-v5", api_key=api_key)
360+
361+
with pytest.raises(TypeError, match="NvidiaDocumentEmbedder expects a list of Documents as input"):
362+
embedder.run(documents="text")
363+
357364
def test_run_empty_document(self, caplog):
358365
model = "nvidia/nv-embedqa-e5-v5"
359366
api_key = Secret.from_token("fake-api-key")

integrations/nvidia/tests/test_generator.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,73 @@ def test_hosted_nim_without_key(self):
230230
)
231231
with pytest.raises(ValueError):
232232
generator1.warm_up()
233+
234+
@pytest.mark.usefixtures("mock_local_models")
235+
def test_warm_up_falls_back_to_default_model(self, monkeypatch):
236+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
237+
generator = NvidiaGenerator(api_url="http://localhost:8080/v1")
238+
239+
with pytest.warns(UserWarning, match="Default model is set as:"):
240+
generator.warm_up()
241+
242+
assert generator._model == "model1"
243+
assert generator.backend.model == "model1"
244+
assert generator.to_dict()["init_parameters"]["model"] == "model1"
245+
246+
def test_default_model_raises_when_no_valid_models(self, monkeypatch, requests_mock):
247+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
248+
requests_mock.get(
249+
"http://localhost:8080/v1/models",
250+
json={"data": [{"id": "derived-model", "object": "model", "root": "base-model"}]},
251+
)
252+
generator = NvidiaGenerator(api_url="http://localhost:8080/v1")
253+
254+
with pytest.raises(ValueError, match="No locally hosted model was found"):
255+
generator.warm_up()
256+
257+
def test_warm_up_is_idempotent(self, monkeypatch):
258+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
259+
generator = NvidiaGenerator("meta/llama3-8b-instruct")
260+
generator.warm_up()
261+
backend = generator.backend
262+
generator.warm_up()
263+
assert generator.backend is backend
264+
265+
def test_available_models_without_backend(self, monkeypatch):
266+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
267+
generator = NvidiaGenerator("meta/llama3-8b-instruct")
268+
assert generator.available_models == []
269+
270+
@pytest.mark.usefixtures("mock_local_models")
271+
def test_available_models_with_backend(self, monkeypatch):
272+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
273+
generator = NvidiaGenerator(model="model1", api_url="http://localhost:8080/v1")
274+
generator.warm_up()
275+
models = generator.available_models
276+
assert len(models) == 1
277+
assert models[0].id == "model1"
278+
279+
def test_from_dict(self, monkeypatch):
280+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
281+
data = {
282+
"type": "haystack_integrations.components.generators.nvidia.generator.NvidiaGenerator",
283+
"init_parameters": {
284+
"api_key": {"env_vars": ["NVIDIA_API_KEY"], "strict": True, "type": "env_var"},
285+
"api_url": "https://my.url.com/v1",
286+
"model": "meta/llama3-8b-instruct",
287+
"model_arguments": {"temperature": 0.5},
288+
},
289+
}
290+
generator = NvidiaGenerator.from_dict(data)
291+
assert generator._model == "meta/llama3-8b-instruct"
292+
assert generator.api_url == "https://my.url.com/v1"
293+
assert generator._model_arguments == {"temperature": 0.5}
294+
295+
def test_run(self, monkeypatch, mock_local_chat_completion): # noqa: ARG002
296+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
297+
generator = NvidiaGenerator(model="model1", api_url="http://localhost:8080/v1")
298+
299+
result = generator.run(prompt="What is the answer?")
300+
301+
assert result["replies"] == ["Hello!", "How are you?"]
302+
assert len(result["meta"]) == 2

integrations/nvidia/tests/test_nim_backend.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,36 @@ def test_models(self, monkeypatch):
233233
timeout=60.0,
234234
)
235235

236+
def test_embed_raises_on_http_error(self, monkeypatch):
237+
error_response = requests.Response()
238+
error_response.status_code = 500
239+
error_response._content = b"server exploded"
240+
with patch("requests.sessions.Session.post", return_value=error_response):
241+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
242+
backend = NimBackend(model="nvidia/nv-embedqa-e5-v5", api_url=DEFAULT_API_URL, client="NvidiaTextEmbedder")
243+
with pytest.raises(ValueError, match="Failed to query embedding endpoint"):
244+
backend.embed(texts=["a"])
245+
246+
def test_generate_raises_on_http_error(self, monkeypatch):
247+
error_response = requests.Response()
248+
error_response.status_code = 500
249+
error_response._content = b"server exploded"
250+
with patch("requests.sessions.Session.post", return_value=error_response):
251+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
252+
backend = NimBackend(model="meta/llama3-8b-instruct", api_url=DEFAULT_API_URL, client="NvidiaGenerator")
253+
with pytest.raises(ValueError, match="Failed to query chat completion endpoint"):
254+
backend.generate(prompt="hi")
255+
256+
def test_models_raises_when_empty(self, monkeypatch):
257+
empty_response = requests.Response()
258+
empty_response.status_code = 200
259+
empty_response._content = json.dumps({"data": []}).encode()
260+
with patch("requests.sessions.Session.get", return_value=empty_response):
261+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
262+
backend = NimBackend(model="custom-model", api_url="http://localhost:8000")
263+
with pytest.raises(ValueError, match="No hosted model were found"):
264+
backend.models()
265+
236266
def test_rank(self, monkeypatch):
237267
with patch("requests.sessions.Session.post", side_effect=mock_rank_post_response) as mock_post:
238268
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
@@ -258,3 +288,27 @@ def test_rank(self, monkeypatch):
258288
},
259289
timeout=60.0,
260290
)
291+
292+
def test_rank_raises_on_http_error(self, monkeypatch):
293+
error_response = requests.Response()
294+
error_response.status_code = 500
295+
error_response._content = b"server exploded"
296+
with patch("requests.sessions.Session.post", return_value=error_response):
297+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
298+
backend = NimBackend(
299+
model="nvidia/llama-3.2-nv-rerankqa-1b-v2", api_url=DEFAULT_API_URL, client="NvidiaRanker"
300+
)
301+
with pytest.raises(ValueError, match="Failed to rank endpoint"):
302+
backend.rank(query_text="q", document_texts=["a"])
303+
304+
def test_rank_raises_when_rankings_missing(self, monkeypatch):
305+
response = requests.Response()
306+
response.status_code = 200
307+
response._content = json.dumps({"unexpected": "payload"}).encode()
308+
with patch("requests.sessions.Session.post", return_value=response):
309+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
310+
backend = NimBackend(
311+
model="nvidia/llama-3.2-nv-rerankqa-1b-v2", api_url=DEFAULT_API_URL, client="NvidiaRanker"
312+
)
313+
with pytest.raises(ValueError, match="Expected 'rankings' in response"):
314+
backend.rank(query_text="q", document_texts=["a"])

integrations/nvidia/tests/test_ranker.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,20 @@ def test_setting_timeout_env(self, monkeypatch):
330330
client.warm_up()
331331
assert client.backend.timeout == 45.0
332332

333+
def test_run_on_empty_list(self, monkeypatch):
334+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
335+
client = NvidiaRanker()
336+
client.warm_up()
337+
assert client.run(query="q", documents=[]) == {"documents": []}
338+
339+
def test_run_without_prior_warm_up(self, requests_mock, monkeypatch):
340+
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
341+
requests_mock.post(re.compile(r".*ranking"), json={"rankings": [{"index": 0, "logit": 1.0}]})
342+
client = NvidiaRanker()
343+
result = client.run(query="q", documents=[Document(content="doc")])
344+
assert client._initialized is True
345+
assert len(result["documents"]) == 1
346+
333347
def test_prepare_texts_to_embed_w_metadata(self):
334348
documents = [
335349
Document(content=f"document number {i}:\ncontent", meta={"meta_field": f"meta_value {i}"}) for i in range(5)

integrations/nvidia/tests/test_text_embedder.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ def test_run_empty_string(self):
183183
with pytest.raises(ValueError, match="empty string"):
184184
embedder.run(text="")
185185

186+
def test_run_validates_input_without_prior_warm_up(self):
187+
api_key = Secret.from_token("fake-api-key")
188+
embedder = NvidiaTextEmbedder("nvidia/nv-embedqa-e5-v5", api_key=api_key)
189+
190+
with pytest.raises(TypeError, match="NvidiaTextEmbedder expects a string as an input"):
191+
embedder.run(text=[1, 2, 3])
192+
193+
def test_run_rejects_empty_string_without_prior_warm_up(self):
194+
api_key = Secret.from_token("fake-api-key")
195+
embedder = NvidiaTextEmbedder("nvidia/nv-embedqa-e5-v5", api_key=api_key)
196+
197+
with pytest.raises(ValueError, match="empty string"):
198+
embedder.run(text="")
199+
186200
def test_setting_timeout(self, monkeypatch):
187201
monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
188202
embedder = NvidiaTextEmbedder(timeout=10.0)

integrations/nvidia/tests/test_utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import pytest
66

77
from haystack_integrations.utils.nvidia import Client, is_hosted
8-
from haystack_integrations.utils.nvidia.models import CHAT_MODEL_TABLE, EMBEDDING_MODEL_TABLE, RANKING_MODEL_TABLE
8+
from haystack_integrations.utils.nvidia.models import (
9+
CHAT_MODEL_TABLE,
10+
EMBEDDING_MODEL_TABLE,
11+
RANKING_MODEL_TABLE,
12+
Model,
13+
)
914
from haystack_integrations.utils.nvidia.utils import (
1015
determine_model,
1116
lookup_model,
@@ -110,3 +115,39 @@ def test_validate_hosted_model_with_client() -> None:
110115
model = validate_hosted_model("meta/codellama-70b", Client.NVIDIA_GENERATOR)
111116
assert model is not None
112117
assert model.client == Client.NVIDIA_GENERATOR
118+
119+
120+
# Model
121+
def test_model_hash_uses_id() -> None:
122+
assert hash(Model(id="foo")) == hash("foo")
123+
124+
125+
def test_model_validate_with_enum_client() -> None:
126+
model = Model(id="foo", model_type="chat", client=Client.NVIDIA_GENERATOR)
127+
assert model.validate() == hash("foo")
128+
129+
130+
def test_model_validate_with_string_client() -> None:
131+
model = Model(id="foo", model_type="chat", client="NvidiaGenerator")
132+
assert model.validate() == hash("foo")
133+
134+
135+
def test_model_validate_raises_on_incompatible_type() -> None:
136+
model = Model(id="foo", model_type="embedding", client=Client.NVIDIA_GENERATOR)
137+
with pytest.raises(ValueError, match="not supported by client"):
138+
model.validate()
139+
140+
141+
def test_model_validate_without_client() -> None:
142+
model = Model(id="foo", model_type="chat")
143+
assert model.validate() == hash("foo")
144+
145+
146+
# Client
147+
def test_client_from_str_invalid() -> None:
148+
with pytest.raises(ValueError, match="Unknown client"):
149+
Client.from_str("NotARealClient")
150+
151+
152+
def test_client_str_returns_value() -> None:
153+
assert str(Client.NVIDIA_GENERATOR) == "NvidiaGenerator"

0 commit comments

Comments
 (0)