Pipeline RAG¶
AKKO embarque un pipeline RAG (Retrieval-Augmented Generation) prêt à l'emploi qui reste 100 % à l'intérieur du cluster. Les embeddings sont calculés par nomic-embed-text via Ollama, stockés dans l'extension pgvector de PostgreSQL, puis récupérés par qwen2.5:3b servi au travers de LiteLLM.
Le notebook interactif se trouve dans
notebooks/rag-pipeline-demo.ipynb, monté en lecture seule dans chaque pod JupyterHub.
Pourquoi RAG sur AKKO¶
- Souverain — aucun appel à OpenAI, Pinecone, Weaviate Cloud ou Anthropic. Modèles et vecteurs ne quittent jamais le cluster.
- Composable — les mêmes tables pgvector sont interrogeables depuis Trino (
postgresql.akko.kb_chunks) et exposées aux agents via MCP. - Économique —
nomic-embed-textest un modèle de 137 Mo qui embedde plusieurs milliers de chunks par minute en CPU. - Auditable — chaque appel d'embedding transite par LiteLLM, qui journalise l'appelant (
X-User-Id) et le modèle utilisé.
Architecture¶
flowchart LR
subgraph Ingest[Ingestion]
DOC[Documents sources<br/>PDF, Markdown, HTML, export Confluence]
CHUNK[Chunker LangChain<br/>RecursiveCharacterTextSplitter<br/>chunk_size=1000, overlap=200]
EMB[Ollama<br/>nomic-embed-text<br/>768 dims]
PG[(PostgreSQL<br/>pgvector · index HNSW)]
end
subgraph Query[Requête]
Q[Question utilisateur]
EMBQ[Ollama<br/>nomic-embed-text]
SEARCH[pgvector<br/>cosinus top-k=5]
PROMPT[Constructeur de prompt<br/>contexte + question]
LLM[Ollama<br/>qwen2.5:3b]
ANS[Réponse + citations]
end
DOC --> CHUNK --> EMB --> PG
Q --> EMBQ --> SEARCH
PG --> SEARCH
SEARCH --> PROMPT
Q --> PROMPT
PROMPT --> LLM --> ANS
Modèle de stockage¶
Définition de la table PostgreSQL (provisionnée automatiquement par postgres-init, voir postgres/init/05-pgvector.sql) :
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE akko.kb_chunks (
id BIGSERIAL PRIMARY KEY,
source TEXT NOT NULL, -- ex. 'docs/handbook.pdf'
chunk_index INT NOT NULL,
content TEXT NOT NULL,
tokens INT NOT NULL,
embedding vector(768) NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Index HNSW pour une recherche cosinus sous-linéaire
CREATE INDEX kb_chunks_hnsw ON akko.kb_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
CREATE INDEX kb_chunks_source_idx ON akko.kb_chunks (source);
Accès via Trino : postgresql.akko.kb_chunks — même table, exposée par le connecteur PostgreSQL. Les colonnes vector sont promues en ARRAY<DOUBLE> dans Trino : akko_ai_similarity et akko_ai_search fonctionnent directement.
Pipeline d'ingestion¶
# notebooks/rag-pipeline-demo.ipynb — simplifié
from langchain_community.vectorstores import PGVector
from langchain_ollama import OllamaEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
emb = OllamaEmbeddings(
base_url=os.environ["OLLAMA_HOST"], # http://akko-ollama:11434
model="nomic-embed-text",
)
store = PGVector(
collection_name="akko.kb_chunks",
connection_string=os.environ["POSTGRES_URL"],
embedding_function=emb,
)
for doc in documents:
chunks = splitter.split_documents([doc])
store.add_documents(chunks)
Chaque variable d'environnement est injectée par le spawner (jupyterhub_config.py). Aucun hostname en dur.
Pipeline de requête¶
from langchain_ollama import ChatOllama
from langchain.prompts import PromptTemplate
chat = ChatOllama(
base_url=os.environ["OLLAMA_HOST"],
model="qwen2.5:3b",
temperature=0,
)
retriever = store.as_retriever(search_type="similarity", search_kwargs={"k": 5})
prompt = PromptTemplate.from_template(
"Réponds à la question en utilisant uniquement le contexte ci-dessous. "
"Cite les sources sous la forme [source:chunk_index].\n"
"Contexte :\n{context}\n\nQuestion : {question}\nRéponse :"
)
chain = prompt | chat
docs = retriever.invoke(question)
ctx = "\n\n".join(f"[{d.metadata['source']}:{d.metadata['chunk_index']}] {d.page_content}" for d in docs)
ans = chain.invoke({"context": ctx, "question": question})
Configuration¶
Valeurs Helm clés (helm/akko/values.yaml) :
akko-ollama:
models:
- name: qwen2.5-coder:7b
- name: qwen2.5:3b
- name: nomic-embed-text
akko-postgresql-data:
extensions:
- pgvector
- postgis
akko-litellm:
routes:
- model_name: nomic-embed-text
litellm_params:
model: ollama/nomic-embed-text
api_base: http://akko-ollama:11434
| Variable (env notebook) | Défaut | Rôle |
|---|---|---|
OLLAMA_HOST |
http://akko-ollama:11434 |
Ollama direct (contourne LiteLLM pour la vitesse) |
LITELLM_HOST |
http://akko-litellm:4000 |
Chemin auditable (recommandé pour les jobs partagés) |
POSTGRES_URL |
postgresql://alice:...@akko-postgresql-data:5432/akko |
PostgreSQL de données (pgvector) |
RAG_EMBED_MODEL |
nomic-embed-text |
Modèle d'embedding |
RAG_CHAT_MODEL |
qwen2.5:3b |
Modèle de génération |
RAG_TOP_K |
5 |
Top-k du retriever |
RBAC¶
| Rôle | Insérer des chunks | Interroger les chunks | Créer une collection KB |
|---|---|---|---|
akko-admin |
oui | oui | oui |
akko-engineer |
oui | oui | oui |
akko-analyst |
non (lecture seule) | oui | non |
akko-user |
non | oui (PII masquées) | non |
akko-viewer |
non | oui (KB publiées uniquement) | non |
Tous les accès sont appliqués par OPA au-dessus des règles standard du connecteur PostgreSQL de Trino (voir Admin / RBAC). La colonne akko.kb_chunks.metadata peut contenir une clé project utilisée par les rowFilters pour restreindre la visibilité inter-équipes.
Notes de performance¶
- Index HNSW — passe la recherche de O(n) à ~O(log n). Sur 1 M de chunks, le p95 reste sous 50 ms.
- Embedding par lot — appeler
store.add_documents([batch])avec 32-64 chunks pour saturer Ollama sans exploser la mémoire. - Stratégie de ré-ingestion — supprimer par
source, re-découper, ré-embedder.pgvectorn'a pas de tombstones : un vacuum hebdomadaire tourne viapg_cron(akko-postgresql-data). - Préférer
akko_ai_searchen SQL — si la question est un littéral, le plugin Trino AI cache l'embedding de la requête par worker (voir IA / Fonctions Trino).
Points d'intégration¶
- JupyterHub —
notebooks/rag-pipeline-demo.ipynbest la référence. - ADEN — quand
AKKO_RAG_ENABLED=true, ADEN appelle le retriever avant le prompt de génération SQL pour injecter du savoir métier (descriptions de tables, règles). - MCP Trino — les agents peuvent exécuter
execute_querysurpostgresql.akko.kb_chunkspour chercher des documents. - Superset — un dashboard dédié (
AKKO RAG insights) trace les embeddings (UMAP) pour surveiller la dérive.
Dépannage¶
| Symptôme | Cause probable | Correctif |
|---|---|---|
could not find extension "vector" |
pgvector absent du cluster de données |
Vérifier les values akko-postgresql-data ; l'image akko-postgres:2026.03 embarque pgvector |
| Ingestion très lente (> 30 s / chunk) | Modèle non chargé en mémoire Ollama | kubectl exec -it deploy/akko-ollama -- ollama pull nomic-embed-text |
HTTP 401 sur OllamaEmbeddings |
Notebook sans clé virtuelle LiteLLM | Utiliser OLLAMA_HOST directement, ou définir LITELLM_KEY dans l'env du spawner |
| Les réponses répètent la question | Chunks retournés non pertinents | Augmenter RAG_TOP_K, ajuster chunk_size/overlap, ré-indexer |
| Reconstruction HNSW bloque les inserts | CREATE INDEX sur une table peuplée |
Utiliser CREATE INDEX CONCURRENTLY ou créer l'index avant l'ingestion massive |
Voir aussi¶
- IA / ADEN · IA / Fonctions Trino · IA / Serveurs MCP
- Services / Ollama · Services / LiteLLM · Services / PostgreSQL
- Architecture / AI Stack
- Notebook :
notebooks/rag-pipeline-demo.ipynbdans le repo