Bienvenue dans ce tutoriel dédié à la transmission par protocole MCP SSE pour可以实现实时流式工具调用. Si vous n'avez jamais travaillé avec les API de streaming ou les outils d'intelligence artificielle,不用担心 — ce guide vous prendra par la main depuis les bases absolues.

Comprendre le Protocole SSE et son Intérêt

Avant de plonge dans le code, posons les fondations. Le protocole SSE (Server-Sent Events) est une technologie permettant à un serveur d'envoyer des données à un client en temps réel, sans que le client n'ait besoin de réclamer chaque information. Imaginez une conversation où le serveur vous parle directement, plutôt que vous deviez demander « avez-vous quelque chose à me dire ? » toutes les secondes.

Dans le contexte de l'intelligence artificielle, cette technologie revolutionne l'expérience utilisateur. Au lieu d'attendre plusieurs secondes pour recevoir une réponse complète, vous voyez les mots apparaître progressivement, comme si quelqu'un les tapait devant vous. C'est exactement ce que propose S'inscrire ici avec leur infrastructure optimisée offrant une latence inférieure à 50 millisecondes.

Architecture de la Communication MCP SSE

Le protocole MCP (Model Context Protocol) définit comment un client peut demander à un modèle d'effectuer des actions spécifiques pendant qu'il génère sa réponse. Voici le flux simplifié :

Configuration de l'Environnement

Pour débuter, vous aurez besoin de Python 3.8+ et de la bibliothèque requests. Installez les dépendances nécessaires :

# Installation des dépendances
pip install requests sseclient-py

Vérification de l'installation

python -c "import requests; print('Requests version:', requests.__version__)"

Implémentation Complète du Client SSE

Passons maintenant à la pratique. Voici un exemple complet et fonctionnel que vous pouvez copier-coller directement dans votre éditeur :

import requests
import json

Configuration HolySheep API

BASE_URL = "https://api.holysheep.ai/v1" API_KEY = "YOUR_HOLYSHEEP_API_KEY"

Définition des outils disponibles

tools = [ { "name": "get_weather", "description": "Récupère la météo d'une ville", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "Nom de la ville"} }, "required": ["city"] } }, { "name": "calculate", "description": "Effectue un calcul mathématique", "parameters": { "type": "object", "properties": { "expression": {"type": "string", "description": "Expression mathématique"} }, "required": ["expression"] } } ] def send_message_streaming(message, context=None): """Envoie un message et reçoit les événements SSE en streaming.""" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "text/event-stream" } payload = { "model": "gpt-4.1", "messages": [ {"role": "user", "content": message} ], "tools": tools, "stream": True } if context: payload["context"] = context response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=payload, stream=True ) return response

Test de connexion

print("Test de connexion à HolySheep AI...") response = send_message_streaming("Bonjour, pouvez-vous me dire la météo à Paris ?") print(f"Statut de la réponse: {response.status_code}")

Gestion des Événements en Temps Réel

Maintenant que nous pouvons envoyer des requêtes, voyons comment traite les événements Stream Events (SSE) de manière professionnelle :

import json
import sseclient

def process_stream_events(response):
    """Traite les événements SSE et gère les appels d'outils."""
    
    client = sseclient.SSEClient(response)
    tool_calls = []
    accumulated_content = ""
    
    try:
        for event in client.events():
            if event.data:
                data = json.loads(event.data)
                
                # Gestion des différents types d'événements
                if data.get("type") == "content_block":
                    # Contenu textuel en cours de génération
                    content = data.get("content", "")
                    accumulated_content += content
                    print(content, end="", flush=True)
                    
                elif data.get("type") == "tool_call":
                    # Demande d'appel d'outil
                    tool_name = data.get("name")
                    tool_args = data.get("arguments", {})
                    print(f"\n\n🔧 Appel de l'outil: {tool_name}")
                    print(f"   Arguments: {tool_args}")
                    
                    # Stocker l'appel pour traitement
                    tool_calls.append({
                        "name": tool_name,
                        "arguments": tool_args
                    })
                    
                elif data.get("type") == "tool_result":
                    # Résultat d'un outil
                    result = data.get("result")
                    print(f"\n✅ Résultat: {result}")
                    
                elif data.get("type") == "done":
                    # Fin du streaming
                    print("\n\n✨ Génération terminée")
                    break
                    
    except KeyboardInterrupt:
        print("\n\n⚠️ Stream interrompu par l'utilisateur")
    
    return {
        "content": accumulated_content,
        "tool_calls": tool_calls
    }

Exemple d'utilisation

print("=" * 60) print("Démonstration du streaming avec outils") print("=" * 60) response = send_message_streaming( "Calculez 15 * 23 + 45, et dites-moi le temps à Lyon" ) result = process_stream_events(response) print(f"\n\n📊 Résumé: {len(result['tool_calls'])} appel(s) d'outil détecté(s)")

Implémentation d'un Exécuteur d'Outils

Pour que les appels d'outils fonctionnent vraiment, vous devez implémenter la logique de chaque outil :

import math

