nvidia-haystack 1.2.0__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.
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/CHANGELOG.md +7 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/PKG-INFO +1 -1
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/pyproject.toml +1 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/nim_backend.py +32 -7
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/conftest.py +10 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_nim_backend.py +23 -1
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_nvidia_chat_generator.py +16 -5
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_ranker.py +31 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/.gitignore +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/LICENSE.txt +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/README.md +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/examples/chat_generator_with_structured_outputs.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/document_embedder.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/text_embedder.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/nvidia/truncate.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/chat/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/chat/chat_generator.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/nvidia/generator.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/ranker.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/components/rankers/nvidia/truncate.py +0 -0
- {nvidia_haystack-1.2.0/src/haystack_integrations/components/rankers/nvidia → nvidia_haystack-1.3.0/src/haystack_integrations/components/rankers}/py.typed +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/client.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/models.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/utils.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/py.typed +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/__init__.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_base_url.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_document_embedder.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_embedding_truncate_mode.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_generator.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_text_embedder.py +0 -0
- {nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nvidia-haystack
|
|
3
|
-
Version: 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
|
|
@@ -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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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_*")
|
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/examples/chat_generator_with_structured_outputs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/__init__.py
RENAMED
|
File without changes
|
{nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/client.py
RENAMED
|
File without changes
|
{nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/models.py
RENAMED
|
File without changes
|
{nvidia_haystack-1.2.0 → nvidia_haystack-1.3.0}/src/haystack_integrations/utils/nvidia/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|