Anleitung

Notion + RAG: Ihr Unternehmens-Wiki verbinden

24. März 2026
Equipe Ailog

Umfassender Leitfaden zur Integration von Notion als Wissensquelle für einen RAG-Chatbot. Synchronisierung, Indexierung, semantische Suche und praktische Anwendungsfälle.

Notion + RAG : Connecter votre wiki d'entreprise

Notion est devenu le wiki de reference pour des milliers d'entreprises. Sa flexibilite, son interface intuitive et ses fonctionnalites de collaboration en font un outil incontournable pour centraliser la connaissance d'equipe. Mais a mesure que votre workspace grandit, un probleme emerge : retrouver l'information devient un cauchemar. Avec des centaines de pages, sous-pages et bases de donnees, meme les utilisateurs les plus experimentes passent de precieuses minutes a chercher ce qu'ils savent exister quelque part.

Un chatbot RAG connecte a Notion transforme cette masse documentaire en assistant intelligent. Au lieu de naviguer, vous posez une question en langage naturel et obtenez une reponse synthetisee, sourcee et contextualisee. Ce guide vous montre comment realiser cette integration pas a pas.

Pourquoi connecter Notion au RAG ?

Les limites de la recherche Notion native

La recherche integree de Notion, bien qu'utile, presente des limitations significatives pour les grandes organisations :

ProblemeImpact concret
Recherche par mots-cles uniquement"Comment demander des conges" ne trouve pas "procedure d'absence"
Pas de recherche dans les bases de donneesLes proprietes et champs ne sont pas indexes
Resultats non classes par pertinencePages recentes privilegiees sur les plus pertinentes
Pas de syntheseL'utilisateur doit ouvrir et lire chaque page
Pas de contexte conversationnelChaque recherche repart de zero

Ce qu'apporte le RAG

L'approche RAG (Retrieval-Augmented Generation) resout ces limitations en combinant recherche semantique et generation de langage :

  • Recherche semantique : Trouve l'information meme formulee differemment. "Comment poser des RTT" matchera "Procedure de demande de jours de repos"
  • Synthese intelligente : Repond directement sans obliger a naviguer dans 5 pages
  • Agregation multi-pages : Combine les informations de plusieurs sources pour une reponse complete
  • Memoire conversationnelle : Chaque question beneficie du contexte des echanges precedents
  • Citations sourcees : Chaque affirmation renvoie vers la page d'origine

Architecture Notion + RAG

L'integration suit une architecture en trois couches qui separe l'extraction, l'indexation et l'interrogation :

┌─────────────────────────────────────────────────────────────────────────┐
│                        Architecture Notion + RAG                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   EXTRACTION                   INDEXATION                INTERROGATION  │
│   ┌──────────────┐            ┌──────────────┐          ┌────────────┐ │
│   │  Notion API  │───────────▶│   Chunking   │─────────▶│   Qdrant   │ │
│   │              │            │              │          │            │ │
│   │  - Pages     │            │  - Sections  │          │  Vecteurs  │ │
│   │  - DBs       │            │  - 500 tokens│          │            │ │
│   │  - Blocs     │            │  - Overlap   │          └──────┬─────┘ │
│   └──────────────┘            └──────────────┘                 │       │
│                                      │                         │       │
│                               ┌──────┴──────┐                  │       │
│                               │  Embeddings │                  │       │
│                               │   BGE-M3    │                  │       │
│                               └─────────────┘                  │       │
│                                                                │       │
│   CHATBOT                                                      │       │
│   ┌──────────────┐     ┌─────────────┐     ┌──────────────┐   │       │
│   │   Question   │────▶│  Retrieval  │◀────│   Reranker   │◀──┘       │
│   │  utilisateur │     │   Top-20    │     │   Top-5      │           │
│   └──────────────┘     └─────────────┘     └──────┬───────┘           │
│                                                    │                   │
│                                             ┌──────┴──────┐            │
│                                             │     LLM     │            │
│                                             │   Reponse   │            │
│                                             └─────────────┘            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Composants cles

  1. Extraction : Le connecteur Notion utilise l'API officielle pour recuperer pages et bases de donnees
  2. Chunking : Les documents longs sont decoupes en segments de 500 tokens avec chevauchement
  3. Embeddings : Chaque chunk est transforme en vecteur semantique (BGE-M3 pour le multilingue)
  4. Base vectorielle : Qdrant stocke et indexe les vecteurs pour recherche rapide
  5. Reranking : Un second modele reordonne les resultats par pertinence
  6. Generation : Le LLM synthetise une reponse a partir des chunks pertinents