class ToolExecutor:
    """Classe responsable de l'exécution des outils MCP."""
    
    def __init__(self):
        self.tools = {
            "get_weather": self.get_weather,
            "calculate": self.calculate
        }
    
    def execute(self, tool_name, arguments):
        """Exécute un outil par son nom avec les arguments fournis."""
        
        if tool_name not in self.tools:
            return {
                "error": f"Outil inconnu: {tool_name}",
                "available_tools": list(self.tools.keys())
            }
        
        try:
            result = self.tools[tool_name](arguments)
            return {"success": True, "result": result}
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def get_weather(self, args):
        """Simule la récupération de données météo."""
        city = args.get("city", "inconnue")
        # Simulation - en production, appeler une API météo réelle
        weathers = {
            "paris": {"temp": 18, "condition": "ensoleillé", "humidity": 65},
            "lyon": {"temp": 22, "condition": "partiellement nuageux", "humidity": 55},
            "marseille": {"temp": 25, "condition": "dégagé", "humidity": 48}
        }
        return weathers.get(city.lower(), {"temp": 20, "condition": "inconnu", "humidity": 50})
    
    def calculate(self, args):
        """Évalue une expression mathématique en toute sécurité."""
        expression = args.get("expression", "0")
        # Validation basique pour éviter les injections
        allowed_chars = set("0123456789+-*/.() ")
        if all(c in allowed_chars for c in expression):
            result = eval(expression)
            return {"expression": expression, "result": result}
        return {"error": "Expression non autorisée"}

Démonstration de l'exécuteur

executor = ToolExecutor()

Test des outils

print("Test de l'exécuteur d'outils MCP:\n") test_cases = [ ("get_weather", {"city": "Paris"}), ("calculate", {"expression": "150 * 23 + 45"}), ("get_weather", {"city": "Lyon"}), ("calculate", {"expression": "(100 + 50) * 2 / 25"}) ] for tool_name, args in test_cases: print(f"Exécution: {tool_name}({args})") result = executor.execute(tool_name, args) print(f" → {result}\n")

Monitoring et Optimisation des Performances

Un aspect crucial du streaming SSE est la surveillance des performances. Voici comment implémenter un système de monitoring complet :

Erreurs Courantes et Solutions

Au cours de mes nombreux déploiements en production, j'ai rencontré de nombreux problèmes. Voici les trois cas les plus fréquents avec leurs solutions éprouvées :

Erreur 1 : Timeouts Fréquents lors du Streaming

# Problème : La connexion expire avant la fin du streaming

Erreur fréquente :

requests.exceptions.ChunkedEncodingError:

"Connection broken: IncompleteRead"

Solution : Configurer des timeouts appropriés et gérer les reconnexions

import time import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_resilient_session(): """Crée une session avec stratégie de reconnexion robuste.""" session = requests.Session() # Configuration des retries automatiques retry_strategy = Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("https://", adapter) session.mount("http://", adapter) return session def streaming_with_retry(message, max_retries=3): """Effectue un streaming avec gestion des erreurs et reconnexion.""" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "text/event-stream", "Connection": "keep-alive" } payload = { "model": "deepseek-v3.2", "messages": [{"role": "user", "content": message}], "stream": True } for attempt in range(max_retries): try: session = create_resilient_session() response = session.post( f"{BASE_URL}/chat/completions", headers=headers, json=payload, timeout=(10, 60), # (connect_timeout, read_timeout) stream=True ) if response.status_code == 200: return process_stream_events(response) elif response.status_code == 429: wait_time = 2 ** attempt * 5 # Exponential backoff print(f"⚠️ Rate limit — attente de {wait_time}s...") time.sleep(wait_time) else: print(f"❌ Erreur {response.status_code}: {response.text}") except requests.exceptions.Timeout: print(f"⏰ Timeout à la tentative {attempt + 1}, reconnexion...") time.sleep(2 ** attempt) except requests.exceptions.ConnectionError as e: print(f"🔌 Erreur de connexion: {e}") time.sleep(2 ** attempt) return {"error": "Échec après toutes les tentatives"}

Utilisation

print("Test avec gestion des reconnexions...") result = streaming_with_retry("Expliquez-moi les avantages du protocole SSE") print(f"\nRésultat final: {result.get('content', result.get('error'))[:100]}...")

Erreur 2 : Décodage Incorrect des Données SSE

# Problème : Les événements SSE ne sont pas correctement parsés

Symptômes : Caractères chinois ou données corrompues

Solution : Implémenter un parser SSE robuste

