Construire un bot météo Telegram

Construisez un bot Telegram qui répond aux questions météo en français, conserve la conversation en cours et la réinitialise lorsque l'utilisateur envoie /new. Vous testez l'agent et le bot en local, puis déployez l'agent sur Mistral sans modifier le code du bot.

Présentation de : sessions et tours (clés d'idempotence, message envoyé pendant un tour en cours), outils, mise à disposition et déploiement d'un agent.

Exécutez (après la configuration ci-dessous), depuis le dossier de l'exemple : uv run --frozen --env-file .env python bot.py

Fonctionnement

Fonctionnement

Telegram envoie chaque message texte à bot.py. Le bot ouvre une session pour la conversation, transmet le message et renvoie la réponse de l'agent à Telegram. La session conserve la conversation jusqu'à ce que l'utilisateur envoie /new ou que la session se termine : le message suivant ouvre alors une nouvelle session.

Le bot envoie chaque message avec son identifiant de message Telegram comme clé d'idempotence. Ainsi, un même message reçu deux fois ne lance qu'un seul tour. Il ignore les messages modifiés, qui conservent leur identifiant.

Telegram chat → bot.py → session → weather agent → get_weather()

L'outil get_weather() récupère la météo actuelle auprès de Open-Meteo, un service gratuit qui ne nécessite aucune clé API : il trouve la ville, puis récupère l'état du ciel, la température et le vent.

Pendant le développement local, agents serve exécute l'agent sur votre ordinateur. Après le déploiement, c'est Mistral qui exécute l'agent. Le code du bot ne change pas : vous ne mettez à jour que le nom de l'agent dans .env.

Le code du bot Telegram

Le code du bot Telegram

L'exemple comprend deux projets : l'agent dans agent/ et le bot Telegram à la racine.

FichierContenu
agent/main.pyTout l'agent : un @agents.tool typé et l'objet agent que la CLI charge
agent/run_agent.pyUne exécution unique in-process qui importe ce même objet agent
agent_client.pyOuverture et fermeture d'une session, envoi d'un message et lecture de la réponse
bot.pyGestionnaires Telegram : un identifiant de session par conversation, conservé après les redémarrages, réinitialisation avec /new, vérification de l'utilisateur autorisé
agent/DockerfileL'image que construit un déploiement géré

Le fichier README.md de l'exemple contient la procédure complète de configuration et le script de conversation.

L'agent se compose d'un outil et d'un Agent. L'outil nécessite httpx, déclaré dans agent/pyproject.toml :

import httpx

from mistralai import agents


def sky(code: int) -> str:
    """Open-Meteo's weather code (WMO) in words."""
    if code <= 3:
        return ["clear sky", "mainly clear", "partly cloudy", "overcast"][code]
    for below, words in [(50, "fog"), (60, "drizzle"), (70, "rain"), (80, "snow"), (95, "rain showers")]:
        if code < below:
            return words
    return "thunderstorm"


@agents.tool
async def get_weather(city: str) -> dict[str, str | float]:
    """Return the current weather in a city: the sky, the temperature and the wind."""
    async with httpx.AsyncClient(timeout=10) as http:  # Open-Meteo is free and needs no key
        found = await http.get("https://geocoding-api.open-meteo.com/v1/search", params={"name": city, "count": 1})
        if not (places := found.json().get("results")):
            raise agents.ToolError(f"No city named {city}.")
        place = places[0]
        forecast = await http.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": place["latitude"],
                "longitude": place["longitude"],
                "current": "weather_code,temperature_2m,wind_speed_10m",
            },
        )
    now = forecast.json()["current"]
    return {
        "city": f"{place['name']}, {place.get('country', '')}",
        "sky": sky(now["weather_code"]),
        "temperature_celsius": now["temperature_2m"],
        "wind_km_per_hour": now["wind_speed_10m"],
    }


# Replace `alice` with a unique lowercase identifier, such as your GitHub handle,
# so the served and deployed names cannot collide with anyone else's.
agent = agents.Agent(
    model="mistral-large-4",
    name="french-weather-agent-alice-local",
    instructions=(
        "Always answer in French. "
        "When the user asks about weather in a city, call get_weather first. "
        "Keep the answer concise."
    ),
    tools=[get_weather],
)

