Головна / Статті

Проєкт RAG — це не один величезний файл Python: структура папок, яка підлаштовується під потреби

Проєкт RAG не повинен бути одним величезним файлом Python. Структура папок має включати етапи отримання даних, їх розділення на частини, створення ембеддингів, зберігання векторів, пошук, формування запитів, API та тести.

Видавець: REACTAPP.TOP9 хв читання2182 слів

Джерела: **Your RAG Project Should Not Be One Giant Python File**

Демонстрація RAG займає півдня: завантажується PDF, розділяється на частини, вбудовуються ці частини, вони поміщаються у векторну базу даних, ставиться запит, читається відповідь. Цього цілком достатньо для експерименту на вихідних. Як основа для реальної роботи вона швидко виявляється недостатньою, і причиною рідко є сама модель чи навіть векторний сховище. Причина — структура.

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

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

Один когнітивний модель: конвеєр проти додатку

Уявіть собі так: конвеєр готує знання, а додаток їх використовує. Тримайте їх окремо.

Частина конвеєра відповідає за завантаження документів, очищення тексту, розділення контенту, створення ембеддингів та їх зберігання. Частина додатку — за отримання запитів, пошук потрібних даних, формування запитів до моделі, повернення відповідей через API та облік поведінки системи у режимі роботи.

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

Початкова структура

Ось структура, яка підходить як перший чернеток. Це не єдина можлива структура, але кожен папка має чітку причину для існування.

