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:
@@ -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:
|
||||
|
||||
@@ -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-...` |
|
||||
|
||||
Reference in New Issue
Block a user