Головна / Статті / Практичні нотатки: проектування RAG для 100 мільйонів документів

Практичні нотатки: проектування RAG для 100 мільйонів документів

Покрокове пояснення до практичних нотаток: проектування RAG для 100 мільйонів документів: контракти, перевірки та слоти для коду для команд, які використовують цю схему.

5618 слів

Використовуйте цей документ як оновлену версію ідей з книги „Designing RAG for 100 Million Documents“, орієнтовану на операторів: чіткі етапи, впорядковані блоки коду та примітки щодо відновлення, які залишаються при передачі обов’язків. Етап Огляду найкраще функціонує, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний запис, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ.

Ось вимоги:

На етапі визначення вимог необхідно перед зміною коду визначити вхідні дані, відповідальну особу за крок та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Необхідно наводити конкретні уривки тексту, які лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем з індексуванням.

Почніть з математики

На етапі «Почнемо з математики» необхідно визначити вхідні дані, відповідального за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Наводьте уривки тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

Documents              = 100,000,000
Average chunks/document = 30
Embedding dimensions    = 768
Storage/dimension       = 4 bytes (float32)
100,000,000 × 30
= 3,000,000,000 vectors
3B × 768 × 4 bytes
≈ 9.2 TB
ANN index structures
chunk text
document metadata
ACL metadata
IDs
lexical indexes
replicas
snapshots
WALs
temporary indexes
operational headroom
100M documents × 50 chunks
= 5 billion vectors

Архітектура, яку ви б побудували

Для архітектури, яку ви плануєте впровадити, необхідно перед зміною коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. Для архітектури, яку ви плануєте впровадити, необхідно перед зміною коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільного середовища.

Коли ви працюєте на етапі „Джерело істини“, спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Вимірюйте рівень відтворення інформації за фіксованим набором запитань перед налаштуванням підказок. Зміни підказок рідко виправляють слабку систему пошуку.

Процес отримання даних має бути асинхронним

Під час роботи над етапом «Завантаження має бути асинхронним» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність у подальших змінах коду. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Перед налаштуванням запитів вимірюйте рівень відтворення даних на фіксованому наборі запитань. Зміна запитів рідко вирішує проблеми слабкої системи пошуку.

POST /documents
       │
       ▼
Store document
       │
       ▼
Create ingestion event
       │
       ▼
Return 202 Accepted

Кожен крок завантаження має бути ідемпотентним

Під час роботи над етапом «Every Ingestion Step Should» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Перед налаштуванням запитів вимірюйте рівень відтворення інформації за фіксованим набором запитань. Часта зміна формулювань запитів рідко допомагає покращити ефективність пошуку. Під час роботи над етапом «Every Ingestion Step Should» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Поруч із функціональними результатами записуйте час виконання та витрати на токени або запити. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних.

document_id
document_version
chunk_index
embedding_model_version
hash(
    tenant_id +
    document_id +
    document_version +
    chunk_index
)
upsert(chunk_id, ...)

Чанкінг стає рішенням інфраструктури

Етап «Чанкінг стає рішенням інфраструктури» працює найкраще, коли його розглядають як вимірювану характеристику. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Розділіть політику чанкінгу від політики отримання даних. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

embedding compute
vector storage
indexing work
retrieval fan-out
reindexing time
replication traffic
{
  "chunk_id": "c_982...",
  "document_id": "doc_51...",
  "document_version": 8,
  "tenant_id": "tenant_17",
  "page": 43,
  "section": "Risk Factors",
  "language": "en",
  "created_at": "...",
  "acl_groups": ["finance", "executives"],
  "embedding_version": "embed_v4"
}

Не шукайте в усьому корпусі даних, якщо це справді не необхідно

Механізм «Не шукати» працює найкраще, коли його розглядають як вимірювану поверхню. Зафіксуйте один ідеальний варіант виконання, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії роботи разом. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Розділіть політику часткової обробки даних від політики їх пошуку. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

organization = current tenant
region       = Europe
topic        = refund policy
time         = current quarter
permissions  = documents user may access

