Auteurs : @hi_im_isaac_, @learnwdaniel, @gaozenghao
Note : la version interactive du blog technique complet est disponible : https://www.cerebras.ai/blog/how-we-built-our-knowledge-base
Les employés posent plus de 15 000 questions par jour à notre base de connaissances interne. Depuis son lancement il y a 3 mois, c'est devenu l'un des outils internes les plus adoptés de l'entreprise. Utilisé par les humains, les automatismes et les agents.
Chez Cerebras, nos équipes travaillent sur l'exploitation des centres de données, la conception de puces, le matériel, l'entraînement, l'inférence, la plateforme cloud, et bien plus encore. Avec des centaines de nouveaux employés qui rejoignent l'entreprise chaque année, nos canaux de communication étaient saturés par les mêmes questions :
- « Où puis-je trouver X ? »
- « Qui est l'expert en Y ? »
- « Qu'est-ce que Z ? »

Nous avons construit Cerebras Knowledge pour aider les personnes et les systèmes à se connecter à des informations utiles.
Aller là où se trouvent les données
Trouver des informations au sein d'une organisation est difficile. Les données sont dispersées entre différents outils, et presque chaque trimestre, quelqu'un propose la même solution brillante : enregistrons tout sur une seule plateforme pour que toutes les informations soient au même endroit. Le rêve d'une source unique de vérité, bien sûr, fonctionne rarement dans la pratique.
Les informations sont générées là où c'est pratique et ergonomique : modifications suggérées dans un document, fils de discussion dans Slack, références de code dans GitHub, et métadonnées de statut dans Jira. Ces plateformes sont conçues sur mesure pour leurs domaines spécifiques, optimisées par des années d'ingénierie produit et d'analytique. Discuter d'une pull request dans Google Docs serait une expérience terrible.
Nous avons donc entrepris de concevoir un système qui nécessitait un changement minimal dans le comportement existant. Du côté de la collecte de données, cela signifiait extraire les données directement de chaque plateforme.
Anatomie d'une base de connaissances
Notre base de connaissances offre trois choses :
- Une plateforme pour collecter et stocker les données internes.
- Une plateforme pour interroger ces données.
- Une couche qui applique l'authentification et l'autorisation, avec audit et analytique.
Au cœur se trouve une seule table Postgres qui contient les embeddings, les résumés bruts et les métadonnées de nombreuses sources. Le système ingère en continu les données de toute l'entreprise et maintient un magasin de données prêt à être interrogé.
Nous voulions une interface de données simple mais capable de fonctionner avec la plupart des formes de données. Nous voulions également que d'autres développeurs chez Cerebras puissent créer des connecteurs personnalisés. Le résultat est délibérément simple : chaque source, des fils Slack aux netlists, atterrit dans la même table d'embeddings, et tout ce qui se trouve dans cette table est immédiatement interrogeable via la même interface :

Chaque source de données définit ce que sont les données, comment s'y connecter et à quelle fréquence elles doivent être récupérées. Chaque ligne d'embedding résultante suit la même interface, qu'elle provienne de Slack, d'un dépôt de code, d'un système de documents ou d'une base de données personnalisée.
Comment nous traitons les conversations Slack non structurées
Slack était la source de données la plus importante que nous devions concevoir. C'est là que se déroulent les discussions d'ingénierie les plus récentes dans toute l'entreprise.

Nous avons d'abord testé si de simples embeddings sur du texte brut étaient suffisamment performants. Nous avons rapidement réalisé que la recherche vectorielle seule ne suffisait pas pour faire correspondre toutes les données pertinentes.
Les messages Slack présentent plusieurs défis :
- La densité d'information varie énormément : « hey ouais bien sûr mike » et une explication détaillée d'un noyau sont tous deux des messages.
- Les longueurs des messages varient, et les messages plus courts surpassent fréquemment les messages plus longs et plus détaillés en similarité cosinus.
- Le sens d'un message dépend souvent de la conversation environnante.
Nous avions besoin d'une approche hybride. Nous avons construit l'ingestion Slack de sorte que chaque fil soit récupérable via plusieurs techniques de recherche à la fois, chaque technique compensant les faiblesses des autres :
- La recherche en texte intégral capture les tokens exacts que les embeddings brouillent : chaînes d'erreur, noms de drapeaux, noms d'hôtes. Lorsqu'un ingénieur colle un message d'erreur littéral, une correspondance lexicale exacte est presque toujours la meilleure preuve, et aucune quantité de similarité sémantique ne devrait la surpasser.
- La recherche par embedding capture la paraphrase. La personne qui demande « le restaurer se bloque après le chargement du manifeste » et celle qui a répondu « le point de contrôle stagne sur le montage NFS » peuvent ne jamais partager de vocabulaire. La similarité vectorielle est ce qui relie une question à une réponse écrite avec des mots différents. (1)
- La fréquence inverse de document sépare le signal du bruit. Un message court construit autour de tokens rares, comme un drapeau de configuration obscur, mérite d'être classé. « super, merci ! » se trouve proche de nombreuses requêtes dans l'espace d'embedding mais obtient un score proche de zéro une fois la rareté du terme prise en compte.
- Le déclin temporel encode le fait que les réponses Slack expirent. Deux fils peuvent répondre à la même question, et celui d'il y a six mois peut décrire une infrastructure qui n'existe plus. Lorsque la pertinence est par ailleurs égale, le fil le plus récent l'emporte.

