Un agent IA qui surveille les modifications de Wikipédia en direct, analyse les changements suspects et donne son verdict… tout ça depuis ton propre ordinateur.
LE STREAMING, C'EST DEUX CHOSES DIFFÉRENTES
Quand on parle d'agents IA, le mot streaming peut vouloir dire deux choses très différentes. Parfois, ça signifie que l'agent consomme un flux d'événements en direct au lieu d'attendre qu'un humain tape un message. Parfois, ça veut dire que sa réponse s'affiche petit à petit, comme un texte qui s'écrit sous nos yeux, plutôt que d'apparaître d'un coup après un long silence.
Ce projet fait les deux. Pourquoi ? Parce que chaque type de streaming résout un problème différent. Un agent vraiment utile, qui tourne en permanence, a besoin des deux. Prenons un exemple concret : un agent qui surveille les modifications de Wikipédia en temps réel pour repérer les vandalismes, le tout depuis ton ordinateur, sans serveur externe.
UN AGENT QUI SE RÉVEILLE TOUT SEUL, SANS QUE TU LUI DÉMANDES RIEN
Ce qu'on appelle un agent ambiant (ou ambient agent en anglais) est déclenché par des événements, pas par une commande humaine. Imagine un réveil qui sonne quand le soleil se lève, pas quand tu appuies sur un bouton. C'est exactement ça : l'agent se lance quand quelque chose se passe sur le flux de modifications de Wikipédia, sans que tu aies besoin de lui envoyer un message.
Le projet que tu vas découvrir ici est bien réel. Il a été testé, chaque ligne de code a été écrite puis vérifiée avant d'être publiée. L'objectif ? Créer un agent local qui surveille le flux public des modifications de Wikipédia, analyse les changements suspects et donne son avis… le tout sur ta machine, avec Ollama, un outil qui permet de faire tourner des modèles d'IA en local.
WIKIPÉDIA, UN FLUX D'ÉDITIONS QUI DÉFIE L'ENTENDEMENT
Le flux des modifications de Wikipédia n'est pas un petit ruisseau tranquille. Sur une journée chargée, il peut envoyer plusieurs modifications par seconde, toutes éditions confondues et dans toutes les langues. Si tu envoies chaque modification à un modèle de langage, deux problèmes se posent immédiatement :
- 1. Tu gaspilles la puissance de calcul de ton ordinateur pour analyser des modifications qui n'ont strictement aucun intérêt.
- 2. L'agent prend du retard sur le flux en direct qu'il est censé surveiller, ce qui rend tout le projet inutile.
La solution ? Un entonnoir à deux étages, la clé de voûte de ce projet.
UN ENTONNOIR POUR NE PAS NOYER L'AGENT DANS LES MODIFICATIONS
L'idée est simple : filtrer d'abord les modifications évidentes avant de les envoyer à l'IA. Voici comment ça marche :
Premier étage : le filtre rapide. Il repère les modifications qui ont l'air suspectes dès le premier coup d'œil, sans avoir besoin d'analyser le contenu. Par exemple, si un utilisateur supprime plus de 1000 caractères en une seule fois, ou s'il fait 5 modifications en 10 secondes, c'est probablement un signe de vandalisme.
Deuxième étage : l'IA locale. Seules les modifications qui passent le premier filtre sont envoyées au modèle de langage. Celui-ci analyse le contexte, la page modifiée, l'historique de l'utilisateur et donne son verdict : est-ce du vandalisme ou une modification légitime ?
Ce système permet de ne pas surcharger l'IA avec des modifications inutiles et de garder l'agent réactif en permanence.
LA STRUCTURE DU PROJET : UN FICHIER PAR ÉTAPE
Chaque fichier du projet correspond à une étape précise du processus. C'est volontaire : ça rend le code plus facile à comprendre et à tester séparément. Voici l'arborescence complète :
streaming-local-agent/
├── src/
│ ├── __init__.py
│ ├── config.py
│ ├── schemas.py
│ ├── stream_source.py
│ ├── filters.py
│ ├── agent.py
│ ├── broadcaster.py
│ └── main.py
├── tests/
│ └── test_filters.py
├── requirements.txt
└── .env.example
Chaque dossier et fichier a un rôle précis :
- src/ : contient tout le code source.
- tests/ : contient les tests, notamment pour vérifier que le filtre fonctionne correctement.
- requirements.txt : liste des bibliothèques Python nécessaires.
- .env.example : exemple de fichier de configuration à adapter.
COMMENT WIKIPÉDIA ENVOIE SES MODIFICATIONS : LE FLUX D'ÉVÉNEMENTS
Wikipédia propose un service appelé EventStreams qui envoie les modifications en temps réel sous forme d'événements servés par serveur (Server-Sent Events). C'est un protocole simple : le serveur ouvre une connexion HTTP avec le client et lui envoie des données au fur et à mesure, sans que le client ait besoin de faire des requêtes répétées.
Pas besoin de clé API ni de protocole compliqué. Une simple requête GET suffit, et la connexion reste ouverte tant que le serveur a des données à envoyer.
DÉCORTIQUER UN FLUX D'ÉVÉNEMENTS : LE CODE DU FICHIER stream_source.py
Voici le code qui gère la connexion au flux de modifications de Wikipédia. On va le décortiquer ensemble.
# src/stream_source.py
import asyncio
import json
import re
import time
from typing import AsyncIterator, Optional
import httpx
from .schemas import RecentChangeEvent
from . import config
# Wikipedia n'envoie pas de champ explicite "est-ce que l'utilisateur est anonyme"
# sur ce flux ; les modifications anonymes sont attribuées à l'adresse IP de l'éditeur
# au lieu d'un nom d'utilisateur, donc on détecte un utilisateur anonyme en vérifiant
# si son nom ressemble à une adresse IPv4 ou IPv6.
IPV4RE = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
IPV6RE = re.compile(r"^[0-9A-Fa-f:]+:[0-9A-Fa-f:]+$")
def isanonymoususer(username: str) -> bool:
return bool(IPV4RE.match(username) or IPV6RE.match(username))
def parsesseline(line: str) -> Optional[dict]:
"""Parse les lignes du flux SSE (Server-Sent Events).
Les lignes commençant par 'data: ' contiennent les données.
Les lignes de commentaire (commençant par ':') ou les lignes vides
sont ignorées, car ce sont des messages de maintien de connexion."""
if not line or line.startswith(":"):
return None
if line.startswith("data:"):
raw = line[len("data:"):].strip()
if not raw:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
return None
return None
def to_event(raw: dict) -> Optional[RecentChangeEvent]:
"""Convertit une donnée brute de Wikimedia en un schéma normalisé.
Retourne None pour les types d'événements qu'on ne surveille pas,
plutôt que de lever une erreur, car un flux aussi volumineux inclut
constamment des formes d'événements qu'on ne surveille pas."""
if raw.get("type") .= "edit":
return None
length = raw.get("length") or {}
if "old" not in length or "new" not in length:
return None
return RecentChangeEvent(
wiki=raw.get("wiki", "unknown"),
user=raw.get("user", "unknown"),
title=raw.get("title", "unknown"),
isanonymous=isanonymous_user(raw.get("user", "")),
is_bot=raw.get("bot", False),
old_length=length["old"],
new_length=length["new"],
timestamp=raw.get("timestamp", time.time()),
comment=raw.get("comment", "") or "",
)
async def wikipediaeventstream() -> AsyncIterator[RecentChangeEvent]:
"""Générateur asynchrone qui consomme le flux de modifications de Wikipédia.
Se reconnecte automatiquement en cas de coupure réseau pour garantir
que l'agent reste toujours actif."""
while True:
try:
async with httpx.AsyncClient(timeout=None) as client:
async with client.stream("GET", config.WIKIPEDIASTREAMURL) as response:
async for line in response.aiter_lines():
raw = parsesseline(line)
if raw is None:
continue
if raw.get("wiki") not in config.WATCHED_WIKIS:
continue
event = to_event(raw)
if event is not None:
yield event
except httpx.HTTPError:
await asyncio.sleep(5)
COMMENT DÉTECTER UN UTILISATEUR ANONYME ? LA RUSE DU CODE
Attention, une astuce importante : Wikipédia n'envoie pas de champ explicite pour savoir si un utilisateur est anonyme. Au lieu de ça, les modifications anonymes sont attribuées à l'adresse IP de l'utilisateur, qui devient son
- KDnuggets
L'indépendance de CLODCO est votre garantie.
Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.
Soutenir CLODCO


