Головна / Статті / Практичні поради: створення локальної системи RAG промислового рівня — 100% безкоштовно, без

Практичні поради: створення локальної системи RAG промислового рівня — 100% безкоштовно, без

Покрокове керівництво з практичними порадами: створення локальної системи RAG промислового рівня — 100% безкоштовно, без контрактів, перевірок та готових блоків коду для команд, які використовують цю схему.

4700 слів

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

Чому це важливо

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

Повний технологічний стек

Під час виконання етапу «The Complete Tech Stack» спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Придумайте назви елементів, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань. Перед налаштуванням запитів вимірюйте рівень точності на фіксованому наборі запитань. Часта зміна формулювань запитів рідко вирішує проблеми слабкого пошуку інформації.

Попередні вимоги

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

Частина 1 — Налаштування середовища

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

Крок 1: Створити проект

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

mkdir local-rag
cd local-rag
uv init

Етап 2: Створення та активація віртуального середовища

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

uv venv
.venv\Scripts\activate

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

Крок 3: Встановити всі залежності

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

uv add google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2

Етап 4: Створення структури проекту

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

mkdir pdfs
mkdir pdfs\versions
type nul > local_rag.ipynb
type nul > .env
type nul > .gitignore
local-rag/
├── .venv/                    ← virtual environment (never commit)
├── pdfs/                     ← drop your PDFs here
│   └── versions/             ← test PDFs for CDC testing
├── chroma_db/                ← auto-created on first ingest
├── memory_checkpoints/       ← auto-created on first memory session
├── staleness_registry.json   ← auto-created
├── chunk_registry.json       ← auto-created
├── local_rag.ipynb           ← your notebook
├── .env                      ← API keys (never commit)
├── .gitignore
└── pyproject.toml

Етап 5: Налаштування API-ключів

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

GEMINI_API_KEY=your_gemini_key_here
HF_API_KEY=your_huggingface_token_here
.env
chroma_db/
memory_checkpoints/
staleness_registry.json
chunk_registry.json
__pycache__/
.venv/
*.pyc

Крок 6: Налаштування VS Code

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

Ctrl+Shift+P → Python: Select Interpreter → .venv\Scripts\python.exe

Частина 2 — Детальний огляд основної системи обробки даних

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

Клітка 1 — Залежності (усередині ноутбука)

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

# Run once inside the notebook if uv add was not used externally
# %pip install google-genai pypdf chromadb rich python-dotenv huggingface_hub fpdf2

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

Клітка 2 — Конфігурація

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

import os
from dotenv import load_dotenv

load_dotenv()
GEMINI_API_KEY     = os.environ.get("GEMINI_API_KEY", "")
EMBED_DIM          = 768
HF_API_KEY         = os.environ.get("HF_API_KEY", "")
GEMINI_EMBED_MODEL = "gemini-embedding-001"
HF_LLM_MODEL       = "openai/gpt-oss-20b:groq"
CHROMA_DB_PATH     = "./chroma_db"
COLLECTION_NAME    = "local_rag"
CHUNK_SIZE         = 800
CHUNK_OVERLAP      = 120
TOP_K              = 5
EMBED_BATCH_SIZE   = 50
BATCH_SLEEP_SEC    = 0.3
LLM_MAX_NEW_TOKENS = 1024
LLM_TEMPERATURE    = 0.1
assert GEMINI_API_KEY, "❌ GEMINI_API_KEY not set"
assert HF_API_KEY,     "❌ HF_API_KEY not set"

Клітка 3 — Вилучення тексту з PDF

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

from pypdf import PdfReader

def extract_text_from_pdf(pdf_path: str) -> tuple[str, int]:
    reader = PdfReader(pdf_path)
    pages = []
    for i, page in enumerate(reader.pages):
        text = page.extract_text()
        if text and text.strip():
            pages.append(f"[Page {i + 1}]\n{text.strip()}")
    return "\n\n".join(pages), len(reader.pages)

Cell 4 — Часткове оброблення даних за принципом «скользячого вікна»

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

def chunk_text(text: str) -> list[str]:
    chunks, start = [], 0
    while start < len(text):
        end   = start + CHUNK_SIZE
        chunk = text[start:end].strip()
        if len(chunk) >= 80:
            chunks.append(chunk)
        start += CHUNK_SIZE - CHUNK_OVERLAP
    return chunks

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

Cell 5 — Gemini Embeddings

На етапі Cell 5 Gemini Embeddings необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись відгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних. Розділіть процес створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без переписування машини станів розмови.

from google import genai
from google.genai import types

