Strona główna / Artykuły / Uwagi praktyczne: Zero-Cost Local Multimodal RAG: Selektywne przetwarzanie obrazu

Uwagi praktyczne: Zero-Cost Local Multimodal RAG: Selektywne przetwarzanie obrazu

Krok po kroku instrukcja obsługi Notatek praktycznych: Zero-Cost Local Multimodal RAG: Selektywne przetwarzanie obrazu: umowy, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.

3095 słów

To przewodnictwo pokazuje, jak odtworzyć proces od surowców do działającego systemu dla rozwiązania Zero-Cost Local Multimodal RAG: Selective Vision Processing with ChromaDB. Skupia się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu wstawić do repozytorium, nie musząc zgadywać intencji. Na etapie przeglądu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny tajemnic oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury.

Główne wyzwania (na Apple Silicon i dalej)

Gdy pracujesz nad etapem „The Core Challenges”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później w celu udoskonalenia. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania.

The Selective Hybrid Pipeline (Optymalizowane dla Apple Silicon)

Gdy przechodzisz przez etap The Selective Hybrid Pipeline, najpierw zapisz specyfikację: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

Architektura systemu

Gdy przechodzisz przez etap architektury systemu, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Zmierz stopień przywoływalności na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

                       ┌───────────────────────────────────────┐
                       │   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)  │
                         └───────────────────────────────────┘

Zmocnienie łańcucha przetwarzania

Podczas prace nad etapem wzmocnienia pipeline’u najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas wykonywania oraz koszt tokena lub zapytania. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z wersji demonstracyjnej do środowisk współdzielonych. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko poprawiają słabą skuteczność wyszukiwania.

Wybór bazy danych wektorowych

Gdy przechodzisz przez etap wyboru bazy danych wektorowych, najpierw zapisz warunki umowy: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny haseł oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zmierz stopę odzyskiwania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

Wyszukiwanie rodzic-dziecko (sekret skuteczności)

Gdy pracujesz nad etapem „The Secret” w ramach procesu pobierania danych rodzic-dziecko, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg operacji, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dopinane później. Zmierz stopień przywoływania informacji na podstawie ustalonego zestawu pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

Zarządzanie dokładnością: Ustrukturyzowane wyniki i weryfikacja

Gdy przechodzisz przez etap Implementacji Strukturalnych Wyjść Odpowiednich pod Kątem Dokładności, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Zmierz stopień odzyskiwania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania.

Pełne ustawienia projektu i kod (gotowe do skopiowania i wklejenia)

Gdy przechodzisz przez etap „Kompletny kod konfiguracji projektu”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć możliwość cichego, częściowego ukończenia zadania. Zmierz stopień przywoływania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko poprawiają słabą efektywność wyszukiwania. Gdy przechodzisz przez etap „Kompletny kod konfiguracji projektu”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

1. Struktura projektu

Faza 1 „Struktura projektu” działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przepis realizacji, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres projektu. Zdokumentuj zarówno ścieżkę pomyślnego przebiegu, jak i ścieżkę przywracania do stanu poprzedniego. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później w celu udoskonalenia. Oddziel zasadę dzielenia na fragmenty od zasady wyszukiwania. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

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

2. requirements.txt

Faza 2 wymagań w formacie txt działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

pymupdf
chromadb
ollama
pillow

3. Kompletny plik main.py (Zintegrowane pobieranie + zapytania)

Faza główna „The 3 The Complete” funkcjonuje najlepiej, gdy traktowana jest jako powierzchnia poddawalna pomiarom. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań. Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości. Faza główna „The 3 The Complete” funkcjonuje najlepiej, gdy traktowana jest jako powierzchnia poddawalna pomiarom. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności czytania całej struktury.

#!/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()

Komendy wykonywania krok po kroku

W etapie polegającym na krokach wykonywania poleceń należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownego wykonania, kontrole ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

Krok 1: Zainstaluj Ollama i pobierz modele

Dla etapu Instalacja Ollamy w kroku 1 należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, łatwe do przetestowania jednostki nad rozbudowanymi skryptami. Gdy krok się nie powiedzie, przyczyna błędu powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Należy oddzielić budowę klienta od pętli przetwarzania wiadomości, aby można było wymieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.

# 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

Krok 2: Ustawienie środowiska Pythona

W fazie „Kroku 2: Konfiguracja” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadkowe, częściowe ukończenie zadania. Oddziel budowę klienta od pętli przekazywania wiadomości, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.

# 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

Krok 3: Zainstaluj zależności Pythona

W etapie instalacji Pythona w kroku 3 należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Należy oddzielić budowanie klienta od pętli komunikacji, aby można było zmieniać dostawców bez konieczności przepisywania automatu stanu rozmowy.

# 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!')"

Krok 4: Pobranie przykładowego pliku PDF

W kroku 4, zanim przystąpi się do pobierania etapu, należy określić dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury. Należy podawać konkretne fragmenty tekstu, które stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od braku danych w indeksie.

# 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_"

Krok 5: Wgranie pliku PDF

W etapie 5, „Ingest the”, przed zmianą kodu należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten etap oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić ten etap na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

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

Krok 6: Zadaj pytanie

W etapie 6 „Zadaj pytanie” należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten etap oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten etap na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy etap zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces. Należy podawać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

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

Lista kontrolna operacyjna

Etap listy kontrolnej operacyjnej funkcjonuje najlepiej, gdy jest traktowany jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy.

Zapisuj czas wykonywania oraz koszt tokena lub zapytania obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

Dodaj test sprawdzający, który symuluje kluczową ścieżkę w procesie CI przy użyciu narzędzi testowych, a nie rzeczywistych, płatnych API, o ile na to pozwalają budżety.

Zachowaj konfigurację poza kodem aplikacji. Pliki środowiskowe, składysek z danymi poufnymi oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

Zanim wdrożysz tę architekturę, zamroź wersje, utwórz dokładny zapis dla kluczowych etapów realizacji oraz potwierdź kroki odwracania zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji przynależności użytkowników oraz wyraźnego właściciela odpowiedzialnego za rotację haseł. Wolimy nudną niezawodność od pomysłowych, jednorazowych demonstracji.

Uwaga dotycząca numeru 313930800633: trzymaj klucze dostawcy poza repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy obok narzędzi do testowania, aby późniejsze zmiany modeli pozostawały porównywalne.