Шардинг — це спосіб, яким векторний шар стає розподіленим

Шардування — це найкращий спосіб функціонування етапу, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткового оброблення даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості. Шардування — це найкращий спосіб функціонування етапу, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ.

tenant_id
region
document namespace
language
time partition
Query
 ↓
128 shards
tenant_id = 429
 ↓
Shard group 18
 ↓
4 shards

Реплікація вирішує іншу проблему

На етапі «Реплікація вирішує іншу проблему» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь граф. Наводьте уривки тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем з індексуванням.

Shard A → Node 1
Shard B → Node 2
Shard C → Node 3
Shard A → Node 1 + Node 4
Shard B → Node 2 + Node 5
Shard C → Node 3 + Node 6

Лише векторний пошук недостатній

На етапі «Лише пошук векторів» необхідно заздалегідь визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Указуйте конкретні уривки тексту, які лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

RRF(d) = Σ 1 / (k + rank_i(d))

Будьте обережні з послідовним попереднім фільтруванням

На етапі «Будь обережний із послідовністю» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Наводьте уривки тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. На етапі «Будь обережний із послідовністю» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільного середовища.

Розділи.

3B documents
   ↓
BM25 returns 10,000
   ↓
vector search only those
tenant
permissions
document status

Авторизація має відбутися перед тим, як докази надійдуть до LLM

Під час роботи над етапом «Авторизація має відбутися перед тим, як докази надійдуть до LLM», спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Зберігайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь код. Зберігайте у кеші стабільні інструкції системи та схеми інструментів. Повторна передача ідентичного вступу є поширеною причиною надмірних витрат.

Engineering documents
HR documents
Board documents
Legal documents
Executive compensation
Customer contracts
Vector Search
     ↓
Retrieve confidential chunk
     ↓
Send chunk to LLM
     ↓
Check permission
User
  ↓
Identity
  ↓
Groups / roles / ACL
  ↓
Authorized search space
  ↓
Retrieval

Завантаження має забезпечувати кандидатів, а не контекст

Під час роботи над етапом «Отримання даних має забезпечувати кандидатів» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Вимірюйте рівень відтворення інформації на фіксованому наборі запитань перед налаштуванням підказок. Зміна підказок рідко вирішує проблеми слабкої системи отримання даних.

Vector candidates: 200
Lexical candidates: 200
          │
          ▼
        Fusion
          │
          ▼
    ~250 unique chunks
          │
          ▼
       Reranker
          │
          ▼
       top 20–40

Потім усуньте зайве

Під час виконання етапу «Потім усунути дублювання» спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну функцію, а не на складну систему обробки даних. Перед налаштуванням запитів вимірюйте рівень точності відповідей на фіксованому наборі запитань. Часта зміна формулювань запитів рідко допомагає покращити якість пошуку. Під час виконання етапу «Потім усунути дублювання» спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Поруч із функціональними результатами записуйте час виконання та витрати на обробку токенів чи запитів. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних середовищ.

Chunk 1 → page 14
Chunk 2 → page 14
Chunk 3 → page 15
Chunk 4 → page 14
Chunk 5 → page 15
deduplication
document diversity
section diversity
near-duplicate detection
adjacent-chunk merging
token budgeting
chunk 46
chunk 47
chunk 48

Створення контексту — це окремий рівень

Процес створення контексту функціонує найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію поза кодом додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

context = "\n\n".join(retrieved_chunks)
Retrieved Evidence
        ↓
Deduplicate
        ↓
Merge related chunks
        ↓
Enforce token budget
        ↓
Preserve source IDs
        ↓
Order evidence
        ↓
Generate context
SOURCE S1
Document: Employee Handbook
Page: 42
Version: 18
Text: ...
SOURCE S2
Document: European Leave Addendum
Page: 7
Version: 4
Text: ...
QUESTION
...

Розуміння запитів має бути простим

Розуміння запиту працює найкраще, якщо його розглядати як вимірювану характеристику. Збережіть один ідеальний запис, один випадок збою та примітки щодо скасування дій перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Розділіть політику часткової обробки та політику пошуку інформації. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