Le bot ne stocke qu'un identifiant de session par conversation. ask envoie chaque message Telegram avec son identifiant de message comme clé d'idempotence. Lorsqu'un message arrive pendant un tour en cours, send_message réoriente ce tour (turn.started vaut False), dont la réponse traite alors les deux messages :

import contextlib

from mistralai import agents


def connect(agent_name: str) -> agents.Agent:
    return agents.Agent.load_from(agent_name=agent_name)


async def start_conversation(agent: agents.Agent) -> str:
    """Start a session and return its id, the only thing the bot stores."""
    session = await agent.start_session()
    return session.id


async def end_conversation(agent: agents.Agent, session_id: str) -> None:
    # A session that already ended is left as it is; one its host no longer has needs nothing.
    with contextlib.suppress(agents.SessionNotFoundError):
        await agent.session(session_id).cancel()


async def ask(agent: agents.Agent, session_id: str, text: str, message_id: int) -> str | None:
    """Send one Telegram message to the conversation and return the agent's reply.

    Returns None when the message steered a turn that was already running: that turn's reply
    answers it.
    """
    session = agent.session(session_id)
    # The same Telegram message, delivered again after a restart, is sent once.
    turn = await session.send_message(text, idempotency_key=f"telegram:{message_id}")
    if not turn.started:
        return None
    match await turn.result():
        case agents.Completed(text=reply) if reply:
            return reply
        case state:
            raise RuntimeError(f"The turn did not complete with a reply: {state!r}")
Prérequis

Prérequis

Vous avez besoin de :

  • Python 3.12 et uv.
  • Une clé API Mistral dont l'utilisateur possède un rôle dans l'espace de travail donnant accès à Workflows, comme Développeur : voir Déployer. agents serve et agents deploy nécessitent tous deux cette clé.
  • Un compte Telegram.
  • Pour déployer l'agent : un dépôt GitHub auquel vous donnez accès via le connecteur GitHub dans Mistral Studio. Vous pouvez effectuer les étapes locales sans ce dépôt.
1. Tester l'agent

1. Tester l'agent

Depuis le projet agent/, copiez .env.example dans .env et définissez-y MISTRAL_API_KEY. Puis installez les dépendances et examinez ce que la CLI détecte :

cd agent
cp .env.example .env
uv sync
uv run --frozen --env-file .env agents info

agents info liste l'agent et son outil get_weather. Exécutez-le ensuite une fois depuis Python avant d'ajouter Telegram :

uv run --frozen --env-file .env python run_agent.py

run_agent.py importe depuis main.py le même objet agent que celui chargé par la CLI. Le script et l'agent accessible via une API ne peuvent donc pas diverger. Il affiche une réponse courte en French avec la météo de Paris à cet instant :

Voici la météo actuelle à **Paris** :
- ☁️ **Ciel** : couvert
- 🌡️ **Température** : 7,6 °C
- 💨 **Vent** : 7,6 km/h
2. Servir l'agent

2. Servir l'agent

bot.py s'exécute séparément de l'agent et ne peut donc pas utiliser l'objet Agent in-process de l'étape

  1. Démarrez l'agent avec agents serve pour que le bot puisse ouvrir des sessions auprès de celui-ci :
uv run --frozen --env-file .env agents serve

La commande enregistre l'agent auprès de Mistral Workflows, l'exécute sur votre ordinateur et affiche des liens pour discuter avec lui ou inspecter ses exécutions. Laissez la commande active pendant que vous testez le bot Telegram.

Mistral identifie les agents servis et déployés par leur nom. L'exemple utilise alice comme placeholder : remplacez-le par un identifiant unique en minuscules, comme votre identifiant GitHub, partout où il apparaît.

3. Exécuter le bot

3. Exécuter le bot

