Головна / Статті / Практичні зауваження: Безкоштовний локальний багатомодальний RAG: вибіркова обробка зображень

Практичні зауваження: Безкоштовний локальний багатомодальний RAG: вибіркова обробка зображень

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

3095 слів

У цьому посібнику описано процес створення системи від сировини до готового продукту для проекту Zero-Cost Local Multimodal RAG: Selective Vision Processing with ChromaDB. Основна увага приділяється крокам виконання, чітким перевіркам та коду, який можна просто додати до репозиторію без необхідності здогадуватися щодо його призначення. На етапі огляду необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись зрозуміти прихований стан системи. Конфігурацію слід тримати окремо від коду програми. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код проекту.

Основні виклики (на Apple Silicon та інших платформах)

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

Вибірковий гібридний конвеєр (оптимізований для Apple Silicon)

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

Архітектура системи

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

                       ┌───────────────────────────────────────┐
                       │   Local PDF Document Store (M1 Mac)   │
                       └───────────────────┬───────────────────┘
                                           │
                           ┌───────────────┴───────────────┐
                           │    Fast Layout-Aware Parser   │
                           └───────┬───────────────┬───────┘
                                   │               │
              [Text & Tables]      │               │     [Embedded Images]
                                   ▼               ▼
                         ┌───────────────────┐   ┌───────────────────┐
                         │  Markdown Stream  │   │ Cropped Images    │
                         └─────────┬─────────┘   └─────────┬─────────┘
                                   │                       │
                                   │                       ▼
                                   │             ┌───────────────────┐
                                   │             │ Base64 Scaling &  │
                                   │             │ Native BBox OCR   │
                                   │             └─────────┬─────────┘
                                   │                       │
                                   │                       ▼
                                   │             ┌───────────────────┐
                                   │             │  Local Metal VLM  │
                                   │             │  (Ollama via UMA) │
                                   │             └─────────┬─────────┘
                                   │                       │
                                   │               [Text Summaries]
                                   │                       │
                                   ▼                       ▼
                         ┌───────────────────────────────────┐
                         │ Unified Chunking & Context Engine │
                         └─────────────────┬─────────────────┘
                                           │
                                           ▼
                         ┌───────────────────────────────────┐
                         │ Disk-Persisted Vector DB & Parent │
                         │ Context Stores (ChromaDB SQLite)  │
                         └───────────────────────────────────┘

Зміцнення конвеєра обробки даних

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

Вибір бази даних векторів

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

Пошук батьківських та дочірніх елементів (секретний інгредієнт)

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

Забезпечення точності: структуровані результати та перевірка

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

Повна налаштування проекту та код (готові до копіювання-вставки)

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

1. Структура проекту

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

mkdir ~/m1_multimodal_rag && cd ~/m1_multimodal_rag
mkdir data chroma_db images_cache
touch main.py requirements.txt

2. requirements.txt

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

pymupdf
chromadb
ollama
pillow

3. Повний файл main.py (Об’єднане отримання та запити)

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

#!/usr/bin/env python3
"""
Multimodal RAG for Apple Silicon (M1/M2/M3)
Usage:
    python main.py ingest --pdf data/report.pdf
    python main.py ingest --pdf data/new_report.pdf --clear
    python main.py query --question "What was the Q3 revenue?"
"""

import argparse
import base64
import os
import sys
import uuid
from io import BytesIO
from pathlib import Path

import chromadb
import pymupdf as fitz
import ollama
from PIL import Image

# ---------- CONFIG ----------
TEXT_MODEL = "llama3.2:3b"          # For final RAG answers (8GB friendly)
VISION_MODEL = "qwen2.5vl:3b"       # For charts (8GB friendly)
CHROMA_PATH = "./chroma_db"
IMAGE_CACHE = "./images_cache"

# Initialize persistent Chroma client (SQLite, not RAM)
chroma_client = chromadb.PersistentClient(path=CHROMA_PATH)
child_collection = chroma_client.get_or_create_collection(name="child_chunks")
parent_collection = chroma_client.get_or_create_collection(name="parent_chunks")

Path(IMAGE_CACHE).mkdir(exist_ok=True)