metric = revenue
region = Europe
year = 2025
intent = financial comparison
How much did revenue grow in Asia in 2025?
small model
    ↓
classification
small model
    ↓
query rewriting
embedding model
    ↓
retrieval
reranker
    ↓
relevance
large model
    ↓
final reasoning
largest model everywhere

Розкладання запиту допомагає при багатоетапних запитах

Метод розкладання запитів ефективно працює на етапі розробки, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед величезними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну послідовність дій. Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу, коли змінюються показники якості. Метод розкладання запитів ефективно працює на етапі розробки, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

Question 1:
Which European product had the largest decline?
Question 2:
What explanation did management provide for that product?
Complex Query
                         │
                         ▼
                 Query Decomposer
                    /         \
                   /           \
                  ▼             ▼
              Subquery A    Subquery B
                  │             │
                  ▼             ▼
              Retrieval     Retrieval
                   \           /
                    \         /
                     ▼       ▼
                  Evidence Join
                       │
                       ▼
                      LLM

Свіжість змінює стратегію індексації

На етапі «Свіжість змінює індексацію» необхідно визначити вхідні дані, власника кроку та критерії завершення ще до змін у коді. Оператори повинні мати можливість перезапустити цей крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь код. Необхідно наводити конкретні уривки тексту, на яких ґрунтується відповідь. Без посилань оператори не зможуть відрізнити галюцинації від прогалин у індексації.

Document updated
       │
       ▼
new document version
       │
       ▼
parse changed document
       │
       ▼
generate new chunks
       │
       ▼
embed
       │
       ▼
write new index entries
       │
       ▼
mark previous version inactive
document_id = D17
version 41 → status=inactive
version 42 → status=active
status = active

Версіонування моделей також має значення

На етапі «Версіонування моделей також має значення» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. У разі, коли наступним кроком є код або виклик інструменту, краще використовувати структуровані результати з перевіркою схеми, ніж вільний текст.

embedding_model
embedding_version
embedding_dimensions
chunking_version
parser_version
Documents
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
   embedding model V3   embedding model V4
          │                   │
          ▼                   ▼
      Index V3            Index V4
                              │
                              ▼
                         shadow traffic
                              │
                              ▼
                           evaluate
                              │
                              ▼
                         switch alias

Кешування має існувати на кількох рівнях

На етапі «Кешування має існувати» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Наводьте уривки тексту, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. На етапі «Кешування має існувати» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільного середовища.

embedding
→ distributed retrieval
→ reranking
→ large LLM
User Query
    │
    ▼
Exact Response Cache
    │ miss
    ▼
Semantic Cache
    │ miss
    ▼
Retrieval Cache
    │ miss
    ▼
Search
hash(normalized_query)
      ↓
query embedding
Policy version 17
Policy version 18

Обробка несправностей має важливіше значення, ніж затримка у успішному сценарії

Під час роботи над етапом «Обробка несправностей має важливе значення» спочатку запишіть умови взаємодії: необхідні дані вхіду, сигнал про успіх та те, що відбувається при частковій несправності. Цей перелік допомагає зберігати чесність пізніших змін у коді. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Зміна формулювань запитів рідко вирішує проблеми слабкої системи пошуку.

embedding provider unavailable
one vector shard unavailable
search cluster degraded
reranker timeout
LLM rate limited
LLM provider outage
queue backlog growing
parser crashing on malformed PDF
OCR worker running out of memory
Vector search fails
        ↓
Can lexical search answer?
        ↓
yes
        ↓
return degraded retrieval mode
Primary LLM unavailable
        ↓
Fallback model
        ↓
Lower quality but service continues
Document parser fails 5 times
        ↓
Dead-letter queue
        ↓
record failure reason
        ↓
operator / automated remediation

Зворотний тиск є необхідним

Під час роботи над етапом «Backpressure Is Essential» спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Перед налаштуванням запитів вимірюйте рівень точності пошуку на фіксованому наборі запитань. Зміна запитів рідко допомагає вирішити проблеми з низькою якістю пошуку.

