Human-in-the-loop
Certains appels d'outils, comme l'envoi d'un e-mail ou la modification de données, nécessitent une validation humaine au préalable. Avec requires_confirmation, la conversation s'interrompt avant l'exécution de ces appels, afin qu'un utilisateur ou votre système puisse approuver ou refuser chacun d'eux.
La confirmation fonctionne pour les outils de connecteur, les outils intégrés comme web_search_premium et les fonctions locales. Les fonctions locales sont disponibles uniquement dans le SDK Python.
Vous pouvez implémenter le flux de confirmation de deux manières :
- API REST : vous gérez les ID de conversation et les ID d'appels d'outils. Fonctionne avec n'importe quel langage ou client HTTP.
- SDK Python :
RunContextetDeferredToolCallsExceptiongèrent les ID pour vous.
Choisir les outils nécessitant une validation
Dans la tool_configuration d'un connecteur ou d'un outil intégré, listez dans requires_confirmation les noms des outils nécessitant une validation :
[
{
"type": "connector",
"connector_id": "gmail",
"tool_configuration": {
"requires_confirmation": ["gmail_search"]
}
},
{
"type": "web_search_premium",
"tool_configuration": {
"requires_confirmation": ["web_search", "news_search"]
}
}
]Confirmer les appels d'outils avec l'API REST
Le flux de l'API REST comprend deux étapes :
- Démarrez la conversation et obtenez un appel
function.callen attente. - Approuvez ou refusez l’appel pour reprendre la conversation.
Démarrer une conversation nécessitant une validation
L’API renvoie une entrée function.call en attente au lieu d’exécuter l’outil.
RESPONSE=$(curl -s -X POST "https://api.mistral.ai/v1/conversations" \
-H "Authorization: Bearer $MISTRAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-medium-latest",
"inputs": "What is the title of my latest email?",
"tools": [
{
"type": "connector",
"connector_id": "gmail",
"tool_configuration": {
"requires_confirmation": ["gmail_search"]
}
}
]
}')
CONVERSATION_ID=$(echo "$RESPONSE" | jq -r '.conversation_id')
TOOL_CALL_ID=$(echo "$RESPONSE" | jq -r '.outputs[0].tool_call_id')La réponse contient un appel function.call en attente :
{
"conversation_id": "conv_abc123...",
"outputs": [
{
"type": "function.call",
"tool_call_id": "WJfo42Ow3",
"name": "gmail_search",
"arguments": "{\"limit\": 1}",
"confirmation_status": "pending"
}
]
}Approuver un appel d'outil en attente
Envoyez "allow" pour exécuter l'outil et obtenir la réponse du modèle.
curl -X POST "https://api.mistral.ai/v1/conversations/$CONVERSATION_ID" \
-H "Authorization: Bearer $MISTRAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool_confirmations": [
{"tool_call_id": "'$TOOL_CALL_ID'", "confirmation": "allow"}
]
}'Refuser un appel d'outil en attente
Pour refuser l'appel, envoyez "deny". L'outil n'est pas exécuté et le modèle répond sans son résultat.
curl -X POST "https://api.mistral.ai/v1/conversations/$CONVERSATION_ID" \
-H "Authorization: Bearer $MISTRAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool_confirmations": [
{"tool_call_id": "'$TOOL_CALL_ID'", "confirmation": "deny"}
]
}'Plusieurs appels d'outils peuvent être en attente simultanément. Approuvez-les ou refusez-les un par un, ou envoyez toutes les décisions dans un seul tableau tool_confirmations.
Confirmer les appels d'outils avec le SDK Python
Le flux du SDK Python nécessite mistralai 2.4 ou ultérieur avec l'extra agents : pip install "mistralai[agents]". Avec une version antérieure, utilisez le flux API REST.
run_async exécute la boucle de conversation et lève DeferredToolCallsException lorsqu'un outil nécessite une validation. Appelez dc.confirm() ou dc.reject() sur chaque appel différé, puis relancez la boucle pour reprendre la conversation.
Confirmer les appels de fonctions locales
Enregistrez vos fonctions Python avec register_func. Pour chaque fonction nécessitant une validation, définissez requires_confirmation=True.
import asyncio
import os
import random
from mistralai.client import Mistral
from mistralai.extra.run.context import RunContext
from mistralai.extra.exceptions import DeferredToolCallsException
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
temp = random.randint(10, 30)
return f"The weather in {city} is {random.choice(['sunny', 'cloudy'])}, {temp}C"
def book_flight(destination: str, date: str) -> str:
"""Book a flight to a destination."""
return f"Flight booked to {destination} on {date}. Confirmation: FL-{random.randint(10000, 99999)}"
async def main():
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
conversation_id = None
pending_inputs = [
{"role": "user", "content": "I need a vacation somewhere warm next Friday. Can you help?"}
]
while True:
async with RunContext(model="mistral-large-latest") as run_ctx:
run_ctx.conversation_id = conversation_id
run_ctx.register_func(get_weather, requires_confirmation=False)
run_ctx.register_func(book_flight, requires_confirmation=True)
try:
result = await client.beta.conversations.run_async(
run_ctx=run_ctx,
inputs=pending_inputs,
instructions="You are a travel assistant. Check the weather and book a flight to the warmest destination.",
)
print(result.output_entries)
break
except DeferredToolCallsException as deferred:
conversation_id = deferred.conversation_id
pending_inputs = []
for dc in deferred.deferred_calls:
print(f"[APPROVAL REQUIRED] {dc.tool_name}({dc.arguments})")
approved = input("Approve? (y/n): ").strip().lower() == "y"
pending_inputs.append(dc.confirm() if approved else dc.reject("Denied by user"))
asyncio.run(main())Confirmer les appels d'outils de connecteur
Les outils de connecteur fonctionnent de la même manière : run_async lève DeferredToolCallsException lorsque le serveur renvoie un function.call avec confirmation_status: "pending".
import asyncio
import os
from mistralai.client import Mistral
from mistralai.extra.exceptions import DeferredToolCallsException
from mistralai.extra.run.context import RunContext
async def main():
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
conversation_id = None
pending_inputs = [{"role": "user", "content": "Summarize my latest emails from Gmail."}]
while True:
async with RunContext(model="mistral-large-latest") as run_ctx:
run_ctx.conversation_id = conversation_id
try:
result = await client.beta.conversations.run_async(
run_ctx=run_ctx,
inputs=pending_inputs,
instructions="You are a helpful assistant. Use Gmail to access the user's emails.",
tools=[
{
"type": "connector",
"connector_id": "gmail",
"tool_configuration": {
"requires_confirmation": ["gmail_search"],
},
},
],
)
for entry in result.output_entries:
if hasattr(entry, "content"):
print(entry.content)
break
except DeferredToolCallsException as deferred:
conversation_id = deferred.conversation_id
pending_inputs = []
for dc in deferred.deferred_calls:
print(f"[APPROVAL REQUIRED] {dc.tool_name}({dc.arguments})")
approved = input("Approve? (y/n): ").strip().lower() == "y"
pending_inputs.append(dc.confirm() if approved else dc.reject("Denied by user"))
asyncio.run(main())Reprendre à partir d'une requête distincte
Lorsque la validation arrive dans une requête distincte, par exemple depuis votre frontend, sérialisez l'état différé et reconstruisez-le à l'arrivée de la validation.
import asyncio, json, os
from mistralai.client import Mistral
from mistralai.extra.run.context import RunContext
from mistralai.extra.exceptions import DeferredToolCallsException
def book_flight(destination: str, date: str) -> str:
"""Book a flight to a destination."""
return f"Flight booked to {destination} on {date}"
async def main():
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
async with RunContext(model="mistral-large-latest") as run_ctx:
run_ctx.register_func(book_flight, requires_confirmation=True)
try:
result = await client.beta.conversations.run_async(
run_ctx=run_ctx,
inputs=[{"role": "user", "content": "Book me a flight to Paris next Friday."}],
instructions="You are a travel assistant. Book the flight directly.",
)
print("No confirmation needed:", result.output_as_text)
except DeferredToolCallsException as deferred:
# Serialize and send to frontend / store in DB
print(json.dumps(deferred.to_dict()))
asyncio.run(main())deferred.executed_results contient les résultats des outils exécutés avant le report. Placez-les en premier dans les entrées lors de la reprise, afin que le modèle dispose du contexte complet.