rag-project/
|-- README.md
|-- requirements.txt
|-- .env
|-- .gitignore
|-- config.yaml
|-- main.py
|-- src/
|   |-- ingestion/
|   |   |-- __init__.py
|   |   `-- loader.py
|   |-- chunking/
|   |   |-- __init__.py
|   |   `-- chunker.py
|   |-- embeddings/
|   |   |-- __init__.py
|   |   `-- embedder.py
|   |-- vectordb/
|   |   |-- __init__.py
|   |   `-- vector_store.py
|   |-- retrieval/
|   |   |-- __init__.py
|   |   `-- retriever.py
|   |-- prompts/
|   |   |-- __init__.py
|   |   `-- prompt_templates.py
|   |-- llm/
|   |   |-- __init__.py
|   |   `-- llm_client.py
|   |-- api/
|   |   |-- __init__.py
|   |   `-- routes.py
|   `-- utils/
|       |-- __init__.py
|       `-- helpers.py
|-- tests/
|   `-- test_app.py
`-- logs/
    `-- app.log

README.md

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

requirements.txt

У цьому файлі перелічені пакети Python, необхідні для проекту. Простий сервіс типу RAG може містити щось на кшталт наступного.

fastapi
uvicorn
python-dotenv
pydantic
langchain
chromadb
sentence-transformers
openai
pypdf

Ваш список залежить від використовуваних інструментів. Правило полягає у тому, щоб не залишати залежності прихованими на власному комп’ютері. Кожен, хто клонує проект, повинен мати можливість встановити все за допомогою однієї команди.

pip install -r requirements.txt

Це робить проект відтворюваним та полегшує його розгортання та перевірку.

.env та .gitignore

У файлі .env зберігаються конфіденційні дані, такі як ключі API. Типовий приклад виглядає ось так.

OPENAI_API_KEY=your_key_here
VECTOR_DB_URL=your_vector_db_url

Воно ні в якому разі не повинно потрапити до GitHub, тому ваш файл .gitignore має виключити його разом із журналами та байткодом.

.env
logs/
__pycache__/
*.pyc

Хардкодування ключів у файлах Python може працювати локально, але є небезпечним. Зберігайте конфіденційну інформацію поза кодом.

config.yaml

Неконфіденційні налаштування мають знаходитися у config.yaml, наприклад:

chunking:
  chunk_size: 800
  chunk_overlap: 120

retrieval:
  top_k: 5

models:
  embedding_model: text-embedding-3-small
  llm_model: gpt-4.1-mini

vector_db:
  provider: chromadb
  collection_name: company_docs

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

Папки з кодом

ingestion/

Ця папка зчитує дані з PDF-файлів, веб-сайтів, CSV-файлів, API, баз даних, формату Markdown, експортів з Notion, папок Drive чи будь-яких інших джерел. Її завдання — не відповідати на запитання, а надавати сирі дані у чистому та передбачуваному форматі. Файл loader.py може містити такі функції, як load_pdf(path), load_website(url), load_csv(path) та load_database_table(connection, table_name), кожна з яких повертає документи у стандартному форматі, зазвичай текст разом із метаданими.

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

Розділення на частини/

Розділення тексту на частини здається простим, але має величезний вплив на якість. Занадто великі частини повертають широкі сегменти, наповнені нерелевантним текстом, тоді як занадто маленькі видаляють контекст, необхідний моделі. Метою є збереження сенсу, тому замість сліпого розділення кожних 500 символів варто розглянути можливість розділення за заголовками, абзацами, розділами, сторінками, блоками коду, таблицями чи семантичними межами. Файл chunker.py перетворює повні документи на частини, готові до використання в моделях.

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

embeddings/

Ембеддинги — це числові представлення тексту, які дозволяють системі порівнювати значення: запит ембеддується та порівнюється з ембеддованими фрагментами. Файл embedder.py має централізувати завантаження моделі, ембеддинг окремих вхідних даних та пакетів, керувати обмеженнями швидкості, повторювати спроби у разі помилок та відстежувати, яка версія моделі створила вектори.

Ця ізоляція важлива, тому що моделі ембеддингів змінюються. Спочатку можна використовувати локальний Sentence-Transformer, потім — API, який працює онлайн, а згодом змінити постачальника через проблеми з вартістю чи якістю. Завдяки єдиному місцю для цієї логіки кожна зміна залишається керованою.

vectordb/

Ця папка містить зберігання ембеддингів та метаданих. Типовими варіантами є FAISS для локальних експериментів, ChromaDB для простих локальних або невеликих продакшн-систем, Pinecone для керованого пошуку в хмарі, а також Weaviate, Qdrant чи Milvus для більших або більш спеціалізованих потреб.

Файл vector_store.py повинен приховувати особливості бази даних від решти проекту. Код для отримання даних не повинен знати деталей про Pinecone чи ChromaDB. Він має використовувати звичайні методи на рівні проекту, такі як add_documents(chunks), similarity_search(query, top_k), delete_collection(name) та get_document_by_id(id). Якщо ви коли-небудь заміните базу даних, потрібно змінити лише цей шар, а ніщо інше.

retrieval/

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

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

prompts/

Шаблони запитів не повинні бути розкидані по всьому кодовому базі. Файл prompt_templates.py може містити такі шаблони.

You are a helpful assistant answering questions using the provided context.

Use only the context below. If the answer is not in the context, say you do not know.

Context:
{context}

Question:
{question}

Answer:

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

llm/

Усі виклики мовних моделей мають знаходитися в одному місці, незалежно від того, чи це OpenAI, Claude, Gemini, локальна модель Ollama чи щось інше. Решта додатку не повинна про це знати. Файл llm_client.py може керувати ініціалізацією, викликами API, повторними спробами, таймаутами, налаштуваннями температури, парсингом відповідей, обліком використання токенів та помилками, специфічними для постачальника. Моделі швидко змінюються: сьогодні GPT, завтра Claude, наступного місяця — менша модель для дешевих запитів та потужніша для складних. Централізовані виклики полегшують зміну моделей.

api/

Цей шар дозволяє іншим людям та додаткам використовувати систему, зазвичай через FastAPI або Flask. Він має отримувати та перевіряти запити, викликати процес отримання та генерації даних, повертати структуровані відповіді та чітко обробляти помилки. У ньому не повинна бути сама логіка. Маршрут має бути простим: приймати запит, викликати відповідний сервіс та повертати результат. Якщо один кінцевий пункт містить функції обробки даних, їх розділення на частини, вбудовування, отримання інформації, формування запитів та виклики моделей, проект стає некерованим. API має бути вхідними дверима, а не всім будинком.

utils/

Маленькі допоміжні функції, які використовуються в усьому проекті, розміщуються тут: завантаження конфігурації, форматування журналів, очищення тексту, перевірка шляхів, вимірювання часу та обробка поширених помилок. Будьте дисципліновані, адже ця папка легко може перетворитися на смітник. Функція, яка безумовно належить до процесу отримання даних, має потрапити до папки ingestion, а та, що стосується їх пошуку — до папки retrieval. Використовуйте utils лише для того, що справді є спільним для всього проекту.

tests/

Тести відокремлюють серйозні проекти від крихких демонстрацій, а системи RAG складаються з багатьох невеликих компонентів, які можна тестувати по одному. Ви можете перевірити, чи правильно завантажуються PDF-файли, чи правильні розміри частин даних, чи зберігається метадана, чи створюються ембеддинги, чи повертаються під час пошуку релевантні документи, чи містять запити як контекст, так і запитання, та чи повертає API очікуваний формат даних.

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

logs/

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

Зберігання короткого файлу main.py

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

Що це дає вам

  • Відлагодження: ізолювати проблему. Якщо результат неправильний, перевірте процес отримання даних, потім їх розділення на частини та формування ембеддингів. Якщо API не працює, перевірте шар маршрутизації.
  • Тестування: перевіряти кожен компонент окремо, щоб підтвердити, що для перевірки завантажувача PDF не потрібно запускати всю програму.
  • Масштабування: покращувати один шар без переписування решти, чи то шляхом додавання пакетного отримання даних, фонових завдань, кешування, переурочування рейтингів чи нової векторної бази даних.
  • Співпраця: одна людина покращує процес отримання даних, тоді як інша працює над запитами чи маршрутами.
  • Розгортання: конфігурація, секрети, залежності та точки входу чітко визначені.
  • Передусім це робить проект зрозумілим, а проект, який легко зрозуміти, легше довіряти.

    Правило для перших проектів

    Використовуйте по одній папці на кожну функцію та не прагніть до ідеальної архітектури. Просто уникайте одного величезного файлу. Почніть із отримання даних, їх розділення на частини, створення ембеддингів, використання векторної бази даних, пошуку інформації, формулювання запитів, викликів ШІ, API, тестів та журналів. Цього достатньо для більшості проектів для початківців та проектів у портфоліо. Додаткові елементи, такі як переранжування даних, оцінка, кешування, автентифікація, фонові завдання, версіонування документів та панелі керування, можна додавати за потребою, проте основна ідея залишається незмінною: тримайте потік обробки даних окремо від самого додатку. Корисною звичкою є запис у файлі README, яка папка буде змінюватися для кожного типу змін, адже якщо відповідь стає неясною, це означає, що структура, ймовірно, почала ставати нечіткою.

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

    Пов’язана література

  • Spring AI в корпоративному середовищі: агент підтримки з інструментами, RAG та механізмом перегляду — Створіть асистента для сортування інцидентів за допомогою Spring AI: запити як контракти, структурований вихід, виклик інструментів, MCP, Qdrant RAG, можливість спостереження та обмежені цикли роботи агента.