20M chunks/hour
25M chunks/hour
200M chunks/hour
embedding service overloaded
        ↓
timeouts
        ↓
retries
        ↓
more load
        ↓
more failures
        ↓
retry storm
incoming workload
      ↓
durable queue
      ↓
workers consume at safe rate
      ↓
backlog temporarily grows

Оперативність моніторингу має вимірювати якість пошуку

Під час роботи над етапом «Оперезорність має вимірюватися» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Часта зміна формулювань запитів рідко виправляє проблеми з пошуком інформації. Під час роботи над етапом «Оперезорність має вимірюватися» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Поруч із функціональними результатами записуйте час виконання та витрати на токени або запити. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних середовищ.

HTTP 200 rate
CPU
memory
disk
p95 latency
error rate
requests/second
request_id: rq_912
query rewrite:
"parental policy?" → "parental leave policy"
vector retrieval:
200 candidates
145 ms
lexical retrieval:
200 candidates
91 ms
fusion:
278 unique candidates
reranking:
278 → 20
212 ms
context:
13 chunks
8,420 tokens
LLM:
input 9,104 tokens
output 681 tokens
1.8 sec
citations:
3 documents
total:
2.4 sec

Оцінюйте канал отримання даних окремо від LLM

Етап оцінки каналу отримання даних працює найкраще, коли його розглядають як вимірювану характеристику. Збережіть один ідеальний запис, один випадок збою та примітку про скасування дій перед розширенням обсягу роботи. Тримайте конфігурацію поза кодом додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Встановіть ліміт токенів на одну інтеракцію та на сеанс. Інструменти типу агентів активно розширюють контекст; жорсткі обмеження запобігають тому, що демонстрації перетворюються на несподівані рахунки.

Збої у отриманні даних

Етап виявлення невдач працює найкраще, коли його розглядають як вимірювану характеристику. Збережіть один ідеальний запис, один випадок невдачі та примітку про скасування змін перед розширенням обсягу. Документуйте як успішний, так і відновлювальний шляхи роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Розділіть політику часткової обробки даних від політики їх пошуку. Зміна однієї не повинна змушувати до переписування іншої при зміні показників якості.

Correct document exists
       ↓
retriever misses it
       ↓
LLM never sees it
       ↓
wrong answer

Невдача у генерації

Етап виявлення помилок у генерації працює найкраще, коли його розглядають як вимірювану характеристику. Збережіть один ідеальний запис, один випадок помилки та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якась крок виконується неправильно, помилка має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткової обробки даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу, коли змінюються показники якості. Етап виявлення помилок у генерації працює найкраще, коли його розглядають як вимірювану характеристику. Збережіть один ідеальний запис, один випадок помилки та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних середовищ.

correct evidence
       ↓
context
       ↓
LLM
       ↓
incorrect interpretation

Латентність має мати бюджет

Щоб зменшити затримку, необхідно спочатку визначити етапи виконання, вхідні дані, відповідального за кожен крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Необхідно наводити конкретні уривки тексту, які лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

API + auth                    50 ms
query understanding          100 ms
retrieval                    300 ms
fusion                        20 ms
reranking                    250 ms
context construction          30 ms
LLM time-to-first-token     1,200 ms
network / safety margin      550 ms
-----------------------------------
target                     ~2,500 ms

Витрати також потребують однакового підходу

Для визначення витрат на ту саму стадію необхідно спочатку визначити вхідні дані, відповідальну особу за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Наводьте цитати з тих частин тексту, які фактично лежать в основі відповіді. Без цитат оператори не зможуть відрізнити галюцинації від проблем з індексуванням.

object storage
document parsing
OCR
embedding generation
vector storage
vector replicas
lexical search
metadata database
network transfer
reranking
LLM input tokens
LLM output tokens
observability
backups
cost / 1,000 indexed documents
cost / million chunks
cost / query
cost / successful answer
cost / tenant
Tenant A
5% of revenue
42% of LLM spend

Не зберігайте все у дорогому сховищі

На етапі «Не ставте все в одну купу» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. На етапі «Не ставте все в одну купу» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних.

