mistral-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.
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/CHANGELOG.md +7 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/PKG-INFO +2 -1
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/pyproject.toml +15 -2
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/converters/mistral/ocr_document_converter.py +4 -3
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/embedders/mistral/document_embedder.py +2 -1
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/embedders/mistral/text_embedder.py +1 -1
- mistral_haystack-1.3.0/src/haystack_integrations/components/generators/mistral/chat/chat_generator.py +506 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/test_mistral_chat_generator.py +411 -22
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/test_mistral_chat_generator_async.py +109 -24
- mistral_haystack-1.2.0/src/haystack_integrations/components/generators/mistral/chat/chat_generator.py +0 -248
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/.gitignore +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/LICENSE.txt +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/README.md +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/examples/chat_generator_with_structured_output.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/examples/indexing_ocr_pipeline.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/examples/indexing_pipeline.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/examples/streaming_chat_with_rag.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/pydoc/config_docusaurus.yml +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/converters/mistral/__init__.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/converters/py.typed +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/embedders/mistral/__init__.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/generators/mistral/__init__.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/generators/mistral/chat/__init__.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/__init__.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/test_mistral_document_embedder.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/test_mistral_text_embedder.py +0 -0
- {mistral_haystack-1.2.0 → mistral_haystack-1.3.0}/tests/test_ocr_document_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mistral-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/mistral#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/mistral
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
19
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
20
|
Requires-Python: >=3.10
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
22
23
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
24
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
25
|
]
|
|
@@ -64,7 +65,8 @@ dependencies = [
|
|
|
64
65
|
unit = 'pytest -m "not integration" {args:tests}'
|
|
65
66
|
integration = 'pytest -m "integration" {args:tests}'
|
|
66
67
|
all = 'pytest {args:tests}'
|
|
67
|
-
cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
|
|
68
|
+
unit-cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x -m "not integration" {args:tests}'
|
|
69
|
+
integration-cov-append-retry = 'pytest --cov=haystack_integrations --cov-append --reruns 3 --reruns-delay 30 -x -m "integration" {args:tests}'
|
|
68
70
|
|
|
69
71
|
types = """mypy -p haystack_integrations.components.embedders.mistral \
|
|
70
72
|
-p haystack_integrations.components.generators.mistral \
|
|
@@ -82,9 +84,17 @@ line-length = 120
|
|
|
82
84
|
[tool.ruff.lint]
|
|
83
85
|
select = [
|
|
84
86
|
"A",
|
|
87
|
+
"ANN",
|
|
85
88
|
"ARG",
|
|
86
89
|
"B",
|
|
87
90
|
"C",
|
|
91
|
+
"D102", # Missing docstring in public method
|
|
92
|
+
"D103", # Missing docstring in public function
|
|
93
|
+
"D205", # 1 blank line required between summary line and description
|
|
94
|
+
"D209", # Closing triple quotes go to new line
|
|
95
|
+
"D213", # summary lines must be positioned on the second physical line of the docstring
|
|
96
|
+
"D417", # Missing argument descriptions in the docstring
|
|
97
|
+
"D419", # Docstring is empty
|
|
88
98
|
"DTZ",
|
|
89
99
|
"E",
|
|
90
100
|
"EM",
|
|
@@ -122,6 +132,8 @@ ignore = [
|
|
|
122
132
|
# Misc
|
|
123
133
|
"B008",
|
|
124
134
|
"S101",
|
|
135
|
+
# Allow `Any` - used legitimately for dynamic types and SDK boundaries
|
|
136
|
+
"ANN401",
|
|
125
137
|
]
|
|
126
138
|
|
|
127
139
|
[tool.ruff.lint.isort]
|
|
@@ -132,11 +144,12 @@ ban-relative-imports = "parents"
|
|
|
132
144
|
|
|
133
145
|
[tool.ruff.lint.per-file-ignores]
|
|
134
146
|
# Tests can use magic values, assertions, and relative imports
|
|
135
|
-
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
|
147
|
+
"tests/**/*" = ["D", "PLR2004", "S101", "TID252", "ANN"]
|
|
136
148
|
|
|
137
149
|
[tool.coverage.run]
|
|
138
150
|
source = ["haystack_integrations"]
|
|
139
151
|
branch = true
|
|
152
|
+
relative_files = true
|
|
140
153
|
parallel = false
|
|
141
154
|
|
|
142
155
|
|
|
@@ -26,8 +26,9 @@ logger = logging.getLogger(__name__)
|
|
|
26
26
|
@component
|
|
27
27
|
class MistralOCRDocumentConverter:
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
Extract text from documents using Mistral's OCR API with optional structured annotations.
|
|
30
|
+
|
|
31
|
+
Supports optional structured annotations for individual image regions (bounding boxes) and full documents.
|
|
31
32
|
|
|
32
33
|
Accepts document sources in various formats (str/Path for local files, ByteStream for in-memory data,
|
|
33
34
|
DocumentURLChunk for document URLs, ImageURLChunk for image URLs, or FileChunk for Mistral file IDs)
|
|
@@ -117,7 +118,7 @@ class MistralOCRDocumentConverter:
|
|
|
117
118
|
image_limit: int | None = None,
|
|
118
119
|
image_min_size: int | None = None,
|
|
119
120
|
cleanup_uploaded_files: bool = True,
|
|
120
|
-
):
|
|
121
|
+
) -> None:
|
|
121
122
|
"""
|
|
122
123
|
Creates a MistralOCRDocumentConverter component.
|
|
123
124
|
|
|
@@ -12,6 +12,7 @@ from haystack.utils.auth import Secret
|
|
|
12
12
|
class MistralDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
13
13
|
"""
|
|
14
14
|
A component for computing Document embeddings using Mistral models.
|
|
15
|
+
|
|
15
16
|
The embedding of each Document is stored in the `embedding` field of the Document.
|
|
16
17
|
|
|
17
18
|
Usage example:
|
|
@@ -55,7 +56,7 @@ class MistralDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
|
55
56
|
timeout: float | None = None,
|
|
56
57
|
max_retries: int | None = None,
|
|
57
58
|
http_client_kwargs: dict[str, Any] | None = None,
|
|
58
|
-
):
|
|
59
|
+
) -> None:
|
|
59
60
|
"""
|
|
60
61
|
Creates a MistralDocumentEmbedder component.
|
|
61
62
|
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, ClassVar
|
|
7
|
+
|
|
8
|
+
from haystack import component, default_to_dict, logging
|
|
9
|
+
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
10
|
+
from haystack.components.generators.chat.openai import _check_finish_reason
|
|
11
|
+
from haystack.dataclasses import (
|
|
12
|
+
ChatMessage,
|
|
13
|
+
ReasoningContent,
|
|
14
|
+
StreamingCallbackT,
|
|
15
|
+
ToolCall,
|
|
16
|
+
select_streaming_callback,
|
|
17
|
+
)
|
|
18
|
+
from haystack.tools import ToolsType, serialize_tools_or_toolset
|
|
19
|
+
from haystack.utils import serialize_callable
|
|
20
|
+
from haystack.utils.auth import Secret
|
|
21
|
+
from openai.lib._pydantic import to_strict_json_schema
|
|
22
|
+
from pydantic import BaseModel
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_mistral_content(content: Any) -> tuple[str | None, ReasoningContent | None]:
|
|
28
|
+
"""Parse Mistral message content which can be a string or an array of typed blocks."""
|
|
29
|
+
if content is None:
|
|
30
|
+
return None, None
|
|
31
|
+
if isinstance(content, str):
|
|
32
|
+
return content or None, None
|
|
33
|
+
if not isinstance(content, list):
|
|
34
|
+
return str(content), None
|
|
35
|
+
|
|
36
|
+
text_parts: list[str] = []
|
|
37
|
+
thinking_parts: list[str] = []
|
|
38
|
+
|
|
39
|
+
for block in content:
|
|
40
|
+
if not isinstance(block, dict):
|
|
41
|
+
continue
|
|
42
|
+
block_type = block.get("type", "")
|
|
43
|
+
if block_type == "thinking":
|
|
44
|
+
for item in block.get("thinking", []):
|
|
45
|
+
if isinstance(item, dict) and item.get("type") == "text":
|
|
46
|
+
thinking_parts.append(item.get("text", ""))
|
|
47
|
+
elif block_type == "text":
|
|
48
|
+
text_parts.append(block.get("text", ""))
|
|
49
|
+
|
|
50
|
+
text = "".join(text_parts) or None
|
|
51
|
+
reasoning = None
|
|
52
|
+
if thinking_parts:
|
|
53
|
+
reasoning = ReasoningContent(reasoning_text="".join(thinking_parts))
|
|
54
|
+
|
|
55
|
+
return text, reasoning
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _convert_mistral_response_to_chat_messages(response_data: dict[str, Any] | str) -> list[ChatMessage]:
|
|
59
|
+
"""Convert a raw Mistral API JSON response to a list of ChatMessages, handling array content."""
|
|
60
|
+
data: dict[str, Any] = json.loads(response_data) if isinstance(response_data, str) else response_data
|
|
61
|
+
completions: list[ChatMessage] = []
|
|
62
|
+
usage = data.get("usage")
|
|
63
|
+
model = data.get("model", "")
|
|
64
|
+
|
|
65
|
+
for choice in data.get("choices", []):
|
|
66
|
+
message = choice.get("message", {})
|
|
67
|
+
text, reasoning = _parse_mistral_content(message.get("content"))
|
|
68
|
+
|
|
69
|
+
tool_calls: list[ToolCall] = []
|
|
70
|
+
for tc in message.get("tool_calls") or []:
|
|
71
|
+
func = tc.get("function", {})
|
|
72
|
+
try:
|
|
73
|
+
arguments = json.loads(func.get("arguments", "{}"))
|
|
74
|
+
tool_calls.append(ToolCall(id=tc.get("id"), tool_name=func.get("name"), arguments=arguments))
|
|
75
|
+
except json.JSONDecodeError:
|
|
76
|
+
logger.warning(
|
|
77
|
+
"Mistral returned malformed JSON for tool call arguments. "
|
|
78
|
+
"Tool call ID: {_id}, Tool name: {_name}, Arguments: {_arguments}",
|
|
79
|
+
_id=tc.get("id"),
|
|
80
|
+
_name=func.get("name"),
|
|
81
|
+
_arguments=func.get("arguments"),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
meta: dict[str, Any] = {
|
|
85
|
+
"model": model,
|
|
86
|
+
"index": choice.get("index", 0),
|
|
87
|
+
"finish_reason": choice.get("finish_reason"),
|
|
88
|
+
"usage": usage,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
completions.append(ChatMessage.from_assistant(text=text, tool_calls=tool_calls, meta=meta, reasoning=reasoning))
|
|
92
|
+
|
|
93
|
+
return completions
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@component
|
|
97
|
+
class MistralChatGenerator(OpenAIChatGenerator):
|
|
98
|
+
"""
|
|
99
|
+
Enables text generation using Mistral AI generative models.
|
|
100
|
+
|
|
101
|
+
For supported models, see [Mistral AI docs](https://docs.mistral.ai/getting-started/models).
|
|
102
|
+
|
|
103
|
+
Users can pass any text generation parameters valid for the Mistral Chat Completion API
|
|
104
|
+
directly to this component via the `generation_kwargs` parameter in `__init__` or the `generation_kwargs`
|
|
105
|
+
parameter in `run` method.
|
|
106
|
+
|
|
107
|
+
Key Features and Compatibility:
|
|
108
|
+
- **Primary Compatibility**: Compatible with the Mistral API Chat Completion endpoint.
|
|
109
|
+
- **Streaming Support**: Supports streaming responses from the Mistral API Chat Completion endpoint.
|
|
110
|
+
- **Customizability**: Supports all parameters supported by the Mistral API Chat Completion endpoint.
|
|
111
|
+
- **Reasoning Support**: Extracts reasoning/thinking content from models that support it
|
|
112
|
+
(e.g., mistral-small with `reasoning_effort`, magistral models) and stores it in the
|
|
113
|
+
`ReasoningContent` field on `ChatMessage`.
|
|
114
|
+
|
|
115
|
+
This component uses the ChatMessage format for structuring both input and output,
|
|
116
|
+
ensuring coherent and contextually relevant responses in chat-based text generation scenarios.
|
|
117
|
+
Details on the ChatMessage format can be found in the
|
|
118
|
+
[Haystack docs](https://docs.haystack.deepset.ai/docs/data-classes#chatmessage)
|
|
119
|
+
|
|
120
|
+
For more details on the parameters supported by the Mistral API, refer to the
|
|
121
|
+
[Mistral API Docs](https://docs.mistral.ai/api/).
|
|
122
|
+
|
|
123
|
+
Usage example:
|
|
124
|
+
```python
|
|
125
|
+
from haystack_integrations.components.generators.mistral import MistralChatGenerator
|
|
126
|
+
from haystack.dataclasses import ChatMessage
|
|
127
|
+
|
|
128
|
+
messages = [ChatMessage.from_user("What's Natural Language Processing?")]
|
|
129
|
+
|
|
130
|
+
client = MistralChatGenerator()
|
|
131
|
+
response = client.run(messages)
|
|
132
|
+
print(response)
|
|
133
|
+
|
|
134
|
+
>>{'replies': [ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>, _content=[TextContent(text=
|
|
135
|
+
>> "Natural Language Processing (NLP) is a branch of artificial intelligence
|
|
136
|
+
>> that focuses on enabling computers to understand, interpret, and generate human language in a way that is
|
|
137
|
+
>> meaningful and useful.")], _name=None,
|
|
138
|
+
>> _meta={'model': 'mistral-small-latest', 'index': 0, 'finish_reason': 'stop',
|
|
139
|
+
>> 'usage': {'prompt_tokens': 15, 'completion_tokens': 36, 'total_tokens': 51}})]}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Reasoning usage example:
|
|
143
|
+
```python
|
|
144
|
+
from haystack_integrations.components.generators.mistral import MistralChatGenerator
|
|
145
|
+
from haystack.dataclasses import ChatMessage
|
|
146
|
+
|
|
147
|
+
messages = [ChatMessage.from_user("Solve: if x + 3 = 7, what is x?")]
|
|
148
|
+
|
|
149
|
+
client = MistralChatGenerator(
|
|
150
|
+
model="mistral-small-latest",
|
|
151
|
+
generation_kwargs={"reasoning_effort": "high"},
|
|
152
|
+
)
|
|
153
|
+
response = client.run(messages)
|
|
154
|
+
print(response["replies"][0].reasoning) # Access reasoning content
|
|
155
|
+
print(response["replies"][0].text) # Access final answer
|
|
156
|
+
```
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
SUPPORTED_MODELS: ClassVar[list[str]] = [
|
|
160
|
+
"mistral-medium-2505",
|
|
161
|
+
"mistral-medium-2508",
|
|
162
|
+
"mistral-medium-latest",
|
|
163
|
+
"mistral-medium",
|
|
164
|
+
"mistral-vibe-cli-with-tools",
|
|
165
|
+
"open-mistral-nemo",
|
|
166
|
+
"open-mistral-nemo-2407",
|
|
167
|
+
"mistral-tiny-2407",
|
|
168
|
+
"mistral-tiny-latest",
|
|
169
|
+
"codestral-2508",
|
|
170
|
+
"codestral-latest",
|
|
171
|
+
"devstral-2512",
|
|
172
|
+
"mistral-vibe-cli-latest",
|
|
173
|
+
"devstral-medium-latest",
|
|
174
|
+
"devstral-latest",
|
|
175
|
+
"mistral-small-2506",
|
|
176
|
+
"mistral-small-latest",
|
|
177
|
+
"labs-mistral-small-creative",
|
|
178
|
+
"magistral-medium-2509",
|
|
179
|
+
"magistral-medium-latest",
|
|
180
|
+
"magistral-small-2509",
|
|
181
|
+
"magistral-small-latest",
|
|
182
|
+
"voxtral-small-2507",
|
|
183
|
+
"voxtral-small-latest",
|
|
184
|
+
"mistral-large-2512",
|
|
185
|
+
"mistral-large-latest",
|
|
186
|
+
"ministral-3b-2512",
|
|
187
|
+
"ministral-3b-latest",
|
|
188
|
+
"ministral-8b-2512",
|
|
189
|
+
"ministral-8b-latest",
|
|
190
|
+
"ministral-14b-2512",
|
|
191
|
+
"ministral-14b-latest",
|
|
192
|
+
"mistral-large-2411",
|
|
193
|
+
"pixtral-large-2411",
|
|
194
|
+
"pixtral-large-latest",
|
|
195
|
+
"mistral-large-pixtral-2411",
|
|
196
|
+
"devstral-small-2507",
|
|
197
|
+
"devstral-medium-2507",
|
|
198
|
+
"labs-devstral-small-2512",
|
|
199
|
+
"devstral-small-latest",
|
|
200
|
+
"voxtral-mini-2507",
|
|
201
|
+
"voxtral-mini-latest",
|
|
202
|
+
"voxtral-mini-2602",
|
|
203
|
+
]
|
|
204
|
+
"""A list of models supported by Mistral AI
|
|
205
|
+
see [Mistral AI docs](https://docs.mistral.ai/getting-started/models) for more information
|
|
206
|
+
and send a GET HTTP request to "https://api.mistral.ai/v1/models" for a full list of model IDs."""
|
|
207
|
+
|
|
208
|
+
def __init__(
|
|
209
|
+
self,
|
|
210
|
+
api_key: Secret = Secret.from_env_var("MISTRAL_API_KEY"),
|
|
211
|
+
model: str = "mistral-small-latest",
|
|
212
|
+
streaming_callback: StreamingCallbackT | None = None,
|
|
213
|
+
api_base_url: str | None = "https://api.mistral.ai/v1",
|
|
214
|
+
generation_kwargs: dict[str, Any] | None = None,
|
|
215
|
+
tools: ToolsType | None = None,
|
|
216
|
+
*,
|
|
217
|
+
timeout: float | None = None,
|
|
218
|
+
max_retries: int | None = None,
|
|
219
|
+
http_client_kwargs: dict[str, Any] | None = None,
|
|
220
|
+
) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Creates an instance of MistralChatGenerator.
|
|
223
|
+
|
|
224
|
+
Unless specified otherwise in the `model`, this is for Mistral's `mistral-small-latest` model.
|
|
225
|
+
|
|
226
|
+
:param api_key:
|
|
227
|
+
The Mistral API key.
|
|
228
|
+
:param model:
|
|
229
|
+
The name of the Mistral chat completion model to use.
|
|
230
|
+
:param streaming_callback:
|
|
231
|
+
A callback function that is called when a new token is received from the stream.
|
|
232
|
+
The callback function accepts StreamingChunk as an argument.
|
|
233
|
+
:param api_base_url:
|
|
234
|
+
The Mistral API Base url.
|
|
235
|
+
For more details, see Mistral [docs](https://docs.mistral.ai/api/).
|
|
236
|
+
:param generation_kwargs:
|
|
237
|
+
Other parameters to use for the model. These parameters are all sent directly to
|
|
238
|
+
the Mistral endpoint. See [Mistral API docs](https://docs.mistral.ai/api/) for more details.
|
|
239
|
+
Some of the supported parameters:
|
|
240
|
+
- `max_tokens`: The maximum number of tokens the output text can have.
|
|
241
|
+
- `temperature`: What sampling temperature to use. Higher values mean the model will take more risks.
|
|
242
|
+
Try 0.9 for more creative applications and 0 (argmax sampling) for ones with a well-defined answer.
|
|
243
|
+
- `top_p`: An alternative to sampling with temperature, called nucleus sampling, where the model
|
|
244
|
+
considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens
|
|
245
|
+
comprising the top 10% probability mass are considered.
|
|
246
|
+
- `stream`: Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent
|
|
247
|
+
events as they become available, with the stream terminated by a data: [DONE] message.
|
|
248
|
+
- `safe_prompt`: Whether to inject a safety prompt before all conversations.
|
|
249
|
+
- `random_seed`: The seed to use for random sampling.
|
|
250
|
+
- `reasoning_effort`: Controls reasoning/thinking tokens for models that support adjustable reasoning
|
|
251
|
+
(e.g., `mistral-small-latest`, `mistral-medium`). Accepted values: `"high"`, `"none"`.
|
|
252
|
+
See [Mistral reasoning docs](https://docs.mistral.ai/capabilities/reasoning/).
|
|
253
|
+
- `prompt_mode`: For native reasoning models (magistral). Set to `"reasoning"` to use the default
|
|
254
|
+
reasoning system prompt, or omit for the model's default behavior.
|
|
255
|
+
- `response_format`: A JSON schema or a Pydantic model that enforces the structure of the model's response.
|
|
256
|
+
If provided, the output will always be validated against this
|
|
257
|
+
format (unless the model returns a tool call).
|
|
258
|
+
For details, see the [OpenAI Structured Outputs documentation](https://platform.openai.com/docs/guides/structured-outputs).
|
|
259
|
+
Notes:
|
|
260
|
+
- For structured outputs with streaming,
|
|
261
|
+
the `response_format` must be a JSON schema and not a Pydantic model.
|
|
262
|
+
:param tools:
|
|
263
|
+
A list of Tool and/or Toolset objects, or a single Toolset for which the model can prepare calls.
|
|
264
|
+
Each tool should have a unique name.
|
|
265
|
+
:param timeout:
|
|
266
|
+
The timeout for the Mistral API call. If not set, it defaults to either the `OPENAI_TIMEOUT`
|
|
267
|
+
environment variable, or 30 seconds.
|
|
268
|
+
:param max_retries:
|
|
269
|
+
Maximum number of retries to contact OpenAI after an internal error.
|
|
270
|
+
If not set, it defaults to either the `OPENAI_MAX_RETRIES` environment variable, or set to 5.
|
|
271
|
+
:param http_client_kwargs:
|
|
272
|
+
A dictionary of keyword arguments to configure a custom `httpx.Client`or `httpx.AsyncClient`.
|
|
273
|
+
For more information, see the [HTTPX documentation](https://www.python-httpx.org/api/#client).
|
|
274
|
+
"""
|
|
275
|
+
super(MistralChatGenerator, self).__init__( # noqa: UP008
|
|
276
|
+
api_key=api_key,
|
|
277
|
+
model=model,
|
|
278
|
+
streaming_callback=streaming_callback,
|
|
279
|
+
api_base_url=api_base_url,
|
|
280
|
+
organization=None,
|
|
281
|
+
generation_kwargs=generation_kwargs,
|
|
282
|
+
tools=tools,
|
|
283
|
+
timeout=timeout,
|
|
284
|
+
max_retries=max_retries,
|
|
285
|
+
http_client_kwargs=http_client_kwargs,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def _prepare_api_call(
|
|
289
|
+
self,
|
|
290
|
+
*,
|
|
291
|
+
messages: list[ChatMessage],
|
|
292
|
+
streaming_callback: StreamingCallbackT | None = None,
|
|
293
|
+
generation_kwargs: dict[str, Any] | None = None,
|
|
294
|
+
tools: ToolsType | None = None,
|
|
295
|
+
tools_strict: bool | None = None,
|
|
296
|
+
) -> dict[str, Any]:
|
|
297
|
+
api_args = super(MistralChatGenerator, self)._prepare_api_call( # noqa: UP008
|
|
298
|
+
messages=messages,
|
|
299
|
+
streaming_callback=streaming_callback,
|
|
300
|
+
generation_kwargs=generation_kwargs,
|
|
301
|
+
tools=tools,
|
|
302
|
+
tools_strict=tools_strict,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if "response_format" in api_args and api_args["response_format"] is None:
|
|
306
|
+
api_args.pop("response_format")
|
|
307
|
+
|
|
308
|
+
extra_body: dict[str, Any] = {}
|
|
309
|
+
for param in ("reasoning_effort", "prompt_mode", "safe_prompt"):
|
|
310
|
+
if param in api_args:
|
|
311
|
+
extra_body[param] = api_args.pop(param)
|
|
312
|
+
if extra_body:
|
|
313
|
+
api_args.setdefault("extra_body", {}).update(extra_body)
|
|
314
|
+
|
|
315
|
+
for i, chat_msg in enumerate(messages):
|
|
316
|
+
if chat_msg.reasoning and chat_msg.reasoning.reasoning_text:
|
|
317
|
+
formatted = api_args["messages"][i]
|
|
318
|
+
text_content = formatted.get("content", "") or ""
|
|
319
|
+
formatted["content"] = [
|
|
320
|
+
{"type": "thinking", "thinking": [{"type": "text", "text": chat_msg.reasoning.reasoning_text}]},
|
|
321
|
+
{"type": "text", "text": text_content},
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
return api_args
|
|
325
|
+
|
|
326
|
+
@component.output_types(replies=list[ChatMessage])
|
|
327
|
+
def run(
|
|
328
|
+
self,
|
|
329
|
+
messages: list[ChatMessage],
|
|
330
|
+
streaming_callback: StreamingCallbackT | None = None,
|
|
331
|
+
generation_kwargs: dict[str, Any] | None = None,
|
|
332
|
+
*,
|
|
333
|
+
tools: ToolsType | None = None,
|
|
334
|
+
tools_strict: bool | None = None,
|
|
335
|
+
) -> dict[str, list[ChatMessage]]:
|
|
336
|
+
"""
|
|
337
|
+
Invokes chat completion on the Mistral API.
|
|
338
|
+
|
|
339
|
+
:param messages:
|
|
340
|
+
A list of ChatMessage instances representing the input messages.
|
|
341
|
+
:param streaming_callback:
|
|
342
|
+
A callback function that is called when a new token is received from the stream.
|
|
343
|
+
:param generation_kwargs:
|
|
344
|
+
Additional keyword arguments for text generation. These parameters will
|
|
345
|
+
override the parameters passed during component initialization.
|
|
346
|
+
For details on Mistral API parameters, see
|
|
347
|
+
[Mistral docs](https://docs.mistral.ai/api/).
|
|
348
|
+
:param tools: A list of Tool and/or Toolset objects, or a single Toolset for which the model can prepare calls.
|
|
349
|
+
If set, it will override the `tools` parameter provided during initialization.
|
|
350
|
+
:param tools_strict:
|
|
351
|
+
Whether to enable strict schema adherence for tool calls.
|
|
352
|
+
|
|
353
|
+
:returns:
|
|
354
|
+
A dictionary with the following key:
|
|
355
|
+
- `replies`: A list containing the generated responses as ChatMessage instances.
|
|
356
|
+
"""
|
|
357
|
+
if not self._is_warmed_up:
|
|
358
|
+
self.warm_up()
|
|
359
|
+
|
|
360
|
+
if len(messages) == 0:
|
|
361
|
+
return {"replies": []}
|
|
362
|
+
|
|
363
|
+
streaming_callback = select_streaming_callback(
|
|
364
|
+
init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=False
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if streaming_callback is not None:
|
|
368
|
+
merged_kwargs = {**self.generation_kwargs, **(generation_kwargs or {})}
|
|
369
|
+
if merged_kwargs.get("reasoning_effort") or merged_kwargs.get("prompt_mode"):
|
|
370
|
+
logger.warning(
|
|
371
|
+
"Streaming with reasoning parameters is active. Reasoning content from thinking "
|
|
372
|
+
"blocks will not be captured during streaming. Use non-streaming mode to extract "
|
|
373
|
+
"reasoning content."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
api_args = self._prepare_api_call(
|
|
377
|
+
messages=messages,
|
|
378
|
+
streaming_callback=streaming_callback,
|
|
379
|
+
generation_kwargs=generation_kwargs,
|
|
380
|
+
tools=tools,
|
|
381
|
+
tools_strict=tools_strict,
|
|
382
|
+
)
|
|
383
|
+
openai_endpoint = api_args.pop("openai_endpoint")
|
|
384
|
+
|
|
385
|
+
if streaming_callback is not None:
|
|
386
|
+
chat_completion = getattr(self.client.chat.completions, openai_endpoint)(**api_args)
|
|
387
|
+
completions = self._handle_stream_response(chat_completion, streaming_callback)
|
|
388
|
+
else:
|
|
389
|
+
raw_response = getattr(self.client.chat.completions.with_raw_response, openai_endpoint)(**api_args)
|
|
390
|
+
completions = _convert_mistral_response_to_chat_messages(raw_response.text)
|
|
391
|
+
|
|
392
|
+
for message in completions:
|
|
393
|
+
_check_finish_reason(message.meta)
|
|
394
|
+
|
|
395
|
+
return {"replies": completions}
|
|
396
|
+
|
|
397
|
+
@component.output_types(replies=list[ChatMessage])
|
|
398
|
+
async def run_async(
|
|
399
|
+
self,
|
|
400
|
+
messages: list[ChatMessage],
|
|
401
|
+
streaming_callback: StreamingCallbackT | None = None,
|
|
402
|
+
generation_kwargs: dict[str, Any] | None = None,
|
|
403
|
+
*,
|
|
404
|
+
tools: ToolsType | None = None,
|
|
405
|
+
tools_strict: bool | None = None,
|
|
406
|
+
) -> dict[str, list[ChatMessage]]:
|
|
407
|
+
"""
|
|
408
|
+
Asynchronously invokes chat completion on the Mistral API.
|
|
409
|
+
|
|
410
|
+
:param messages:
|
|
411
|
+
A list of ChatMessage instances representing the input messages.
|
|
412
|
+
:param streaming_callback:
|
|
413
|
+
A callback function that is called when a new token is received from the stream.
|
|
414
|
+
Must be a coroutine.
|
|
415
|
+
:param generation_kwargs:
|
|
416
|
+
Additional keyword arguments for text generation.
|
|
417
|
+
:param tools: A list of Tool and/or Toolset objects, or a single Toolset.
|
|
418
|
+
:param tools_strict:
|
|
419
|
+
Whether to enable strict schema adherence for tool calls.
|
|
420
|
+
|
|
421
|
+
:returns:
|
|
422
|
+
A dictionary with the following key:
|
|
423
|
+
- `replies`: A list containing the generated responses as ChatMessage instances.
|
|
424
|
+
"""
|
|
425
|
+
if not self._is_warmed_up:
|
|
426
|
+
self.warm_up()
|
|
427
|
+
|
|
428
|
+
if len(messages) == 0:
|
|
429
|
+
return {"replies": []}
|
|
430
|
+
|
|
431
|
+
streaming_callback = select_streaming_callback(
|
|
432
|
+
init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=True
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if streaming_callback is not None:
|
|
436
|
+
merged_kwargs = {**self.generation_kwargs, **(generation_kwargs or {})}
|
|
437
|
+
if merged_kwargs.get("reasoning_effort") or merged_kwargs.get("prompt_mode"):
|
|
438
|
+
logger.warning(
|
|
439
|
+
"Streaming with reasoning parameters is active. Reasoning content from thinking "
|
|
440
|
+
"blocks will not be captured during streaming. Use non-streaming mode to extract "
|
|
441
|
+
"reasoning content."
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
api_args = self._prepare_api_call(
|
|
445
|
+
messages=messages,
|
|
446
|
+
streaming_callback=streaming_callback,
|
|
447
|
+
generation_kwargs=generation_kwargs,
|
|
448
|
+
tools=tools,
|
|
449
|
+
tools_strict=tools_strict,
|
|
450
|
+
)
|
|
451
|
+
openai_endpoint = api_args.pop("openai_endpoint")
|
|
452
|
+
|
|
453
|
+
if streaming_callback is not None:
|
|
454
|
+
chat_completion = await getattr(self.async_client.chat.completions, openai_endpoint)(**api_args)
|
|
455
|
+
completions = await self._handle_async_stream_response(chat_completion, streaming_callback)
|
|
456
|
+
else:
|
|
457
|
+
raw_response = await getattr(self.async_client.chat.completions.with_raw_response, openai_endpoint)(
|
|
458
|
+
**api_args
|
|
459
|
+
)
|
|
460
|
+
completions = _convert_mistral_response_to_chat_messages(raw_response.text)
|
|
461
|
+
|
|
462
|
+
for message in completions:
|
|
463
|
+
_check_finish_reason(message.meta)
|
|
464
|
+
|
|
465
|
+
return {"replies": completions}
|
|
466
|
+
|
|
467
|
+
def to_dict(self) -> dict[str, Any]:
|
|
468
|
+
"""
|
|
469
|
+
Serialize this component to a dictionary.
|
|
470
|
+
|
|
471
|
+
:returns:
|
|
472
|
+
The serialized component as a dictionary.
|
|
473
|
+
"""
|
|
474
|
+
callback_name = serialize_callable(self.streaming_callback) if self.streaming_callback else None
|
|
475
|
+
generation_kwargs = self.generation_kwargs.copy()
|
|
476
|
+
response_format = generation_kwargs.get("response_format")
|
|
477
|
+
|
|
478
|
+
# If the response format is a Pydantic model, it's converted to openai's json schema format
|
|
479
|
+
# If it's already a json schema, it's left as is
|
|
480
|
+
if response_format and isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
481
|
+
json_schema = {
|
|
482
|
+
"type": "json_schema",
|
|
483
|
+
"json_schema": {
|
|
484
|
+
"name": response_format.__name__,
|
|
485
|
+
"strict": True,
|
|
486
|
+
"schema": to_strict_json_schema(response_format),
|
|
487
|
+
},
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
generation_kwargs["response_format"] = json_schema
|
|
491
|
+
|
|
492
|
+
# if we didn't implement the to_dict method here then the to_dict method of the superclass would be used
|
|
493
|
+
# which would serialiaze some fields that we don't want to serialize (e.g. the ones we don't have in
|
|
494
|
+
# the __init__)
|
|
495
|
+
return default_to_dict(
|
|
496
|
+
self,
|
|
497
|
+
model=self.model,
|
|
498
|
+
streaming_callback=callback_name,
|
|
499
|
+
api_base_url=self.api_base_url,
|
|
500
|
+
generation_kwargs=generation_kwargs,
|
|
501
|
+
api_key=self.api_key.to_dict(),
|
|
502
|
+
tools=serialize_tools_or_toolset(self.tools),
|
|
503
|
+
timeout=self.timeout,
|
|
504
|
+
max_retries=self.max_retries,
|
|
505
|
+
http_client_kwargs=self.http_client_kwargs,
|
|
506
|
+
)
|