Construire une pile d'IA locale et privée (de bout en bout)
Vous avez vu les pièces séparément : un modèle local, une boucle d'agent locale, des outils exposés via MCP, et les patterns hybrides Claude+local. Voici la pièce maîtresse — la page qui les câble ensemble en un seul assistant privé fonctionnel sur votre propre machine : un modèle à poids ouverts fonctionnant en local, une boucle d'agent agnostique au modèle capable d'appeler des outils, ces outils exposés via un serveur MCP local, un garde-fou devant les plus dangereux, et — optionnellement — Claude comme « couche intelligente » à activer pour les 5 % d'étapes les plus difficiles. Le fil conducteur : tout ce qui est sensible reste sur l'appareil ; le cloud est optionnel et réservé à la minorité difficile.
- Voir toute la pile comme un seul schéma : modèle local + boucle d'agent + outils MCP locaux + garde-fou (+ Claude optionnel)
- Faire tourner un modèle à poids ouverts en local et confirmer qu'il sait faire de l'appel d'outils
- Monter une boucle d'agent minimale qui est agnostique au modèle — même boucle, on change le endpoint
- Exposer quelques outils via un serveur MCP local et laisser l'agent les appeler
- Ajouter un garde-fou : approbation pour les actions destructrices, un plafond de boucle/budget, et la gestion des résultats non fiables
- Optionnellement, router uniquement le raisonnement le plus difficile vers Claude, en gardant le chemin par défaut entièrement local
Toute la pile, en une image
Le modèle mental se résume à un petit nombre de boîtes, chacune déjà rencontrée sur une page sœur. L'assistant n'est que ces boîtes câblées ensemble :
Lisez-le comme une boucle. L'agent demande au modèle local quoi faire ensuite. Le modèle répond, ou émet un appel d'outil. Chaque appel d'outil passe par un garde-fou avant d'atteindre le serveur MCP local, qui fait réellement le travail (lit un fichier, exécute une commande, cherche dans vos notes) et renvoie un résultat. L'agent renvoie le résultat au modèle et répète jusqu'à ce que la tâche soit terminée. Le chemin en pointillés vers Claude est à activer : l'agent n'escalade que les étapes que le modèle local ne peut pas gérer, et uniquement quand vous l'autorisez.
Trois propriétés rendent cette pile digne d'être construite :
- Local par défaut. Le modèle, la boucle, les outils et vos données vivent tous sur votre matériel. Rien ne quitte la machine sauf si le chemin Claude optionnel se déclenche — et même alors, uniquement ce que vous choisissez d'envoyer.
- Boucle agnostique au modèle. L'agent parle à un endpoint de chat de forme OpenAI. Pointez-le vers le endpoint local d'Ollama aujourd'hui ; pointez-le vers un autre fournisseur demain sans réécrire la boucle.
- Des outils derrière un standard. Les capacités vivent dans un serveur MCP, et ne sont pas codées en dur dans la boucle. Construisez un outil une fois et n'importe quel client parlant MCP (votre agent, Claude Code, une autre application) peut l'utiliser.
Construction étape par étape
- Installez Ollama et démarrez un modèle qui supporte l'appel d'outils. ollama run télécharge à la première utilisation et expose une API locale compatible OpenAI sur localhost:11434. C'est votre « cerveau » par défaut — privé et hors ligne. (Installation complète : la page Faire tourner des modèles en local.)
- Écrivez une petite boucle : envoyez les messages + un schéma d'outils au endpoint de chat, lisez la réponse, si elle contient des tool_calls exécutez-les, ajoutez les résultats, et bouclez jusqu'à ce que le modèle renvoie une réponse finale. La boucle ne sait rien du modèle auquel elle parle — seulement la forme de chat OpenAI.
- Mettez vos vraies capacités (lire un fichier, exécuter une commande, chercher dans les notes) dans un serveur MCP local via stdio plutôt que de les coder en dur. L'agent liste les outils du serveur, les mappe dans le schéma d'outils du modèle, et les appelle à la demande. Construisez une fois, réutilisez sur plusieurs clients.
- Avant qu'un outil ne s'exécute, filtrez-le : auto-autorisez les outils en lecture seule, exigez une approbation explicite pour les destructeurs (run_shell, write_file, delete), plafonnez le nombre d'itérations de boucle et le total de tokens, et traitez chaque résultat d'outil comme une entrée non fiable qui pourrait tenter de piloter le modèle.
- Gardez le chemin local par défaut. Quand une étape est vraiment difficile — raisonnement multi-étapes délicat, un plan que le modèle local rate sans cesse — laissez l'agent escalader uniquement cette étape vers l'API Claude, puis revenez à la boucle locale. C'est l'idée du routeur / brouillon-puis-raffinement de la page hybride, appliquée une étape à la fois.
1. Le modèle local (votre cerveau par défaut)
Démarrez le modèle et confirmez que le endpoint local est actif. Choisissez un modèle qui annonce l'appel d'outils — la boucle d'agent en dépend.
Faire tourner un modèle local capable d'outils + confirmer l'API
# Start a model that supports tool/function calling
ollama run llama3.1
# In another terminal, confirm the local OpenAI-compatible endpoint is live.
# Ollama serves it at http://localhost:11434/v1 — no internet required.
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama3.1",
"messages": [{"role": "user", "content": "Reply with the single word: ready"}]
}'2. La boucle d'agent agnostique au modèle
La boucle est délibérément bête : elle transmet les messages et un schéma d'outils au endpoint de chat, et chaque fois que le modèle demande d'appeler un outil, elle exécute l'outil et renvoie le résultat. Comme elle ne parle que la forme de chat OpenAI, la même boucle fonctionne contre le endpoint local aujourd'hui et un autre fournisseur plus tard — vous changez une base_url, pas la logique.
from openai import OpenAI
# Point at the LOCAL model. Swap base_url/api_key later to change providers —
# the loop below does not change. That is what "model-agnostic" means here.
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
MODEL = "llama3.1"
MAX_STEPS = 8 # hard cap on loop iterations (a guardrail — see step 4)
def run_agent(user_goal, tool_schemas, dispatch):
messages = [
{"role": "system", "content": "You are a local assistant. Use tools when needed."},
{"role": "user", "content": user_goal},
]
for _ in range(MAX_STEPS):
resp = client.chat.completions.create(
model=MODEL, messages=messages, tools=tool_schemas,
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content # model gave a final answer
messages.append(msg)
for call in msg.tool_calls:
result = dispatch(call) # runs through the guardrail + MCP server
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
return "Stopped: hit the step cap." # never loop forever
tool_schemas est la liste des outils (au format d'appel de fonctions OpenAI), et dispatch est l'unique fonction qui décide si et comment exécuter réellement un outil demandé — c'est là que vivent le garde-fou et le serveur MCP.
3. Les outils via un serveur MCP local
Plutôt que de coder les outils en dur dans la boucle, exposez-les via un serveur MCP local. MCP est un standard ouvert pour connecter un client IA à des outils externes ; un serveur local s'exécute comme un petit programme sur votre machine et parle au client via stdio, de sorte que vos données et vos actions restent sur la machine. (Pourquoi c'est la bonne frontière, et comment construire un serveur, est couvert dans Connecter Claude à des outils locaux avec MCP.)
Un serveur MCP Python minimal qui expose un outil sûr, en lecture seule :
# server.py — a tiny local MCP server exposing one read-only tool.
# Run it over stdio; an MCP client (your agent, Claude Code, ...) connects to it.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("local-tools")
@mcp.tool()
def search_notes(query: str) -> str:
"""Search the user's local notes folder and return matching snippets."""
# ... read from a LOCAL directory only; never reach outside it ...
return f"(stub) matches for: {query}"
if __name__ == "__main__":
mcp.run() # stdio transport by default — local, no network
L'agent se connecte à ce serveur, lui demande de lister ses outils, convertit chacun dans le schéma d'outils OpenAI que votre boucle comprend déjà, et route les appels d'outils du modèle vers le serveur. Même boucle, vraies capacités — et le serveur est réutilisable par n'importe quel client parlant MCP.
4. Le garde-fou (ne sautez pas cette étape)
C'est la différence entre un jouet et quelque chose à qui vous feriez confiance sur votre propre machine. La fonction dispatch de l'étape 2 est l'unique point d'étranglement où chaque appel d'outil est inspecté avant de s'exécuter. Trois missions :
READ_ONLY = {"search_notes", "read_file", "list_dir"}
def dispatch(call):
name = call.function.name
args = call.function.arguments
# 1) APPROVAL: read-only tools auto-run; everything else asks a human first.
if name not in READ_ONLY:
if not human_approves(name, args): # destructive => require consent
return "DENIED by user."
# 2) The MCP server does the actual work (it, too, is sandboxed to safe paths).
result = call_mcp_tool(name, args)
# 3) UNTRUSTED RESULT: a tool result is data, not instructions. Do not let it
# silently become a new command to the model (prompt-injection defense).
return f"<tool_result name={name}>\n{result}\n</tool_result>"
Combinez cela avec les plafonds de boucle/budget déjà dans la boucle (MAX_STEPS, plus un plafond de tokens que vous suivez par exécution) et vous avez les trois contrôles qui comptent : un humain dans la boucle pour tout ce qui est destructeur, un arrêt net pour que l'agent ne puisse pas tourner ou dépenser indéfiniment, et l'habitude de traiter la sortie des outils comme du texte non fiable.
5. Optionnel — Claude comme couche intelligente
Par défaut, n'appelez jamais le cloud. Mais certaines étapes dépassent vraiment un petit modèle local — planification multi-étapes épineuse, un refactoring qui doit être correct, une synthèse à travers un long contexte. Pour ces étapes uniquement, l'agent peut escalader vers l'API Claude, obtenir une meilleure réponse, et retomber dans la boucle locale. C'est l'idée du routeur / brouillon-puis-raffinement de Claude + modèles locaux, appliquée une étape à la fois.
import anthropic
cloud = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env
def hard_step(prompt, allow_cloud=False):
"""Escalate ONE hard step to Claude — only when explicitly allowed."""
if not allow_cloud:
return None # default: stay fully local, send nothing off-device
msg = cloud.messages.create(
model="claude-sonnet-4-5", # check current model ids before pinning
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return msg.content[0].text
Deux règles gardent tout cela honnête : le chemin cloud est à activer (désactivé par défaut), et vous n'envoyez que ce dont cette seule étape a besoin — pas tout votre contexte. Le modèle local reste le cheval de trait ; Claude est le spécialiste que vous appelez pour les 5 % difficiles. Pour les identifiants de modèle et les prix exacts actuels, voyez la note de vérification ci-dessous.
- Les agents locaux effectuent quand même de vraies actions sur votre machine — mettez les outils en bac à sable, exigez une approbation pour les étapes destructrices, plafonnez boucles/budget, et traitez les résultats d'outils comme non fiables (injection de prompt).
Testez-vous
Testez-vous
0/4- Un assistant privé, ce sont quatre boîtes câblées en boucle : modèle local + agent agnostique au modèle + outils MCP locaux + un garde-fou — avec Claude comme cinquième boîte optionnelle
- Le local est le défaut et la garantie de confidentialité : le modèle, la boucle, les outils et vos données restent tous sur votre machine à moins que VOUS n'optiez pour le chemin cloud
- Gardez la boucle bête et agnostique au modèle (forme de chat OpenAI) et mettez les vraies capacités derrière un serveur MCP local — construisez une fois, réutilisez sur plusieurs clients
- Le garde-fou est la partie qu'on ne peut pas sauter : approuver les étapes destructrices, plafonner boucles/budget, mettre les outils en bac à sable, et traiter les résultats d'outils comme non fiables
- Claude est la couche intelligente à activer pour les 5 % difficiles — escaladez une étape à la fois et n'envoyez que ce dont cette étape a besoin
- Les spécificités volatiles (noms de modèles, identifiants, prix, API des SDK) sont derrière des notes de vérification ; l'architecture est durable, les chiffres ne le sont pas
Sources et lectures complémentaires
- Ollama — API compatible OpenAI (localhost:11434, paramètre tools)
- Ollama — annonce du support des outils
- Bibliothèque de modèles Ollama (modèles actuels capables d'outils)
- Model Context Protocol — introduction
- Model Context Protocol — SDK officiels (Python, TypeScript)
- SDK Python MCP (GitHub)
- SDK TypeScript MCP (GitHub)
- Anthropic — modèles et prix de Claude