Главная / Статьи / Practical notes: Создание локальной RAG-пайплайны производственного уровня — 100% бесплатно, без

Practical notes: Создание локальной RAG-пайплайны производственного уровня — 100% бесплатно, без

Пошаговое руководство по Practical notes: Создание локальной 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: Установка всех зависимостей

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

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-ключей

На этапе «Настройка API» шага 5 необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости заранее предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Указывайте конкретные фрагменты текста, на которых основан ответ. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации. На этапе «Настройка API» шага 5 необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

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 — Подробный обзор основной структуры обработки данных

При работе над этапом Core Pipeline части 2 сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успешности и не допускайте молчаливого частичного завершения работы. Измеряйте степень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.

Ячейка 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 работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

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 работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Документируйте одновременно успешный путь выполнения и путь восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки.

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 для ячейки 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)

Ячейка 7 — Запуск процесса вставки данных

На этапе ввода данных для ячейки 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))

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

Ячейка 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 — Пайплайн ask() и 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)

Механизм CDC Engine Cell 14B работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один пример успешного выполнения, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, тестируемые единицы вместо обширных скриптов. При сбое какого-либо шага причина должна указывать на конкретный элемент ответственности, а не на запутанную цепочку операций. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.

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)

Этап извлечения с учетом свежести данных 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

Чек-лист операций

Этап чек-листа операций работает наилучшим образом, когда его рассматривают как измеряемую характеристику. Соберите один идеальный пример ответа, один случай сбоя и запись о возможности отката перед расширением объема работ.

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

Разделяйте политику разбиения на части и политику получения данных. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

При наличии бюджета добавляйте тесты на работоспособность, которые проверяют критически важные этапы в процессе интеграционного тестирования с использованием фикстур, а не реальных платных API.

Документируйте как успешный, так и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

Разделяйте политику разбиения на части и политику получения данных. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

Перед внедрением данной стек-технологии необходимо заморозить версии, сгенерировать эталонный отчет для критической части процесса и уточнить шаги возврата к предыдущему состоянию. В совместных средах требуются ограничения на скорость работы, проверки принадлежности ресурсов и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, даже если она кажется менее привлекательной, чем красивые одноразовые демонстрации.

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