<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nlp on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/nlp/</link>
    <description>Recent content in Nlp on DevOps Way - Практические гайды</description>
    <image>
      <title>DevOps Way - Практические гайды</title>
      <url>https://devopsway.ru/images/devopsway-og.png</url>
      <link>https://devopsway.ru/images/devopsway-og.png</link>
    </image>
    <generator>Hugo -- 0.161.1</generator>
    <language>ru</language>
    <lastBuildDate>Fri, 15 May 2026 11:39:43 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/nlp/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>RAG Pipeline 2/N: Embeddings – как текст превращается в числа</title>
      <link>https://devopsway.ru/posts/rag-02-embeddings/</link>
      <pubDate>Thu, 14 May 2026 12:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/rag-02-embeddings/</guid>
      <description>Что такое embeddings, почему all-MiniLM не понимает русский текст, как выбрать модель и не сломать pipeline. Практика: три модели, одна фраза, реальные числа.</description>
      <content:encoded><![CDATA[<table>
  <thead>
      <tr>
          <th>Параметр</th>
          <th>Значение</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bloom</td>
          <td>L3–L4 (Применение → Анализ)</td>
      </tr>
      <tr>
          <td>SFIA</td>
          <td>Уровень 2–3</td>
      </tr>
      <tr>
          <td>Dreyfus</td>
          <td>Advanced Beginner → Competent</td>
      </tr>
      <tr>
          <td>Артефакт</td>
          <td>Скрипт сравнения моделей + benchmark</td>
      </tr>
      <tr>
          <td>Проверка</td>
          <td>Три модели, одна фраза – сравниваем score</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="tldr">TL;DR</h2>
<p>all-MiniLM и nomic-embed-text плохо различают русский текст: борщ и nginx получают одинаковый score. mxbai-embed-large – единственная приемлемая из трёх протестированных, но требует правильной настройки порога.</p>
<hr>
<h2 id="проблема-мусор-на-входе--мусор-на-выходе">Проблема: мусор на входе – мусор на выходе</h2>
<p>В <a href="/posts/rag-01-qdrant-vectors/">прошлом посте</a> мы запустили Qdrant и сделали семантический поиск. Но использовали случайные вектора (<code>random.uniform</code>). В реальном pipeline вектора создаёт embedding-модель – и от неё зависит <strong>всё</strong>.</p>
<p>Плохая модель превращает &ldquo;настройка reverse proxy&rdquo; и &ldquo;проксирование запросов через nginx&rdquo; в далёкие точки. Хорошая – в соседние. Если модель не понимает русский текст, ваш RAG будет находить ерунду, даже если Qdrant работает идеально. Garbage in – garbage out. Только тут garbage не в данных, а в модели.</p>
<hr>
<h2 id="как-работает-embedding">Как работает embedding</h2>
<p>Embedding-модель – это нейросеть, обученная на миллионах пар текстов. На входе – строка. На выходе – массив чисел фиксированной длины (вектор).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Отправляем текст в Ollama</span>
</span></span><span class="line"><span class="cl">curl -s http://localhost:11434/api/embed <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{&#34;model&#34;:&#34;all-minilm&#34;,&#34;input&#34;:&#34;Docker контейнер&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="p">|</span> python3 -c <span class="s2">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">import sys, json
</span></span></span><span class="line"><span class="cl"><span class="s2">emb = json.load(sys.stdin)[&#39;embeddings&#39;][0]
</span></span></span><span class="line"><span class="cl"><span class="s2">print(f&#39;Размерность: {len(emb)}&#39;)
</span></span></span><span class="line"><span class="cl"><span class="s2">print(f&#39;Первые 5: {[round(x,4) for x in emb[:5]]}&#39;)
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Размерность: 384</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Первые 5: [-0.0312, 0.0891, -0.0456, 0.1234, -0.0678]</span>
</span></span></code></pre></div><p>Два правила, которые нельзя нарушать:</p>
<ol>
<li><strong>Детерминированность</strong>: один и тот же текст всегда даёт один и тот же вектор</li>
<li><strong>Одна модель на pipeline</strong>: индексировали через <code>mxbai-embed-large</code> – ищите через неё же. Вектора разных моделей <strong>несовместимы</strong> (разная размерность, разное пространство смыслов)</li>
</ol>
<p>Нарушение второго правила – типичная причина &ldquo;RAG ничего не находит&rdquo;. Переехали на новую модель – переиндексируйте всю базу.</p>
<hr>
<h2 id="три-модели-сравнение-на-практике">Три модели: сравнение на практике</h2>
<p>Все три доступны через Ollama. Скачиваем:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ollama pull all-minilm        <span class="c1"># 23 MB, 384d</span>
</span></span><span class="line"><span class="cl">ollama pull nomic-embed-text  <span class="c1"># 274 MB, 768d</span>
</span></span><span class="line"><span class="cl">ollama pull mxbai-embed-large <span class="c1"># 670 MB, 1024d</span>
</span></span></code></pre></div><h3 id="характеристики">Характеристики</h3>
<table>
  <thead>
      <tr>
          <th>Модель</th>
          <th>Размерность</th>
          <th>Размер</th>
          <th>Контекст</th>
          <th>Русский</th>
          <th>Скорость</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>all-MiniLM</td>
          <td>384</td>
          <td>23 MB</td>
          <td>256 tokens</td>
          <td>Плохо</td>
          <td>Очень быстрая</td>
      </tr>
      <tr>
          <td>nomic-embed-text</td>
          <td>768</td>
          <td>274 MB</td>
          <td>8192 tokens</td>
          <td>Плохо (не отличает релевантное от нерелевантного)</td>
          <td>Быстрая</td>
      </tr>
      <tr>
          <td>mxbai-embed-large</td>
          <td>1024</td>
          <td>670 MB</td>
          <td>512 tokens</td>
          <td>Приемлемо (при правильном пороге)</td>
          <td>Средняя</td>
      </tr>
  </tbody>
</table>
<h3 id="эксперимент-одна-фраза-три-модели">Эксперимент: одна фраза, три модели</h3>
<p>Проверим, как модели понимают семантическую близость русского текста. Две пары фраз с одинаковым смыслом, но разными словами, и одна контрольная – заведомо нерелевантная (&ldquo;рецепт борща&rdquo;), чтобы проверить, отличает ли модель полезное от мусора:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="c1"># compare-embeddings.py – сравниваем три модели</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">OLLAMA</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:11434&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OLLAMA</span><span class="si">}</span><span class="s2">/api/embed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="n">model</span><span class="p">,</span> <span class="s2">&#34;input&#34;</span><span class="p">:</span> <span class="n">text</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;embeddings&#34;</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">cosine_sim</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">linalg</span><span class="o">.</span><span class="n">norm</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="o">.</span><span class="n">linalg</span><span class="o">.</span><span class="n">norm</span><span class="p">(</span><span class="n">b</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">queries</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;настройка reverse proxy&#34;</span><span class="p">,</span> <span class="s2">&#34;проксирование запросов через nginx&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;контейнер упал с OOM&#34;</span><span class="p">,</span> <span class="s2">&#34;процесс убит из-за нехватки памяти&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;настройка reverse proxy&#34;</span><span class="p">,</span> <span class="s2">&#34;рецепт борща&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">model</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;all-minilm&#34;</span><span class="p">,</span> <span class="s2">&#34;nomic-embed-text&#34;</span><span class="p">,</span> <span class="s2">&#34;mxbai-embed-large&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== </span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s2"> ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">q1</span><span class="p">,</span> <span class="n">q2</span> <span class="ow">in</span> <span class="n">queries</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">e1</span> <span class="o">=</span> <span class="n">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">q1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">e2</span> <span class="o">=</span> <span class="n">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">q2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">score</span> <span class="o">=</span> <span class="n">cosine_sim</span><span class="p">(</span><span class="n">e1</span><span class="p">,</span> <span class="n">e2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">score</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">  &#39;</span><span class="si">{</span><span class="n">q1</span><span class="si">}</span><span class="s2">&#39; ↔ &#39;</span><span class="si">{</span><span class="n">q2</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Результаты (реальные замеры на нашем сервере, Ollama):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">=== all-minilm ===
</span></span><span class="line"><span class="cl">  0.24  &#39;настройка reverse proxy&#39; ↔ &#39;проксирование запросов через nginx&#39;
</span></span><span class="line"><span class="cl">  0.55  &#39;контейнер упал с OOM&#39; ↔ &#39;процесс убит из-за нехватки памяти&#39;
</span></span><span class="line"><span class="cl">  0.15  &#39;настройка reverse proxy&#39; ↔ &#39;рецепт борща&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">=== nomic-embed-text ===
</span></span><span class="line"><span class="cl">  0.45  &#39;настройка reverse proxy&#39; ↔ &#39;проксирование запросов через nginx&#39;
</span></span><span class="line"><span class="cl">  0.66  &#39;контейнер упал с OOM&#39; ↔ &#39;процесс убит из-за нехватки памяти&#39;
</span></span><span class="line"><span class="cl">  0.46  &#39;настройка reverse proxy&#39; ↔ &#39;рецепт борща&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">=== mxbai-embed-large ===
</span></span><span class="line"><span class="cl">  0.72  &#39;настройка reverse proxy&#39; ↔ &#39;проксирование запросов через nginx&#39;
</span></span><span class="line"><span class="cl">  0.75  &#39;контейнер упал с OOM&#39; ↔ &#39;процесс убит из-за нехватки памяти&#39;
</span></span><span class="line"><span class="cl">  0.50  &#39;настройка reverse proxy&#39; ↔ &#39;рецепт борща&#39;
</span></span></code></pre></div><p><strong>Что видно по цифрам:</strong></p>
<ul>
<li>all-MiniLM: score 0.24 для семантически идентичных фраз – провал. На разумном пороге (0.5+) RAG ничего не найдёт. Единственный плюс: борщ (0.15) хотя бы далеко от proxy.</li>
<li>nomic-embed-text: proxy 0.45, но борщ <strong>тоже 0.46</strong>. Модель не отличает nginx от кулинарии на русском тексте. Это хуже, чем бесполезно – это опасно.</li>
<li>mxbai-embed-large: proxy 0.72 – уже рабочий score. Но борщ 0.50 – всё ещё высоковато. На пороге 0.6 борщ проскочит. На пороге 0.7 – нет. Настройка порога критична.</li>
</ul>
<p><strong>Главный вывод:</strong> ни одна из моделей не даёт на русском тексте score выше 0.8 для семантически идентичных фраз. На английском all-MiniLM выдаёт 0.68 для &ldquo;Docker container&rdquo; / &ldquo;containerization&rdquo; – тоже не блестяще, но мусор получает заметно более низкий score. На русском tokenizer разбивает слово на 8-11 частей (почти по буквам), и модель теряет контекст – это не &ldquo;цена мультиязычности&rdquo;, а следствие того, что русского текста в обучающих данных было мало.</p>
<p><strong>Почему на практике это работает лучше, чем в тесте.</strong> Тест выше – worst case: чисто русские фразы без единого английского слова. Реальный DevOps-контент выглядит иначе: &ldquo;настройка reverse proxy в nginx&rdquo;, &ldquo;деплой через docker compose&rdquo;, &ldquo;kubectl apply -f deployment.yaml&rdquo;. Английские технические термины токенизируются нормально (1 токен) и несут основной смысловой сигнал. Русские слова вокруг них – связующая ткань, менее важная для поиска. Наш продакшен pipeline (206K векторов) работает на таком смешанном контенте без проблем. Но если индексировать чисто русскую документацию без технических терминов – score будут такими же низкими, как в тесте.</p>
<hr>
<h2 id="подводные-камни-с-русским-текстом">Подводные камни с русским текстом</h2>
<h3 id="1-токенизация-русское-слово--8-11-токенов">1. Токенизация: русское слово = 8-11 токенов</h3>
<p>Embedding-модели используют tokenizer, обученный преимущественно на английском тексте. Одно английское слово – обычно 1 токен. Русское слово раскладывается почти по буквам:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">English: &#34;container&#34;     → 1 token  → [&#34;container&#34;]
</span></span><span class="line"><span class="cl">Русский: &#34;контейнер&#34;     → 9 tokens → [&#34;к&#34;, &#34;о&#34;, &#34;н&#34;, &#34;т&#34;, &#34;е&#34;, &#34;и&#34;, &#34;н&#34;, &#34;е&#34;, &#34;р&#34;]
</span></span><span class="line"><span class="cl">Русский: &#34;проксирование&#34; → 11 tokens
</span></span></code></pre></div><p>Проверено на реальных tokenizer&rsquo;ах all-MiniLM, nomic-embed-text и mxbai-embed-large – результат одинаковый. Одно русское слово = 8-11 токенов.</p>
<p>Последствие: русский текст длиной 800 символов может содержать 600-800 токенов. Модель с контекстом 256 токенов (all-MiniLM) обрежет его молча, потеряв большую часть. Модель с контекстом 512 (mxbai-embed-large) – тоже может не уместить.</p>
<h3 id="2-truncation-ollama-truncatetrue-не-работает">2. Truncation: Ollama truncate=true не работает</h3>
<p>Документация Ollama обещает параметр <code>truncate: true</code> для автоматической обрезки. На практике поведение нестабильно: в одних версиях модель молча обрезает текст (теряя конец), в других – возвращает ошибку. Полагаться на это нельзя.</p>
<p>Решение – обрезать самостоятельно <strong>до</strong> отправки:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">safe_embed</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">max_chars</span><span class="o">=</span><span class="mi">800</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Progressive truncation: 800 → 600 → 400 при ошибке&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">limit</span> <span class="ow">in</span> <span class="p">[</span><span class="n">max_chars</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="mi">400</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="n">chunk</span> <span class="o">=</span> <span class="n">text</span><span class="p">[:</span><span class="n">limit</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OLLAMA</span><span class="si">}</span><span class="s2">/api/embed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                 <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="n">model</span><span class="p">,</span> <span class="s2">&#34;input&#34;</span><span class="p">:</span> <span class="n">chunk</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">                                 <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;embeddings&#34;</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">None</span>  <span class="c1"># Не удалось получить embedding</span>
</span></span></code></pre></div><p>Это реальный код из нашего продакшен pipeline. Progressive truncation: сначала пробуем 800 символов, если модель не справляется – 600, потом 400.</p>
<h3 id="3-batch-обработка-ломается">3. Batch-обработка ломается</h3>
<p>Ollama поддерживает batch embedding – отправить несколько текстов за один запрос. На коротких фразах работает даже с русским. Но на длинных текстах (300+ символов, реальные чанки документации) – ломается: модель молча возвращает пустой массив или ошибку. Воспроизводимость зависит от версии Ollama и модели.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ТАК НЕ НАДО (с русским текстом):</span>
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OLLAMA</span><span class="si">}</span><span class="s2">/api/embed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="s2">&#34;mxbai-embed-large&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;input&#34;</span><span class="p">:</span> <span class="p">[</span><span class="n">text1</span><span class="p">,</span> <span class="n">text2</span><span class="p">,</span> <span class="n">text3</span><span class="p">]})</span>  <span class="c1"># batch – ненадёжно</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ТАК НАДЁЖНО:</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">text</span> <span class="ow">in</span> <span class="p">[</span><span class="n">text1</span><span class="p">,</span> <span class="n">text2</span><span class="p">,</span> <span class="n">text3</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OLLAMA</span><span class="si">}</span><span class="s2">/api/embed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="s2">&#34;mxbai-embed-large&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;input&#34;</span><span class="p">:</span> <span class="n">text</span><span class="p">})</span>  <span class="c1"># по одному</span>
</span></span></code></pre></div><p>Да, это медленнее. Но на первой итерации pipeline (all-MiniLM, 16K чанков) индексация поштучно занимала ~20 минут. На текущем объёме (206K, mxbai-embed-large) полная переиндексация дольше, но в штатном режиме systemd timer переиндексирует только изменённые файлы – и это незаметно.</p>
<h3 id="4-санитизация-текста">4. Санитизация текста</h3>
<p>Перед embedding текст нужно очистить:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sanitize_for_embedding</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Убираем мусор, который ломает embedding&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\ufffd</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>           <span class="c1"># replacement character</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;[\x00-\x08\x0b\x0c\x0e-\x1f]&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>  <span class="c1"># control chars</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(.)\1{20,}&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;\1\1\1&#39;</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>  <span class="c1"># aaaa...aaa → aaa</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">20</span><span class="p">:</span>       <span class="c1"># quality gate</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">text</span>
</span></span></code></pre></div><p>Без этого <code>\ufffd</code> (Unicode replacement character) и длинные повторяющиеся последовательности (<code>=====...=====</code> из markdown) генерируют мусорные вектора, которые &ldquo;притягивают&rdquo; нерелевантные результаты.</p>
<hr>
<h2 id="ollama-vs-api">Ollama vs API</h2>
<table>
  <thead>
      <tr>
          <th>Критерий</th>
          <th>Ollama (self-hosted)</th>
          <th>OpenAI API (text-embedding-3-small)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Стоимость</td>
          <td>Бесплатно (ваше железо)</td>
          <td>$0.02 / 1M tokens</td>
      </tr>
      <tr>
          <td>Приватность</td>
          <td>Данные не покидают сервер</td>
          <td>Данные уходят в OpenAI</td>
      </tr>
      <tr>
          <td>Скорость</td>
          <td>Зависит от GPU/CPU</td>
          <td>Стабильно быстро</td>
      </tr>
      <tr>
          <td>Качество на русском</td>
          <td>mxbai-embed-large – приемлемо (score ~0.72 для похожих фраз)</td>
          <td>text-embedding-3-large – по отзывам лучше (не тестировали)</td>
      </tr>
      <tr>
          <td>Offline</td>
          <td>Да</td>
          <td>Нет</td>
      </tr>
      <tr>
          <td>Зависимость</td>
          <td>Нет</td>
          <td>API key, rate limits, downtime</td>
      </tr>
  </tbody>
</table>
<p>Мы используем <strong>Ollama + mxbai-embed-large</strong>. Данные остаются на сервере, нет зависимости от внешнего API, нет счетов за токены. Качество на русском достаточное для RAG – при правильном пороге.</p>
<p>OpenAI API оправдан, если: нет GPU, нужно максимальное качество на русском, или объём данных маленький (стоимость копейки).</p>
<hr>
<h2 id="мини-тест">Мини-тест</h2>
<p><strong>1. RAG находил фрагменты, вы сменили модель эмбеддинга. Теперь ничего не находит. Почему?</strong></p>
<details>
<summary>Ответ</summary>
<p>Вектора старой и новой модели несовместимы – разная размерность и/или разное пространство смыслов. Нужно переиндексировать всю базу Qdrant новой моделью. Старые вектора бесполезны.</p>
</details>
<p><strong>2. Русский текст 800 символов, модель all-MiniLM (контекст 256 токенов). Что произойдёт?</strong></p>
<details>
<summary>Ответ</summary>
<p>800 символов русского текста ≈ 600-800 токенов (одно русское слово = 8-11 токенов – побуквенное разбиение). Модель с контекстом 256 (all-MiniLM) потеряет большую часть текста. Даже mxbai-embed-large с контекстом 512 не уместит всё. Решение: обрезать текст перед отправкой (safe_embed) или уменьшить размер чанка.</p>
</details>
<p><strong>3. Score между двумя явно похожими фразами = 0.4. Это нормально?</strong></p>
<details>
<summary>Ответ</summary>
<p>Зависит от модели и языка. На английском – нет, ожидается 0.7+. На русском – score 0.4 может быть лучшим результатом для all-MiniLM. Проблема в том, что нерелевантный текст (например, &ldquo;рецепт борща&rdquo;) тоже может получить 0.4 на некоторых моделях (nomic-embed-text). Если разница score между релевантным и нерелевантным меньше 0.15 – модель непригодна для вашего языка. Для русского mxbai-embed-large даёт разницу ~0.22 (proxy 0.72, борщ 0.50), что уже рабочий вариант.</p>
</details>
<p><strong>4. Зачем <code>sanitize_for_embedding()</code> перед отправкой текста?</strong></p>
<details>
<summary>Ответ</summary>
<p>Мусорные символы (\ufffd, control chars) и длинные повторяющиеся последовательности (===&hellip;===) генерируют шумные вектора, которые &ldquo;притягивают&rdquo; нерелевантные результаты при поиске. Порог качества (&lt;20 символов) отсекает слишком короткие фрагменты, которые не несут достаточного смысла для embedding.</p>
</details>
<hr>
<h2 id="артефакт-скрипт-сравнения-моделей">Артефакт: скрипт сравнения моделей</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">compare-embeddings.py – benchmark embedding-моделей для вашего RAG
</span></span></span><span class="line"><span class="cl"><span class="s2">Запуск: python3 compare-embeddings.py
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Требования:
</span></span></span><span class="line"><span class="cl"><span class="s2">  pip install requests numpy
</span></span></span><span class="line"><span class="cl"><span class="s2">  ollama pull all-minilm nomic-embed-text mxbai-embed-large
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">OLLAMA</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:11434&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">MODELS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;all-minilm&#34;</span><span class="p">,</span> <span class="s2">&#34;nomic-embed-text&#34;</span><span class="p">,</span> <span class="s2">&#34;mxbai-embed-large&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Пары: (текст1, текст2, ожидание)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># similar = должны быть близки, different = должны быть далеки</span>
</span></span><span class="line"><span class="cl"><span class="n">PAIRS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;настройка reverse proxy&#34;</span><span class="p">,</span> <span class="s2">&#34;проксирование запросов через nginx&#34;</span><span class="p">,</span> <span class="s2">&#34;similar&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;контейнер упал с OOM&#34;</span><span class="p">,</span> <span class="s2">&#34;процесс убит из-за нехватки памяти&#34;</span><span class="p">,</span> <span class="s2">&#34;similar&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;Kubernetes pod в CrashLoopBackOff&#34;</span><span class="p">,</span> <span class="s2">&#34;контейнер перезапускается циклически&#34;</span><span class="p">,</span> <span class="s2">&#34;similar&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;ansible playbook для деплоя&#34;</span><span class="p">,</span> <span class="s2">&#34;автоматизация развёртывания через YAML&#34;</span><span class="p">,</span> <span class="s2">&#34;similar&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;настройка reverse proxy&#34;</span><span class="p">,</span> <span class="s2">&#34;рецепт борща&#34;</span><span class="p">,</span> <span class="s2">&#34;different&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s2">&#34;мониторинг CPU&#34;</span><span class="p">,</span> <span class="s2">&#34;история Древнего Рима&#34;</span><span class="p">,</span> <span class="s2">&#34;different&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OLLAMA</span><span class="si">}</span><span class="s2">/api/embed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="n">model</span><span class="p">,</span> <span class="s2">&#34;input&#34;</span><span class="p">:</span> <span class="n">text</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">                         <span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">resp</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;embeddings&#34;</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">cosine_sim</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">linalg</span><span class="o">.</span><span class="n">norm</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="o">.</span><span class="n">linalg</span><span class="o">.</span><span class="n">norm</span><span class="p">(</span><span class="n">b</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">model</span> <span class="ow">in</span> <span class="n">MODELS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="si">{</span><span class="s1">&#39;=&#39;</span> <span class="o">*</span> <span class="mi">70</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  MODEL: </span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;=&#39;</span> <span class="o">*</span> <span class="mi">70</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">scores_sim</span><span class="p">,</span> <span class="n">scores_diff</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">q1</span><span class="p">,</span> <span class="n">q2</span><span class="p">,</span> <span class="n">expect</span> <span class="ow">in</span> <span class="n">PAIRS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">e1</span> <span class="o">=</span> <span class="n">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">q1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">e2</span> <span class="o">=</span> <span class="n">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">q2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">score</span> <span class="o">=</span> <span class="n">cosine_sim</span><span class="p">(</span><span class="n">e1</span><span class="p">,</span> <span class="n">e2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">marker</span> <span class="o">=</span> <span class="s2">&#34;OK&#34;</span> <span class="k">if</span> <span class="p">(</span><span class="n">expect</span> <span class="o">==</span> <span class="s2">&#34;similar&#34;</span> <span class="ow">and</span> <span class="n">score</span> <span class="o">&gt;</span> <span class="mf">0.6</span><span class="p">)</span> <span class="ow">or</span> \
</span></span><span class="line"><span class="cl">                         <span class="p">(</span><span class="n">expect</span> <span class="o">==</span> <span class="s2">&#34;different&#34;</span> <span class="ow">and</span> <span class="n">score</span> <span class="o">&lt;</span> <span class="mf">0.3</span><span class="p">)</span> <span class="k">else</span> <span class="s2">&#34;!!&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">marker</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">score</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">  &#39;</span><span class="si">{</span><span class="n">q1</span><span class="si">}</span><span class="s2">&#39; ↔ &#39;</span><span class="si">{</span><span class="n">q2</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">expect</span> <span class="o">==</span> <span class="s2">&#34;similar&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">scores_sim</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">score</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">scores_diff</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">score</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">  Avg similar: </span><span class="si">{</span><span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">scores_sim</span><span class="p">)</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Avg different: </span><span class="si">{</span><span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">scores_diff</span><span class="p">)</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Separation: </span><span class="si">{</span><span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">scores_sim</span><span class="p">)</span> <span class="o">-</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">scores_diff</span><span class="p">)</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Time: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s (</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">PAIRS</span><span class="p">)</span><span class="si">}</span><span class="s2"> pairs)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Dimensions: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">get_embedding</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="s1">&#39;test&#39;</span><span class="p">))</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Запуск:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install requests numpy
</span></span><span class="line"><span class="cl">python3 compare-embeddings.py
</span></span></code></pre></div><p>Чем больше <code>Separation</code> (разница между avg similar и avg different) – тем лучше модель отличает релевантное от нерелевантного.</p>
<hr>
<h2 id="продакшен-параметры">Продакшен-параметры</h2>
<p>В <a href="/posts/rag-01-qdrant-vectors/">RAG Pipeline 1/N</a> мы показывали параметры учебного pipeline (all-MiniLM, 384d, 16K чанков курса). С тех пор pipeline вырос: переехали на mxbai-embed-large, объём данных увеличился на порядок. Вот актуальные параметры (206,000+ векторов):</p>
<table>
  <thead>
      <tr>
          <th>Параметр</th>
          <th>Значение</th>
          <th>Почему</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Модель</td>
          <td>mxbai-embed-large</td>
          <td>Лучшее качество на русском из Ollama</td>
      </tr>
      <tr>
          <td>Размерность</td>
          <td>1024</td>
          <td>Определяется моделью</td>
      </tr>
      <tr>
          <td>Контекст модели</td>
          <td>512 tokens</td>
          <td>Ограничение mxbai</td>
      </tr>
      <tr>
          <td>CHUNK_SIZE</td>
          <td>800 chars</td>
          <td>С progressive truncation не превышает 512 tokens</td>
      </tr>
      <tr>
          <td>CHUNK_OVERLAP</td>
          <td>150 chars</td>
          <td>Контекст на границах чанков</td>
      </tr>
      <tr>
          <td>Truncation</td>
          <td>Progressive: 800→600→400</td>
          <td>Fallback при &ldquo;context length exceeded&rdquo;</td>
      </tr>
      <tr>
          <td>Batch</td>
          <td>Поштучно (не batch)</td>
          <td>Batch ломается на длинном русском</td>
      </tr>
      <tr>
          <td>Sanitize</td>
          <td>strip \ufffd, control chars, dedup</td>
          <td>Чистые вектора = чистый поиск</td>
      </tr>
      <tr>
          <td>Quality gate</td>
          <td>min 20 chars</td>
          <td>Слишком короткие фрагменты → шум</td>
      </tr>
      <tr>
          <td>Объём</td>
          <td>206,000+ векторов</td>
          <td>Техническая документация + рабочие заметки</td>
      </tr>
      <tr>
          <td>Sync</td>
          <td>systemd timer, 10 min</td>
          <td>Re-index только изменённые</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="что-дальше">Что дальше</h2>
<p>Модель выбрана, вектора генерируются. Но качество RAG зависит не только от модели – оно зависит от того, <strong>как вы нарезаете текст на куски</strong>:</p>
<ul>
<li><strong>RAG Pipeline 3/N – Chunking</strong> – размер чанка, overlap, split по границам функций, metadata enrichment. Почему 800 символов, а не 500 или 1200.</li>
</ul>
<hr>
<p>Telegram: <a href="https://t.me/DevITWay">@DevITWay</a>
Сайт: <a href="https://devopsway.ru/">devopsway.ru</a></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