# ---------- HELPER: Encode Image for Ollama ----------
def encode_image_for_ollama(image_bytes: bytes, max_size=800) -> str:
    """Convert PDF image bytes to Base64 data URI with size limiting."""
    img = Image.open(BytesIO(image_bytes))

    # Convert RGBA/P to RGB to avoid JPEG alpha errors
    if img.mode in ('RGBA', 'LA', 'P'):
        img = img.convert('RGB')

    # Downscale massive images to save VRAM on M1
    img.thumbnail((max_size, max_size))

    buffered = BytesIO()
    img.save(buffered, format="JPEG", quality=85)
    img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
    return img_base64


# ---------- PHASE 1: INGESTION ----------
def ingest_pdf(pdf_path: str):
    """Parse PDF, extract text, crop images, run VLM, and store in Chroma."""
    print(f" Processing: {pdf_path}")
    doc = fitz.open(pdf_path)

    for page_num in range(len(doc)):
        page = doc[page_num]
        print(f"  Page {page_num + 1}/{len(doc)}")

        # 1. Extract main text
        page_text = page.get_text("text").strip()
        if not page_text:
            page_text = "[No extractable text on this page]"

        # 2. Find and process images
        image_list = page.get_images(full=True)
        visual_summaries = []

        for img_idx, img in enumerate(image_list):
            xref = img[0]
            try:
                base_image = doc.extract_image(xref)
                image_bytes = base_image["image"]

                # Encode for Ollama
                encoded_img = encode_image_for_ollama(image_bytes)

                # Prompt designed for financial charts with structured output
                prompt = """
                Extract key insights from this chart and return valid JSON.
                Use this schema: {"chart_type": "", "x_axis": [], "y_axis": [], "key_trend": "", "data_points": []}
                If it's not a chart, describe it briefly in text.
                """

                response = ollama.chat(
                    model=VISION_MODEL,
                    messages=[{
                        "role": "user",
                        "content": prompt,
                        "images": [encoded_img]
                    }]
                )
                summary = response["message"]["content"]
                visual_summaries.append(f"[Chart on page {page_num+1}]: {summary}")

            except Exception as e:
                print(f"    Skipped image {img_idx} (Error: {e})")
                continue

        # 3. Merge text and summaries
        full_page_content = page_text + "\n" + "\n".join(visual_summaries)
        if not full_page_content.strip():
            continue  # Skip completely empty pages

        # 4. Split into Parent (big) and Child (small) for retrieval
        parent_text = full_page_content  # Full page is the "Parent"

        # Split into ~200 token chunks for children (roughly 800 chars)
        child_chunks = []
        chunk_size = 800
        for i in range(0, len(parent_text), chunk_size):
            child_chunks.append(parent_text[i:i+chunk_size])

        if not child_chunks:
            child_chunks = [parent_text]  # Fallback

        # 5. Store in Chroma (Parent-Child)
        parent_id = str(uuid.uuid4())
        metadata = {
            "source": os.path.basename(pdf_path),
            "page": page_num + 1,
            "type": "hybrid"
        }

        # Store Parent (full context) - Persisted to disk, not RAM
        parent_collection.add(
            ids=[parent_id],
            documents=[parent_text],
            metadatas=[metadata]
        )

        # Store Children (granular search)
        child_ids = []
        child_metadatas = []
        for idx, chunk in enumerate(child_chunks):
            child_id = f"{parent_id}_child_{idx}"
            child_ids.append(child_id)
            child_metadatas.append({
                **metadata,
                "parent_ref": parent_id
            })

        child_collection.add(
            ids=child_ids,
            documents=child_chunks,
            metadatas=child_metadatas
        )

    doc.close()
    print(" Ingestion complete!")