genai_client = genai.Client(api_key=GEMINI_API_KEY)
def embed_documents_batch(chunks: list[str]) -> list[list[float]]:
    all_embeddings = []
    for i, chunk in enumerate(chunks, 1):
        result = genai_client.models.embed_content(
            model=GEMINI_EMBED_MODEL,
            contents=chunk,
            config=types.EmbedContentConfig(
                task_type="RETRIEVAL_DOCUMENT",
                output_dimensionality=EMBED_DIM,
            ),
        )
        all_embeddings.append(result.embeddings[0].values)
    return all_embeddings
def embed_query(text: str) -> list[float]:
    result = genai_client.models.embed_content(
        model=GEMINI_EMBED_MODEL,
        contents=text,
        config=types.EmbedContentConfig(
            task_type="RETRIEVAL_QUERY",
            output_dimensionality=EMBED_DIM,
        ),
    )
    return result.embeddings[0].values

Cell 6 — Зберігання та отримання даних у ChromaDB

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

import uuid, chromadb

def get_collection():
    client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
    return client.get_or_create_collection(
        name=COLLECTION_NAME,
        metadata={"hnsw:space": "cosine"},
    )
def store_in_chroma(chunks, embeddings, doc_name):
    collection = get_collection()
    ids        = [str(uuid.uuid4()) for _ in chunks]
    metadatas  = [{"source": doc_name, "chunk_index": i}
                  for i in range(len(chunks))]
    collection.add(ids=ids, embeddings=embeddings,
                   documents=chunks, metadatas=metadatas)
    return len(chunks)
def retrieve_context(query: str) -> list[dict]:
    collection      = get_collection()
    query_embedding = embed_query(query)
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=TOP_K,
        include=["documents", "metadatas", "distances"],
    )
    chunks = []
    for doc, meta, dist in zip(results["documents"][0],
                               results["metadatas"][0],
                               results["distances"][0]):
        chunks.append({
            "text":        doc,
            "source":      meta.get("source", "unknown"),
            "chunk_index": meta.get("chunk_index", -1),
            "score":       round(1 - dist, 4),
        })
    return sorted(chunks, key=lambda x: x["score"], reverse=True)

Cell 7 — Виконання процесу інгестування

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

PDF_PATH = "./pdfs/attention.pdf"

raw_text, page_count = extract_text_from_pdf(PDF_PATH)
chunks               = chunk_text(raw_text)
embeddings           = embed_documents_batch(chunks)
stored               = store_in_chroma(chunks, embeddings,
                                        os.path.basename(PDF_PATH))

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

Cell 8 — LLM: gpt-oss-20b через HuggingFace

Під час роботи над етапом Cell 8 LLM gpt-oss-20b спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Якщо якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Зберігайте у кеші стабільні інструкції системи та схеми інструментів. Повторна передача ідентичних даних є поширеною причиною витрат ресурсів.

from huggingface_hub import InferenceClient

hf_client = InferenceClient(api_key=HF_API_KEY)
def build_messages(query: str, context_chunks: list[dict]) -> list[dict]:
    context_str  = "\n\n---\n\n".join([
        f"[Source: {c['source']} | Chunk #{c['chunk_index']} | "
        f"Relevance: {c['score']}]\n{c['text']}"
        for c in context_chunks
    ])
    return [
        {"role": "system",  "content": SYSTEM_MSG},
        {"role": "user",    "content":
            f"CONTEXT:\n{context_str}\n\nQUESTION:\n{query}"},
    ]
def generate_answer(messages: list[dict]) -> str:
    completion = hf_client.chat.completions.create(
        model=HF_LLM_MODEL,
        messages=messages,
        max_tokens=LLM_MAX_NEW_TOKENS,
        temperature=LLM_TEMPERATURE,
    )
    return completion.choices[0].message.content.strip()

Cell 9–11 — The ask() Pipeline and REPL

Під час роботи над етапом «Клітина 9–11: Сцена» спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Дайте назви елементам, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань. Вимірюйте рівень запам’ятовування на фіксованому наборі запитань перед налаштуванням підказок. Часта зміна підказок рідко виправляє проблеми з недостатньою ефективністю пошуку інформації.

query → embed_query() → ChromaDB cosine search → top-5 chunks
      → build_messages() → generate_answer() → printed answer
1. attention.pdf chunk #34  [██████████████████████░░░░░░░░]  0.7335

Частина 3 — Пам’ять розмови

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

Проблема

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

Рішення: ConversationMemory

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

class ConversationMemory:
    def __init__(self, session_id: str = None):
        self.session_id = session_id or datetime.now().strftime("%Y%m%d_%H%M%S")
        self.filepath   = os.path.join(MEMORY_DIR, f"{self.session_id}.json")
        self.history    = []
        # Auto-loads if resuming an existing session
        if os.path.exists(self.filepath):
            self._load()

    def add_turn(self, question: str, answer: str, chunks: list[dict]):
        # Append user + assistant turns, checkpoint immediately
        ...
        self._save()
    def get_messages_with_history(self, query, context_chunks, system_msg):
        # Injects last 6 Q&A pairs into the message list before the current turn
        ...