import re import json def parse_sse_chunk(chunk): """ Parse correctement un chunk de données SSE. Gère les problèmes d'encodage et les formatsvariables. """ # Nettoyer le chunk text = chunk.decode('utf-8', errors='replace') # Expression régulière pour extraire les champs SSE event_pattern = re.compile(r'^(?::([^\n]*)\n)?((?:[^\n]*\n)*)', re.M) events = [] lines = text.split('\n') current_event = {} for line in lines: if line.startswith('data:'): # Extraire le contenu après 'data: ' data_content = line[5:].strip() # Gestion des données JSON if data_content.startswith('[') or data_content.startswith('{'): try: parsed_data = json.loads(data_content) current_event['data'] = parsed_data except json.JSONDecodeError: # Données partielles — accumulation if 'partial_data' not in current_event: current_event['partial_data'] = '' current_event['partial_data'] += data_content else: current_event['data'] = data_content elif line.startswith('event:'): current_event['type'] = line[6:].strip() elif line.startswith('id:'): current_event['id'] = line[3:].strip() elif line == '': # Fin d'un événement if current_event: events.append(current_event) current_event = {} # Ajouter le dernier événement si incomplet if current_event and 'partial_data' in current_event: try: current_event['data'] = json.loads(current_event['partial_data']) del current_event['partial_data'] events.append(current_event) except: pass return events def robust_stream_processor(response): """Traite le flux de manière robuste.""" buffer = b'' for chunk in response.iter_content(chunk_size=64): if chunk: buffer += chunk # Traiter les lignes complètes while b'\n' in buffer: line_end = buffer.index(b'\n') line = buffer[:line_end] buffer = buffer[line_end + 1:] if line.startswith(b'data:'): events = parse_sse_chunk(line) for event in events: yield event

Test du parser robuste

print("Test du parser SSE robuste...") test_chunk = b"data: {\"type\":\"content_block\",\"content\":\"Bonjour\"}\n\n" parsed = parse_sse_chunk(test_chunk) print(f"✅ Chunk parsé: {parsed}")

Erreur 3 : Appels d'Outils Multiples et Ordre Incorrect

# Problème : Les résultats des outils arrivent dans le désordre

Causant des incohérences dans la réponse finale

Solution : Implémenter un système de file d'attente ordonnée

import asyncio from collections import OrderedDict from typing import Dict, Any class OrderedToolExecutor: """ Gère les appels d'outils en maintenant l'ordre d'exécution. Résout les problèmes de concurrence et de latence variable. """ def __init__(self, base_executor: ToolExecutor): self.base_executor = base_executor self.pending_calls = OrderedDict() self.results = {} self.sequence_number = 0 def register_call(self, tool_name: str, arguments: Dict, sequence: int): """Enregistre un nouvel appel d'outil dans la file.""" call_id = f"call_{sequence}" self.pending_calls[call_id] = { "tool_name": tool_name, "arguments": arguments, "status": "pending", "sequence": sequence } print(f"📝 Appel enregistré: {call_id} → {tool_name}") return call_id async def execute_all_pending(self): """Exécute tous les appels en attente, dans l'ordre.""" results = [] while self.pending_calls: # Prendre le premier appel de la file call_id, call_data = self.pending_calls.popitem(last=False) print(f"⚙️ Exécution: {call_id}") # Exécution asynchrone result = await asyncio.to_thread( self.base_executor.execute, call_data["tool_name"], call_data["arguments"] ) results.append({ "call_id": call_id, "sequence": call_data["sequence"], "result": result, "tool_name": call_data["tool_name"] }) # Petite pause pour éviter la surcharge await asyncio.sleep(0.1) return sorted(results, key=lambda x: x["sequence"]) def get_result_by_sequence(self, all_results, sequence): """Récupère le résultat correspondant à une séquence.""" for res in all_results: if res["sequence"] == sequence: return res["result"] return None

Démonstration

async def demo_ordered_execution(): """Démontration de l'exécution ordonnée.""" executor = OrderedToolExecutor(ToolExecutor()) # Simuler des appels dans le désordre executor.register_call("get_weather", {"city": "Marseille"}, sequence=2) executor.register_call("calculate", {"expression": "100 * 5"}, sequence=1) executor.register_call("get_weather", {"city": "Lyon"}, sequence=3) executor.register_call("calculate", {"expression": "200 / 4"}, sequence=0) print("\n📋 File d'attente:") for call_id, data in executor.pending_calls.items(): print(f" {call_id}: {data['tool_name']} (séquence: {data['sequence']})") print("\n🚀 Exécution dans l'ordre:") results = await executor.execute_all_pending() print("\n📊 Résultats triés par séquence:") for res in results: print(f" Séquence {res['sequence']}: {res['tool_name']} → {res['result']}")

Exécuter la démo

asyncio.run(demo_ordered_execution())

Bonnes Pratiques et Recommandations

Après des années d'expérience avec les protocoles de streaming en production, voici mes recommandations personnelles :

Conclusion

Le protocole MCP SSE représente une avancée majeure dans l'interaction avec les modèles d'intelligence artificielle. En maîtrisant ces concepts et en suivant les exemples fournis dans cet article, vous serez en mesure de construire des applications réactives et performantes.

Mon expérience personnelle m'a appris que la clé du succès réside dans une bonne gestion des erreurs et une supervision constante des performances. N'hésitez pas à expérimenter avec différents modèles — HolySheep AI offre un excellent rapport qualité-prix avec son système de paiement WeChat et Alipay, permettant des économies de plus de 85% par rapport aux solutions traditionnelles.

👉 Inscrivez-vous sur HolySheep AI — crédits offerts