Aucun score unique n'est considéré comme fiable en soi. Chaque technique produit sa propre vue classée du même corpus, et ces vues sont fusionnées au moment de la requête (voir Reclassement).
Mode Socket
Pour collecter les données en temps réel, nous avons installé un bot Slack dans notre espace de travail et l'avons exécuté en mode Socket. Slack nous envoie chaque événement de message via un WebSocket persistant, ce qui nous permet d'obtenir des mises à jour en temps réel sans interroger l'API Web et épuiser ses limites de débit.
Lorsqu'un événement arrive, nous l'accusons immédiatement réception, le dédupliquons à l'aide de l'ID d'événement stable, et marquons le message pour le consommateur d'ingestion.
Le consommateur d'ingestion n'enregistre pas un nouveau message isolément. Il résout le fil auquel le message appartient et récupère l'intégralité de la conversation, y compris le message parent et chaque réponse, à partir de l'API Slack. Il écrit ensuite l'intégralité du fil comme une seule ligne. Une réponse à un fil existant récupère donc à nouveau le parent et tous les frères, de sorte que le contenu stocké, la liste des participants et l'horodatage de la dernière activité reflètent toujours la conversation complète.
Chaque canal Slack de notre système possède sa propre source de données. Cela permet un réglage fin de la fraîcheur des données. Une équipe peut choisir d'ingérer un canal d'incident très actif plus fréquemment, par exemple.
Fils et messages
Le texte Slack brut est interrogeable par mots-clés dès son arrivée car nous maintenons un index Postgres en texte intégral (GIN) sur le contenu brut. Cependant, pour permettre une recherche vectorielle utile, nous effectuons un traitement supplémentaire. (8)
Pendant la distillation, un LLM extrait des données structurées du fil complet :
- Une question en une ligne qu'un ingénieur rechercherait réellement.
- Un court résumé.
- La résolution.
- Les systèmes et références de code mentionnés.

Nous intégrons ces points de données et les écrivons dans la table d'embeddings partagée. La transcription originale n'est pas intégrée directement. Dans nos expériences, la précision a augmenté de manière significative lorsque le fil a été normalisé dans un format cohérent. (7,9) Les métadonnées supplémentaires donnent également à la correspondance sémantique un signal plus utile.
Bursting
À ce stade, la recherche Slack était bonne, mais nous rencontrions toujours le même problème : les messages importants dans les longs fils n'étaient pas toujours représentés dans le résumé au niveau du fil.
Pour renforcer le signal des messages individuels, nous utilisons le bursting. Un burst est une série de messages consécutifs du même auteur. Nous intégrons les bursts individuels avec le sujet du fil ajouté comme contexte (2) car parfois la réponse se trouve dans un message tangent dont le vocabulaire n'apparaît jamais dans le résumé du fil. Les embeddings de burst rendent ce message trouvable par lui-même.
Pour empêcher les données à faible signal d'atteindre la base de données, chaque burst est noté par rapport à une combinaison pondérée de signaux et doit franchir un seuil avant d'être intégré :
- Il contient un token relativement rare dans l'ensemble du corpus, avec un IDF d'au moins 4,0.
- Le burst combiné fait au moins 200 caractères.
- Un ou plusieurs messages dans le burst contiennent des réactions, fournissant un boost social.

Après distillation, les bursts éligibles sont intégrés et stockés dans la table d'embeddings aux côtés de l'enregistrement au niveau du fil.
Dépôts de code
Au départ, nous avons débattu de la nécessité d'intégrer les dépôts de code. Avec l'essor de Claude Code et d'autres outils en ligne de commande, créer des embeddings de code semblait contre-intuitif alors qu'il semblait que « grep est tout ce dont vous avez besoin ». Après avoir discuté avec d'autres personnes du secteur et lu les conclusions de Cursor sur la recherche sémantique dans les grandes bases de code, nous avons décidé d'essayer.
Nous avons de nombreux dépôts internes, certains de plus de 40 Go. Notre principale préoccupation était de savoir comment les maintenir à jour efficacement.
Utilisation de @cocoindex_io pour maintenir les embeddings de code
Après plusieurs expériences, nous avons choisi CocoIndex, un framework open source d'embedding de documents spécialisé dans la vectorisation de bases de code.
Pour chaque dépôt, nous divisons le code en utilisant des limites d'expressions régulières spécifiques au langage, ordonnées du grossier au fin. Le diviseur essaie d'abord les limites de niveau supérieur, comme les classes. Si un morceau résultant est encore trop grand, il revient aux limites des méthodes, puis aux blocs plus petits. Nous intégrons les morceaux résultants et écrivons les vecteurs dans Postgres. Un seul fichier peut générer plusieurs embeddings à différents niveaux de spécificité, comme des enregistrements au niveau du fichier et au niveau de la fonction.