# ---------- PHASE 2: QUERY ----------
def query_rag(question: str):
    """Retrieve relevant context using Child chunks, fetch Parent, ask LLM."""
    print(f"❓ Query: {question}")

    # 1. Retrieve top matching child chunks
    results = child_collection.query(
        query_texts=[question],
        n_results=3
    )

    if not results["ids"] or not results["ids"][0]:
        print(" No relevant documents found in the database.")
        return

    # 2. Fetch the full Parent contexts
    parent_ids = list(set([m["parent_ref"] for m in results["metadatas"][0]]))
    parent_results = parent_collection.get(ids=parent_ids)
    full_context = "\n\n---\n\n".join(parent_results["documents"])

    # 3. Build prompt for the text-only LLM
    prompt = f"""
    You are a financial research assistant. Answer the question based strictly on the context below.
    If the context contains chart summaries or tables, use those numbers specifically.
    If you cannot answer from the context, say "I don't have that information."

    Context:
    {full_context}

    Question: {question}
    Answer:
    """

    # 4. Generate answer locally
    response = ollama.chat(
        model=TEXT_MODEL,
        messages=[{"role": "user", "content": prompt}]
    )

    print("\n Answer:")
    print(response["message"]["content"])
    print("\n Sources:", ", ".join(parent_ids))


# ---------- DATABASE CLEAR FUNCTION ----------
def clear_database():
    """Delete all collections to reset the database."""
    try:
        chroma_client.delete_collection("child_chunks")
        chroma_client.delete_collection("parent_chunks")
        print(" Database cleared successfully!")
    except ValueError:
        print(" Database was already empty. Nothing to clear.")
    except Exception as e:
        print(f" Could not clear database: {e}")


# ---------- CLI ENTRY POINT ----------
def main():
    parser = argparse.ArgumentParser(description="M1 Multimodal RAG Pipeline")
    subparsers = parser.add_subparsers(dest="command", required=True)

    # Ingest command with --clear flag
    ingest_parser = subparsers.add_parser("ingest", help="Ingest a PDF")
    ingest_parser.add_argument("--pdf", required=True, help="Path to PDF file")
    ingest_parser.add_argument("--clear", action="store_true", help="Clear the database before ingesting")

    # Query command
    query_parser = subparsers.add_parser("query", help="Ask a question")
    query_parser.add_argument("--question", required=True, help="Your question")

    args = parser.parse_args()

    if args.command == "ingest":
        if not os.path.exists(args.pdf):
            print(f" File not found: {args.pdf}")
            sys.exit(1)

        # Clear the database if the flag is set
        if args.clear:
            clear_database()
            # Re-initialize collections after clearing
            global child_collection, parent_collection
            child_collection = chroma_client.get_or_create_collection(name="child_chunks")
            parent_collection = chroma_client.get_or_create_collection(name="parent_chunks")

        ingest_pdf(args.pdf)

    elif args.command == "query":
        query_rag(args.question)


if __name__ == "__main__":
    main()

Команди крокового виконання

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

Крок 1: Встановити Ollama та завантажити моделі

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

# Install Ollama via Homebrew
brew install ollama

# Verify version (requires >= 0.7.0 for qwen2.5vl models)
ollama --version

# Start the Ollama service (keep this running in a separate terminal tab)
ollama serve

# Pull the recommended models (For 8GB M1 Mac)
ollama pull qwen2.5vl:3b
ollama pull llama3.2:3b

# (For 16GB+ M1/M2/M3, optionally pull larger models)
# ollama pull llama3.2-vision:11b
# ollama pull qwen2.5:7b

# displays a list of all AI models stored locally on your machine
ollama list

Етап 2: Налаштування середовища Python

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

# Ensure you are in the project root
cd ~/m1_multimodal_rag

# Create a virtual environment
python3 -m venv venv

# Activate the environment
source venv/bin/activate

Крок 3: Встановлення залежностей Python

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

# Upgrade pip
pip install --upgrade pip

# Install requirements
pip install -r requirements.txt

# Verify installation
python -c "import chromadb, fitz, ollama, PIL; print(' All dependencies ready!')"

Крок 4: Завантаження зразка PDF

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

# Download a sample financial report (EY IFRS Illustrative)
curl -L -o data/sample_financials.pdf \
  "https://drive.google.com/uc?export=download&id=1OOE1vPBwPP31cB0_MNgooTrhw6KpY6n_"

Крок 5: Завантаження PDF

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

python main.py ingest --pdf data/sample_financials.pdf
# Ingest a new PDF and clear the database first
python main.py ingest --pdf data/<your financial data file>.pdf --clear

Крок 6: Задати запитання

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

python main.py query --question "What was the total revenue shown in the financial statements?"
git clone https://github.com/froilan-sia/m1_multimodal_rag.git
cd m1_multimodal_rag
./setup.sh

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

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

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

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

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

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

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

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

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