# New session
memory = ConversationMemory()

# Resume yesterday's session
memory = ConversationMemory("20260413_104959")
Turn 4 question: "How does that compare to what you said about the BLEU score?"
Turn 4 answer: "The context also reports a BLEU score of 28.4 for the
               Transformer (big) on WMT 2014 English-to-German. This matches
               exactly what I previously stated."

Частина 4 — Відстеження старості, CDC та надання ваги свіжості

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

Проблема у реальному світі

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

Відстеження застарілості (Клітка 14A)

Під час роботи над етапом Staleness Tracking Cell 14A спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Запишіть час виконання та витрати на токени або запити поруч із результатами функціонування. Відображення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Вимірюйте ефективність пошуку на фіксованому наборі запитань перед налаштуванням формулювань запитів. Часта зміна формулювань рідко допомагає покращити якість пошуку. Під час роботи над етапом Staleness Tracking Cell 14A спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте оптимальний та альтернативний шляхи вирішення проблем. Повторні спроби, людський контроль та обробка невдалих запитів є частиною продукту, а не етапом подальшої оптимізації.

def compute_file_hash(pdf_path: str) -> str:
    sha = hashlib.sha256()
    with open(pdf_path, "rb") as f:
        for block in iter(lambda: f.read(65536), b""):
            sha.update(block)
    return sha.hexdigest()

CDC Engine (Cell 14B)

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

def compute_chunk_hash(text: str) -> str:
    return hashlib.md5(text.encode("utf-8")).hexdigest()

def diff_chunks(old_registry: dict, new_chunks: list[str]) -> dict:
    new_hash_map = {compute_chunk_hash(c): c for c in new_chunks}
    old_hashes   = set(old_registry.keys())
    new_hashes   = set(new_hash_map.keys())
    return {
        "added":     {h: new_hash_map[h] for h in (new_hashes - old_hashes)},
        "removed":   {h: old_registry[h] for h in (old_hashes - new_hashes)},
        "unchanged": {h: old_registry[h] for h in (old_hashes & new_hashes)},
    }
v1 → v2 CDC result:
  ✅ Unchanged : 8   (kept — zero re-embedding cost)
  ➕ Added     : 6   (embedded + inserted)
  ➖ Removed   : 4   (deleted from ChromaDB)
  💰 API calls saved: 8/14 (57% reuse)

v2 → v3 CDC result:
  ✅ Unchanged : 10  (kept - zero re-embedding cost)
  ➕ Added     : 4   (embedded + inserted)
  ➖ Removed   : 2   (deleted from ChromaDB)
  💰 API calls saved: 10/14 (71% reuse)

Отримання даних з урахуванням актуальності (Cell 14C)

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

def recency_decay(ingested_at_str: str, half_life_days: float = 30) -> float:
    ingested  = datetime.fromisoformat(ingested_at_str)
    days_gone = (datetime.now() - ingested).total_seconds() / 86400
    λ         = math.log(2) / half_life_days
    return round(math.exp(-λ * days_gone), 4)
blended = alpha * cosine_score + (1 - alpha) * recency_score
# Default: 0.85 * cosine + 0.15 * recency

Частина 5 — Тестування з PDF-файлами багатьох версій

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

TEST 1: Full ingest of v1
TEST 2: Staleness check — same file, correctly skipped
TEST 3: Baseline queries against v1
TEST 4: Copy v2 over active file → CDC kicks in
TEST 5: Same queries now return v2 content, newer chunks visible in recency scores
TEST 6: Copy v3 over active file → second CDC cycle
TEST 7: Recency verification — v3 chunks score highest across the board
TEST 8: Full stack test — weighted retrieval + conversation memory combined

Результати та перевірені відповіді

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

Підсумок ефективності

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

Відомі обмеження

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

Що ви створили

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

PDF on disk
 └► SHA256 hash check (staleness)
      ├► Unchanged → skip
      └► Changed → CDC diff
           ├► Unchanged chunks → kept in ChromaDB (zero API cost)
           ├► Removed chunks → deleted from ChromaDB
           └► Added chunks → embed (Gemini) → store (ChromaDB)
                                                    ↓
User question
 └► embed_query() [RETRIEVAL_QUERY task type]
      └► ChromaDB cosine search (TOP_K × 3 candidates)
           └► recency_decay() per chunk
                └► blended score re-ranking
                     └► top-5 chunks as context
                          └► ConversationMemory.get_messages_with_history()
                               └► gpt-oss-20b via Groq/HuggingFace
                                    └► grounded answer + checkpoint to disk

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

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

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

Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

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

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

Розділіть політику часткового оброблення даних від політики їх отримання. Зміна однієї з них не повинна змушувати переписувати іншу при зміні показників якості.

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

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