CocoIndex suit les métadonnées de synchronisation dans Postgres. À chaque commit, il ré-intègre et ré-exporte uniquement les morceaux de code modifiés au lieu de recalculer l'intégralité du dépôt. Cela a particulièrement bien fonctionné pour nous car l'état de synchronisation et le magasin d'embeddings vivent dans la même base de données.
À mesure que le nombre de bases de code augmentait, nous avons déplacé l'intégration des dépôts vers des fichiers de configuration que les équipes peuvent soumettre elles-mêmes, y compris les listes d'autorisation et de blocage au niveau du chemin de fichier.
Sources de données personnalisées
Certaines équipes avaient déjà leurs propres bases de données et ne voulaient pas déplacer les données vers Slack ou un système de documents simplement pour participer à la base de connaissances. Elles voulaient la même surface d'interrogation sur leurs tables existantes.
Pour prendre en charge cela, nous traitons les sources personnalisées comme des scripts de plugin. Une équipe ouvre une pull request avec un petit module Python qui sait lire à partir de son système et émettre des lignes formatées comme notre table d'embeddings, plus une entrée de source de données correspondante.
Tant que le script écrit dans la base de données partagée en utilisant le même schéma que toutes les autres lignes d'embedding, le reste de la pile fonctionne inchangé. Les données deviennent interrogeables aux côtés de Slack, du code et des documents, sans traitement spécial ailleurs dans le système.
Planification et déploiement des outils
Pour chaque requête, nous exécutons d'abord un court passage de planification où un LLM décide quels outils et sources de données sont susceptibles d'être importants. Les principaux outils :
- subsystem_index : résumés LLM par fichier.
- search : le pipeline vectoriel unifié à travers Slack, le wiki, le code et d'autres sources indexées, fusionné et reclassé en interne.
- search_slack : récupération directe dans Slack.
- search_code : ripgrep sur les dépôts source.
- recent_prs : pull requests récentes pertinentes pour la question.
- who_knows : personnes ayant démontré une expertise sur un sujet.
Le planificateur travaille sur une description compacte de ce que nous avons indexé : quels projets existent, quelles sources sont disponibles dans chaque projet, et à quoi chaque source est bonne pour répondre. Compte tenu de la requête de l'utilisateur et du périmètre actif, il émet des sélections d'outils que l'exécuteur déploie en parallèle, normalise dans un format de preuve commun, et transmet à un LLM de synthèse final. (4)

Reclassement
Un document peut apparaître près du haut simplement parce qu'il partage du vocabulaire avec la requête tout en répondant à une question différente. Avant le reclassement, nous combinons les listes de résultats incompatibles des récupérateurs avec la fusion de rang réciproque, ou RRF. Pour chaque document, nous ajoutons poids / (60 + rang) pour chaque liste dans laquelle il apparaît, avec un poids par défaut de 1,0 et une constante de lissage de 60.