Connecteur Notion complet

Voici une implementation de reference pour extraire le contenu Notion :

DEVELOPERpython
from notion_client import Client from datetime import datetime import hashlib class NotionConnector: def __init__(self, token: str): """Initialise le connecteur avec le token d'integration.""" self.client = Client(auth=token) self.processed_ids = set() def get_all_pages(self, filter_by_parent: str = None) -> list: """ Recupere toutes les pages accessibles par l'integration. Args: filter_by_parent: ID de page parent pour filtrer (optionnel) Returns: Liste de documents formates pour le RAG """ pages = [] has_more = True cursor = None while has_more: results = self.client.search( filter={"property": "object", "value": "page"}, start_cursor=cursor, page_size=100 ) for page in results['results']: # Duplikate vermeiden if page['id'] in self.processed_ids: continue # Nach Parent filtern, falls angegeben if filter_by_parent: parent = page.get('parent', {}) if parent.get('page_id') != filter_by_parent: continue doc = self._format_page(page) if doc and len(doc['content']) > 50: # Leere Seiten ignorieren pages.append(doc) self.processed_ids.add(page['id']) has_more = results['has_more'] cursor = results.get('next_cursor') return pages def _format_page(self, page: dict) -> dict: """Formate une page Notion en document RAG.""" title = self._extract_title(page) content = self._extract_content(page['id']) # Generer un hash pour detecter les changements content_hash = hashlib.md5(content.encode()).hexdigest() return { "id": f"notion_{page['id']}", "title": title, "content": f"# {title}\n\n{content}", "metadata": { "source": "notion", "source_type": "wiki", "page_id": page['id'], "url": page.get('url', ''), "last_edited": page['last_edited_time'], "created_time": page['created_time'], "content_hash": content_hash, "parent_type": page.get('parent', {}).get('type'), "icon": self._extract_icon(page) } } def _extract_title(self, page: dict) -> str: """Extrait le titre d'une page.""" props = page.get('properties', {}) # Chercher dans les proprietes 'title' ou 'Name' for key in ['title', 'Title', 'Name', 'name']: if key in props and props[key].get('title'): title_parts = props[key]['title'] return ''.join([t['plain_text'] for t in title_parts]) return "Sans titre" def _extract_content(self, page_id: str) -> str: """Extrait le contenu textuel complet d'une page.""" content_parts = [] def process_blocks(block_id: str, depth: int = 0): """Recursif pour gerer les blocs imbriques.""" if depth > 5: # Begrenzung der Tiefe return blocks = self.client.blocks.children.list(block_id=block_id) for block in blocks['results']: text = self._block_to_text(block, depth) if text: content_parts.append(text) # Kinder verarbeiten, falls der Block welche hat if block.get('has_children'): process_blocks(block['id'], depth + 1) process_blocks(page_id) return "\n\n".join(content_parts) def _block_to_text(self, block: dict, depth: int = 0) -> str: """Convertit un bloc Notion en Markdown.""" block_type = block['type'] indent = " " * depth handlers = { 'paragraph': lambda b: self._rich_text(b['paragraph']['rich_text']), 'heading_1': lambda b: f"# {self._rich_text(b['heading_1']['rich_text'])}", 'heading_2': lambda b: f"## {self._rich_text(b['heading_2']['rich_text'])}", 'heading_3': lambda b: f"### {self._rich_text(b['heading_3']['rich_text'])}", 'bulleted_list_item': lambda b: f"{indent}- {self._rich_text(b['bulleted_list_item']['rich_text'])}", 'numbered_list_item': lambda b: f"{indent}1. {self._rich_text(b['numbered_list_item']['rich_text'])}", 'to_do': lambda b: f"{indent}- [{'x' if b['to_do']['checked'] else ' '}] {self._rich_text(b['to_do']['rich_text'])}", 'toggle': lambda b: f"{indent}> {self._rich_text(b['toggle']['rich_text'])}", 'quote': lambda b: f"> {self._rich_text(b['quote']['rich_text'])}", 'callout': lambda b: f"> {b['callout'].get('icon', {}).get('emoji', '')} {self._rich_text(b['callout']['rich_text'])}", 'code': lambda b: f"```{b['code']['language']}\n{self._rich_text(b['code']['rich_text'])}\n```", 'divider': lambda b: "---", 'table_row': lambda b: self._table_row_to_text(b), } handler = handlers.get(block_type) return handler(block) if handler else "" def _rich_text(self, rich_text: list) -> str: """Convertit le rich text Notion en texte avec mise en forme Markdown.""" parts = [] for rt in rich_text: text = rt['plain_text'] annotations = rt.get('annotations', {}) if annotations.get('bold'): text = f"**{text}**" if annotations.get('italic'): text = f"*{text}*" if annotations.get('code'): text = f"`{text}`" if rt.get('href'): text = f"[{text}]({rt['href']})" parts.append(text) return ''.join(parts) def _table_row_to_text(self, block: dict) -> str: """Convertit une ligne de tableau.""" cells = block['table_row']['cells'] row = [self._rich_text(cell) for cell in cells] return "| " + " | ".join(row) + " |" def _extract_icon(self, page: dict) -> str: """Extrait l'icone de la page.""" icon = page.get('icon', {}) if icon.get('type') == 'emoji': return icon.get('emoji', '') return '' class NotionDatabaseConnector(NotionConnector): """Extension pour extraire les bases de donnees Notion.""" def get_database_entries(self, database_id: str) -> list: """ Recupere toutes les entrees d'une base de donnees. Chaque entree devient un document avec ses proprietes comme metadonnees structurees. """ entries = [] has_more = True cursor = None while has_more: results = self.client.databases.query( database_id=database_id, start_cursor=cursor, page_size=100 ) for entry in results['results']: doc = self._format_database_entry(entry, database_id) if doc: entries.append(doc) has_more = results['has_more'] cursor = results.get('next_cursor') return entries def _format_database_entry(self, entry: dict, db_id: str) -> dict: """Formate une entree de base de donnees.""" props = entry.get('properties', {}) # Alle Eigenschaften als strukturierten Text extrahieren prop_texts = [] metadata_props = {} for name, prop in props.items(): value = self._extract_property_value(prop) if value: prop_texts.append(f"**{name}**: {value}") metadata_props[name] = value title = metadata_props.get('Name', metadata_props.get('Titre', 'Entree')) content = "\n".join(prop_texts) # Falls vorhanden, Seiteninhalt hinzufügen page_content = self._extract_content(entry['id']) if page_content: content += f"\n\n{page_content}" return { "id": f"notion_db_{entry['id']}", "title": title, "content": f"# {title}\n\n{content}", "metadata": { "source": "notion", "source_type": "database", "database_id": db_id, "entry_id": entry['id'], "url": entry.get('url', ''), "last_edited": entry['last_edited_time'], **metadata_props } } def _extract_property_value(self, prop: dict) -> str: """Extrait la valeur d'une propriete Notion.""" prop_type = prop.get('type') extractors = { 'title': lambda p: self._rich_text(p.get('title', [])), 'rich_text': lambda p: self._rich_text(p.get('rich_text', [])), 'number': lambda p: str(p.get('number', '')), 'select': lambda p: p.get('select', {}).get('name', '') if p.get('select') else '', 'multi_select': lambda p: ', '.join([s['name'] for s in p.get('multi_select', [])]), 'date': lambda p: p.get('date', {}).get('start', '') if p.get('date') else '', 'checkbox': lambda p: 'Oui' if p.get('checkbox') else 'Non', 'url': lambda p: p.get('url', ''), 'email': lambda p: p.get('email', ''), 'phone_number': lambda p: p.get('phone_number', ''), 'status': lambda p: p.get('status', {}).get('name', '') if p.get('status') else '', } extractor = extractors.get(prop_type) return extractor(prop) if extractor else ''

