ollama-haystack 2.2.0__tar.gz → 2.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.
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/CHANGELOG.md +7 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/PKG-INFO +2 -1
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/pyproject.toml +2 -2
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/chat/chat_generator.py +19 -2
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_chat_generator.py +71 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/.gitignore +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/LICENSE.txt +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/README.md +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/chat_generator_example.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/embedders_example.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/generator_example.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/pydoc/config.yml +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/__init__.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/document_embedder.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/text_embedder.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/__init__.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/chat/__init__.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/generator.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/__init__.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_document_embedder.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_generator.py +0 -0
- {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_text_embedder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ollama-haystack
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: An integration between the Ollama LLM framework and Haystack
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/ollama#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
20
20
|
Requires-Python: >=3.8
|
|
21
21
|
Requires-Dist: haystack-ai
|
|
22
22
|
Requires-Dist: ollama>=0.4.0
|
|
23
|
+
Requires-Dist: pydantic
|
|
23
24
|
Description-Content-Type: text/markdown
|
|
24
25
|
|
|
25
26
|
# ollama-haystack
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
27
27
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
28
28
|
]
|
|
29
|
-
dependencies = ["haystack-ai", "ollama>=0.4.0"]
|
|
29
|
+
dependencies = ["haystack-ai", "ollama>=0.4.0", "pydantic"]
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
32
32
|
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/ollama#readme"
|
|
@@ -165,5 +165,5 @@ markers = [
|
|
|
165
165
|
addopts = ["--import-mode=importlib"]
|
|
166
166
|
|
|
167
167
|
[[tool.mypy.overrides]]
|
|
168
|
-
module = ["haystack.*", "haystack_integrations.*", "pytest.*", "ollama.*"]
|
|
168
|
+
module = ["haystack.*", "haystack_integrations.*", "pytest.*", "ollama.*", "pydantic.*"]
|
|
169
169
|
ignore_missing_imports = true
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
|
1
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Union
|
|
2
2
|
|
|
3
3
|
from haystack import component, default_from_dict, default_to_dict
|
|
4
4
|
from haystack.dataclasses import ChatMessage, StreamingChunk, ToolCall
|
|
5
5
|
from haystack.tools import Tool, _check_duplicate_tool_names, deserialize_tools_inplace
|
|
6
6
|
from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
|
|
7
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
7
8
|
|
|
8
9
|
from ollama import ChatResponse, Client
|
|
9
10
|
|
|
@@ -97,6 +98,7 @@ class OllamaChatGenerator:
|
|
|
97
98
|
keep_alive: Optional[Union[float, str]] = None,
|
|
98
99
|
streaming_callback: Optional[Callable[[StreamingChunk], None]] = None,
|
|
99
100
|
tools: Optional[List[Tool]] = None,
|
|
101
|
+
response_format: Optional[Union[None, Literal["json"], JsonSchemaValue]] = None,
|
|
100
102
|
):
|
|
101
103
|
"""
|
|
102
104
|
:param model:
|
|
@@ -124,6 +126,11 @@ class OllamaChatGenerator:
|
|
|
124
126
|
A list of tools for which the model can prepare calls.
|
|
125
127
|
Not all models support tools. For a list of models compatible with tools, see the
|
|
126
128
|
[models page](https://ollama.com/search?c=tools).
|
|
129
|
+
:param response_format:
|
|
130
|
+
The format for structured model outputs. The value can be:
|
|
131
|
+
- None: No specific structure or format is applied to the response. The response is returned as-is.
|
|
132
|
+
- "json": The response is formatted as a JSON object.
|
|
133
|
+
- JSON Schema: The response is formatted as a JSON object that adheres to the specified JSON Schema.
|
|
127
134
|
"""
|
|
128
135
|
|
|
129
136
|
_check_duplicate_tool_names(tools)
|
|
@@ -135,7 +142,7 @@ class OllamaChatGenerator:
|
|
|
135
142
|
self.keep_alive = keep_alive
|
|
136
143
|
self.streaming_callback = streaming_callback
|
|
137
144
|
self.tools = tools
|
|
138
|
-
|
|
145
|
+
self.response_format = response_format
|
|
139
146
|
self._client = Client(host=self.url, timeout=self.timeout)
|
|
140
147
|
|
|
141
148
|
def to_dict(self) -> Dict[str, Any]:
|
|
@@ -156,6 +163,7 @@ class OllamaChatGenerator:
|
|
|
156
163
|
timeout=self.timeout,
|
|
157
164
|
streaming_callback=callback_name,
|
|
158
165
|
tools=serialized_tools,
|
|
166
|
+
response_format=self.response_format,
|
|
159
167
|
)
|
|
160
168
|
|
|
161
169
|
@classmethod
|
|
@@ -237,6 +245,14 @@ class OllamaChatGenerator:
|
|
|
237
245
|
msg = "Ollama does not support tools and streaming at the same time. Please choose one."
|
|
238
246
|
raise ValueError(msg)
|
|
239
247
|
|
|
248
|
+
if self.response_format and tools:
|
|
249
|
+
msg = "Ollama does not support tools and response_format at the same time. Please choose one."
|
|
250
|
+
raise ValueError(msg)
|
|
251
|
+
|
|
252
|
+
if self.response_format and stream:
|
|
253
|
+
msg = "Ollama does not support streaming and response_format at the same time. Please choose one."
|
|
254
|
+
raise ValueError(msg)
|
|
255
|
+
|
|
240
256
|
ollama_tools = [{"type": "function", "function": {**t.tool_spec}} for t in tools] if tools else None
|
|
241
257
|
|
|
242
258
|
ollama_messages = [_convert_chatmessage_to_ollama_format(msg) for msg in messages]
|
|
@@ -247,6 +263,7 @@ class OllamaChatGenerator:
|
|
|
247
263
|
stream=stream,
|
|
248
264
|
keep_alive=self.keep_alive,
|
|
249
265
|
options=generation_kwargs,
|
|
266
|
+
format=self.response_format,
|
|
250
267
|
)
|
|
251
268
|
|
|
252
269
|
if stream:
|
|
@@ -165,6 +165,7 @@ class TestOllamaChatGenerator:
|
|
|
165
165
|
assert component.streaming_callback is None
|
|
166
166
|
assert component.tools is None
|
|
167
167
|
assert component.keep_alive is None
|
|
168
|
+
assert component.response_format is None
|
|
168
169
|
|
|
169
170
|
def test_init(self, tools):
|
|
170
171
|
component = OllamaChatGenerator(
|
|
@@ -175,6 +176,7 @@ class TestOllamaChatGenerator:
|
|
|
175
176
|
keep_alive="10m",
|
|
176
177
|
streaming_callback=print_streaming_chunk,
|
|
177
178
|
tools=tools,
|
|
179
|
+
response_format={"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}}},
|
|
178
180
|
)
|
|
179
181
|
|
|
180
182
|
assert component.model == "llama2"
|
|
@@ -184,6 +186,10 @@ class TestOllamaChatGenerator:
|
|
|
184
186
|
assert component.keep_alive == "10m"
|
|
185
187
|
assert component.streaming_callback is print_streaming_chunk
|
|
186
188
|
assert component.tools == tools
|
|
189
|
+
assert component.response_format == {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {"name": {"type": "string"}, "age": {"type": "number"}},
|
|
192
|
+
}
|
|
187
193
|
|
|
188
194
|
def test_init_fail_with_duplicate_tool_names(self, tools):
|
|
189
195
|
|
|
@@ -206,6 +212,7 @@ class TestOllamaChatGenerator:
|
|
|
206
212
|
generation_kwargs={"max_tokens": 10, "some_test_param": "test-params"},
|
|
207
213
|
tools=[tool],
|
|
208
214
|
keep_alive="5m",
|
|
215
|
+
response_format={"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}}},
|
|
209
216
|
)
|
|
210
217
|
data = component.to_dict()
|
|
211
218
|
assert data == {
|
|
@@ -235,6 +242,10 @@ class TestOllamaChatGenerator:
|
|
|
235
242
|
},
|
|
236
243
|
},
|
|
237
244
|
],
|
|
245
|
+
"response_format": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"properties": {"name": {"type": "string"}, "age": {"type": "number"}},
|
|
248
|
+
},
|
|
238
249
|
},
|
|
239
250
|
}
|
|
240
251
|
|
|
@@ -273,6 +284,10 @@ class TestOllamaChatGenerator:
|
|
|
273
284
|
},
|
|
274
285
|
},
|
|
275
286
|
],
|
|
287
|
+
"response_format": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"properties": {"name": {"type": "string"}, "age": {"type": "number"}},
|
|
290
|
+
},
|
|
276
291
|
},
|
|
277
292
|
}
|
|
278
293
|
component = OllamaChatGenerator.from_dict(data)
|
|
@@ -286,6 +301,10 @@ class TestOllamaChatGenerator:
|
|
|
286
301
|
}
|
|
287
302
|
assert component.timeout == 120
|
|
288
303
|
assert component.tools == [tool]
|
|
304
|
+
assert component.response_format == {
|
|
305
|
+
"type": "object",
|
|
306
|
+
"properties": {"name": {"type": "string"}, "age": {"type": "number"}},
|
|
307
|
+
}
|
|
289
308
|
|
|
290
309
|
@patch("haystack_integrations.components.generators.ollama.chat.chat_generator.Client")
|
|
291
310
|
def test_run(self, mock_client):
|
|
@@ -319,6 +338,7 @@ class TestOllamaChatGenerator:
|
|
|
319
338
|
tools=None,
|
|
320
339
|
options={},
|
|
321
340
|
keep_alive=None,
|
|
341
|
+
format=None,
|
|
322
342
|
)
|
|
323
343
|
|
|
324
344
|
assert "replies" in result
|
|
@@ -456,3 +476,54 @@ class TestOllamaChatGenerator:
|
|
|
456
476
|
assert isinstance(tool_call, ToolCall)
|
|
457
477
|
assert tool_call.tool_name == "weather"
|
|
458
478
|
assert tool_call.arguments == {"city": "Paris"}
|
|
479
|
+
|
|
480
|
+
@pytest.mark.integration
|
|
481
|
+
def test_run_with_response_format(self):
|
|
482
|
+
response_format = {
|
|
483
|
+
"type": "object",
|
|
484
|
+
"properties": {"capital": {"type": "string"}, "population": {"type": "number"}},
|
|
485
|
+
}
|
|
486
|
+
chat_generator = OllamaChatGenerator(model="llama3.2:3b", response_format=response_format)
|
|
487
|
+
|
|
488
|
+
message = ChatMessage.from_user("What's the capital of France and its population?")
|
|
489
|
+
response = chat_generator.run([message])
|
|
490
|
+
|
|
491
|
+
assert isinstance(response, dict)
|
|
492
|
+
assert isinstance(response["replies"], list)
|
|
493
|
+
|
|
494
|
+
# Parse the response text as JSON and verify its structure
|
|
495
|
+
response_data = json.loads(response["replies"][0].text)
|
|
496
|
+
assert isinstance(response_data, dict)
|
|
497
|
+
assert "capital" in response_data
|
|
498
|
+
assert isinstance(response_data["capital"], str)
|
|
499
|
+
assert "population" in response_data
|
|
500
|
+
assert isinstance(response_data["population"], (int, float))
|
|
501
|
+
assert response_data["capital"] == "Paris"
|
|
502
|
+
|
|
503
|
+
def test_run_with_streaming_and_format(self):
|
|
504
|
+
response_format = {
|
|
505
|
+
"type": "object",
|
|
506
|
+
"properties": {"answer": {"type": "string"}},
|
|
507
|
+
}
|
|
508
|
+
streaming_callback = Mock()
|
|
509
|
+
chat_generator = OllamaChatGenerator(
|
|
510
|
+
model="llama3.2:3b", streaming_callback=streaming_callback, response_format=response_format
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
chat_messages = [
|
|
514
|
+
ChatMessage.from_user("What is the largest city in the United Kingdom by population?"),
|
|
515
|
+
ChatMessage.from_assistant("London is the largest city in the United Kingdom by population"),
|
|
516
|
+
ChatMessage.from_user("And what is the second largest?"),
|
|
517
|
+
]
|
|
518
|
+
with pytest.raises(ValueError):
|
|
519
|
+
chat_generator.run([chat_messages])
|
|
520
|
+
|
|
521
|
+
def test_run_with_tools_and_format(self, tools):
|
|
522
|
+
response_format = {
|
|
523
|
+
"type": "object",
|
|
524
|
+
"properties": {"capital": {"type": "string"}, "population": {"type": "number"}},
|
|
525
|
+
}
|
|
526
|
+
chat_generator = OllamaChatGenerator(model="llama3.2:3b", tools=tools, response_format=response_format)
|
|
527
|
+
message = ChatMessage.from_user("What's the weather in Paris?")
|
|
528
|
+
with pytest.raises(ValueError):
|
|
529
|
+
chat_generator.run([message])
|
|
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
|
|
File without changes
|