>
expensive / fast
                      ▲
                      │
               vector indexes
               search indexes
               Redis caches
                      │
               metadata DB
                      │
               object storage
                      │
                      ▼
                 cheap / large

Гарячі та холодні дані можуть потребувати різного підходу

Під час роботи з етапом гарячих та холодних даних спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успішну обробку та наслідки часткової невдачі. Такий перелік допомагає зберігати чесність у подальших змінах коду. Зберігайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Перед налаштуванням запитів вимірюйте рівень відтворення інформації на фіксованому наборі запитань. Зміни запитів рідко допомагають покращити ефективність пошуку.

Last 30 days        → 70% of queries
Last 12 months      → 25%
Older archive       → 5%
Query
  │
  ├────→ Hot index
  │
  └────→ Archive index when necessary

Багатокористувацька архітектура все змінює

Під час роботи над етапом «Багатокористувацькість — це все змінює», спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Перед налаштуванням запитів вимірюйте рівень відтворення інформації за фіксованим набором запитань. Зміна запитів рідко вирішує проблеми слабкої системи пошуку інформації.

10,000 organizations
10,000 completely independent vector clusters
Small tenants
        ↓
shared shard groups
Medium tenants
        ↓
partitioned shared infrastructure
Very large tenants
        ↓
dedicated shard groups

LLM має знаходитися ближче до кінця архітектури

Під час роботи над етапом «The LLL Should Be» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Зберігайте у кеші стабільні інструкції системи та схеми інструментів. Повторна передача ідентичних даних є поширеною причиною зайвих витрат. Під час роботи над етапом «The LLL Should Be» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

User
 ↓
API Gateway
 ↓
Authentication
 ↓
Authorization
 ↓
Rate Limiter
 ↓
Query Understanding
 ↓
Shard Routing
 ↓
Hybrid Candidate Retrieval
 ↓
Rank Fusion
 ↓
Reranker
 ↓
Deduplication
 ↓
Context Builder
 ↓
LLM
 ↓
Citation Validation
 ↓
Response

Повна архітектура продакшну

Етап «Повна архітектура продукції» функціонує найкраще, якщо його розглядати як вимірювану структуру. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Розділіть політику часткової обробки даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

CLIENTS
                                 │
                                 ▼
                         ┌──────────────┐
                         │ API Gateway  │
                         └──────┬───────┘
                                │
                         ┌──────▼───────┐
                         │ Auth / ACL   │
                         │ Rate Limits  │
                         └──────┬───────┘
                                │
                                ▼
                     ┌────────────────────┐
                     │ Query Orchestrator │
                     └─────────┬──────────┘
                               │
                 ┌─────────────┼──────────────┐
                 │             │              │
                 ▼             ▼              ▼
              Cache      Query Rewrite    Metadata
                                           Filters
                 │             │              │
                 └─────────────┼──────────────┘
                               ▼
                        Shard Router
                               │
                  ┌────────────┴────────────┐
                  ▼                         ▼
           Lexical Search              Vector Search
                  │                         │
                  └───────────┬─────────────┘
                              ▼
                         Rank Fusion
                              │
                              ▼
                           Reranker
                              │
                              ▼
                         Deduplicate
                              │
                              ▼
                       Context Builder
                              │
                              ▼
                             LLM
                              │
                              ▼
                     Citation Validation
                              │
                              ▼
                          RESPONSE
================================================================
INGESTION SYSTEM
Documents
                            │
                            ▼
                       Object Store
                            │
                            ▼
                       Event Queue
                            │
               ┌────────────┼────────────┐
               ▼            ▼            ▼
            Parser        Parser       Parser
               │            │            │
               └────────────┼────────────┘
                            ▼
                      Normalization
                            │
                            ▼
                         Chunker
                            │
             ┌──────────────┴──────────────┐
             ▼                             ▼
        Embedding Queue               Text Index Queue
             │                             │
       ┌─────┼─────┐                       ▼
       ▼     ▼     ▼                 Lexical Index
      GPU   GPU   GPU
       │     │     │
       └─────┼─────┘
             ▼
       Vector Index
