Et si vous pouviez lancer un focus group à 2 h du matin, avec 50 agents IA très opinionnés, sur n’importe quel document — puis récupérer avant votre café du matin une liste classée des objections les plus probables ?
C’est l’idée centrale de MiroFish. Il s’agit d’un moteur open source de simulation multi-agents qui crée des agents pilotés par LLM avec des personas distincts, leur donne un document source auquel réagir, puis les laisse interagir sur une plateforme sociale simulée pendant plusieurs tours. Le résultat n’est pas une prédiction : c’est une simulation d’opinion publique synthétique. Pensez ethnographie numérique, pas prévision.
Quelques points à clarifier avant de vous emballer :
- La licence est AGPL v3.0, pas MIT. Si vous intégrez cela dans un SaaS commercial fermé, vous devrez open-sourcer vos modifications. Pour un outil interne, de la recherche ou un workflow de conseil, c’est généralement acceptable.
- Ce n’est pas un oracle. MiroFish ne vous dira pas votre taux de churn du trimestre prochain. En revanche, il peut révéler quels types de personas réagissent le plus négativement à votre page de prix.
- Le coût en tokens n’est pas négligeable. 50 agents × 20 tours = beaucoup d’appels LLM. Commencez en local avec Ollama ou avec un endpoint économique comme DeepInfra (~0,20 $ / 1 M tokens).
Pour l’installation, le README couvre tout : github.com/666ghj/MiroFish. Il vous faut Node.js 18+, Python 3.11–3.12, uv et une clé API LLM. Docker Compose est disponible si vous voulez éviter la douleur de l’installation manuelle.
Comment ça fonctionne réellement (architecture système)
Voici ce qui se passe sous le capot lorsque vous cliquez sur Launch. Trois participants interviennent : votre navigateur, la stack MiroFish (frontend React sur :3000 et backend Python/OASIS sur :5001) et l’API LLM que vous avez branchée.
browser { # Votre navigateur
n1: circle label="Start"
n2: rectangle label="Coller le document\nde départ et configurer\nles agents"
n3: rectangle label="Launch"
n10: rectangle label="Voir les logs\net les insights"
n11: circle label="Done"
n1.handle(right) -> n2.handle(left)
n2.handle(right) -> n3.handle(left)
n3.handle(bottom) -> mirofish.n4.handle(top) [label="POST /simulation/start"]
n10.handle(right) -> n11.handle(left)
}
mirofish { # Stack MiroFish (:3000 / :5001)
n4: rectangle label="Créer N agents\navec personas"
n5: rectangle label="Distribuer le document\nà chaque agent"
n6: rectangle label="Orchestrer les\ntours d’interaction"
n9: rectangle label="Agrégér les logs\net préparer les résultats"
n4.handle(right) -> n5.handle(left)
n5.handle(right) -> n6.handle(left)
n6.handle(bottom) -> llm.n7.handle(top) [label="Prompts agents\nen parallèle"]
n9.handle(top) -> browser.n10.handle(bottom) [label="JSON résultats"]
}
llm { # API LLM (OpenAI / Ollama / DeepInfra)
n7: rectangle label="Exécuter l’inférence\npour chaque agent"
n8: rectangle label="Retourner la\nréponse agent"
n7.handle(right) -> n8.handle(left)
loop [par tour] n7 n8
n8.handle(top) -> mirofish.n9.handle(bottom) [label="Sorties agents"]
}
Observez la lane LLM : elle est appelée par agent, à chaque tour, en parallèle. Avec 50 agents et 10 tours, cela représente 500 appels LLM pour une seule simulation. On comprend immédiatement pourquoi le coût en tokens devient un sujet sérieux.
Scénario 1 : valider votre copy avant le lancement
Votre landing page est prête. Vous pensez qu’elle est bonne. MiroFish permet de la tester contre 50 versions synthétiques de votre ICP avant que vous dépensiez le moindre euro en acquisition.
On retrouve ici trois participants : vous, le moteur MiroFish qui orchestre la simulation, et l’essaim d’agents qui lit, débat et réagit.
builder { # Builder
n1: circle label="Start"
n2: rectangle label="Rédiger le copy\n+ définir les personas"
n3: rectangle label="Soumettre à\nMiroFish"
n12: rectangle label="Lire le rapport\nd’objections"
n13: circle label="Affiner et itérer"
n1.handle(right) -> n2.handle(left)
n2.handle(right) -> n3.handle(left)
n3.handle(bottom) -> engine.n4.handle(top) [label="POST /simulation/start"]
n12.handle(right) -> n13.handle(left)
}
engine { # Moteur MiroFish
n4: rectangle label="Initialiser la simulation\net créer les agents"
n5: rectangle label="Distribuer le document\n+ la config personas"
n10: rectangle label="Collecter et classer\nles objections majeures"
n11: rectangle label="Construire le rapport"
n4.handle(right) -> n5.handle(left)
n5.handle(bottom) -> agents.n6.handle(top) [label="Document + persona"]
n10.handle(right) -> n11.handle(left)
n11.handle(top) -> builder.n12.handle(bottom) [label="Top 3 objections"]
}
agents { # Essaim d’agents (50 agents LLM)
n6: rectangle label="Lire le copy\net former un avis"
n7: rectangle label="Publier sur la\nplateforme simulée"
n8: rectangle label="Lire les réactions\ndes pairs"
n9: rectangle label="Mettre à jour\nmémoire et position"
n6.handle(right) -> n7.handle(left)
n7.handle(right) -> n8.handle(left)
n8.handle(right) -> n9.handle(left)
loop [10 tours] n6 n7 n8 n9
n9.handle(top) -> engine.n10.handle(bottom) [label="Sorties de tour"]
}
La boucle des agents est le cœur du système : ils publient leur opinion, lisent les réactions des autres, puis ajustent leur position à chaque tour. Au bout de 10 tours, des clusters d’opinion dominants apparaissent. Le moteur les classe et vous renvoie les principales objections.
Exemple de configuration de personas pour un produit SaaS B2B :
{
"seed_document": "Le texte de votre landing page...",
"agent_count": 50,
"personas": [
{ "type": "skeptical_cto", "weight": 0.2 },
{ "type": "budget_conscious_pm", "weight": 0.3 },
{ "type": "early_adopter", "weight": 0.2 },
{ "type": "enterprise_buyer", "weight": 0.3 }
],
"max_rounds": 10
}
Post-traitement de la sortie : la sortie brute est un log JSON de discussion. Faites-le ensuite résumer par un LLM avec une instruction du type : À partir de ce log de discussion, extrais les 3 objections les plus fréquentes sur le pricing, la fonctionnalité la plus appréciée et la capacité manquante la plus demandée.
Scénario 2 : injection de crise en cours de simulation
C’est là que MiroFish devient vraiment intéressant. Vous pouvez mettre en pause une simulation en cours à n’importe quel tour, injecter un nouvel événement — actualité, annonce concurrente, changement réglementaire — puis observer comment les agents réévaluent tout ce qu’ils pensaient jusqu’ici.
Ce schéma montre la chaîne complète à trois participants : vous déclenchez l’injection, le moteur diffuse l’événement, et les agents débattent à nouveau avec ce nouveau contexte intégré dans leur mémoire.
builder { # Builder
n1: circle label="Start"
n2: rectangle label="Définir le scénario\nde base + document"
n3: rectangle label="Lancer la simulation\nde base"
n9: rectangle label="Injecter un\névénement de crise"
n15: rectangle label="Comparer le delta :\navant vs après"
n16: circle label="End"
n1.handle(right) -> n2.handle(left)
n2.handle(right) -> n3.handle(left)
n3.handle(bottom) -> engine.n4.handle(top) [label="POST /simulation/start"]
n9.handle(bottom) -> engine.n10.handle(top) [label="POST /simulation/{id}/inject"]
n15.handle(right) -> n16.handle(left)
}
engine { # Moteur MiroFish
n4: rectangle label="Initialiser la\nsimulation de base"
n5: rectangle label="Envoyer les agents\npour les tours 1–5"
n8: rectangle label="Base capturée\n— prévenir le builder"
n10: rectangle label="Recevoir la variable\nde crise"
n11: rectangle label="Diffuser l’événement\naux agents"
n14: rectangle label="Agrégér les logs\naprès crise"
n4.handle(right) -> n5.handle(left)
n5.handle(bottom) -> agents.n6.handle(top) [label="Exécuter 5 tours"]
n8.handle(top) -> builder.n9.handle(bottom) [label="Base prête — injecter maintenant"]
n10.handle(right) -> n11.handle(left)
n11.handle(bottom) -> agents.n12.handle(top) [label="Injection : contexte de crise"]
n14.handle(top) -> builder.n15.handle(bottom) [label="Logs post-crise"]
}
agents { # Essaim d’agents
n6: rectangle label="Exécuter les tours\nde base 1–5"
n7: rectangle label="Base terminée"
n12: rectangle label="Absorber le\ncontexte de crise"
n13: rectangle label="Redébattre avec\nla nouvelle information"
n6.handle(right) -> n7.handle(left)
n7.handle(top) -> engine.n8.handle(bottom) [label="Sorties de base"]
n12.handle(right) -> n13.handle(left)
n13.handle(top) -> engine.n14.handle(bottom) [label="Sorties post-crise"]
}
Exemple de payload d’injection :
{
"event_type": "market_event",
"content": "Breaking: un concurrent majeur vient de lancer une alternative entièrement gratuite avec 80 % de parité fonctionnelle.",
"inject_at_round": 5
}
Le delta de sentiment entre avant et après l’injection vous indique quels types de personas sont les plus vulnérables — et dans quelle mesure. C’est votre carte du risque concurrentiel.
Scénario 3 : tests d’offres A/B
Deux mécaniques de programme de fidélité. Deux simulations. Un gagnant. Sans attendre quatre semaines de résultats A/B réels.
pm { # Product Manager
n1: circle label="Start"
n2: rectangle label="Définir la variante A\net la variante B"
n3: rectangle label="Soumettre la variante A\nà la simulation"
n7: rectangle label="Soumettre la variante B\nà la simulation"
n11: rectangle label="Comparer les résultats :\nchoisir le gagnant"
n12: circle label="Ship it"
n1.handle(right) -> n2.handle(left)
n2.handle(right) -> n3.handle(left)
n3.handle(bottom) -> mirofish.n4.handle(top) [label="POST /simulation/start (A)"]
n7.handle(bottom) -> mirofish.n8.handle(top) [label="POST /simulation/start (B)"]
n11.handle(right) -> n12.handle(left)
}
mirofish { # Moteur MiroFish
n4: rectangle label="Simuler la variante A :\n50 agents réagissent"
n5: rectangle label="Les agents évaluent\nla valeur perçue"
n6: rectangle label="Agrégér :\nscores de la variante A"
n8: rectangle label="Simuler la variante B :\n50 agents réagissent"
n9: rectangle label="Les agents évaluent\nla valeur perçue"
n10: rectangle label="Agrégér :\nscores de la variante B"
n4.handle(right) -> n5.handle(left)
n5.handle(right) -> n6.handle(left)
n6.handle(top) -> pm.n7.handle(bottom) [label="Variante A : sentiment + intention"]
n8.handle(right) -> n9.handle(left)
n9.handle(right) -> n10.handle(left)
n10.handle(top) -> pm.n11.handle(bottom) [label="Variante B : sentiment + intention"]
}
Ce que vous comparez entre les deux runs : le score d’intention d’activation, la note de valeur perçue et le signal de risque de churn. Si la variante A gagne sur l’activation mais que la variante B gagne sur la valeur perçue, vous avez une vraie décision de segmentation — pas un pile ou face.
Avant d’expédier quoi que ce soit
| Point de contrôle | Pourquoi c’est important |
|---|---|
| Qualité du seed | Un seed dense et spécifique produit des agents plus utiles. Un seed vague produit des sorties vagues. |
| Pondération des personas | Reflétez votre vraie distribution d’audience pour éviter une simulation biaisée. |
| Commencer petit | Lancez 5 à 10 tours d’abord. Montez ensuite seulement si la sortie paraît cohérente. |
| Choix du modèle | Utilisez un modèle local 7B ou GPT-4o-mini pour le développement. Gardez les modèles premium pour les validations finales. |
| Post-traitement | Passez toujours les logs par un agrégateur LLM. Le JSON brut n’est pas un insight. |
| Run de base neutre | Commencez avec un seed neutre pour établir un niveau de sentiment de référence. |
| AGPL | Outil interne = généralement acceptable. SaaS emballé autour de MiroFish = obligation d’ouvrir le code. |
Visualiser ces diagrammes
Collez n’importe lequel des blocs FlowZap Code ci-dessus, ou enregistrez-les dans un fichier .fz, puis importez-les dans votre projet FlowZap pour les transformer en diagrammes visuels partageables avec votre équipe.
Arrêtez de déplacer des flèches à la main. Concevez des systèmes, avec FlowZap.
