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.
Files changed (22) hide show
  1. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/CHANGELOG.md +7 -0
  2. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/PKG-INFO +2 -1
  3. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/pyproject.toml +2 -2
  4. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/chat/chat_generator.py +19 -2
  5. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_chat_generator.py +71 -0
  6. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/.gitignore +0 -0
  7. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/LICENSE.txt +0 -0
  8. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/README.md +0 -0
  9. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/chat_generator_example.py +0 -0
  10. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/embedders_example.py +0 -0
  11. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/examples/generator_example.py +0 -0
  12. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/pydoc/config.yml +0 -0
  13. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/__init__.py +0 -0
  14. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/document_embedder.py +0 -0
  15. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/embedders/ollama/text_embedder.py +0 -0
  16. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/__init__.py +0 -0
  17. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/chat/__init__.py +0 -0
  18. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/src/haystack_integrations/components/generators/ollama/generator.py +0 -0
  19. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/__init__.py +0 -0
  20. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_document_embedder.py +0 -0
  21. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_generator.py +0 -0
  22. {ollama_haystack-2.2.0 → ollama_haystack-2.3.0}/tests/test_text_embedder.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/ollama-v2.2.0] - 2025-01-16
4
+
5
+ ### 🚀 Features
6
+
7
+ - Ollama - add support for tools (#1294)
8
+
9
+
3
10
  ## [integrations/ollama-v2.1.2] - 2024-12-18
4
11
 
5
12
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ollama-haystack
3
- Version: 2.2.0
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])