Stack IA 0€ : explication détaillée avec exemples
1. Vue d’ensemble
Beaucoup pensent que construire un système IA — agent, RAG, chatbot interne — coûte quelques milliers de dollars par mois parce qu’il faut payer les API d’OpenAI ou Anthropic. Ce n’est pas le cas : il existe une stack composée de 8 couches, chaque couche ayant une version gratuite ou auto-hébergée, suffisant pour que tu construises et exécutes un système IA complet avec 0€. Cet article passe en revue chaque couche de cette stack, explique son rôle, accompagné d’exemples de code concrets pour que tu voies comment les assembler. Après cette lecture, tu sauras quels outils choisir pour chaque couche et où se situent les véritables limites du « gratuit ».
2. Pourquoi « 0€ » ne signifie pas gratuit pour toujours
Avant d’entrer dans les détails, il faut clarifier une chose : « 0€ » ici signifie coût logiciel/licence, pas coût d’infrastructure illimité. La tier gratuite de Vercel a des limites de bande passante, la tier gratuite de Supabase a des limites de stockage, exécuter Ollama localement consomme le CPU/RAM de ta machine. Cette stack convient pour prototyper, apprendre, faire une démo pour les petits clients, pas pour scaler à un million d’utilisateurs.
Note : Si le trafic réel dépasse la tier gratuite, tu dois quand même payer — juste pour l’infrastructure (compute, stockage) au lieu de payer le vendeur LLM par token. C’est une distinction importante à comprendre avant de committer à cette stack pour la vraie production.
3. Couche Frontend : Next.js ou Streamlit
La couche interface n’a pas besoin d’être complexe. Si tu veux une belle UI, utilise Next.js déployé sur Vercel free tier. Si tu as besoin juste d’une démo rapide pour un pipeline IA, Streamlit est un choix beaucoup plus rapide car tu n’as pas besoin d’écrire un frontend séparé.
# app.py
import streamlit as st
import requests
st.title("Assistant de questions-réponses sur les documents internes")
question = st.text_input("Entrez votre question")
if st.button("Poser") and question:
response = requests.post(
"http://localhost:8000/ask",
json={"question": question},
timeout=30,
)
st.write(response.json()["answer"])
Ce code crée un champ input et un bouton. Quand tu cliques sur le bouton, il appelle l’API backend (exécuté localement) et affiche la réponse. Exécute avec streamlit run app.py, Streamlit s’héberge automatiquement sur localhost:8501. Pas besoin d’écrire React, pas de pipeline build — convient pour la phase de validation d’idée avant d’investir dans Next.js.
4. Couche Agent Orchestration : LangGraph
Quand ton système n’est pas juste « appeler le LLM une fois puis retourner le résultat » mais nécessite plusieurs étapes (récupération → synthèse → vérification → réponse), tu as besoin d’un orchestrator. LangGraph modélise l’agent comme un graphe avec état, permettant des branches et des boucles.
<!-- pom.xml ne s'applique pas ici car LangGraph est une bibliothèque Python -->
pip install langgraph langchain-ollama
from langgraph.graph import StateGraph, END
from typing import TypedDict
class AgentState(TypedDict):
question: str
answer: str
def retrieve_step(state: AgentState) -> AgentState:
state["answer"] = f"Recherche de documents pour : {state['question']}"
return state
def generate_step(state: AgentState) -> AgentState:
state["answer"] += " -> réponse synthétisée"
return state
graph = StateGraph(AgentState)
graph.add_node("retrieve", retrieve_step)
graph.add_node("generate", generate_step)
graph.set_entry_point("retrieve")
graph.add_edge("retrieve", "generate")
graph.add_edge("generate", END)
app = graph.compile()
result = app.invoke({"question": "Quelle est la politique de garantie ?", "answer": ""})
print(result["answer"])
Recherche de documents pour : Quelle est la politique de garantie ? -> réponse synthétisée
StateGraph définit un flux de traitement avec deux nœuds : retrieve et generate, chaque nœud reçoit un état et retourne un nouvel état. set_entry_point spécifie le nœud de démarrage, add_edge connecte les étapes. Quand tu appelles app.invoke(...), LangGraph traverse le graphe séquentiellement et retourne l’état final. Tout s’exécute localement, aucun appel à une API payante.
5. Couche RAG Pipeline : Chroma et Qdrant
RAG (Retrieval-Augmented Generation) a besoin d’un vector store pour stocker les embeddings et faire des recherches par similarité. Chroma s’exécute en mode embarqué, sans serveur séparé — idéal pour le prototypage.
pip install chromadb
import chromadb
client = chromadb.PersistentClient(path="./chroma_data")
collection = client.get_or_create_collection(name="policies")
collection.add(
documents=["Garantie du produit 12 mois à partir de la date d'achat.",
"Retour dans 7 jours si l'emballage est intact."],
ids=["doc1", "doc2"],
)
results = collection.query(query_texts=["politique de garantie"], n_results=1)
print(results["documents"])
[['Garantie du produit 12 mois à partir de la date d'achat.']]
PersistentClient persiste les données sur le disque au lieu de seulement en RAM, donc les données survivent aux redémarrages. collection.add calcule automatiquement les embeddings pour les documents (Chroma a une fonction d’embedding par défaut). collection.query trouve le document le plus proche vectoriellement de la question. Si ton dataset est plus grand, passe à Qdrant exécuté localement via Docker — API similaire mais optimisé pour une meilleure scalabilité.
6. Couche LLM : Ollama exécutant des modèles locaux
C’est la couche essentielle pour s’échapper des factures d’API. Ollama te permet d’exécuter Llama, Mistral, Gemma directement sur ta machine, sans limite de requêtes.
ollama pull llama3.1
ollama run llama3.1 "Résume la politique de garantie en une phrase."
Le produit est garanti 12 mois à partir de la date d'achat.
La commande ollama pull télécharge le modèle sur ta machine (le modèle est quantisé pour s’exécuter sur des CPU/GPU courants). La commande ollama run démarre le modèle et envoie le prompt, retournant le résultat directement dans le terminal. Ollama expose aussi une API REST sur localhost:11434, tu peux l’intégrer en Python ou Java.
Si ton backend est Spring Boot, appeler Ollama via RestClient est très simple :
import org.springframework.web.client.RestClient;
public class OllamaClient {
private final RestClient restClient = RestClient.create("http://localhost:11434");
public String generate(String model, String prompt) {
var response = restClient.post()
.uri("/api/generate")
.body(new OllamaRequest(model, prompt, false))
.retrieve()
.body(OllamaResponse.class);
return response.response();
}
record OllamaRequest(String model, String prompt, boolean stream) {}
record OllamaResponse(String response) {}
}
OllamaClient encapsule l’appel HTTP à Ollama. RestClient de Spring 6 remplace l’ancien RestTemplate, c’est plus court à écrire. OllamaRequest set stream = false pour recevoir toute la réponse d’un coup au lieu de streamer chaque token — plus facile à traiter pour un exemple de démo.
7. Utilisation des outils via MCP (Model Context Protocol)
Un agent qui parle juste avec le LLM ne peut pas vraiment accomplir grand-chose. MCP est un standard de communication qui aide l’agent à appeler des outils externes — lire des fichiers, interroger des bases de données, appeler l’API GitHub — selon une interface unifiée, sans écrire d’intégration séparée pour chaque outil.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data/docs"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
}
}
}
Ce fichier de config déclare deux serveurs MCP : un pour le filesystem (lecture/écriture de fichiers dans /data/docs), un pour GitHub (créer des issues, lire les PR). Ton agent (LangGraph, Claude Code, ou tout client supportant MCP) se connecte juste à ces serveurs et appelle les outils selon le schéma standard, sans besoin d’écrire un wrapper API séparé pour chaque service.
Note : Les serveurs MCP exécutés localement via
npxne coûtent pas de licence, mais certains serveurs demandent un access token (par exemple un token GitHub) — ce token doit toujours être obtenu du service d’origine.
8. Agent de code : Aider
Quand tu veux qu’un agent écrive et modifie du code dans ton repo, Aider est un outil CLI open-source qui s’intègre bien avec les modèles locaux via Ollama.
pip install aider-chat
aider --model ollama/llama3.1 --file src/main/java/OrderService.java
La commande ci-dessus ouvre Aider, spécifie l’utilisation du modèle llama3.1 via Ollama (pas d’API payante), et précharge le fichier OrderService.java dans le contexte. De là tu tapes une demande en vietnamien ou anglais (« ajouter la validation de quantité négative »), Aider génère un diff et commit automatiquement si tu es d’accord.
9. Couche Données : SQLite, DuckDB, Supabase
Tout système IA n’a pas besoin d’un cluster Postgres. SQLite suffit pour un petit stockage structuré, DuckDB excelle pour analyser les données de fichiers (CSV, Parquet) sans serveur séparé.
import duckdb
con = duckdb.connect()
result = con.execute("""
SELECT category, COUNT(*) AS total
FROM read_csv_auto('orders.csv')
GROUP BY category
ORDER BY total DESC
""").fetchall()
print(result)
[('electronics', 120), ('books', 45), ('clothing', 30)]
DuckDB lit directement les fichiers CSV avec read_csv_auto, pas besoin d’ETL ou d’import dans une base de données séparée. Les requêtes SQL s’exécutent quasi instantanément sur des centaines de milliers de lignes — suffisant pour la plupart des cas d’analyse interne. Quand tu as besoin d’un backend avec une API REST et de l’authentification prêts à l’emploi, la tier gratuite de Supabase fournit un Postgres géré avec du realtime et du stockage.
10. Déploiement : Docker, Cloudflare Workers, Hugging Face
La conteneurisation avec Docker aide à déployer de façon cohérente entre ta machine de dev et l’hôte gratuit.
# docker-compose.yml
services:
ollama:
image: ollama/ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
app:
build: .
ports:
- "8000:8000"
depends_on:
- ollama
volumes:
ollama_data:
Ce fichier définit deux services : ollama exécute le modèle LLM, app est ton backend, dépendant d’ollama pour les inférences. docker compose up lance les deux à la fois, le volume ollama_data conserve le modèle téléchargé à travers les redémarrages du conteneur. Quand tu as besoin d’une démo publique, tu pousses le frontend sur Cloudflare Workers (edge, tier gratuit généreux) et les petits modèles sur Hugging Face Spaces pour que d’autres testent sans configurer leur machine.
11. Assembler la stack complète
Le flux réel : l’utilisateur tape une question sur Streamlit → LangGraph reçoit la requête, appelle Chroma pour récupérer le contexte pertinent → incorpore le contexte dans le prompt, envoie à Ollama → si un outil est nécessaire (lire un fichier, interroger la DB), l’agent l’appelle via MCP → le résultat retourne à l’UI. Tout s’exécute sur une seule machine ou conteneur, aucune requête ne sort vers une API payante.
def answer_question(question: str) -> str:
docs = collection.query(query_texts=[question], n_results=2)
context = "\n".join(docs["documents"][0])
prompt = f"En fonction des informations suivantes :\n{context}\n\nRéponds : {question}"
import ollama
response = ollama.generate(model="llama3.1", prompt=prompt)
return response["response"]
Cette fonction est le « cœur » de RAG : elle récupère le contexte de Chroma, l’incorpore dans le prompt, appelle Ollama pour générer la réponse finale. C’est le pattern retrieve-then-generate le plus basique, tu n’as pas besoin de frameworks complexes si ton use case est simple.
12. Écrire des tests pour la partie traitement des données
La partie la plus facile à tester dans un pipeline RAG est généralement l’étape de segmentation du texte (chunking) avant l’incorporation vectorielle. Si ton backend Java traite cette étape, un test unitaire simple aide à éviter les erreurs off-by-one lors de la division.
import java.util.List;
import java.util.ArrayList;
public class DocumentChunker {
public List<String> chunk(String text, int maxLength) {
List<String> chunks = new ArrayList<>();
for (int i = 0; i < text.length(); i += maxLength) {
chunks.add(text.substring(i, Math.min(i + maxLength, text.length())));
}
return chunks;
}
}
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class DocumentChunkerTest {
@Test
void shouldSplitTextIntoFixedSizeChunks() {
DocumentChunker chunker = new DocumentChunker();
List<String> chunks = chunker.chunk("Garantie 12 mois pour tous les produits", 10);
assertThat(chunks).hasSizeGreaterThan(1);
assertThat(chunks.get(0)).hasSize(10);
assertThat(String.join("", chunks)).isEqualTo("Garantie 12 mois pour tous les produits");
}
}
Ce test affirme trois choses : le texte long est divisé en plusieurs chunks, le premier chunk a exactement la bonne longueur, et la réunion de tous les chunks redonne exactement le texte original — aucun caractère perdu. C’est le genre de bug silencieux facile à créer en écrivant soi-même la logique de chunking sans tests rigoureux.
13. Erreurs courantes et quand ne pas utiliser cette stack
- Les modèles locaux sont beaucoup plus lents que les API cloud si ta machine n’a pas de GPU. Avec un CPU courant, une requête llama3.1 8B peut prendre 5-15 secondes — acceptable pour une démo, pas pour un produit qui a besoin de réponses instantanées.
- Les tiers gratuits ont des rate limits silencieuses. Vercel, Supabase, Cloudflare limitent tous les requêtes/mois ; dépasser le seuil peut bloquer ton app à moins que tu surveilles le dashboard.
- Chroma embarqué ne convient pas pour le multi-instance. Si tu scales ton backend sur plusieurs pods, chaque pod ayant son propre fichier Chroma causera des incohérences — il faut passer à un serveur Qdrant ou à un vector store géré.
- Les serveurs MCP locaux augmentent la surface d’attaque. Un serveur filesystem MCP permet à l’agent de lire/écrire des fichiers — si tu l’exposes accidentellement à Internet, c’est une faille de sécurité sérieuse.
Note : Si tu construis un produit avec un SLA, tu as besoin d’une latence basse stable, ou un gros trafic dès le départ, cette stack 0€ ne doit être utilisée qu’au stade prototype. Lors d’un vrai go-live, considère payer pour une API LLM gérée ou une instance GPU dédiée pour garantir la qualité du service.
14. Conclusion
La stack 0€ n’est pas du marketing — c’est une combinaison d’outils open-source et de tiers gratuits réels, suffisante pour que tu construises un système IA complet avec RAG, agents, tool use et déploiement sans avoir besoin de carte de crédit. La vraie barrière n’est pas l’argent, c’est de bien comprendre comment assembler chaque couche, connaître les limites des tiers gratuits, et savoir quand passer à l’infrastructure payante pour garantir la qualité. Commence avec cette stack pour apprendre et valider ton idée, puis améliore chaque couche quand ton produit a vraiment besoin de scaler.