{"id":1241,"date":"2026-10-27T17:29:58","date_gmt":"2026-10-27T16:29:58","guid":{"rendered":"https:\/\/remivernier.com\/?p=1241"},"modified":"2026-10-27T21:54:40","modified_gmt":"2026-10-27T20:54:40","slug":"rag-local-lm-studio-n8n-embeddings-decoupage","status":"publish","type":"post","link":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/","title":{"rendered":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques"},"content":{"rendered":"<p>L&rsquo;objectif de cet article n&rsquo;est pas de refaire un \u00e9ni\u00e8me tutoriel \u00ab RAG en 10 minutes \u00bb. Brancher un mod\u00e8le sur une base vectorielle, n8n le fait en quelques clics. Obtenir des r\u00e9ponses justes sur des documents techniques est une autre affaire. Le sens d&rsquo;un paragraphe d\u00e9pend souvent du bloc de commande juste au dessus, et la question de l&rsquo;utilisateur contient un nom d&rsquo;attribut ou un code d&rsquo;erreur exact.<\/p>\n<p>Nous allons construire un RAG enti\u00e8rement local, sans aucun appel \u00e0 une API externe, sur un corpus que je connais bien: les articles de ce blog. Sur cette maquette, le mod\u00e8le de langage est rarement en cause. La plupart des mauvaises r\u00e9ponses viennent du d\u00e9coupage des documents, et de ce que l&rsquo;on envoie ou non au mod\u00e8le.<\/p>\n<h2>Pr\u00e9sentation de la maquette<\/h2>\n<p>Comme pour les articles pr\u00e9c\u00e9dents sur l&rsquo;IA locale, la maquette est la suivante:<\/p>\n<ul>\n<li>Machine: Mac mini M4, 32 Go de m\u00e9moire unifi\u00e9e<\/li>\n<li>LM Studio, serveur local activ\u00e9 sur le port par d\u00e9faut 1234<\/li>\n<li>Mod\u00e8le d&#8217;embeddings: Qwen3-Embedding-0.6B<\/li>\n<li>Mod\u00e8le de g\u00e9n\u00e9ration: un mod\u00e8le instruct de taille moyenne charg\u00e9 dans LM Studio (le choix pr\u00e9cis importe peu pour la d\u00e9monstration)<\/li>\n<li>n8n auto-h\u00e9berg\u00e9 dans Docker<\/li>\n<li>Qdrant dans Docker, collection nomm\u00e9e <code>blog<\/code><\/li>\n<li>Corpus: les articles de remivernier.com r\u00e9cup\u00e9r\u00e9s via l&rsquo;API REST WordPress<\/li>\n<\/ul>\n<p>Les noms des n\u0153uds n8n cit\u00e9s dans la suite sont ceux de la documentation officielle \u00e0 l&rsquo;heure o\u00f9 j&rsquo;\u00e9cris ces lignes (octobre 2026). n8n renomme r\u00e9guli\u00e8rement ses n\u0153uds IA, v\u00e9rifiez donc dans votre version.<\/p>\n<h2>Architecture<\/h2>\n<p>Il y a deux workflows distincts. Le premier sert \u00e0 l&rsquo;ingestion: il r\u00e9cup\u00e8re les articles, les d\u00e9coupe, calcule les embeddings et les stocke. Le second sert \u00e0 la requ\u00eate: il re\u00e7oit une question, cherche les passages proches et demande au mod\u00e8le de r\u00e9pondre en citant ses sources.<\/p>\n<figure class=\"wp-block-image\">\n<svg viewBox=\"0 0 720 340\" width=\"100%\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" font-family=\"sans-serif\" font-size=\"13\">\n<defs>\n<marker id=\"fl\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"5\" orient=\"auto\"><path d=\"M0,0 L10,5 L0,10 z\" fill=\"#333\"\/><\/marker>\n<\/defs>\n<text x=\"10\" y=\"25\" font-size=\"15\" fill=\"#333\">Ingestion<\/text>\n<rect x=\"10\" y=\"40\" width=\"160\" height=\"64\" fill=\"#fdf3e1\" stroke=\"#333\"\/>\n<text x=\"90\" y=\"66\" text-anchor=\"middle\">API REST WordPress<\/text>\n<text x=\"90\" y=\"86\" text-anchor=\"middle\">\/wp-json\/wp\/v2\/posts<\/text>\n<rect x=\"190\" y=\"40\" width=\"160\" height=\"64\" fill=\"#e8f0fe\" stroke=\"#333\"\/>\n<text x=\"270\" y=\"66\" text-anchor=\"middle\">n8n: Markdown,<\/text>\n<text x=\"270\" y=\"86\" text-anchor=\"middle\">d\u00e9coupage par section<\/text>\n<rect x=\"370\" y=\"40\" width=\"160\" height=\"64\" fill=\"#e6f4ea\" stroke=\"#333\"\/>\n<text x=\"450\" y=\"66\" text-anchor=\"middle\">LM Studio<\/text>\n<text x=\"450\" y=\"86\" text-anchor=\"middle\">\/v1\/embeddings<\/text>\n<rect x=\"550\" y=\"40\" width=\"160\" height=\"64\" fill=\"#fdf3e1\" stroke=\"#333\"\/>\n<text x=\"630\" y=\"66\" text-anchor=\"middle\">Qdrant<\/text>\n<text x=\"630\" y=\"86\" text-anchor=\"middle\">collection blog (1024)<\/text>\n<line x1=\"170\" y1=\"72\" x2=\"188\" y2=\"72\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<line x1=\"350\" y1=\"72\" x2=\"368\" y2=\"72\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<line x1=\"530\" y1=\"72\" x2=\"548\" y2=\"72\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<text x=\"10\" y=\"195\" font-size=\"15\" fill=\"#333\">Requ\u00eate<\/text>\n<rect x=\"10\" y=\"210\" width=\"160\" height=\"64\" fill=\"#fdf3e1\" stroke=\"#333\"\/>\n<text x=\"90\" y=\"236\" text-anchor=\"middle\">Question<\/text>\n<text x=\"90\" y=\"256\" text-anchor=\"middle\">(Chat Trigger)<\/text>\n<rect x=\"190\" y=\"210\" width=\"160\" height=\"64\" fill=\"#e8f0fe\" stroke=\"#333\"\/>\n<text x=\"270\" y=\"236\" text-anchor=\"middle\">Qdrant \u00ab Get Many \u00bb<\/text>\n<text x=\"270\" y=\"256\" text-anchor=\"middle\">top k + score<\/text>\n<rect x=\"370\" y=\"210\" width=\"160\" height=\"64\" fill=\"#e8f0fe\" stroke=\"#333\"\/>\n<text x=\"450\" y=\"236\" text-anchor=\"middle\">Code: seuil,<\/text>\n<text x=\"450\" y=\"256\" text-anchor=\"middle\">contexte num\u00e9rot\u00e9<\/text>\n<rect x=\"550\" y=\"210\" width=\"160\" height=\"64\" fill=\"#e6f4ea\" stroke=\"#333\"\/>\n<text x=\"630\" y=\"236\" text-anchor=\"middle\">LLM LM Studio<\/text>\n<text x=\"630\" y=\"256\" text-anchor=\"middle\">r\u00e9ponse + [n]<\/text>\n<line x1=\"170\" y1=\"242\" x2=\"188\" y2=\"242\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<line x1=\"350\" y1=\"242\" x2=\"368\" y2=\"242\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<line x1=\"530\" y1=\"242\" x2=\"548\" y2=\"242\" stroke=\"#333\" marker-end=\"url(#fl)\"\/>\n<line x1=\"630\" y1=\"104\" x2=\"290\" y2=\"208\" stroke=\"#333\" stroke-dasharray=\"5,4\" marker-end=\"url(#fl)\"\/>\n<text x=\"470\" y=\"150\" text-anchor=\"middle\" fill=\"#333\">recherche vectorielle<\/text>\n<text x=\"360\" y=\"315\" text-anchor=\"middle\" fill=\"#333\">Hors base (score trop faible): r\u00e9ponse directe sans appel au LLM<\/text>\n<\/svg><figcaption>Les deux workflows n8n de la maquette<\/figcaption><\/figure>\n<p>C\u00f4t\u00e9 LM Studio, tout passe par l&rsquo;API compatible OpenAI. La <a href=\"https:\/\/lmstudio.ai\/docs\/api\/openai-api\">documentation<\/a> liste les endpoints <code>\/v1\/models<\/code>, <code>\/v1\/chat\/completions<\/code>, <code>\/v1\/embeddings<\/code> et <code>\/v1\/completions<\/code>. C\u00f4t\u00e9 n8n, nous utiliserons donc les n\u0153uds OpenAI en changeant simplement l&rsquo;URL de base:<\/p>\n<ul>\n<li>\u00ab Embeddings OpenAI \u00bb pour les vecteurs. Le n\u0153ud expose une option \u00ab Base URL \u00bb pr\u00e9vue pour les services auto-h\u00e9berg\u00e9s (<a href=\"https:\/\/docs.n8n.io\/integrations\/builtin\/cluster-nodes\/sub-nodes\/n8n-nodes-langchain.embeddingsopenai\">doc n8n<\/a>).<\/li>\n<li>\u00ab OpenAI Chat Model \u00bb pour la g\u00e9n\u00e9ration, avec un credential OpenAI dont l&rsquo;URL de base pointe vers LM Studio.<\/li>\n<\/ul>\n<p>n8n tourne dans Docker. <code>localhost<\/code> d\u00e9signe donc le conteneur et non le Mac. L&rsquo;URL \u00e0 renseigner est du type <code>http:\/\/host.docker.internal:1234\/v1<\/code>, avec le <code>\/v1<\/code> mais sans le chemin complet de l&rsquo;endpoint: n8n ajoute lui m\u00eame <code>\/embeddings<\/code> ou <code>\/chat\/completions<\/code>. C&rsquo;est l&rsquo;erreur la plus fr\u00e9quente, et elle est bien d\u00e9crite dans <a href=\"https:\/\/community.n8n.io\/t\/running-a-local-llm-with-lm-studio-n8n-fixed-workflow-real-case-step-by-step\/272190\">ce retour d&rsquo;exp\u00e9rience sur le forum n8n<\/a>. La cl\u00e9 API peut \u00eatre une valeur quelconque, LM Studio ne la v\u00e9rifie pas par d\u00e9faut.<\/p>\n<h2>Embeddings: quel mod\u00e8le, quelle dimension?<\/h2>\n<p>Le corpus est en fran\u00e7ais, truff\u00e9 de termes anglais et de noms d&rsquo;attributs. J&rsquo;\u00e9carte donc les mod\u00e8les entra\u00een\u00e9s principalement sur l&rsquo;anglais. Deux candidats courants dans LM Studio:<\/p>\n<table>\n<thead>\n<tr>\n<th>Mod\u00e8le<\/th>\n<th>Dimension<\/th>\n<th>Contexte max<\/th>\n<th>Particularit\u00e9<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>nomic-embed-text-v1.5<\/td>\n<td>768 (r\u00e9ductible jusqu&rsquo;\u00e0 64)<\/td>\n<td>8192 tokens<\/td>\n<td>pr\u00e9fixes <code>search_document:<\/code> \/ <code>search_query:<\/code> obligatoires<\/td>\n<\/tr>\n<tr>\n<td>Qwen3-Embedding-0.6B<\/td>\n<td>1024 (r\u00e9ductible de 32 \u00e0 1024)<\/td>\n<td>32K tokens<\/td>\n<td>multilingue (100+ langues), instruction c\u00f4t\u00e9 requ\u00eate<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Les valeurs viennent des fiches <a href=\"https:\/\/huggingface.co\/nomic-ai\/nomic-embed-text-v1.5\">nomic-embed-text-v1.5<\/a> et <a href=\"https:\/\/huggingface.co\/Qwen\/Qwen3-Embedding-0.6B-GGUF\">Qwen3-Embedding-0.6B-GGUF<\/a> sur Hugging Face. Je retiens Qwen3-Embedding-0.6B pour le multilinguisme. Le point qui pi\u00e8ge tout le monde avec nomic, ce sont les pr\u00e9fixes de t\u00e2che. Le n\u0153ud \u00ab Embeddings OpenAI \u00bb ne les ajoute pas, et sans eux la qualit\u00e9 de la recherche se d\u00e9grade sans aucun message d&rsquo;erreur.<\/p>\n<p>Ne vous fiez jamais \u00e0 une dimension lue sur une fiche: mesurez la sur ce que renvoie r\u00e9ellement le serveur. Commencez par r\u00e9cup\u00e9rer l&rsquo;identifiant exact du mod\u00e8le tel que LM Studio l&rsquo;expose:<\/p>\n<pre class=\"wp-block-code\"><code>curl -s http:\/\/localhost:1234\/v1\/models | jq -r '.data[].id'\n\ncurl -s http:\/\/localhost:1234\/v1\/embeddings \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"model\":\"text-embedding-qwen3-embedding-0.6b\",\"input\":\"Petit rappel sur Kerberos\"}' \\\n  | jq '.data[0].embedding | length'<\/code><\/pre>\n<p>L&rsquo;identifiant ci dessus correspond au nommage habituel de LM Studio (pr\u00e9fixe <code>text-embedding-<\/code>), mais reprenez celui que renvoie <code>\/v1\/models<\/code> chez vous. Le second appel doit renvoyer 1024. C&rsquo;est ce chiffre qui sert \u00e0 cr\u00e9er la collection Qdrant. Je pr\u00e9f\u00e8re la cr\u00e9er explicitement plut\u00f4t que de laisser n8n le faire \u00e0 la premi\u00e8re insertion:<\/p>\n<pre class=\"wp-block-code\"><code>docker run -d --name qdrant -p 6333:6333 -v qdrant_data:\/qdrant\/storage qdrant\/qdrant\n\ncurl -X PUT http:\/\/localhost:6333\/collections\/blog \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"vectors\":{\"size\":1024,\"distance\":\"Cosine\"}}'<\/code><\/pre>\n<p>Si vous changez un jour de mod\u00e8le d&#8217;embeddings, il faut recr\u00e9er la collection et tout r\u00e9indexer. M\u00eame \u00e0 dimension identique, les vecteurs de deux mod\u00e8les diff\u00e9rents ne sont pas comparables entre eux.<\/p>\n<h2>Ingestion des articles du blog<\/h2>\n<p>Le workflow d&rsquo;ingestion est volontairement lin\u00e9aire:<\/p>\n<ol>\n<li>\u00ab HTTP Request \u00bb vers <code>https:\/\/remivernier.com\/wp-json\/wp\/v2\/posts?per_page=100&amp;_fields=id,link,title,content,modified<\/code>. Au del\u00e0 de 100 articles, il faut paginer avec le param\u00e8tre <code>page<\/code> (WordPress renvoie le nombre total de pages dans l&rsquo;en-t\u00eate <code>X-WP-TotalPages<\/code>).<\/li>\n<li>\u00ab Markdown \u00bb en mode HTML vers Markdown, sur <code>content.rendered<\/code>. Les titres <code>&lt;h2&gt;<\/code> deviennent des lignes <code>## <\/code> et les blocs <code>&lt;pre&gt;<\/code> deviennent des blocs de code d\u00e9limit\u00e9s. Le d\u00e9coupage s&rsquo;appuiera sur ces marqueurs.<\/li>\n<li>\u00ab Code \u00bb pour un premier d\u00e9coupage par section (voir plus bas).<\/li>\n<li>\u00ab Qdrant Vector Store \u00bb en mode \u00ab Insert Documents \u00bb, avec en sous-n\u0153uds \u00ab Embeddings OpenAI \u00bb et \u00ab Default Data Loader \u00bb.<\/li>\n<\/ol>\n<h2>D\u00e9coupage: le vrai sujet<\/h2>\n<p>Par d\u00e9faut, le \u00ab Default Data Loader \u00bb propose un mode \u00ab Simple \u00bb qui, d&rsquo;apr\u00e8s la <a href=\"https:\/\/docs.n8n.io\/integrations\/builtin\/cluster-nodes\/sub-nodes\/n8n-nodes-langchain.documentdefaultdataloader\">documentation<\/a>, utilise le Recursive Character Text Splitter avec des chunks de 1000 caract\u00e8res et un recouvrement de 200. Ce d\u00e9coupeur essaie de garder ensemble les paragraphes, puis les phrases, puis les mots (<a href=\"https:\/\/docs.n8n.io\/integrations\/builtin\/cluster-nodes\/sub-nodes\/n8n-nodes-langchain.textsplitterrecursivecharactertextsplitter\">doc<\/a>). Sur de la prose, le r\u00e9sultat est correct. Sur un article technique, voici ce que l&rsquo;on obtient:<\/p>\n<ul>\n<li>-> une commande <code>netsh trace start<\/code> se retrouve dans un chunk et son explication dans le suivant<\/li>\n<li>-> un tableau de valeurs d&rsquo;attributs est coup\u00e9 au milieu, sans sa ligne d&rsquo;en-t\u00eate<\/li>\n<li>-> un chunk de 1000 caract\u00e8res pris au milieu d&rsquo;une section ne dit plus de quel article ni de quel protocole il parle<\/li>\n<\/ul>\n<p>Le troisi\u00e8me cas est le plus difficile \u00e0 rep\u00e9rer. Un paragraphe qui commence par \u00ab Dans ce cas, le serveur renvoie l&rsquo;erreur\u2026 \u00bb est quasiment inexploitable une fois isol\u00e9. Il ressemble s\u00e9mantiquement \u00e0 des dizaines d&rsquo;autres.<\/p>\n<p>Ma d\u00e9marche est donc la suivante: d\u00e9couper d&rsquo;abord selon la structure du document (les sections <code>## <\/code>), et pr\u00e9fixer chaque morceau avec le titre de l&rsquo;article et celui de la section. Le d\u00e9coupeur r\u00e9cursif ne sert ensuite plus que de filet de s\u00e9curit\u00e9 pour les sections trop longues. Voici le n\u0153ud \u00ab Code \u00bb (mode \u00ab Run Once for All Items \u00bb):<\/p>\n<pre class=\"wp-block-code\"><code>const out = [];\nfor (const item of $input.all()) {\n  const md = item.json.markdown;          \/\/ cl\u00e9 de sortie choisie dans le n\u0153ud \u00ab Markdown \u00bb\n  const titre = item.json.title.rendered;\n  const url = item.json.link;\n  const sections = md.split(\/\\n(?=## )\/);\n  sections.forEach((s, i) =&gt; {\n    const m = s.match(\/^## (.+)\/);\n    const section = m ? m[1].trim() : 'Introduction';\n    out.push({\n      json: {\n        texte: `Article: ${titre}\\nSection: ${section}\\n\\n${s.trim()}`,\n        titre, url, section,\n        ordre: i,\n        modifie: item.json.modified\n      }\n    });\n  });\n}\nreturn out;<\/code><\/pre>\n<p>Dans le \u00ab Default Data Loader \u00bb:<\/p>\n<ul>\n<li>\u00ab Type of Data \u00bb: JSON<\/li>\n<li>\u00ab Mode \u00bb: \u00ab Load Specific Data \u00bb, avec l&rsquo;expression <code>{{ $json.texte }}<\/code><\/li>\n<li>\u00ab Text Splitting \u00bb: \u00ab Custom \u00bb, avec un \u00ab Recursive Character Text Splitter \u00bb (chunk de 2000 caract\u00e8res, recouvrement de 200 par exemple)<\/li>\n<li>Dans les options, ajoutez les m\u00e9tadonn\u00e9es <code>titre<\/code>, <code>url<\/code>, <code>section<\/code> et <code>modifie<\/code><\/li>\n<\/ul>\n<p>Il reste un d\u00e9faut: quand une section d\u00e9passe la taille de chunk, seul le premier sous-morceau garde l&rsquo;en-t\u00eate \u00ab Article \/ Section \u00bb dans son texte. Les suivants l&rsquo;ont uniquement en m\u00e9tadonn\u00e9es, et les m\u00e9tadonn\u00e9es ne participent pas \u00e0 l&#8217;embedding. Pour un blog comme celui-ci, les sections sont assez courtes pour que ce soit rare. Sur une documentation fournisseur de 200 pages, il faudrait r\u00e9injecter l&rsquo;en-t\u00eate dans chaque sous-morceau, toujours dans un n\u0153ud \u00ab Code \u00bb.<\/p>\n<p>Une fois l&rsquo;ingestion lanc\u00e9e, v\u00e9rifiez ce qui a r\u00e9ellement \u00e9t\u00e9 stock\u00e9, vecteur compris:<\/p>\n<pre class=\"wp-block-code\"><code>curl -s -X POST http:\/\/localhost:6333\/collections\/blog\/points\/scroll \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"limit\":1,\"with_payload\":true,\"with_vector\":true}' | jq '.result.points[0] | {payload, dim: (.vector | length), debut: .vector[0:5]}'<\/code><\/pre>\n<p>Ce contr\u00f4le r\u00e9pond \u00e0 un probl\u00e8me d\u00e9j\u00e0 rencontr\u00e9. Une <a href=\"https:\/\/github.com\/n8n-io\/n8n\/issues\/16985\">issue GitHub<\/a> d\u00e9crit un cas o\u00f9 le n\u0153ud \u00ab Embeddings OpenAI \u00bb renvoyait des vecteurs remplis de z\u00e9ros avec l&rsquo;API de LM Studio, alors que le m\u00eame appel en curl fonctionnait. Je n&rsquo;ai pas v\u00e9rifi\u00e9 dans quelles versions le probl\u00e8me se pose. Un vecteur nul ne provoque aucune erreur \u00e0 l&rsquo;insertion: la recherche renvoie simplement n&rsquo;importe quoi.<\/p>\n<p>Dernier pi\u00e8ge c\u00f4t\u00e9 ingestion: relancer le workflow ajoute les m\u00eames points une seconde fois. R\u00e9sultat, les m\u00eames chunks remontent en double dans le top k et prennent la place d&rsquo;autres passages. Pour une maquette, je supprime et recr\u00e9e la collection avant chaque ingestion compl\u00e8te. En production, il faudrait supprimer les points par <code>url<\/code> avant de r\u00e9ins\u00e9rer un article modifi\u00e9.<\/p>\n<h2>Requ\u00eate et prompt avec citations<\/h2>\n<p>n8n propose des cha\u00eenes toutes faites (\u00ab Question and Answer Chain \u00bb avec un \u00ab Vector Store Retriever \u00bb, ou le Vector Store branch\u00e9 comme outil d&rsquo;un \u00ab AI Agent \u00bb). Je ne les utilise pas ici, pour une raison pr\u00e9cise: je veux voir le score de chaque passage et d\u00e9cider moi m\u00eame de ce qui part au mod\u00e8le. Le workflow de requ\u00eate est donc le suivant:<\/p>\n<ol>\n<li>\u00ab Chat Trigger \u00bb<\/li>\n<li>\u00ab Qdrant Vector Store \u00bb en mode \u00ab Get Many \u00bb, limite \u00e0 6, avec en sous-n\u0153ud le m\u00eame \u00ab Embeddings OpenAI \u00bb que pour l&rsquo;ingestion<\/li>\n<li>\u00ab Code \u00bb: filtrage par score et construction d&rsquo;un contexte num\u00e9rot\u00e9<\/li>\n<li>\u00ab Basic LLM Chain \u00bb avec \u00ab OpenAI Chat Model \u00bb pointant vers LM Studio<\/li>\n<li>\u00ab Code \u00bb: ajout de la liste des sources \u00e0 partir des m\u00e9tadonn\u00e9es<\/li>\n<\/ol>\n<p>Qwen3-Embedding est un mod\u00e8le \u00ab instruction aware \u00bb: la fiche du mod\u00e8le recommande de pr\u00e9fixer la requ\u00eate (mais pas les documents) par une instruction de la forme <code>Instruct: \u2026\\nQuery:\u2026<\/code>. Dans le champ \u00ab Prompt \u00bb du n\u0153ud Qdrant:<\/p>\n<pre class=\"wp-block-code\"><code>Instruct: Given a technical question, retrieve passages from IT security articles that answer it\nQuery:{{ $json.chatInput }}<\/code><\/pre>\n<p>Avec nomic-embed-text, ce serait <code>search_query: {{ $json.chatInput }}<\/code>, et il aurait fallu ajouter <code>search_document: <\/code> devant chaque texte \u00e0 l&rsquo;ingestion.<\/p>\n<p>Le n\u0153ud \u00ab Code \u00bb qui construit le contexte doit lire la sortie de \u00ab Get Many \u00bb. Regardez la structure exacte dans l&rsquo;onglet de sortie du n\u0153ud: chez moi chaque item contient un objet <code>document<\/code> (avec <code>pageContent<\/code> et <code>metadata<\/code>) et un <code>score<\/code>, mais le code ci dessous reste tol\u00e9rant au cas o\u00f9:<\/p>\n<pre class=\"wp-block-code\"><code>const SEUIL = 0.45;   \/\/ \u00e0 calibrer sur vos propres questions, voir plus bas\nconst items = $input.all().map(i =&gt; ({\n  texte: i.json.document?.pageContent ?? i.json.pageContent,\n  meta: i.json.document?.metadata ?? i.json.metadata ?? {},\n  score: i.json.score\n}));\nconst retenus = items.filter(x =&gt; x.score &gt;= SEUIL);\nconst question = $('When chat message received').first().json.chatInput;\n\nif (retenus.length === 0) {\n  return [{ json: { horsBase: true, question } }];\n}\nconst contexte = retenus.map((x, n) =&gt;\n  `[${n + 1}] ${x.meta.titre} \/ ${x.meta.section}\\n${x.texte}`\n).join('\\n\\n---\\n\\n');\n\nreturn [{ json: {\n  horsBase: false, question, contexte,\n  sources: retenus.map((x, n) =&gt; ({ n: n + 1, titre: x.meta.titre, section: x.meta.section, url: x.meta.url, score: x.score }))\n}}];<\/code><\/pre>\n<p>Le nom du n\u0153ud d\u00e9clencheur dans <code>$('\u2026')<\/code> doit correspondre exactement au v\u00f4tre. Un n\u0153ud \u00ab If \u00bb sur <code>horsBase<\/code> renvoie directement une r\u00e9ponse fixe sans appeler le mod\u00e8le. C&rsquo;est plus fiable que de compter sur le LLM pour avouer qu&rsquo;il ne sait pas.<\/p>\n<p>Le prompt syst\u00e8me de la \u00ab Basic LLM Chain \u00bb:<\/p>\n<pre class=\"wp-block-code\"><code>Tu r\u00e9ponds \u00e0 des questions techniques uniquement \u00e0 partir des extraits fournis.\nChaque extrait est num\u00e9rot\u00e9 [1], [2], etc.\n\nR\u00e8gles:\n- Chaque affirmation doit \u00eatre suivie du num\u00e9ro de l'extrait qui la justifie, par exemple [2].\n- Si les extraits ne contiennent pas la r\u00e9ponse, r\u00e9ponds exactement:\n  \"Je ne trouve pas cette information dans les articles index\u00e9s.\"\n- Ne compl\u00e8te pas avec tes connaissances g\u00e9n\u00e9rales.\n- Ne r\u00e9dige aucune URL.\n- Recopie les commandes, noms d'attributs et valeurs exactement comme dans les extraits.\n\nExtraits:\n{{ $json.contexte }}\n\nQuestion: {{ $json.question }}<\/code><\/pre>\n<p>La consigne \u00ab Ne r\u00e9dige aucune URL \u00bb est volontaire. Un mod\u00e8le local de taille moyenne invente tr\u00e8s facilement un slug plausible mais faux. Les liens sont ajout\u00e9s apr\u00e8s coup par le dernier n\u0153ud \u00ab Code \u00bb, \u00e0 partir des m\u00e9tadonn\u00e9es et des seuls num\u00e9ros effectivement cit\u00e9s dans la r\u00e9ponse:<\/p>\n<pre class=\"wp-block-code\"><code>const rep = $json.text;\nconst src = $('Contexte').first().json.sources;\nconst cites = [...new Set([...rep.matchAll(\/\\[(\\d+)\\]\/g)].map(m =&gt; Number(m[1])))];\nconst inconnus = cites.filter(n =&gt; !src.find(s =&gt; s.n === n));\nconst liste = src.filter(s =&gt; cites.includes(s.n))\n  .map(s =&gt; `[${s.n}] ${s.titre} \/ ${s.section} - ${s.url}`).join('\\n');\nreturn [{ json: { reponse: rep + (liste ? `\\n\\nSources:\\n${liste}` : ''), citationsInvalides: inconnus } }];<\/code><\/pre>\n<p>-> si <code>citationsInvalides<\/code> n&rsquo;est pas vide, le mod\u00e8le a cit\u00e9 un extrait qui n&rsquo;existe pas. C&rsquo;est un bon indicateur d&rsquo;hallucination, qu&rsquo;il est utile de journaliser.<\/p>\n<h2>Questions pi\u00e8ges<\/h2>\n<p>Pour \u00e9valuer un RAG, il faut chercher les questions sur lesquelles il \u00e9choue. Je conseille de constituer une petite liste fixe, une vingtaine de questions avec la r\u00e9ponse attendue et l&rsquo;article qui la contient, et de la rejouer \u00e0 chaque changement de d\u00e9coupage, de mod\u00e8le ou de seuil. Les cat\u00e9gories ci dessous sont celles qui r\u00e9v\u00e8lent le plus de d\u00e9fauts.<\/p>\n<h3>Documents mal d\u00e9coup\u00e9s<\/h3>\n<p>Exemple de question: \u00ab Quelle commande permet de lancer une capture r\u00e9seau native sous Windows sans installer de driver? \u00bb. La r\u00e9ponse se trouve dans un article dont la commande et ses param\u00e8tres tiennent dans un bloc de code, et l&rsquo;explication dans le paragraphe d&rsquo;apr\u00e8s. Avec le d\u00e9coupage \u00ab Simple \u00bb \u00e0 1000 caract\u00e8res, il est fr\u00e9quent que le passage remont\u00e9 contienne le texte mais pas la commande, ou l&rsquo;inverse. Le mod\u00e8le donne alors une r\u00e9ponse incompl\u00e8te, ou compl\u00e8te de m\u00e9moire, ce que le prompt lui interdit pourtant.<\/p>\n<p>Pour diagnostiquer, d\u00e9sactivez temporairement le n\u0153ud LLM et regardez seulement les 6 passages renvoy\u00e9s par Qdrant avec leur score. Si la bonne r\u00e9ponse n&rsquo;y figure pas, inutile de toucher au prompt: le probl\u00e8me est en amont.<\/p>\n<p>M\u00eame famille de probl\u00e8me: les questions qui contiennent un identifiant exact (\u00ab MaxTokenSize? \u00bb, un nom d&rsquo;attribut comme <code>msDS-SupportedEncryptionTypes<\/code>, un code d&rsquo;erreur). Les embeddings denses capturent le sens g\u00e9n\u00e9ral mais peuvent faire remonter un passage \u00ab sur Kerberos \u00bb qui ne contient pas le terme exact. Si ce type de question est fr\u00e9quent chez vous, une recherche hybride (vecteurs + recherche lexicale) ou un reranker apporte souvent plus qu&rsquo;un changement de mod\u00e8le d&#8217;embeddings. Je ne l&rsquo;ai pas mis en place sur cette maquette.<\/p>\n<h3>Questions hors base<\/h3>\n<p>Exemple: \u00ab Comment configurer le SSO entre Okta et SAP? \u00bb. La base contient des articles SAP et des articles SAML, donc Qdrant renverra toujours 6 passages: une recherche vectorielle trouve toujours un plus proche voisin. Sans seuil, le mod\u00e8le re\u00e7oit des extraits sur SAML et sur SAP, et la tentation de combiner les deux en une r\u00e9ponse plausible est forte.<\/p>\n<p>Le seuil de 0.45 dans le code ci dessus n&rsquo;est pas une valeur universelle. Elle d\u00e9pend du mod\u00e8le d&#8217;embeddings, de la langue et de la longueur des chunks. Pour la calibrer, relevez le score du premier r\u00e9sultat sur vos questions \u00ab dans la base \u00bb et sur une dizaine de questions volontairement hors sujet, puis placez le seuil entre les deux distributions. Si elles se chevauchent beaucoup, le seuil seul ne suffira pas, et la consigne de refus du prompt prend le relais. Le seuil \u00e9carte les cas nets, le prompt traite une partie de ceux qui passent.<\/p>\n<h3>Contexte trop long<\/h3>\n<p>Le r\u00e9flexe naturel quand une r\u00e9ponse est incompl\u00e8te est d&rsquo;augmenter le top k. Sur une maquette locale, il faut faire le calcul. Avec 6 chunks de 2000 caract\u00e8res, on envoie d\u00e9j\u00e0 12 000 caract\u00e8res de contexte. En ordre de grandeur, un token repr\u00e9sente environ 4 caract\u00e8res en anglais, et plut\u00f4t moins en fran\u00e7ais ou sur du code. On approche donc les 4000 tokens avant m\u00eame le prompt syst\u00e8me et la r\u00e9ponse.<\/p>\n<p>Or LM Studio charge un mod\u00e8le avec la longueur de contexte que vous lui donnez, pas forc\u00e9ment le maximum support\u00e9 par le mod\u00e8le. V\u00e9rifiez la valeur dans les param\u00e8tres de chargement, ou chargez le mod\u00e8le explicitement:<\/p>\n<pre class=\"wp-block-code\"><code>lms ls\nlms load &lt;identifiant-du-modele&gt; --context-length 16384<\/code><\/pre>\n<p>Si le prompt d\u00e9passe la fen\u00eatre, vous aurez selon les r\u00e9glages une erreur ou une troncature. La troncature est la plus g\u00eanante: aucun message ne signale qu&rsquo;une partie du contexte a disparu. Augmenter la fen\u00eatre a aussi un co\u00fbt: le KV cache occupe de la m\u00e9moire unifi\u00e9e en plus des poids du mod\u00e8le, et le temps de traitement du prompt augmente avec sa longueur. Mesurez sur votre machine en comparant les statistiques affich\u00e9es par LM Studio pour un m\u00eame prompt \u00e0 4 et \u00e0 12 chunks.<\/p>\n<p>Faire tenir tout le contexte dans la fen\u00eatre ne suffit pas non plus. Les mod\u00e8les exploitent moins bien les informations plac\u00e9es au milieu d&rsquo;un long contexte (le ph\u00e9nom\u00e8ne est connu sous le nom de \u00ab lost in the middle \u00bb). Sur cette maquette, je pr\u00e9f\u00e8re des chunks bien construits, un top k modeste et un seuil, plut\u00f4t que de tout envoyer en esp\u00e9rant que le mod\u00e8le fasse le tri.<\/p>\n<p>Avant d&rsquo;ouvrir ce type de workflow au del\u00e0 de votre poste, un dernier point de s\u00e9curit\u00e9. Le \u00ab Chat Trigger \u00bb de n8n peut \u00eatre rendu public, et LM Studio peut \u00e9couter sur le r\u00e9seau local avec l&rsquo;option \u00ab Serve on local network \u00bb. Une base vectorielle construite \u00e0 partir de documents internes reste une copie de ces documents. Elle doit \u00eatre prot\u00e9g\u00e9e avec les m\u00eames droits que la source, et un RAG qui r\u00e9pond \u00e0 tout le monde \u00e0 partir de documents \u00e0 acc\u00e8s restreint est une fuite de donn\u00e9es, m\u00eame s&rsquo;il ne cite que des extraits.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[59,58,18],"tags":[71,69,72,60,68,70,67],"class_list":["post-1241","post","type-post","status-publish","format-standard","hentry","category-intelligence-artificielle","category-llm-en-local","category-outils","tag-chunking","tag-embeddings","tag-llm-local","tag-lm-studio","tag-n8n","tag-qdrant","tag-rag"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER<\/title>\n<meta name=\"description\" content=\"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER\" \/>\n<meta property=\"og:description\" content=\"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/\" \/>\n<meta property=\"og:site_name\" content=\"R\u00e9mi VERNIER\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-27T16:29:58+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-27T20:54:40+00:00\" \/>\n<meta name=\"author\" content=\"R\u00e9mi\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"R\u00e9mi\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/\"},\"author\":{\"name\":\"R\u00e9mi\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\"},\"headline\":\"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques\",\"datePublished\":\"2026-10-27T16:29:58+00:00\",\"dateModified\":\"2026-10-27T20:54:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/\"},\"wordCount\":2703,\"commentCount\":0,\"keywords\":[\"chunking\",\"embeddings\",\"llm local\",\"lm studio\",\"n8n\",\"qdrant\",\"rag\"],\"articleSection\":[\"Intelligence artificielle\",\"LLM en local\",\"Outils\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/\",\"url\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/\",\"name\":\"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/#website\"},\"datePublished\":\"2026-10-27T16:29:58+00:00\",\"dateModified\":\"2026-10-27T20:54:40+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\"},\"description\":\"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/rag-local-lm-studio-n8n-embeddings-decoupage\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/remivernier.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#website\",\"url\":\"https:\\\/\\\/remivernier.com\\\/\",\"name\":\"R\u00e9mi VERNIER\",\"description\":\"Identit\u00e9, s\u00e9curit\u00e9, SAP et IA : Active Directory, Azure &amp; co...\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/remivernier.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\",\"name\":\"R\u00e9mi\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"caption\":\"R\u00e9mi\"},\"sameAs\":[\"http:\\\/\\\/www.linkedin.com\\\/in\\\/remi-vernier\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER","description":"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/","og_locale":"fr_FR","og_type":"article","og_title":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER","og_description":"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.","og_url":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/","og_site_name":"R\u00e9mi VERNIER","article_published_time":"2026-10-27T16:29:58+00:00","article_modified_time":"2026-10-27T20:54:40+00:00","author":"R\u00e9mi","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"R\u00e9mi","Dur\u00e9e de lecture estim\u00e9e":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/#article","isPartOf":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/"},"author":{"name":"R\u00e9mi","@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906"},"headline":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques","datePublished":"2026-10-27T16:29:58+00:00","dateModified":"2026-10-27T20:54:40+00:00","mainEntityOfPage":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/"},"wordCount":2703,"commentCount":0,"keywords":["chunking","embeddings","llm local","lm studio","n8n","qdrant","rag"],"articleSection":["Intelligence artificielle","LLM en local","Outils"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/","url":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/","name":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques - R\u00e9mi VERNIER","isPartOf":{"@id":"https:\/\/remivernier.com\/#website"},"datePublished":"2026-10-27T16:29:58+00:00","dateModified":"2026-10-27T20:54:40+00:00","author":{"@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906"},"description":"Construire un RAG 100% local avec LM Studio, n8n et Qdrant sur les articles du blog: d\u00e9coupage, dimension des embeddings, citations et questions pi\u00e8ges.","breadcrumb":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/rag-local-lm-studio-n8n-embeddings-decoupage\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/remivernier.com\/"},{"@type":"ListItem","position":2,"name":"RAG local avec LM Studio et n8n: embeddings, d\u00e9coupage et pi\u00e8ges de qualit\u00e9 sur une base de documents techniques"}]},{"@type":"WebSite","@id":"https:\/\/remivernier.com\/#website","url":"https:\/\/remivernier.com\/","name":"R\u00e9mi VERNIER","description":"Identit\u00e9, s\u00e9curit\u00e9, SAP et IA : Active Directory, Azure &amp; co...","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/remivernier.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Person","@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906","name":"R\u00e9mi","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","caption":"R\u00e9mi"},"sameAs":["http:\/\/www.linkedin.com\/in\/remi-vernier"]}]}},"_links":{"self":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1241","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/comments?post=1241"}],"version-history":[{"count":2,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1241\/revisions"}],"predecessor-version":[{"id":1251,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1241\/revisions\/1251"}],"wp:attachment":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/media?parent=1241"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/categories?post=1241"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/tags?post=1241"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}