Python: Add Mistral chat client (#7392)

* feat(python): add Mistral chat client

Implements native Mistral support (#7366) with streaming, tool calling,
and structured output. Talks to the REST API directly over httpx: the
mistralai SDK's pinned OpenTelemetry deps conflict with the workspace.

* refactor(python): simplify Mistral client per review

Drop the streamed tool-call accumulator and multi-choice parsing in
favor of the framework's built-in fragment merging, mark n unsupported,
omit unset strict from json_schema, and leave CI secret wiring to
maintainers.

* test(python): drop n forwarding assertion

n is typed as unsupported on MistralChatOptions; the option-mapping test
still passed n, failing pyrefly/ty/zuban/mypy in CI.

* refactor(python): drop n from MistralChatOptions

n is not part of the base ChatOptions, so removing the key rejects it
without an explicit None override.

* feat(python): mark Mistral feature usage

Both clients flip the shared FeatureIndex.MISTRAL bit before each
request, matching the feature-usage telemetry other providers emit.

* fix(python): key streamed tool calls by index

Mistral omits the tool call id on continuation fragments, and the
framework only coalesces empty-id fragments into the immediately
preceding call, so interleaved parallel calls merged into the wrong
call with corrupted arguments. Accumulate fragments per (choice,
index) and emit each call only once complete.

* fix(python): restore Mistral SDK client injection

Dropping the mistralai dependency turned the embedding client's
client= parameter into a breaking change for injected SDK clients.
Add http_client= for httpx.AsyncClient and keep client= working:
httpx goes to the REST path, a duck-typed mistralai.Mistral goes
through the legacy SDK path with a DeprecationWarning until the
next major release.

* chore(python): tidy Mistral sample header
This commit is contained in:
NekoPunch
2026-08-02 23:29:09 -07:00
committed by GitHub
parent 43309018be
commit f5dfb1413e
19 changed files with 2530 additions and 296 deletions
@@ -1,15 +1,17 @@
# Mistral AI Embedding Examples
# Mistral AI Examples
This folder contains examples demonstrating how to use Mistral AI embedding models with the Agent Framework.
This folder contains examples demonstrating how to use Mistral AI models with the Agent Framework.
## Examples
| File | Description |
|------|-------------|
| [`mistral_agent_basic.py`](mistral_agent_basic.py) | Basic agent with tool usage using the Mistral AI chat client. |
| [`mistral_embeddings.py`](mistral_embeddings.py) | Basic embedding generation with the Mistral AI embedding client. |
## Environment Variables
- `MISTRAL_API_KEY`: Your Mistral AI API key
- `MISTRAL_CHAT_MODEL`: Chat model name (e.g., `mistral-small-latest`)
- `MISTRAL_EMBEDDING_MODEL`: Embedding model name (e.g., `mistral-embed`)
- `MISTRAL_SERVER_URL` (optional): Server URL override for custom deployments
@@ -0,0 +1,95 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from datetime import datetime
from zoneinfo import ZoneInfo
from agent_framework import Agent, tool
from agent_framework.mistral import MistralChatClient
from dotenv import load_dotenv
# Load environment variables from the local .env file.
load_dotenv()
"""Demonstrates a Mistral AI agent with basic tool usage.
Requires ``MISTRAL_API_KEY`` and ``MISTRAL_CHAT_MODEL`` environment variables
(e.g. MISTRAL_CHAT_MODEL=mistral-small-latest).
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_time(timezone: str) -> str:
"""Get the current time in an IANA timezone (e.g. 'America/Los_Angeles')."""
now = datetime.now(ZoneInfo(timezone))
return f"The current time in {timezone} is {now.strftime('%I:%M %p')}."
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
client = MistralChatClient()
agent = Agent(
client=client,
name="TimeAgent",
instructions="You are a helpful time agent, answer in one sentence.",
tools=get_time,
)
query = "What time is it in Seattle? Use a tool call"
print(f"User: {query}")
try:
result = await agent.run(query)
print(f"Result: {result}\n")
finally:
await client.close()
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
client = MistralChatClient()
agent = Agent(
client=client,
name="TimeAgent",
instructions="You are a helpful time agent, answer in one sentence.",
tools=get_time,
)
query = "What time is it in San Francisco? Use a tool call"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
try:
async for chunk in agent.run(query, stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
finally:
await client.close()
async def main() -> None:
print("=== Basic Mistral Chat Client Agent Example ===")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
=== Basic Mistral Chat Client Agent Example ===
=== Non-streaming Response Example ===
User: What time is it in Seattle? Use a tool call
Result: The current time in Seattle is 10:30 AM.
=== Streaming Response Example ===
User: What time is it in San Francisco? Use a tool call
Agent: The current time in San Francisco is 10:30 AM.
"""
@@ -21,23 +21,26 @@ async def basic_embedding_example() -> None:
"""Generate embeddings for a list of texts."""
print("=== Basic Embedding Generation ===")
# 1. Create the embedding client (uses MISTRAL_API_KEY and MISTRAL_EMBEDDING_MODEL env vars).
# 1. Create the embedding client using environment-based configuration.
client = MistralEmbeddingClient()
# 2. Generate embeddings for multiple texts.
texts = ["Hello, world!", "How are you?", "Agent Framework with Mistral AI"]
result = await client.get_embeddings(texts)
try:
result = await client.get_embeddings(texts)
# 3. Print results.
print(f"Generated {len(result)} embeddings")
for i, embedding in enumerate(result):
print(f" Text {i + 1}: dimensions={embedding.dimensions}, vector={embedding.vector[:5]}...")
# 3. Print the generated vectors and usage metadata.
print(f"Generated {len(result)} embeddings")
for i, embedding in enumerate(result):
print(f" Text {i + 1}: dimensions={embedding.dimensions}, vector={embedding.vector[:5]}...")
if result.usage:
print(
f" Usage: {result.usage['input_token_count']} input tokens, "
f"{result.usage['total_token_count']} total tokens"
)
if result.usage:
print(
f" Usage: {result.usage['input_token_count']} input tokens, "
f"{result.usage['total_token_count']} total tokens"
)
finally:
await client.close()
async def embedding_with_options_example() -> None:
@@ -46,14 +49,16 @@ async def embedding_with_options_example() -> None:
from agent_framework.mistral import MistralEmbeddingOptions
client = MistralEmbeddingClient()
# Only some models support a custom output dimension (e.g. codestral-embed; mistral-embed does not).
client = MistralEmbeddingClient(model="codestral-embed")
# Request a specific output dimension (model must support it).
options: MistralEmbeddingOptions = {"dimensions": 256}
result = await client.get_embeddings(["Dimensionality reduction example"], options=options)
print(f" Dimensions: {result[0].dimensions}")
print(f" Vector (first 5): {result[0].vector[:5]}...")
try:
result = await client.get_embeddings(["Dimensionality reduction example"], options=options)
print(f" Dimensions: {result[0].dimensions}")
print(f" Vector (first 5): {result[0].vector[:5]}...")
finally:
await client.close()
async def main() -> None:
+4
View File
@@ -148,6 +148,10 @@ variable.
| `agent-framework-github-copilot` | `GitHubCopilotAgent` | `GITHUB_COPILOT_TIMEOUT` | `60` |
| `agent-framework-github-copilot` | `GitHubCopilotAgent` | `GITHUB_COPILOT_LOG_LEVEL` | `info` |
| `agent-framework-mem0` | `agent_framework_mem0 package import` | `MEM0_TELEMETRY` | `false` |
| `agent-framework-mistral` | `MistralChatClient / MistralEmbeddingClient` | `MISTRAL_API_KEY` | `your-api-key` |
| `agent-framework-mistral` | `MistralChatClient` | `MISTRAL_CHAT_MODEL` | `mistral-small-latest` |
| `agent-framework-mistral` | `MistralEmbeddingClient` | `MISTRAL_EMBEDDING_MODEL` | `mistral-embed` |
| `agent-framework-mistral` | `MistralChatClient / MistralEmbeddingClient` | `MISTRAL_SERVER_URL` | `https://api.mistral.ai` |
| `agent-framework-ollama` | `OllamaChatClient` | `OLLAMA_HOST` | `http://localhost:11434` |
| `agent-framework-ollama` | `OllamaChatClient` | `OLLAMA_MODEL` | `llama3.1:8b` |
| `agent-framework-openai` | `OpenAIChatClient / OpenAIChatCompletionClient / OpenAIEmbeddingClient` | `OPENAI_API_KEY` | `sk-proj-...` |