================================================================
SUPPORTING SYSTEMS
Metadata DB         → document state, versions, ownership
Object Storage      → original source documents
Vector Cluster      → semantic retrieval
Search Cluster      → lexical retrieval
Redis               → caches and rate limiting
Message Queue       → asynchronous ingestion
Observability       → traces, metrics, evaluation
Secrets / IAM       → service authorization
Evaluation System   → retrieval + answer-quality tests

Що ви не повинні робити

Підхід «Того, що не варто реалізовувати» найкраще функціонує, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний приклад роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії роботи разом. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Розділіть політику часткової обробки даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

One giant vector collection with no routing strategy
Synchronous PDF ingestion
Only vector retrieval, no lexical search
ACL filtering after retrieval
No document versioning
No embedding model version
No reranking
Sending top-100 chunks directly to the LLM
Using the LLM for every trivial classification task
No dead-letter queue
No retrieval-level evaluation
No tenant-level cost attribution
Treating the vector DB as permanent document storage
Re-embedding the entire corpus for every small content change
Benchmarking only average latency instead of tail latency

Основний принцип проектування

Етап Основного принципу проектування функціонує найкраще, коли його розглядають як вимірювану поверхню. Зафіксуйте один ідеальний зразок виконання, один випадок збою та примітку щодо скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Розділяйте політику часткового оброблення даних та політику їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу, коли змінюються показники якості. Етап Основного принципу проектування функціонує найкраще, коли його розглядають як вимірювану поверхню. Зафіксуйте один ідеальний зразок виконання, один випадок збою та примітку щодо скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

question
→ vector search
→ LLM
billions of possible evidence units
              ↓
        routing constraints
              ↓
      relevant partitions
              ↓
      cheap candidate search
              ↓
        hundreds of items
              ↓
      expensive reranking
              ↓
        tens of passages
              ↓
      context optimization
              ↓
             LLM
              ↓
      grounded answer
Cheap operation      → huge search space
Moderate operation   → smaller candidate set
Expensive operation  → tiny candidate set
Most expensive model → final context only

Остаточні висновки

На етапі остаточного підсумку необхідно визначити вхідні дані, відповідальну особу за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Наводьте уривки тексту, які фактично лягли в основу відповіді. Без посилань оператори не зможуть відрізнити галюцинацію від проблем із індексуванням.

Чек-лист для експлуатації

Під час роботи за чек-листом для експлуатації спочатку запишіть умови виконання: необхідні вхідні дані, сигнал про успіх та наслідки часткової невдачі. Цей чек-лист допомагає зберігати чесність подальших змін у коді.

Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Дайте назви кожним елементам, визначте критерії успіху та не погоджуйтесь на мовчазне часткове виконання завдань.

Вимірюйте рівень відтворення інформації за фіксованим набором запитань перед налаштуванням промптів. Часта зміна промптів рідко допомагає покращити слабкі механізми пошуку інформації.

Фіксуйте версії залежностей та записуйте дайджест зображень, які використовувалися під час демонстрації. Відтворюваність результатів краща за індивідуальні знання спеціалістів.

Записуйте час виконання та витрати на обробку токенів чи запитів разом із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним витратам під час переходу від демо-версії до спільних середовищ.

Вимірюйте рівень відтворення інформації за фіксованим набором запитань перед налаштуванням промптів. Часта зміна промптів рідко допомагає покращити слабкі механізми пошуку інформації.

Перш ніж запускати стек у продакшн, заморозьте версії, створіть «золотий» запис для критичного шляху та підтвердьте кроки відкату. У спільних середовищах необхідні обмеження швидкості, перевірки прав на використання та чіткий власник для зміни секретів. Віддавайте перевагу надійності перед креативними одноразовими демонстраціями.

Примітка до 1f03733b8d4b: не зберігайте ключі постачальника у репозиторії, встановіть ліміт токенів на сеанс та зберігайте записи поруч із фікстурами для оцінки, щоб подальша заміна моделей залишалася порівнянною.