Skip to content

Commit d2d5384

Browse files
authored
Python: Add Mistral AI embedding client package (#5480)
* Python: Add Mistral AI embedding client package Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Address review feedback: fix dimensions check, sort embeddings by index, align docs Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Address review feedback: downgrade to alpha, remove integration tests - Change version to 1.0.0a260505 (alpha) - Update classifier to Development Status :: 3 - Alpha - Update PACKAGE_STATUS.md to alpha - Remove Mistral from integration test workflows (no API keys yet) Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Add samples directory for alpha package compliance Per python-package-management skill: alpha packages must include samples inside the package directory. Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Fix ruff formatting in sample file Signed-off-by: Daria Korenieva <daric2612@gmail.com> --------- Signed-off-by: Daria Korenieva <daric2612@gmail.com>
1 parent 1fccf16 commit d2d5384

15 files changed

Lines changed: 2506 additions & 1743 deletions

File tree

python/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ GEMINI_MODEL=""
4444
# Ollama
4545
OLLAMA_ENDPOINT=""
4646
OLLAMA_MODEL=""
47+
# Mistral AI
48+
MISTRAL_API_KEY=""
49+
MISTRAL_EMBEDDING_MODEL=""
4750
# Observability (instrumentation is enabled by default; set "ENABLE_INSTRUMENTATION" to "false" to opt out)
4851
ENABLE_SENSITIVE_DATA=true
4952
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317/"

python/PACKAGE_STATUS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Status is grouped into these buckets:
3737
| `agent-framework-hyperlight` | `python/packages/hyperlight` | `beta` |
3838
| `agent-framework-lab` | `python/packages/lab` | `beta` |
3939
| `agent-framework-mem0` | `python/packages/mem0` | `beta` |
40+
| `agent-framework-mistral` | `python/packages/mistral` | `alpha` |
4041
| `agent-framework-monty` | `python/packages/monty` | `alpha` |
4142
| `agent-framework-ollama` | `python/packages/ollama` | `beta` |
4243
| `agent-framework-openai` | `python/packages/openai` | `released` |

python/packages/mistral/AGENTS.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Mistral Package (agent-framework-mistral)
2+
3+
Integration with Mistral AI for embedding generation.
4+
5+
## Main Classes
6+
7+
- **`MistralEmbeddingClient`** - Embedding client for Mistral AI models
8+
- **`MistralEmbeddingOptions`** - Options TypedDict for Mistral-specific embedding parameters
9+
- **`MistralEmbeddingSettings`** - TypedDict settings for Mistral configuration
10+
11+
## Usage
12+
13+
```python
14+
from agent_framework_mistral import MistralEmbeddingClient
15+
16+
# Requires MISTRAL_API_KEY environment variable (or pass api_key= directly)
17+
client = MistralEmbeddingClient(model="mistral-embed")
18+
result = await client.get_embeddings(["Hello, world!"])
19+
print(result[0].vector)
20+
```
21+
22+
## Import Path
23+
24+
```python
25+
from agent_framework_mistral import MistralEmbeddingClient
26+
```

python/packages/mistral/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE

python/packages/mistral/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Get Started with Microsoft Agent Framework Mistral AI
2+
3+
Please install this package:
4+
5+
```bash
6+
pip install agent-framework-mistral --pre
7+
```
8+
9+
and see the [README](https://github.com/microsoft/agent-framework/tree/main/python/README.md) for more information.
10+
11+
## Embedding Client
12+
13+
The `MistralEmbeddingClient` provides embedding generation using Mistral AI models.
14+
15+
### Quick Start
16+
17+
```python
18+
from agent_framework_mistral import MistralEmbeddingClient
19+
20+
# Using environment variables (MISTRAL_API_KEY, MISTRAL_EMBEDDING_MODEL)
21+
client = MistralEmbeddingClient()
22+
23+
# Or passing parameters directly
24+
client = MistralEmbeddingClient(
25+
model="mistral-embed",
26+
api_key="your-api-key",
27+
)
28+
29+
# Generate embeddings
30+
result = await client.get_embeddings(["Hello, world!", "How are you?"])
31+
for embedding in result:
32+
print(f"Dimensions: {embedding.dimensions}")
33+
print(f"Vector: {embedding.vector[:5]}...")
34+
```
35+
36+
### Configuration
37+
38+
| Environment Variable | Description |
39+
|---|---|
40+
| `MISTRAL_API_KEY` | Your Mistral AI API key |
41+
| `MISTRAL_EMBEDDING_MODEL` | Embedding model name (e.g., `mistral-embed`) |
42+
| `MISTRAL_SERVER_URL` | Optional server URL override |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import importlib.metadata
4+
5+
from ._embedding_client import MistralEmbeddingClient, MistralEmbeddingOptions, MistralEmbeddingSettings
6+
7+
try:
8+
__version__ = importlib.metadata.version(__name__)
9+
except importlib.metadata.PackageNotFoundError:
10+
__version__ = "0.0.0" # Fallback for development mode
11+
12+
__all__ = [
13+
"MistralEmbeddingClient",
14+
"MistralEmbeddingOptions",
15+
"MistralEmbeddingSettings",
16+
"__version__",
17+
]
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
import sys
7+
from collections.abc import Sequence
8+
from typing import Any, ClassVar, Generic, TypedDict
9+
10+
from agent_framework import (
11+
BaseEmbeddingClient,
12+
Embedding,
13+
EmbeddingGenerationOptions,
14+
GeneratedEmbeddings,
15+
UsageDetails,
16+
load_settings,
17+
)
18+
from agent_framework._settings import SecretString
19+
from agent_framework.observability import EmbeddingTelemetryLayer
20+
from mistralai.client import Mistral
21+
22+
if sys.version_info >= (3, 13):
23+
from typing import TypeVar # type: ignore # pragma: no cover
24+
else:
25+
from typing_extensions import TypeVar # type: ignore # pragma: no cover
26+
27+
28+
logger = logging.getLogger("agent_framework.mistral")
29+
30+
31+
class MistralEmbeddingOptions(EmbeddingGenerationOptions, total=False):
32+
"""Mistral AI-specific embedding options.
33+
34+
Extends EmbeddingGenerationOptions with Mistral-specific fields.
35+
36+
Examples:
37+
.. code-block:: python
38+
39+
from agent_framework_mistral import MistralEmbeddingOptions
40+
41+
options: MistralEmbeddingOptions = {
42+
"model": "mistral-embed",
43+
"dimensions": 1024,
44+
}
45+
"""
46+
47+
48+
MistralEmbeddingOptionsT = TypeVar(
49+
"MistralEmbeddingOptionsT",
50+
bound=TypedDict, # type: ignore[valid-type]
51+
default="MistralEmbeddingOptions",
52+
covariant=True,
53+
)
54+
55+
56+
class MistralEmbeddingSettings(TypedDict, total=False):
57+
"""Mistral AI embedding settings.
58+
59+
Fields:
60+
api_key: Mistral API key. Resolved from ``MISTRAL_API_KEY``.
61+
embedding_model: Embedding model name. Resolved from ``MISTRAL_EMBEDDING_MODEL``.
62+
server_url: Optional server URL override. Resolved from ``MISTRAL_SERVER_URL``.
63+
"""
64+
65+
api_key: str | None
66+
embedding_model: str | None
67+
server_url: str | None
68+
69+
70+
class RawMistralEmbeddingClient(
71+
BaseEmbeddingClient[str, list[float], MistralEmbeddingOptionsT],
72+
Generic[MistralEmbeddingOptionsT],
73+
):
74+
"""Raw Mistral AI embedding client without telemetry.
75+
76+
Keyword Args:
77+
model: The Mistral embedding model (e.g. "mistral-embed").
78+
Can also be set via environment variable ``MISTRAL_EMBEDDING_MODEL``.
79+
api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable.
80+
server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL``
81+
environment variable, or the Mistral default.
82+
client: Optional pre-configured ``Mistral`` client instance.
83+
additional_properties: Additional properties stored on the client instance.
84+
env_file_path: Path to ``.env`` file for settings.
85+
env_file_encoding: Encoding for ``.env`` file.
86+
"""
87+
88+
INJECTABLE: ClassVar[set[str]] = {"client"}
89+
90+
def __init__(
91+
self,
92+
*,
93+
model: str | None = None,
94+
api_key: str | SecretString | None = None,
95+
server_url: str | None = None,
96+
client: Mistral | None = None,
97+
additional_properties: dict[str, Any] | None = None,
98+
env_file_path: str | None = None,
99+
env_file_encoding: str | None = None,
100+
) -> None:
101+
"""Initialize a raw Mistral AI embedding client."""
102+
mistral_settings = load_settings(
103+
MistralEmbeddingSettings,
104+
env_prefix="MISTRAL_",
105+
required_fields=["embedding_model", "api_key"],
106+
api_key=str(api_key) if isinstance(api_key, SecretString) else api_key,
107+
embedding_model=model,
108+
server_url=server_url,
109+
env_file_path=env_file_path,
110+
env_file_encoding=env_file_encoding,
111+
)
112+
113+
self.model: str = mistral_settings["embedding_model"] # type: ignore[assignment]
114+
resolved_api_key: str = mistral_settings["api_key"] # type: ignore[assignment]
115+
resolved_server_url = mistral_settings.get("server_url")
116+
117+
if client is not None:
118+
self.client = client
119+
else:
120+
client_kwargs: dict[str, Any] = {"api_key": resolved_api_key}
121+
if resolved_server_url:
122+
client_kwargs["server_url"] = resolved_server_url
123+
self.client = Mistral(**client_kwargs)
124+
125+
self.server_url = resolved_server_url
126+
super().__init__(additional_properties=additional_properties)
127+
128+
def service_url(self) -> str:
129+
"""Get the URL of the service."""
130+
return self.server_url or "https://api.mistral.ai"
131+
132+
async def get_embeddings(
133+
self,
134+
values: Sequence[str],
135+
*,
136+
options: MistralEmbeddingOptionsT | None = None,
137+
) -> GeneratedEmbeddings[list[float], MistralEmbeddingOptionsT]:
138+
"""Call the Mistral AI embeddings API.
139+
140+
Args:
141+
values: The text values to generate embeddings for.
142+
options: Optional embedding generation options.
143+
144+
Returns:
145+
Generated embeddings with usage metadata.
146+
147+
Raises:
148+
ValueError: If model is not provided or values is empty.
149+
"""
150+
if not values:
151+
return GeneratedEmbeddings([], options=options)
152+
153+
opts: dict[str, Any] = options or {} # type: ignore
154+
model = opts.get("model") or self.model
155+
if not model:
156+
raise ValueError("model is required")
157+
158+
kwargs: dict[str, Any] = {"model": model, "inputs": list(values)}
159+
if "dimensions" in opts:
160+
kwargs["output_dimension"] = opts["dimensions"]
161+
162+
response = await self.client.embeddings.create_async(**kwargs)
163+
164+
embeddings: list[Embedding[list[float]]] = []
165+
if response and response.data:
166+
items = sorted(response.data, key=lambda d: d.index if d.index is not None else 0)
167+
for item in items:
168+
vector = list(item.embedding) if item.embedding else []
169+
embeddings.append(
170+
Embedding(
171+
vector=vector,
172+
dimensions=len(vector),
173+
model=response.model or model,
174+
)
175+
)
176+
177+
usage_dict: UsageDetails | None = None
178+
if response and response.usage:
179+
usage_dict = {
180+
"input_token_count": response.usage.prompt_tokens,
181+
"total_token_count": response.usage.total_tokens,
182+
}
183+
184+
return GeneratedEmbeddings(embeddings, options=options, usage=usage_dict)
185+
186+
187+
class MistralEmbeddingClient(
188+
EmbeddingTelemetryLayer[str, list[float], MistralEmbeddingOptionsT],
189+
RawMistralEmbeddingClient[MistralEmbeddingOptionsT],
190+
Generic[MistralEmbeddingOptionsT],
191+
):
192+
"""Mistral AI embedding client with telemetry support.
193+
194+
Keyword Args:
195+
model: The Mistral embedding model (e.g. "mistral-embed").
196+
Can also be set via environment variable ``MISTRAL_EMBEDDING_MODEL``.
197+
api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable.
198+
server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL``
199+
environment variable, or the Mistral default.
200+
client: Optional pre-configured ``Mistral`` client instance.
201+
otel_provider_name: Optional telemetry provider name override.
202+
env_file_path: Path to ``.env`` file for settings.
203+
env_file_encoding: Encoding for ``.env`` file.
204+
205+
Examples:
206+
.. code-block:: python
207+
208+
from agent_framework_mistral import MistralEmbeddingClient
209+
210+
# Using environment variables
211+
# Set MISTRAL_API_KEY=your-key
212+
# Set MISTRAL_EMBEDDING_MODEL=mistral-embed
213+
client = MistralEmbeddingClient()
214+
215+
# Or passing parameters directly
216+
client = MistralEmbeddingClient(
217+
model="mistral-embed",
218+
api_key="your-api-key",
219+
)
220+
221+
# Generate embeddings
222+
result = await client.get_embeddings(["Hello, world!"])
223+
print(result[0].vector)
224+
"""
225+
226+
OTEL_PROVIDER_NAME: ClassVar[str] = "mistralai"
227+
228+
def __init__(
229+
self,
230+
*,
231+
model: str | None = None,
232+
api_key: str | SecretString | None = None,
233+
server_url: str | None = None,
234+
client: Mistral | None = None,
235+
otel_provider_name: str | None = None,
236+
additional_properties: dict[str, Any] | None = None,
237+
env_file_path: str | None = None,
238+
env_file_encoding: str | None = None,
239+
) -> None:
240+
"""Initialize a Mistral AI embedding client."""
241+
super().__init__(
242+
model=model,
243+
api_key=api_key,
244+
server_url=server_url,
245+
client=client,
246+
additional_properties=additional_properties,
247+
otel_provider_name=otel_provider_name,
248+
env_file_path=env_file_path,
249+
env_file_encoding=env_file_encoding,
250+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)