# Собираем RAG-пайплайн на .NET: практический разбор

Пошаговая сборка production-ready RAG на .NET 10 — от извлечения текста до evaluation-пайплайна. С реальными решениями, а не абстракциями.

> For the index of this section of the site, see [llms.txt](https://anetlab.com/llms.txt)

06 октября 2026 г.4 мин чтения

# Собираем RAG-пайплайн на .NET: практический разбор

Пошаговая сборка production-ready RAG на .NET 10 — от извлечения текста до evaluation-пайплайна. С реальными решениями, а не абстракциями.

[Виктор Бузин](https://anetlab.com/about.md) · Founder & Principal Engineer, AnetLab

16+ лет в enterprise-разработке: .NET, AI/LLM-системы, RAG, MCP. Автор материала и практик в студии.

За последний год мы собрали несколько RAG-пайплайнов на .NET для корпоративных систем. Ниже — схема, которая работает в проде, и грабли, на которые мы наступали.

## Порядок сборки

Порядок важен: если начать с промпта, потом прикрутить retrieval и только затем задуматься про данные — потеряете недели на переделку.

1.  Извлечение и структурирование
    
    PDF, DOCX, HTML → единый внутренний формат с сохранением иерархии заголовков, таблиц и подписей. Без этого шага все следующие работают вслепую.
    
2.  Индексация
    
    Эмбеддинги в векторное хранилище плюс полнотекстовый индекс. Два индекса, один retrieval.
    
3.  Retrieval
    
    Гибридный поиск с RRF поверх двух выдач. Без ранжирования качество упрётся в потолок эмбеддингов.
    
4.  Генерация с цитированием
    
    Ответ только из переданного контекста, каждое утверждение привязано к источнику.
    
5.  Evaluation
    
    Набор реальных вопросов, метрики Recall@k и MRR, регрессия на каждом релизе.
    

## Почему чанкинг решает больше, чем модель

За время этих проектов мы убедились: качество retrieval определяет ~80% итогового качества ответа. Выбор модели на оставшиеся 20% влияет в разы меньше, чем правильная нарезка документов.

**Что делаем:** режем по структуре. Для договоров — стороны, предмет, сроки, ответственность, приложения. Для технической документации — разделы и подразделы, блоки кода сохраняются целиком вместе с соседним пояснением.

```
public sealed record Section(
    string Title,
    int Level,
    string Text,
    string? ParentId);

public static IEnumerable<Section> Split(Document doc)
{
    // Режем по заголовкам, а не по длине. Длина — только
    // верхняя граница: слишком крупный раздел режем дальше,
    // но уже с сохранением соседнего контекста.
    var buffer = new List<string>();
    foreach (var block in doc.Blocks)
    {
        if (block.IsHeading && buffer.Count > 0)
        {
            yield return Flush(buffer);
            buffer.Clear();
        }
        buffer.Add(block.Text);
    }
    if (buffer.Count > 0) yield return Flush(buffer);
}
```

## Гибридный поиск: вектор плюс FTS

Векторный поиск отлично ловит смысл и проваливает точные идентификаторы. «Контракт 44-А/2024» не найдётся, потому что эмбеддинг буквенно-цифрового кода почти не несёт смысла.

```
const double K = 60.0; // сглаживающий коэффициент RRF

public static IReadOnlyList<Chunk> Fuse(
    IReadOnlyList<Chunk> vectorHits,
    IReadOnlyList<Chunk> keywordHits,
    int limit)
{
    var scores = new Dictionary<string, double>();

    foreach (var (chunk, rank) in vectorHits.Select((c, i) => (c, i)))
        scores[chunk.Id] = scores.GetValueOrDefault(chunk.Id) + 1.0 / (K + rank + 1);

    foreach (var (chunk, rank) in keywordHits.Select((c, i) => (c, i)))
        scores[chunk.Id] = scores.GetValueOrDefault(chunk.Id) + 1.0 / (K + rank + 1);

    return scores
        .OrderByDescending(kv => kv.Value)
        .Take(limit)
        .Select(kv => store.Get(kv.Key))
        .OfType<Chunk>()
        .ToList();
}
```

## Evaluation — этап, который все пропускают

Минимальный жизнеспособный вариант — 50–100 вопросов из реальной поддержки, покрывающих типовые сценарии:

| Метрика | Что показывает | Порог для продакшна |
| --- | --- | --- |
| Recall@5 | Нашёл ли нужный документ в выдаче | \> 0.85 |
| MRR | Насколько высок ранжирован верный ответ | \> 0.75 |
| Faithfulness | Не выдумала ли LLM то, чего нет в контексте | \> 0.90 |
| Answer relevancy | Отвечает ли на вопрос, а не на смежную тему | \> 0.85 |

**Faithfulness** — метрика, которую чаще всего игнорируют, и именно она определяет, пойдёт ли система в прод. Модель, уверенно выдумывающая несуществующий пункт договора, хуже, чем честно сказавшая «не нашла».

## Стоимость: два рычага

**Кеш эмбеддингов.** Документы меняются редко — пересчитываем только дельту. Это сразу снимает 70–80% повторных затрат на эмбеддинг.

**Маршрутизация по сложности.** Простые вопросы («где реквизиты») дешёвой моделью, сложные с рассуждением — дорогой. На типовом проекте это даёт снижение счёта в 3–4 раза без потери качества.

```
public sealed class Router(ModelCatalog catalog) : IModelRouter
{
    public IModel Select(Question q) => q switch
    {
        { RequiresReasoning: true } => catalog.Advanced,
        { EntityLookup: true }      => catalog.Fast,   // точные идентификаторы
        _                            => catalog.Fast,
    };
}
```

## Что обязательно до продакшна

*   [ ]  Набор из 50+ eval-вопросов из реальных обращений
*   [ ]  Recall@5 выше 0.85 на этом наборе
*   [ ]  Чанкинг по структуре, а не по фиксированному окну
*   [ ]  Гибридный поиск, а не только векторный
*   [ ]  Faithfulness замеряется, а не предполагается
*   [ ]  Кеширование эмбеддингов настроено
*   [ ]  Трейсинг каждого шага доступен в проде

Пункт 7 отсутствует чаще всего — и когда в проде что-то ломается, диагностировать нечем.

Если хотите разобрать свой пайплайн на конкретике — [напишите нам](https://anetlab.com/contact.md).

Читайте также

20 октября 2026 г.AI/LLM

### [Оценка качества RAG на .NET: Recall@5, MRR и гейт релиза](https://anetlab.com/blog/rag-evaluation-dotnet.md)

Как измерять качество RAG на .NET: Golden Set из 50–100 вопросов, расчёт Recall@5 и MRR, измерение faithfulness и eval-как-гейт-релиза в CI.

[Читать разбор](https://anetlab.com/blog/rag-evaluation-dotnet.md)

[.NET](https://anetlab.com/blog?tag=.NET)[RAG](https://anetlab.com/blog?tag=RAG.md)[Оценка качества](https://anetlab.com/blog?tag=%D0%9E%D1%86%D0%B5%D0%BD%D0%BA%D0%B0%20%D0%BA%D0%B0%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%B0.md)

13 октября 2026 г..NET

### [.NET и Azure для AI-систем: референсная архитектура](https://anetlab.com/blog/dotnet-azure-ai-architecture.md)

Как собрать production-ready AI-систему на .NET и Azure: async-пайплайн, RAG, Polly, observability и контроль стоимости — с рабочим кодом.

[Читать разбор](https://anetlab.com/blog/dotnet-azure-ai-architecture.md)

[AI/LLM](https://anetlab.com/blog?tag=AI%2FLLM.md)[Архитектура](https://anetlab.com/blog?tag=%D0%90%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0.md)[RAG](https://anetlab.com/blog?tag=RAG.md)