Synchronisation intelligente

La synchronisation peut etre declenchee de plusieurs manieres selon vos besoins :

Synchronisation par polling

DEVELOPERpython
from datetime import datetime, timedelta class NotionSyncManager: def __init__(self, connector: NotionConnector, indexer): self.connector = connector self.indexer = indexer self.last_sync = None def sync_incremental(self): """ Synchronisation incrementale : ne traite que les pages modifiees depuis la derniere synchronisation. """ pages = self.connector.get_all_pages() updated = [] for page in pages: last_edited = datetime.fromisoformat( page['metadata']['last_edited'].replace('Z', '+00:00') ) if self.last_sync is None or last_edited > self.last_sync: updated.append(page) if updated: self.indexer.upsert_documents(updated) print(f"Synchronise {len(updated)} pages") self.last_sync = datetime.now() def sync_full(self): """Synchronisation complete : re-indexe tout.""" pages = self.connector.get_all_pages() self.indexer.replace_all(pages) self.last_sync = datetime.now() print(f"Indexe {len(pages)} pages")

Synchronisation temps reel

Pour une synchronisation en temps reel, utilisez les webhooks Notion (disponibles via l'API) ou un worker qui poll regulierement avec une granularite fine :

DEVELOPERpython
import schedule import time def start_sync_worker(sync_manager: NotionSyncManager): """Demarre le worker de synchronisation.""" # Inkrementelle Synchronisierung alle 5 Minutes schedule.every(5).minutes.do(sync_manager.sync_incremental) # Tägliche Vollsynchronisierung (Aufräumen) schedule.every().day.at("03:00").do(sync_manager.sync_full) while True: schedule.run_pending() time.sleep(60)

Prompt systeme optimise pour Notion

Le prompt systeme est crucial pour obtenir des reponses de qualite. Voici une version optimisee pour les wikis d'entreprise :

DEVELOPERpython
NOTION_KB_SYSTEM_PROMPT = """Tu es l'assistant base de connaissances de {company_name}. Tu aides les employes a trouver rapidement les informations dans notre wiki Notion. ## Ta mission - Repondre aux questions en te basant UNIQUEMENT sur le contenu du wiki - Citer systematiquement les sources avec le titre de la page - Orienter vers la bonne personne/equipe si la question depasse le wiki ## Regles strictes 1. Ne jamais inventer d'information absente du wiki 2. Si tu n'es pas sur, dis-le clairement 3. Privilegier les informations les plus recentes 4. Adapter le niveau de detail a la question (resume vs detail) ## Format de reponse - Commence par repondre directement a la question - Ajoute des details si pertinents - Termine par "Source: [Titre de la page]" pour chaque information ## Contexte disponible {context} ## Historique de conversation {chat_history} """ def build_notion_prompt(company: str, docs: list, history: list) -> str: """Construit le prompt avec le contexte.""" context_parts = [] for doc in docs: context_parts.append(f"### {doc['title']}\n{doc['content'][:1000]}") history_parts = [] for msg in history[-5:]: # 5 derniers messages role = "Utilisateur" if msg['role'] == 'user' else "Assistant" history_parts.append(f"{role}: {msg['content']}") return NOTION_KB_SYSTEM_PROMPT.format( company_name=company, context="\n\n---\n\n".join(context_parts), chat_history="\n".join(history_parts) if history_parts else "Aucun" )

Cas d'usage concrets

1. Onboarding des nouveaux employes

Question : "Comment configurer mon environnement de developpement ?"

Reponse RAG :

Pour configurer ton environnement de dev, voici les etapes :

  1. Clone le repo principal : git clone [email protected]:company/main.git
  2. Installe les dependances : npm install
  3. Copie le fichier .env.example vers .env
  4. Demande les credentials a l'equipe DevOps sur Slack (#devops-support)

Source: [Guide Setup Dev] | [Onboarding Tech]

2. Questions RH frequentes

Question : "Comment poser des conges ?"

Reponse RAG :

Pour poser des conges :

  1. Va sur Payfit > Absences > Nouvelle demande
  2. Selectionne le type (CP, RTT, sans solde)
  3. Indique les dates et valide

Delai de validation : 48h par ton manager. Pour les urgences, contacte RH.

Source: [Procedure conges et absences]

3. Documentation technique

Question : "C'est quoi l'architecture du service de paiement ?"

Reponse RAG :

Le service de paiement suit une architecture event-driven :

  • API Gateway : Authentification et routing
  • Payment Service : Orchestration des transactions
  • Stripe Adapter : Integration Stripe
  • Event Bus : Kafka pour les notifications

Diagramme complet disponible sur la page dediee.

Source: [Architecture Payment Service] | [Diagrammes techniques]

Bonnes pratiques

Structurer Notion pour le RAG

PratiquePourquoi
Titres descriptifsAmeliore la recherche
Structure hierarchique claireFacilite le chunking
Mise a jour des datesPermet de prioriser le recent
Tags et categoriesEnrichit les metadonnees
Liens internesAide le contexte

Gerer les permissions

Le RAG herite des permissions de l'integration Notion. Pour un controle granulaire :

  1. Creer une integration dediee au RAG
  2. Partager uniquement les pages publiques avec l'integration
  3. Gerer les acces par workspace si multi-tenant

Monitorer la qualite

  • Tracker les questions sans reponse
  • Collecter les feedbacks utilisateurs
  • Identifier les pages les plus citees
  • Detecter le contenu obsolete

Ressources complementaires

FAQ

Les bases de donnees Notion sont extraites avec toutes leurs proprietes (colonnes). Chaque entree devient un document avec ses attributs structures en metadonnees. Le chatbot peut rechercher par criteres ("tous les projets en cours du Q1") et synthetiser les informations de plusieurs entrees.
Non, le RAG s'adapte a votre structure existante. Cependant, quelques bonnes pratiques ameliorent les resultats : titres descriptifs, hierarchie claire (3 niveaux max), utilisation de tags/labels. Ces optimisations beneficient aussi a la navigation humaine.
Une synchronisation incrementale toutes les 5-15 minutes convient a la plupart des usages. La synchronisation complete peut etre quotidienne (nuit) pour nettoyer les pages supprimees. Pour les wikis critiques, la synchronisation temps reel via webhooks est possible.
L'acces depend de l'integration Notion creee. Par defaut, l'integration n'accede qu'aux pages explicitement partagees avec elle. Pour un usage d'equipe, partagez les espaces pertinents. Les pages privees non partagees ne seront jamais indexees, preservant la confidentialite.
Le RAG gere tres bien les gros volumes grace a l'indexation vectorielle. La premiere synchronisation prend plus de temps, mais les recherches restent rapides. Vous pouvez aussi filtrer par espaces ou labels pour indexer uniquement les pages pertinentes. ---

Connectez Notion avec Ailog

Transformez votre wiki Notion en assistant intelligent sans ecrire une ligne de code. Ailog simplifie l'integration :

  • Connecteur Notion natif : Synchronisation automatique en quelques clics
  • Recherche semantique : Trouvez l'info avec vos mots, pas ceux du wiki
  • Multi-workspace : Gerez plusieurs espaces Notion dans une meme interface
  • Controle des acces : Respectez les permissions de votre organisation
  • Hebergement France : Donnees sur serveurs francais, conformite RGPD native

Testez Ailog gratuitement et deployez votre assistant Notion en 10 minutes.

Tags

ragnotionknowledge basewiki entrepriseintegrationchatbot interne

Verwandte Artikel

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !