llama-stack-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.
Files changed (16) hide show
  1. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/CHANGELOG.md +11 -0
  2. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/PKG-INFO +1 -1
  3. llama_stack_haystack-1.3.0/examples/chatgenerator_with_structured_outputs.py +35 -0
  4. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pyproject.toml +3 -3
  5. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/llama_stack/chat/chat_generator.py +31 -6
  6. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/tests/test_llama_stack_chat_generator.py +90 -2
  7. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/.gitignore +0 -0
  8. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/LICENSE.txt +0 -0
  9. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/README.md +0 -0
  10. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/examples/llama-stack-with-tools.py +0 -0
  11. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pydoc/config.yml +0 -0
  12. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
  13. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/llama_stack/__init__.py +0 -0
  14. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/llama_stack/chat/__init__.py +0 -0
  15. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
  16. {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/tests/__init__.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/llama_stack-v1.2.0] - 2025-10-24
4
+
5
+ ### 🚀 Features
6
+
7
+ - `LlamaStackChatGenerator` update tools param to ToolsType (#2436)
8
+
9
+ ### 📚 Documentation
10
+
11
+ - Add pydoc configurations for Docusaurus (#2411)
12
+
13
+
3
14
  ## [integrations/llama_stack-v1.1.1] - 2025-08-22
4
15
 
5
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llama-stack-haystack
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Use Llama Stack framework for text generation
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/llama-stack#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -0,0 +1,35 @@
1
+ # SPDX-FileCopyrightText: 2024-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+
6
+ # This example demonstrates how to use the LlamaStackChatGenerator component
7
+ # with structured outputs.
8
+ # To run this example, you will need to
9
+ # set up Llama Stack Server and have a model available
10
+
11
+ from haystack.dataclasses import ChatMessage
12
+ from pydantic import BaseModel
13
+
14
+ from haystack_integrations.components.generators.llama_stack import LlamaStackChatGenerator
15
+
16
+
17
+ class NobelPrizeInfo(BaseModel):
18
+ recipient_name: str
19
+ award_year: int
20
+ category: str
21
+ achievement_description: str
22
+ nationality: str
23
+
24
+
25
+ chat_messages = [
26
+ ChatMessage.from_user(
27
+ "In 2021, American scientist David Julius received the Nobel Prize in"
28
+ " Physiology or Medicine for his groundbreaking discoveries on how the human body"
29
+ " senses temperature and touch."
30
+ )
31
+ ]
32
+ component = LlamaStackChatGenerator(generation_kwargs={"response_format": NobelPrizeInfo})
33
+ results = component.run(chat_messages)
34
+
35
+ # print(results)
@@ -60,8 +60,8 @@ dependencies = [
60
60
  [tool.hatch.envs.test.scripts]
61
61
  unit = 'pytest -m "not integration" {args:tests}'
62
62
  integration = 'pytest -m "integration" {args:tests}'
63
- all = 'pytest {args:tests}'
64
- cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
63
+ all = 'pytest --reruns 3 --reruns-delay 30 -x {args:tests}'
64
+ cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
65
65
 
66
66
  types = "mypy -p haystack_integrations.components.generators.llama_stack {args}"
67
67
 
@@ -73,7 +73,7 @@ disallow_incomplete_defs = true
73
73
 
74
74
 
75
75
  [tool.ruff]
76
- target-version = "py38"
76
+ target-version = "py39"
77
77
  line-length = 120
78
78
 
79
79
  [tool.ruff.lint]
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from typing import Any, Dict, Optional
5
+ from typing import Any, Optional
6
6
 
7
7
  from haystack import component, default_from_dict, default_to_dict, logging
8
8
  from haystack.components.generators.chat import OpenAIChatGenerator
@@ -10,6 +10,8 @@ from haystack.dataclasses import StreamingCallbackT
10
10
  from haystack.tools import ToolsType, deserialize_tools_or_toolset_inplace, serialize_tools_or_toolset
11
11
  from haystack.utils import deserialize_callable, serialize_callable
12
12
  from haystack.utils.auth import Secret
13
+ from openai.lib._pydantic import to_strict_json_schema
14
+ from pydantic import BaseModel
13
15
 
14
16
  logger = logging.getLogger(__name__)
15
17
 
@@ -60,12 +62,12 @@ class LlamaStackChatGenerator(OpenAIChatGenerator):
60
62
  api_base_url: str = "http://localhost:8321/v1/openai/v1",
61
63
  organization: Optional[str] = None,
62
64
  streaming_callback: Optional[StreamingCallbackT] = None,
63
- generation_kwargs: Optional[Dict[str, Any]] = None,
65
+ generation_kwargs: Optional[dict[str, Any]] = None,
64
66
  timeout: Optional[int] = None,
65
67
  tools: Optional[ToolsType] = None,
66
68
  tools_strict: bool = False,
67
69
  max_retries: Optional[int] = None,
68
- http_client_kwargs: Optional[Dict[str, Any]] = None,
70
+ http_client_kwargs: Optional[dict[str, Any]] = None,
69
71
  ):
70
72
  """
71
73
  Creates an instance of LlamaStackChatGenerator. To use this chat generator,
@@ -94,6 +96,13 @@ class LlamaStackChatGenerator(OpenAIChatGenerator):
94
96
  events as they become available, with the stream terminated by a data: [DONE] message.
95
97
  - `safe_prompt`: Whether to inject a safety prompt before all conversations.
96
98
  - `random_seed`: The seed to use for random sampling.
99
+ - `response_format`: A JSON schema or a Pydantic model that enforces the structure of the model's response.
100
+ If provided, the output will always be validated against this
101
+ format (unless the model returns a tool call).
102
+ For details, see the [OpenAI Structured Outputs documentation](https://platform.openai.com/docs/guides/structured-outputs).
103
+ Notes:
104
+ - For structured outputs with streaming,
105
+ the `response_format` must be a JSON schema and not a Pydantic model.
97
106
  :param timeout:
98
107
  Timeout for client calls using OpenAI API. If not set, it defaults to either the
99
108
  `OPENAI_TIMEOUT` environment variable, or 30 seconds.
@@ -129,7 +138,7 @@ class LlamaStackChatGenerator(OpenAIChatGenerator):
129
138
  http_client_kwargs=http_client_kwargs,
130
139
  )
131
140
 
132
- def to_dict(self) -> Dict[str, Any]:
141
+ def to_dict(self) -> dict[str, Any]:
133
142
  """
134
143
  Serialize this component to a dictionary.
135
144
 
@@ -137,13 +146,29 @@ class LlamaStackChatGenerator(OpenAIChatGenerator):
137
146
  The serialized component as a dictionary.
138
147
  """
139
148
  callback_name = serialize_callable(self.streaming_callback) if self.streaming_callback else None
149
+ generation_kwargs = self.generation_kwargs.copy()
150
+ response_format = generation_kwargs.get("response_format")
151
+ # If the response format is a Pydantic model, it's converted to openai's json schema format
152
+ # If it's already a json schema, it's left as is
153
+ if response_format and isinstance(response_format, type) and issubclass(response_format, BaseModel):
154
+ json_schema = {
155
+ "type": "json_schema",
156
+ "json_schema": {
157
+ "name": response_format.__name__,
158
+ "strict": True,
159
+ "schema": to_strict_json_schema(response_format),
160
+ },
161
+ }
162
+
163
+ generation_kwargs["response_format"] = json_schema
164
+
140
165
  return default_to_dict(
141
166
  self,
142
167
  model=self.model,
143
168
  streaming_callback=callback_name,
144
169
  api_base_url=self.api_base_url,
145
170
  organization=self.organization,
146
- generation_kwargs=self.generation_kwargs,
171
+ generation_kwargs=generation_kwargs,
147
172
  timeout=self.timeout,
148
173
  max_retries=self.max_retries,
149
174
  tools=serialize_tools_or_toolset(self.tools),
@@ -152,7 +177,7 @@ class LlamaStackChatGenerator(OpenAIChatGenerator):
152
177
  )
153
178
 
154
179
  @classmethod
155
- def from_dict(cls, data: Dict[str, Any]) -> "LlamaStackChatGenerator":
180
+ def from_dict(cls, data: dict[str, Any]) -> "LlamaStackChatGenerator":
156
181
  """
157
182
  Deserialize this component from a dictionary.
158
183
 
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from datetime import datetime
2
3
  from unittest.mock import patch
3
4
 
@@ -8,10 +9,22 @@ from haystack.dataclasses import ChatMessage, ChatRole, StreamingChunk, ToolCall
8
9
  from haystack.tools import Tool, Toolset
9
10
  from openai.types.chat import ChatCompletion, ChatCompletionMessage
10
11
  from openai.types.chat.chat_completion import Choice
12
+ from pydantic import BaseModel
11
13
 
12
14
  from haystack_integrations.components.generators.llama_stack.chat.chat_generator import LlamaStackChatGenerator
13
15
 
14
16
 
17
+ class CalendarEvent(BaseModel):
18
+ event_name: str
19
+ event_date: str
20
+ event_location: str
21
+
22
+
23
+ @pytest.fixture
24
+ def calendar_event_model():
25
+ return CalendarEvent
26
+
27
+
15
28
  @pytest.fixture
16
29
  def chat_messages():
17
30
  return [
@@ -135,12 +148,17 @@ class TestLlamaStackChatGenerator:
135
148
 
136
149
  def test_to_dict_with_parameters(
137
150
  self,
151
+ calendar_event_model,
138
152
  ):
139
153
  component = LlamaStackChatGenerator(
140
154
  model="ollama/llama3.2:3b",
141
155
  streaming_callback=print_streaming_chunk,
142
156
  api_base_url="test-base-url",
143
- generation_kwargs={"max_tokens": 10, "some_test_param": "test-params"},
157
+ generation_kwargs={
158
+ "max_tokens": 10,
159
+ "some_test_param": "test-params",
160
+ "response_format": calendar_event_model,
161
+ },
144
162
  timeout=10,
145
163
  max_retries=10,
146
164
  tools=None,
@@ -158,7 +176,28 @@ class TestLlamaStackChatGenerator:
158
176
  "model": "ollama/llama3.2:3b",
159
177
  "api_base_url": "test-base-url",
160
178
  "streaming_callback": "haystack.components.generators.utils.print_streaming_chunk",
161
- "generation_kwargs": {"max_tokens": 10, "some_test_param": "test-params"},
179
+ "generation_kwargs": {
180
+ "max_tokens": 10,
181
+ "some_test_param": "test-params",
182
+ "response_format": {
183
+ "type": "json_schema",
184
+ "json_schema": {
185
+ "name": "CalendarEvent",
186
+ "strict": True,
187
+ "schema": {
188
+ "properties": {
189
+ "event_name": {"title": "Event Name", "type": "string"},
190
+ "event_date": {"title": "Event Date", "type": "string"},
191
+ "event_location": {"title": "Event Location", "type": "string"},
192
+ },
193
+ "required": ["event_name", "event_date", "event_location"],
194
+ "title": "CalendarEvent",
195
+ "type": "object",
196
+ "additionalProperties": False,
197
+ },
198
+ },
199
+ },
200
+ },
162
201
  "timeout": 10,
163
202
  "max_retries": 10,
164
203
  "tools": None,
@@ -407,3 +446,52 @@ class TestLlamaStackChatGenerator:
407
446
  assert len(final_message.text) > 0
408
447
  assert "paris" in final_message.text.lower()
409
448
  assert "berlin" in final_message.text.lower()
449
+
450
+ @pytest.mark.integration
451
+ def test_live_run_with_response_format_json_schema(self):
452
+ response_schema = {
453
+ "type": "json_schema",
454
+ "json_schema": {
455
+ "name": "CapitalCity",
456
+ "strict": True,
457
+ "schema": {
458
+ "title": "CapitalCity",
459
+ "type": "object",
460
+ "properties": {
461
+ "city": {"title": "City", "type": "string"},
462
+ "country": {"title": "Country", "type": "string"},
463
+ },
464
+ "required": ["city", "country"],
465
+ "additionalProperties": False,
466
+ },
467
+ },
468
+ }
469
+
470
+ chat_messages = [ChatMessage.from_user("What's the capital of France?")]
471
+ comp = LlamaStackChatGenerator(
472
+ model="ollama/llama3.2:3b", generation_kwargs={"response_format": response_schema}
473
+ )
474
+ results = comp.run(chat_messages)
475
+ assert len(results["replies"]) == 1
476
+ message: ChatMessage = results["replies"][0]
477
+ msg = json.loads(message.text)
478
+ assert "Paris" in msg["city"]
479
+ assert isinstance(msg["country"], str)
480
+ assert "France" in msg["country"]
481
+ assert message.meta["finish_reason"] == "stop"
482
+
483
+ @pytest.mark.integration
484
+ def test_live_run_with_response_format_pydantic_model(self, calendar_event_model):
485
+ chat_messages = [
486
+ ChatMessage.from_user("The marketing summit takes place on October 12th at the Hilton Hotel downtown.")
487
+ ]
488
+ component = LlamaStackChatGenerator(
489
+ model="ollama/llama3.2:3b", generation_kwargs={"response_format": calendar_event_model}
490
+ )
491
+ results = component.run(chat_messages)
492
+ assert len(results["replies"]) == 1
493
+ message: ChatMessage = results["replies"][0]
494
+ msg = json.loads(message.text)
495
+ assert "Marketing Summit" in msg["event_name"]
496
+ assert isinstance(msg["event_date"], str)
497
+ assert isinstance(msg["event_location"], str)