Créez un bot avec le compte vérifié @BotFather de Telegram (/newbot, choisissez un nom et un nom d'utilisateur se terminant par bot, copiez le token). Gardez le token secret : toute personne qui le possède peut contrôler votre bot.

De retour dans le répertoire telegram_weather_bot/, copiez .env.example dans .env et renseignez le token du bot, votre clé API Mistral et le nom de l'agent. TELEGRAM_ALLOWED_USER_ID reste à 0 jusqu'à la première exécution — la commande /id du bot vous indique votre identifiant utilisateur Telegram, et cette vérification empêche les autres utilisateurs de communiquer avec votre agent.

uv sync
uv run --frozen --env-file .env python bot.py

Dans votre conversation directe avec le bot, envoyez /id, reportez le numéro dans TELEGRAM_ALLOWED_USER_ID, redémarrez le bot, puis :

/start
Quel temps fait-il à Paris ?
Souviens-toi que ma ville préférée est Lyon.
Quelle est ma ville préférée ?
/new
Quelle est ma ville préférée ?

La réponse météo devrait être en français. L'agent devrait se souvenir de Lyon au sein de cette session. Après /new, il ne devrait plus savoir quelle ville vous avez choisie.

4. Déployer l'agent sur Mistral

4. Déployer l'agent sur Mistral

Le bot nécessite que agents serve s'exécute sur votre ordinateur. Un déploiement géré construit l'agent à partir de Git — poussez agent/ vers un dépôt accessible au connecteur GitHub (ne faites jamais de commit de .env), puis déployez sans le suffixe -local :

cd agent
uv run --frozen --env-file .env \
  agents deploy . --name french-weather-agent-alice

L'agent servi en local et le déploiement géré partagent un espace de travail Mistral, où les noms sont uniques. Utiliser french-weather-agent-alice-local en local et french-weather-agent-alice pour le déploiement évite que les deux enregistrements entrent en collision.

Arrêtez agents serve, remplacez AGENT_NAME dans le .env du bot par le nom déployé, puis redémarrez bot.py. Si le bot répond toujours, il utilise l'agent hébergé par Mistral. Seul l'agent est déployé ; bot.py continue de s'exécuter sur votre ordinateur.

Pour mettre à jour le déploiement, poussez la modification et déployez à nouveau avec le même nom — réutiliser un nom met à jour le déploiement correspondant, un nom différent crée un déploiement séparé.

Inspecter les sessions dans Mistral Studio

Inspecter les sessions dans Mistral Studio

Les sessions gérées par les agents démarrés avec agents serve ou agents deploy apparaissent dans Sessions d'agent dans Mistral Studio. Ouvrez une session pour examiner ses messages, ses appels d'outils et son statut.

Dépannage

Dépannage

agents serve ne peut pas enregistrer l'agent

agents serve ne peut pas enregistrer l'agent

Vérifiez que MISTRAL_API_KEY est valide et que son utilisateur possède un rôle dans l'espace de travail donnant accès à Workflows (voir Déployer pour les rôles). Une clé qui permet d'appeler un modèle peut ne pas avoir l'autorisation d'enregistrer l'agent.

uv run --frozen ne trouve pas uv.lock

uv run --frozen ne trouve pas uv.lock

Exécutez uv sync une fois dans ce projet, puis réessayez la commande.

Le bot Telegram ne répond pas

Le bot Telegram ne répond pas

Laissez bot.py en cours d'exécution. Pendant les tests en local, laissez également agents serve actif. Envoyez des messages dans une conversation directe, vérifiez que TELEGRAM_ALLOWED_USER_ID correspond à /id et contrôlez AGENT_NAME. Redémarrez le bot après avoir modifié .env, puis examinez son terminal pour identifier l'erreur signalée.

Telegram signale un conflit de polling

Telegram signale un conflit de polling

Un seul processus bot.py peut effectuer le polling avec un token de bot. Arrêtez l'autre processus et redémarrez celui-ci.

Le déploiement géré ne peut pas lire le code source

Le déploiement géré ne peut pas lire le code source

Vérifiez que le commit a été poussé vers le dépôt auquel vous avez donné accès au connecteur GitHub et que le connecteur est activé dans Mistral Studio.

Ressources et prochaines étapes

Ressources et prochaines étapes

  • Sessions explique les sessions que bot.py conserve pour chaque conversation et leur historique de conversation.
  • Outils montre comment ajouter un second outil à côté de get_weather.
  • Modes d'exécution compare l'exécution de l'agent dans votre processus avec son déploiement via Studio (déploiements Auto-hébergés ou gérés par Mistral) et décrit ce que la CLI charge depuis un projet.