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.
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/CHANGELOG.md +11 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/PKG-INFO +1 -1
- llama_stack_haystack-1.3.0/examples/chatgenerator_with_structured_outputs.py +35 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pyproject.toml +3 -3
- {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
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/tests/test_llama_stack_chat_generator.py +90 -2
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/.gitignore +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/LICENSE.txt +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/README.md +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/examples/llama-stack-with-tools.py +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pydoc/config.yml +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/llama_stack/__init__.py +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/llama_stack/chat/__init__.py +0 -0
- {llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {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.
|
|
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 = '
|
|
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 = "
|
|
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,
|
|
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[
|
|
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[
|
|
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) ->
|
|
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=
|
|
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:
|
|
180
|
+
def from_dict(cls, data: dict[str, Any]) -> "LlamaStackChatGenerator":
|
|
156
181
|
"""
|
|
157
182
|
Deserialize this component from a dictionary.
|
|
158
183
|
|
{llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/tests/test_llama_stack_chat_generator.py
RENAMED
|
@@ -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={
|
|
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": {
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llama_stack_haystack-1.2.0 → llama_stack_haystack-1.3.0}/examples/llama-stack-with-tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|