La constante de lissage fait en sorte que le consensus compte plus qu'un seul vote fort : un document qui apparaît près du haut dans plusieurs récupérateurs peut battre un document qui est premier dans un seul d'entre eux. Nous fusionnons ensuite les morceaux en double en une seule source, plafonnons le nombre de résultats que chaque fichier peut contribuer, et obtenons un top vingt plus diversifié.
Nous envoyons la requête originale et ces candidats à un petit modèle de reclassement. Il donne à chaque document un score de zéro à dix, et nous conservons les dix premiers. (6)
Une fois le classement finalisé, nous ajoutons du contexte aux gagnants. Par exemple, si nous correspondons à une section wiki, nous incluons les deux sections voisines afin que le titre, les préconditions et les mises en garde que le découpage a séparés ne soient pas perdus. Cela donne aux lecteurs un extrait complet au lieu d'un paragraphe isolé manquant de contexte important.
Ainsi, le résultat de la recherche est un riche ensemble de preuves : résultats fusionnés de différents récupérateurs, dédupliqués au niveau de la source, reclassés par rapport à la question réelle, et ensuite seulement élargis avec le contexte environnant.
MCP
Dans l'intégration MCP, nous exposons les blocs de construction de la récupération comme des outils directs au lieu de les cacher derrière un point de terminaison « répondre à cette question ». Ces outils sont intentionnellement simples et aussi exempts de LLM que possible afin que les clients puissent les interroger rapidement et à moindre coût. (5)
Chaque outil MCP correspond à une primitive de récupération sous-jacente, comme search_slack, search_code, search, ou who_knows. Les entrées et sorties des outils sont étroites, structurées et stables, ce qui les rend faciles à appeler depuis n'importe quel client ou agent sans intégrer de logique d'orchestration supplémentaire à l'intérieur de l'outil lui-même.
La plupart des outils exécutent un pipeline de requête unique, comme la recherche vectorielle, la recherche lexicale ou ripgrep, appliquent des heuristiques de score légères et renvoient des lignes de preuve brutes.
Claude Code, ou tout agent compatible MCP, devient le moteur d'orchestration. Il décide quels outils appeler, dans quel ordre, et comment assembler les résultats en une réponse finale ou une modification de code. La couche de récupération elle-même ne dépend pas de ces décisions LLM pour servir les requêtes.
Interface Web
Dans l'interface Web, les mêmes outils existent, mais ils sont connectés à un pipeline de requête complet qui s'exécute de bout en bout pour chaque question d'utilisateur. L'agent de l'interface utilisateur possède les étapes de planification et d'exécution.
- Planificateur : Un passage LLM léger inspecte la requête et le projet actif, puis choisit quels outils de récupération invoquer, tels que search, search_slack et subsystem_index.
- Exécuteur : Le système déploie ces appels d'outils en parallèle, rassemble les résultats et les normalise dans un schéma de preuve partagé avec des scores, la récence et des indices de source.
- Synthèse : Un passage LLM final prend le lot de preuves typées et la question originale, puis produit la réponse affichée dans l'interface utilisateur, y compris les citations, les mises en garde et la synthèse inter-sources.
Du point de vue de l'utilisateur, l'interface Web est simplement « poser une question et obtenir une réponse ». Sous le capot, elle exécute le même modèle planificateur → exécuteur → synthétiseur que les clients MCP peuvent recréer explicitement.

Organisation
À mesure que le corpus grandissait, « tout rechercher partout » a rapidement cessé d'être utile. Les ingénieurs des équipes de compilation ne voulaient pas de manuels d'infrastructure dans leurs résultats, et vice versa. Les projets sont notre façon de rendre la recherche pertinente par défaut.
Projets et recherche ciblée
Nous avons introduit les projets comme principal moyen d'organiser l'espace de travail sur lequel une requête s'exécute. Un projet est un ensemble nommé de sources de données : canaux Slack spécifiques, dépôts de code, bases de données internes et espaces documentaires pertinents pour une équipe ou une initiative.
Les projets sont intentionnellement légers. La même source de données, comme un canal d'incidents partagé ou un dépôt de plateforme central, peut être référencée par plusieurs projets au lieu d'être dupliquée.

Intégration et paramètres par défaut
Lors de l'intégration, les utilisateurs sont invités à sélectionner ou créer un projet par défaut qui correspond à leur façon de travailler, comme l'infrastructure d'entraînement ML, le compilateur ou les opérations du centre de données.
Ce projet par défaut est stocké sur le profil de l'utilisateur et limite automatiquement les requêtes. Un nouvel ingénieur obtient des réponses à fort signal sans avoir d'abord à apprendre quels canaux Slack, dépôts ou espaces documentaires sont importants.
Réflexions finales
En fin de compte, la base de connaissances fonctionne parce qu'elle va là où les informations vivent déjà, au lieu de tout forcer dans un système rigide. En combinant diverses techniques de recherche, nous pouvons faire remonter les preuves rapidement. Le résultat est une expérience de recherche qui reste suffisamment flexible pour les données réelles de l'entreprise, mais suffisamment structurée pour rester utile à mesure que Cerebras continue de croître.
Si vous avez lu jusqu'ici et que cela vous intéresse, l'équipe ai/croissance recrute, contactez-nous si vous êtes intéressé @learnwdaniel
Références
- Malkov et Yashunin, Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs, arXiv:1603.09320 / IEEE TPAMI 2018.
- Anthropic, Introducing Contextual Retrieval, 2024.
- Cormack, Clarke et Büttcher, Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods, SIGIR 2009.
- Li et al., Search-o1: Agentic Search-Enhanced Large Reasoning Models, arXiv:2501.05366, 2025.
- Anthropic, Code Execution with MCP, 2025.
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, arXiv:2307.03172, 2023.
- Anthropic, Use XML Tags.
- Salesforce/Slack Engineering, How Slack AI Processes Billions of Messages.
- Improving Agents, Best Nested Data Format.
- Cursor, Improving Agent with Semantic Search, 2025.





