nvidia-haystack 1.1.1__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/CHANGELOG.md +19 -0
  2. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/PKG-INFO +1 -1
  3. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/pyproject.toml +1 -0
  4. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/generator.py +6 -0
  5. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/nim_backend.py +32 -7
  6. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/conftest.py +10 -0
  7. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_base_url.py +2 -1
  8. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_generator.py +5 -5
  9. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_nim_backend.py +23 -1
  10. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_nvidia_chat_generator.py +16 -5
  11. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_ranker.py +31 -0
  12. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/.gitignore +0 -0
  13. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/LICENSE.txt +0 -0
  14. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/README.md +0 -0
  15. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/examples/chat_generator_with_structured_outputs.py +0 -0
  16. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
  17. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/__init__.py +0 -0
  18. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/document_embedder.py +0 -0
  19. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/text_embedder.py +0 -0
  20. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/truncate.py +0 -0
  21. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
  22. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/__init__.py +0 -0
  23. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/chat/__init__.py +0 -0
  24. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/chat/chat_generator.py +0 -0
  25. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
  26. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/__init__.py +0 -0
  27. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/ranker.py +0 -0
  28. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/truncate.py +0 -0
  29. {nvidia_haystack-1.1.1/src/haystack_integrations/components/rankers/nvidia → nvidia_haystack-1.3.0/src/haystack_integrations/components/rankers}/py.typed +0 -0
  30. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/__init__.py +0 -0
  31. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/client.py +0 -0
  32. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/models.py +0 -0
  33. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/utils.py +0 -0
  34. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/py.typed +0 -0
  35. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/__init__.py +0 -0
  36. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_document_embedder.py +0 -0
  37. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_embedding_truncate_mode.py +0 -0
  38. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_text_embedder.py +0 -0
  39. {nvidia_haystack-1.1.1 → nvidia_haystack-1.3.0}/tests/test_utils.py +0 -0
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/nvidia-v1.2.0] - 2026-06-05
4
+
5
+ ### 🧹 Chores
6
+
7
+ - NVIDIA - deprecate generator (#3391)
8
+
9
+
10
+ ## [integrations/nvidia-v1.1.1] - 2026-04-14
11
+
12
+ ### 🐛 Bug Fixes
13
+
14
+ - Replace in-place dataclass mutations with dataclasses.replace() (#3112)
15
+
16
+ ### 🧪 Testing
17
+
18
+ - Track test coverage for all integrations (#3065)
19
+ - Add unit tests for Nvidia integration (#3162)
20
+
21
+
3
22
  ## [integrations/nvidia-v1.1.0] - 2026-03-30
4
23
 
5
24
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvidia-haystack
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/nvidia#readme
5
5
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
6
6
  Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/nvidia
@@ -133,6 +133,7 @@ ignore = [
133
133
  "PLR0912",
134
134
  "PLR0913",
135
135
  "PLR0915",
136
+ "PLR0917",
136
137
  # Misc
137
138
  "B008",
138
139
  "S101",
@@ -75,6 +75,12 @@ class NvidiaGenerator:
75
75
  Timeout for request calls, if not set it is inferred from the `NVIDIA_TIMEOUT` environment variable
76
76
  or set to 60 by default.
77
77
  """
78
+ warnings.warn(
79
+ "The `NvidiaGenerator` component is deprecated and will be removed in a future version. "
80
+ "Use `NvidiaChatGenerator` instead, which now also supports string inputs.",
81
+ FutureWarning,
82
+ stacklevel=2,
83
+ )
78
84
  self._model = model
79
85
  self.api_url = url_validation(api_url)
80
86
  self._api_key = api_key
@@ -42,6 +42,11 @@ class NimBackend:
42
42
  self.session.headers.update(headers)
43
43
 
44
44
  self.api_url = api_url
45
+ # Hosted ranking models are assigned a per-model endpoint that already includes the
46
+ # full path (e.g. ".../nv-rerankqa-mistral-4b-v3/reranking"), unlike other hosted
47
+ # endpoints which only override the host. `rank()` uses this to decide whether it
48
+ # still needs to append the `/ranking` path itself.
49
+ self._has_custom_ranking_endpoint = False
45
50
  if isinstance(client, str):
46
51
  client = Client.from_str(client)
47
52
  validated_model: Model | None = None
@@ -64,6 +69,8 @@ class NimBackend:
64
69
  # we override the endpoint to use the custom endpoint
65
70
  self.api_url = validated_model.endpoint
66
71
  self.model_type = validated_model.model_type
72
+ if validated_model.model_type == "ranking":
73
+ self._has_custom_ranking_endpoint = True
67
74
 
68
75
  self.model = validated_model.id if validated_model else model
69
76
  self.model_kwargs = model_kwargs or {}
@@ -89,8 +96,13 @@ class NimBackend:
89
96
  )
90
97
  res.raise_for_status()
91
98
  except requests.HTTPError as e:
92
- logger.error("Error when calling NIM embedding endpoint: Error - {error}", error=e.response.text)
93
- msg = f"Failed to query embedding endpoint: Error - {e.response.text}"
99
+ response_text = e.response.text.strip() if e.response is not None else ""
100
+ error_details = f": {response_text}" if response_text else ""
101
+ logger.error(
102
+ "Error when calling NIM embedding endpoint{error}",
103
+ error=error_details,
104
+ )
105
+ msg = f"Failed to query embedding endpoint{error_details}"
94
106
  raise ValueError(msg) from e
95
107
 
96
108
  data = res.json()
@@ -123,8 +135,13 @@ class NimBackend:
123
135
  )
124
136
  res.raise_for_status()
125
137
  except requests.HTTPError as e:
126
- logger.error("Error when calling NIM chat completion endpoint: Error - {error}", error=e.response.text)
127
- msg = f"Failed to query chat completion endpoint: Error - {e.response.text}"
138
+ response_text = e.response.text.strip() if e.response is not None else ""
139
+ error_details = f": {response_text}" if response_text else ""
140
+ logger.error(
141
+ "Error when calling NIM chat completion endpoint{error}",
142
+ error=error_details,
143
+ )
144
+ msg = f"Failed to query chat completion endpoint{error_details}"
128
145
  raise ValueError(msg) from e
129
146
 
130
147
  completions = res.json()
@@ -179,7 +196,10 @@ class NimBackend:
179
196
 
180
197
  def rank(self, query_text: str, document_texts: list[str]) -> list[dict[str, Any]]:
181
198
  """Rank documents by relevance to a query via the NIM API."""
182
- url = self.api_url
199
+ # Hosted ranking models already carry the full path in their custom endpoint;
200
+ # everything else (local NIM containers, non-ranking-table models) needs the
201
+ # `/ranking` suffix appended explicitly, like the other endpoints below do.
202
+ url = self.api_url if self._has_custom_ranking_endpoint else f"{self.api_url}/ranking"
183
203
 
184
204
  try:
185
205
  res = self.session.post(
@@ -194,8 +214,13 @@ class NimBackend:
194
214
  )
195
215
  res.raise_for_status()
196
216
  except requests.HTTPError as e:
197
- logger.error("Error when calling NIM ranking endpoint: Error - {error}", error=e.response.text)
198
- msg = f"Failed to rank endpoint: Error - {e.response.text}"
217
+ response_text = e.response.text.strip() if e.response is not None else ""
218
+ error_details = f": {response_text}" if response_text else ""
219
+ logger.error(
220
+ "Error when calling NIM ranking endpoint{error}",
221
+ error=error_details,
222
+ )
223
+ msg = f"Failed to query ranking endpoint{error_details}"
199
224
  raise ValueError(msg) from e
200
225
 
201
226
  data = res.json()
@@ -47,3 +47,13 @@ def mock_local_models(requests_mock: Mocker) -> None:
47
47
  ]
48
48
  },
49
49
  )
50
+
51
+
52
+ @pytest.fixture(autouse=True)
53
+ def allow_deserialization_of_test_modules(monkeypatch):
54
+ """
55
+ haystack-ai >= 3.0 refuses to deserialize classes and callables from modules outside its
56
+ trusted-module allowlist. Tools and callbacks defined in the test modules live outside that
57
+ allowlist, so trust them explicitly; haystack-ai 2.x ignores this environment variable.
58
+ """
59
+ monkeypatch.setenv("HAYSTACK_DESERIALIZATION_ALLOWLIST", "tests,test_*")
@@ -28,7 +28,8 @@ from haystack_integrations.components.rankers.nvidia import NvidiaRanker
28
28
  def test_base_url_invalid_not_hosted(base_url: str, component) -> None:
29
29
  with pytest.warns(UserWarning) as msg:
30
30
  component(api_url=base_url, model="x")
31
- assert "you may have inference and listing issues" in str(msg[0].message)
31
+ user_warnings = [w for w in msg if issubclass(w.category, UserWarning)]
32
+ assert any("you may have inference and listing issues" in str(w.message) for w in user_warnings)
32
33
 
33
34
 
34
35
  @pytest.mark.parametrize(
@@ -193,7 +193,7 @@ class TestNvidiaGenerator:
193
193
  @pytest.mark.integration
194
194
  def test_run_integration_with_api_catalog(self):
195
195
  generator = NvidiaGenerator(
196
- model="meta/llama3-8b-instruct",
196
+ model="meta/llama-3.1-8b-instruct",
197
197
  api_url="https://integrate.api.nvidia.com/v1",
198
198
  api_key=Secret.from_env_var("NVIDIA_API_KEY"),
199
199
  model_arguments={
@@ -256,7 +256,7 @@ class TestNvidiaGenerator:
256
256
 
257
257
  def test_warm_up_is_idempotent(self, monkeypatch):
258
258
  monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
259
- generator = NvidiaGenerator("meta/llama3-8b-instruct")
259
+ generator = NvidiaGenerator("meta/llama-3.1-8b-instruct")
260
260
  generator.warm_up()
261
261
  backend = generator.backend
262
262
  generator.warm_up()
@@ -264,7 +264,7 @@ class TestNvidiaGenerator:
264
264
 
265
265
  def test_available_models_without_backend(self, monkeypatch):
266
266
  monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
267
- generator = NvidiaGenerator("meta/llama3-8b-instruct")
267
+ generator = NvidiaGenerator("meta/llama-3.1-8b-instruct")
268
268
  assert generator.available_models == []
269
269
 
270
270
  @pytest.mark.usefixtures("mock_local_models")
@@ -283,12 +283,12 @@ class TestNvidiaGenerator:
283
283
  "init_parameters": {
284
284
  "api_key": {"env_vars": ["NVIDIA_API_KEY"], "strict": True, "type": "env_var"},
285
285
  "api_url": "https://my.url.com/v1",
286
- "model": "meta/llama3-8b-instruct",
286
+ "model": "meta/llama-3.1-8b-instruct",
287
287
  "model_arguments": {"temperature": 0.5},
288
288
  },
289
289
  }
290
290
  generator = NvidiaGenerator.from_dict(data)
291
- assert generator._model == "meta/llama3-8b-instruct"
291
+ assert generator._model == "meta/llama-3.1-8b-instruct"
292
292
  assert generator.api_url == "https://my.url.com/v1"
293
293
  assert generator._model_arguments == {"temperature": 0.5}
294
294
 
@@ -289,6 +289,28 @@ class TestNimBackend:
289
289
  timeout=60.0,
290
290
  )
291
291
 
292
+ def test_rank_local_nim_appends_ranking_path(self, monkeypatch):
293
+ # Local/self-hosted NIM deployments have no model-specific endpoint override,
294
+ # so `rank()` must append the `/ranking` path itself instead of relying on
295
+ # `validate_hosted_model` having already baked it into `self.api_url`.
296
+ with patch("requests.sessions.Session.post", side_effect=mock_rank_post_response) as mock_post:
297
+ monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
298
+ backend = NimBackend(model="custom-rerank-model", api_url="http://localhost:8000/v1", client="NvidiaRanker")
299
+ query_text = "query"
300
+ document_texts = ["text1", "text2"]
301
+ backend.rank(query_text=query_text, document_texts=document_texts)
302
+
303
+ expected_url = "http://localhost:8000/v1/ranking"
304
+ mock_post.assert_called_once_with(
305
+ expected_url,
306
+ json={
307
+ "model": "custom-rerank-model",
308
+ "query": {"text": query_text},
309
+ "passages": [{"text": text} for text in document_texts],
310
+ },
311
+ timeout=60.0,
312
+ )
313
+
292
314
  def test_rank_raises_on_http_error(self, monkeypatch):
293
315
  error_response = requests.Response()
294
316
  error_response.status_code = 500
@@ -298,7 +320,7 @@ class TestNimBackend:
298
320
  backend = NimBackend(
299
321
  model="nvidia/llama-3.2-nv-rerankqa-1b-v2", api_url=DEFAULT_API_URL, client="NvidiaRanker"
300
322
  )
301
- with pytest.raises(ValueError, match="Failed to rank endpoint"):
323
+ with pytest.raises(ValueError, match="Failed to query ranking endpoint"):
302
324
  backend.rank(query_text="q", document_texts=["a"])
303
325
 
304
326
  def test_rank_raises_when_rankings_missing(self, monkeypatch):
@@ -119,15 +119,23 @@ class TestNvidiaChatGenerator:
119
119
  def test_init_default(self, monkeypatch):
120
120
  monkeypatch.setenv("NVIDIA_API_KEY", "test-api-key")
121
121
  component = NvidiaChatGenerator()
122
- assert component.client.api_key == "test-api-key"
122
+ assert component.api_key.resolve_value() == "test-api-key"
123
123
  assert component.model == "meta/llama-3.1-8b-instruct"
124
124
  assert component.streaming_callback is None
125
125
  assert not component.generation_kwargs
126
126
 
127
+ def test_warm_up(self, monkeypatch):
128
+ monkeypatch.setenv("NVIDIA_API_KEY", "test-api-key")
129
+ component = NvidiaChatGenerator()
130
+ component.warm_up() # with haystack-ai >= 3.0 the client is created during warm-up
131
+ assert component.client.api_key == "test-api-key"
132
+
127
133
  def test_init_fail_wo_api_key(self, monkeypatch):
128
134
  monkeypatch.delenv("NVIDIA_API_KEY", raising=False)
129
135
  with pytest.raises(ValueError, match=r"None of the .* environment variables are set"):
130
- NvidiaChatGenerator()
136
+ # haystack-ai 2.x raises at init; haystack-ai >= 3.0 raises when the client is created in warm_up
137
+ component = NvidiaChatGenerator()
138
+ component.warm_up()
131
139
 
132
140
  def test_init_with_parameters(self):
133
141
  component = NvidiaChatGenerator(
@@ -137,7 +145,7 @@ class TestNvidiaChatGenerator:
137
145
  api_base_url="test-base-url",
138
146
  generation_kwargs={"max_tokens": 10, "some_test_param": "test-params"},
139
147
  )
140
- assert component.client.api_key == "test-api-key"
148
+ assert component.api_key.resolve_value() == "test-api-key"
141
149
  assert component.model == "meta/llama-3.1-8b-instruct"
142
150
  assert component.streaming_callback is print_streaming_chunk
143
151
  assert component.generation_kwargs == {"max_tokens": 10, "some_test_param": "test-params"}
@@ -463,13 +471,16 @@ class TestNvidiaChatGenerator:
463
471
 
464
472
 
465
473
  class TestNvidiaChatGeneratorAsync:
466
- def test_init_default_async(self, monkeypatch):
474
+ @pytest.mark.asyncio
475
+ async def test_warm_up_async(self, monkeypatch):
467
476
  monkeypatch.setenv("NVIDIA_API_KEY", "test-api-key")
468
477
  component = NvidiaChatGenerator()
478
+ if hasattr(component, "warm_up_async"):
479
+ # haystack-ai >= 3.0 creates the async client during async warm-up
480
+ await component.warm_up_async()
469
481
 
470
482
  assert isinstance(component.async_client, AsyncOpenAI)
471
483
  assert component.async_client.api_key == "test-api-key"
472
- assert not component.generation_kwargs
473
484
 
474
485
  @pytest.mark.asyncio
475
486
  async def test_run_async(self, chat_messages, mock_async_chat_completion, monkeypatch): # noqa: ARG002
@@ -120,6 +120,36 @@ class TestNvidiaRanker:
120
120
  assert response[0].content == documents[1].content
121
121
  assert response[0].score == 4.2
122
122
 
123
+ @pytest.mark.parametrize(
124
+ ("model", "api_url", "expected_url"),
125
+ [
126
+ (
127
+ _DEFAULT_MODEL,
128
+ None,
129
+ "https://ai.api.nvidia.com/v1/retrieval/nvidia/nv-rerankqa-mistral-4b-v3/reranking",
130
+ ),
131
+ ("custom-rerank-model", "http://localhost:8000/v1", "http://localhost:8000/v1/ranking"),
132
+ ],
133
+ ids=["hosted", "local-nim"],
134
+ )
135
+ def test_ranking_url(self, requests_mock, monkeypatch, model, api_url, expected_url) -> None:
136
+ # Hosted models are assigned a per-model endpoint that already includes the full path,
137
+ # while local/self-hosted NIM deployments get no such override and need `/ranking`
138
+ # appended to the base URL. Registering only the exact expected URL means any other
139
+ # URL raises NoMockAddress instead of silently passing.
140
+ monkeypatch.setenv("NVIDIA_API_KEY", "fake-api-key")
141
+ requests_mock.post(expected_url, json={"rankings": [{"index": 0, "logit": 1.0}]}, complete_qs=True)
142
+
143
+ api_url_param = {} if api_url is None else {"api_url": api_url}
144
+ client = NvidiaRanker(model=model, top_k=1, **api_url_param)
145
+ client.warm_up()
146
+
147
+ response = client.run(query="q", documents=[Document(content="doc")])["documents"]
148
+
149
+ assert len(response) == 1
150
+ assert requests_mock.last_request is not None
151
+ assert requests_mock.last_request.url == expected_url
152
+
123
153
  @pytest.mark.parametrize("truncate", [True, False, 1, 0, 1.0, "START", "BOGUS"])
124
154
  def test_truncate_invalid(self, truncate: Any) -> None:
125
155
  with pytest.raises(ValueError) as e:
@@ -181,6 +211,7 @@ class TestNvidiaRanker:
181
211
  client = NvidiaRanker(
182
212
  model=os.environ["NVIDIA_NIM_RANKER_MODEL"],
183
213
  api_url=os.environ["NVIDIA_NIM_RANKER_ENDPOINT_URL"],
214
+ api_key=None,
184
215
  top_k=2,
185
216
  )
186
217
  client.warm_up()