Strona główna / Artykuły / Notatki praktyczne: Jak przekształciłem moją galerię zdjęć w autonomicznego agenta AI

Notatki praktyczne: Jak przekształciłem moją galerię zdjęć w autonomicznego agenta AI

Krok po kroku instrukcja dotycząca Notatek praktycznych: Jak przekształciłem moją galerię zdjęć w autonomicznego agenta AI: umowy, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.

7088 słów

To przewodnictwo pokazuje, jak przejść od surowców do gotowego systemu w ramach: Jak przekształciłem moją galerię zdjęć w autonomicznego agenta AI — Kompletny przewodnik. Skupiamy się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu wdrożyć do repozytorium, bez konieczności zgadywania intencji. Na etapie przeglądu 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. Lepiej używać małych, testowalnych jednostek niż rozbudowanych skryptów. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.

Wprowadzenie

Gdy przechodzisz przez etap wprowadzenia, 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. Ustal punkty kontrolne po kosztownych krokach. System powrotu nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy element.

Dlaczego tradycyjne wyszukiwanie zdjęć nie działa

Gdy przechodzisz przez etap „Dlaczego tradycyjne wyszukiwanie zdjęć?”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co się dzieje w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się od środowiska demonstracyjnego do współdzielonych środowisk. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

Podejście oparte na albumie zawodzi w praktyce

Gdy pracujesz nad etapem „The album approach collapses”, 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. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, aby operatorzy mogli je sprawdzić bez konieczności czytania całej struktury. Ustaw punkty kontrolne po kosztownych krokach. System powinien unikać ponownego naliczania opłat za tę samą próbę wywołania LLM, gdy operator ponawia działanie późniejszego węzła. Gdy pracujesz nad etapem „The album approach collapses”, 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 skomplikowaną sekwencję operacji.

Szukanie według słów kluczowych daje tylko ograniczone rezultaty

Szukanie według słów kluczowych działa najlepiej, gdy traktuje się je jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przykład działania, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Traktuj tę fazę jako umowę pomiędzy wprowadzanymi danymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań. Utrzymuj stan grafu w prostej formie i określonej strukturze typów. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji pracy po zakłóceniach.

Szukanie w galerii chmurowej: dobre, ale z kosztem

Wyszukiwanie w galerii Cloud gallery działa najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden przykład udanego działania, jeden przypadek niepowodzenia oraz notatkę o cofnięciu zmian, zanim rozszerzysz zakres badania. Zapisuj czasy wykonywania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane w danym polu, co powoduje przerwanie kontynuacji pracy po zakłóceniach.

Prawdziwa luka: brak zrozumienia semantycznego

Prawdziwy problem polega na tym, że żaden etap nie funkcjonuje najlepiej, gdy jest traktowany jako coś mierzalnego. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, aby operatorzy mogli je sprawdzić bez konieczności przeglądania całej struktury. Utrzymuj stan struktury w prostym i sprecyzowanym formacie. Wtórne elementy ukrywają informację o tym, który węzeł zapisał dane w danym polu, co utrudnia kontynuację pracy po przerwach. Prawdziwy problem polega na tym, że żaden etap nie funkcjonuje najlepiej, gdy jest traktowany jako coś mierzalnego. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.

Koncepcja: wyszukiwanie semantyczne, wyjaśnione prosto

W fazie The Concept Semantic Search należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany 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. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Wymagaj ludzkiej aprobaty w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się pełności biznesowej.

Gdzie odgrywa rolę język?

Dla etapu „Skąd pochodzi język” należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Zapisuj czas trwania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Konieczne jest ludzkie zatwierdzenie w przypadkach, gdy dochodzi do wydatków lub zmian w danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się pełnej kompletności rozwiązania biznesowego.

Wybór odpowiednich narzędzi (i dlaczego zajęło to tygodnie)

W fazie wyboru odpowiednich narzędzi należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od 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ć bez konieczności czytania całej struktury. Autoryzacja powinna odbywać się przy bramce, a ponowna autoryzacja – na poziomie przetwarzania danych. Sam token nie stanowi granicy między poszczególnymi użytkownikami. W fazie wyboru odpowiednich narzędzi należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, łatwe do przetestowania jednostki nad rozbudowanymi skryptami. Gdy jakiś krok zawiedzie, powinien wskazywać na konkretną przyczynę, a nie na skomplikowaną strukturę procesów.

CLIP ViT-B/32 via FastEmbed — oczy systemu

Gdy pracujesz z etapem CLIP ViT-B 32 via FastEmbed, najpierw spisz 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 zadania. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko poprawiają słabą skuteczność wyszukiwania.

from fastembed import ImageEmbeddingModel, TextEmbeddingModel

# Loaded once, reused forever
image_model = ImageEmbeddingModel.from_pretrained("Qdrant/clip-ViT-B-32-vision")
text_model = TextEmbeddingModel.from_pretrained("Qdrant/clip-ViT-B-32-text")
# After embedding:
image_vector = embed_image("sunset_photo.jpg") # shape: (512,)
query_vector = embed_text("beautiful sunset") # shape: (512,)
# Cosine similarity — just a dot product on normalised vectors
similarity = np.dot(image_vector, query_vector)
# If image is a sunset → similarity ≈ 0.85 (strong match)
# If image is food → similarity ≈ 0.15 (weak match)

Qdrant Edge — pamięć

Gdy pracujesz z etapem pamięci w Qdrant Edge, 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. Obok wyników funkcjonalnych zapisz czasy wykonywania oraz koszt tokena lub zapytania. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Ustal punkty kontrolne po kosztownych krokach. Funkcja wznowienia nie powinna ponownie naliczać opłaty za ten sam wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

from qdrant_edge import(
    Distance,
    EdgeConfig,
    EdgeShard,
    EdgeVectorParams,
)
config = EdgeConfig(
            vectors={
                VECTOR_NAME: EdgeVectorParams(
                    size=EMBED_DIM,
                    distance=Distance.Cosine
                )
            }
        )
        _shard = EdgeShard.create(path=str(SHARD_DIR), config=config)

Łączenie wszystkiego razem

Gdy przechodzisz przez etap „Łączenie wszystkiego w całość”, 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. Trzymaj 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. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł. Gdy przechodzisz przez etap „Łączenie wszystkiego w całość”, 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 skomplikowaną ścieżkę przetwarzania.

OpenClaw — mózg

Faza OpenClaw polegająca na przetwarzaniu danych działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden udany przypadek, jeden przypadek niepowodzenia 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ń. Utrzymuj stan grafu w prostej formie i określ jego typ. Wложone struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji pracy po zakłóceniach.

Ustawianie środowiska

Etap konfiguracji środowiska działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę o cofnięciu zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.

Krok 1: Sklonuj repozytorium i utwórz wirtualne środowisko

Krok 1 – klonowanie etapu działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Trzymaj 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. Utrzymuj stan struktury w prostym formacie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane w danym polu, co utrudnia kontynuację pracy po przerwach. Krok 1 – klonowanie etapu działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję operacji.

# Clone the repo
git clone https://github.com/vatsala-singh/AI-Powered-Photo-Search-and-Tagging-Agent.git
cd AI-Powered-Photo-Search-and-Tagging-Agent

# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate        # Mac/Linux
# .venv\Scripts\activate         # Windows

Krok 2: Zainstaluj zależności

W drugim kroku, przed zmianą kodu, należy zainstalować etap, określić dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadania. Wymagaj ludzkiej aprobaty w przypadkach, gdy dochodzi do wydawania pieniędzy lub zmiany danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się pełności biznesowej.

pip install -r requirements.txt

Krok 3: Zrozumienie struktury projektu

W etapie 3, „Zrozumienie”, przed zmianą kodu należy określić dane wejściowe, osobę odpowiedzialną za ten etap oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić ten etap od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Obok wyników funkcjonalnych należy zapisywać czas trwania oraz koszt tokenów lub zapytań. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Zatwierdzenie przez człowieka powinno być wymagane w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Podłączenia realizowane w czasie kompilacji nie równają się pełnej kompletności rozwiązania biznesowego.

AI-Powered-Photo-Search-and-Tagging-Agent/
│
├── main.py                  # FastAPI app + OpenClaw agent entry point
├── config.py                # All configurable parameters in one place
├── requirements.txt
│
├── pipeline/
│   ├── embedder.py          # CLIP embedding logic (image + text)
│   └── indexer.py           # Batch photo processing and indexing
│
├── store/
│   └── qdrant_client.py     # Qdrant Edge setup and collection management
│
├── tools/
│   ├── search.py            # Semantic search tool
│   ├── tag.py               # Zero-shot auto-tagging tool
│   ├── duplicates.py        # Near-duplicate detection tool
│   └── albums.py            # Smart album grouping tool
│
├── test/
│   ├── embedder_test.py
│   ├── indexer_test.py
│   ├── search_test.py
│   └── qdrant_edge_client_test.py
│
└── qdrant-edge-data/        # Auto-created at runtime
    ├── storage/             # Qdrant's internal shard data
    ├── models/              # Cached CLIP model weights
    └── photos/              # Collection data

Etap 4: Przegląd pliku config.py

W fazie „Glance at” kroku 4 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 od 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ć bez konieczności przeglądania całej struktury. Wprowadź ludzką aprobatę dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie równają się pełnej kompletności biznesowej. W fazie „Glance at” kroku 4 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 od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną ścieżkę przetwarzania.

# config.py

CLIP_IMAGE_MODEL = "Qdrant/clip-ViT-B-32-vision"
CLIP_TEXT_MODEL  = "Qdrant/clip-ViT-B-32-text"
EMBEDDING_DIM    = 512          # CLIP ViT-B/32 output dimension

COLLECTION_NAME  = "photos"
QDRANT_PATH      = "./qdrant-edge-data"

BATCH_SIZE             = 32     # Images per indexing batch
TOP_K                  = 10     # Default search results returned
TAG_THRESHOLD          = 0.20   # Min similarity score for a tag to apply
DUPLICATE_THRESHOLD    = 0.97   # Min similarity to flag as duplicate

Krok 5: Uruchomienie serwera

Podczas pracy nad krokiem 5 „Uruchomienie serwera”, 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ć przypadki cichego, częściowego ukończenia zadania. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą funkcję LLM, gdy operator próbuje ponownie uruchomić późniejszy element.

uvicorn main:app --reload --port 8000

Krok 6: Weryfikacja konfiguracji

Gdy przechodzisz do etapu 6 „Weryfikacja”, 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. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

# Quick sanity check - should return {"status": "ok"}
curl http://localhost:8000/health
curl http://localhost:8000/api/status

Budowanie pipeline do embedowania obrazów

Podczas prace nad etapem tworzenia embeddingów obrazów, 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. Trzymaj 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. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania. Podczas pracy nad etapem tworzenia embeddingów obrazów, 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 skomplikowaną ścieżkę przetwarzania.

Embedder

Faza wgrzania działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, 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.

# pipeline/embedder.py

from fastembed import ImageEmbeddingModel, TextEmbeddingModel
from PIL import Image
import numpy as np
from config import CLIP_IMAGE_MODEL, CLIP_TEXT_MODEL

# Load once, reuse for the lifetime of the process
# Models are large (~150MB each) — we never want to reload them per request
_image_model = ImageEmbeddingModel.from_pretrained(CLIP_IMAGE_MODEL)
_text_model  = TextEmbeddingModel.from_pretrained(CLIP_TEXT_MODEL)

def embed_image(image_path: str) -> np.ndarray:
    """Convert an image file to a 512-d CLIP embedding."""
    image = Image.open(image_path).convert("RGB")
    embeddings = list(_image_model.embed([image]))
    return np.array(embeddings[0])   # shape: (512,)

def embed_text(query: str) -> np.ndarray:
    """Convert a text string to a 512-d CLIP embedding."""
    embeddings = list(_text_model.embed([query]))
    return np.array(embeddings[0])   # shape: (512,)

Indeksator

Faza indeksowania funkcjonuje najlepiej, gdy traktowana jest jako mierzalna powierzchnia. Zapisz jeden idealny przepis, jeden przypadek awarii oraz notatkę o cofnięciu zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.

# pipeline/indexer.py

import os
import uuid
from pathlib import Path
from datetime import datetime
from PIL import Image

from pipeline.embedder import embed_image
from store.qdrant_client import get_shard
from tools.tag import generate_tags
from config import BATCH_SIZE

SUPPORTED_FORMATS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff"}

def index_folder(folder_path: str) -> dict:
    """
    Recursively index all images in a folder into Qdrant Edge.
    Returns a summary: total found, indexed, skipped.
    """
    folder   = Path(folder_path)
    shard    = get_shard()

    image_paths = [
        p for p in folder.rglob("*")
        if p.suffix.lower() in SUPPORTED_FORMATS
    ]

    total    = len(image_paths)
    indexed  = 0
    skipped  = 0
    batch    = []

    for i, path in enumerate(image_paths):
        try:
            vector   = embed_image(str(path))
            tags     = generate_tags(str(path))
            img      = Image.open(path)

            point = {
                "id":      str(uuid.uuid4()),
                "vector":  vector.tolist(),
                "payload": {
                    "filename":  path.name,
                    "filepath":  str(path.absolute()),
                    "tags":      tags,
                    "timestamp": int(path.stat().st_mtime),
                    "width":     img.width,
                    "height":    img.height,
                }
            }
            batch.append(point)
            indexed += 1

        except Exception as e:
            print(f"Skipping {path.name}: {e}")
            skipped += 1

        # Flush every BATCH_SIZE images
        if len(batch) >= BATCH_SIZE:
            shard.upsert(points=batch)
            batch = []
            print(f"  Progress: {i+1}/{total} images indexed...")

    # Flush remaining
    if batch:
        shard.upsert(points=batch)

    return {"total": total, "indexed": indexed, "skipped": skipped}

Indeksowanie obrazów w Qdrant Edge

Etap indeksowania obrazów w Qdrant działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Trzymaj 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łego grafu. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co utrudnia kontynuację pracy po przerwach. Etap indeksowania obrazów w Qdrant działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.

Jak tutaj działa Qdrant Edge

W etapie „Jak działa Qdrant Edge” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany 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. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij pliki generowane w trakcie pracy, zdefiniuj kryteria sukcesu i odrzuć przypadkowe, częściowe ukończenie zadań. Wymagaj ludzkiej aprobaty dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się pełnej kompletności procesu biznesowego.

Ustawianie sharda

Aby przygotować etap tworzenia fragmentów, należy zdefiniować 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. 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. Konieczne jest ludzkie zatwierdzenie w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie gwarantują pełnej kompletności biznesowej.

def get_shard() -> EdgeShard:
    """
    Return the singleton EdgeShard, creating it on first call.

    - If SHARD_DIR does not exist → create a brand-new shard.
    - If SHARD_DIR already contains data → reopen it (no config needed).

    EdgeShard runs entirely in-process. No binary, no port, no network.
    """
    global _shard
    if _shard is not None:
        return _shard

    SHARD_DIR.mkdir(parents=True, exist_ok=True)
    # Detect whether this is a fresh shard or an existing one.
    # EdgeShard.create() fails if data already exists on disk.
    shard_has_data = any(SHARD_DIR.iterdir())

    if shard_has_data:
        print(f"[qdrant_client] Reopening existing shard at '{SHARD_DIR}'")
        _shard = EdgeShard.load(path=SHARD_DIR)
    else:
        print(f"[qdrant_client] Creating new shard at '{SHARD_DIR}'")
        config = EdgeConfig(
            vectors={
                VECTOR_NAME: EdgeVectorParams(
                    size=EMBED_DIM,
                    distance=Distance.Cosine
                )
            }
        )
        _shard = EdgeShard.create(path=str(SHARD_DIR), config=config)
        print(f"[store] Shard ready — vector: '{VECTOR_NAME}', dim: {EMBED_DIM}")
    return _shard

Rozróżnienie między tworzeniem a ładowaniem

Dla etapu tworzenia vs ładowania należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od 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ć bez konieczności czytania całej struktury. Wprowadź ludzką aprobatę dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie równają się pełnej kompletności biznesowej. Dla etapu tworzenia vs ładowania należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną strukturę procesów.

Wzorzec singletona

Gdy przechodzisz przez etap wzorca singletona, 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 zadania. Zrób kontrolę po kosztownych krokach. System powrotu nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie zrealizować późniejszy element.

@app.on_event("shutdown")
def on_shutdown():
  close_shard()

Schemat treści

Gdy przechodzisz przez etap schematu payload, 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. Zapisz czas trwania operacji 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. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłaty za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

from dataclasses import dataclass, field
from typing import List, Optional

@dataclass
class PhotoPayload:
    filename:str
    path:str
    tags: list[str] = field(default_factory=list)
    timestamp: Optional[str] = None
    width: Optional[int] = None
    height: Optional[int] = None

    def to_dict(self) -> dict:
        return {
            "filename": self.filename,
            "path": self.path,
            "tags": self.tags,
            "timestamp": self.timestamp,
            "width": self.width,
            "height": self.height
        }

Zapisywanie danych do shardu

Gdy przechodzisz przez etapy pisania, 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. Trzymaj 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. Ustaw punkty kontrolne po kosztownych krokach. System powinien unikać ponownego pobierania opłat za tę samą funkcję LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

from qdrant_edge import PointStruct
from store.qdrant_client import get_shard
from schema import PhotoPayload

shard = get_shard()

payload = PhotoPayload(
    filename  = "beach_sunset.jpg",
    path      = "/Users/me/Pictures/2024/Goa/beach_sunset.jpg",
    tags      = ["sunset", "beach", "outdoor"],
    timestamp = "2024-05-01T18:42:00",
    width     = 4032,
    height    = 3024
)

point = PointStruct(
    id      = "3f7a2b1c-8e4d-4f9a-b2c1-7d8e9f0a1b2c",
    vector  = {"image": vector.tolist()},   # named vector matching VECTOR_NAME
    payload = payload.to_dict()
)

shard.upsert(points=[point])

Gdy przechodzisz przez etapy pisania, 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 skomplikowaną strukturę procesów.

Automaticzne tagowanie zdjęć

Etap automatycznego tagowania zdjęć działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zrób jedno idealne przykład rozwiązania, jeden przypadek niepowodzenia oraz notatkę o cofnięciu działań przed rozszerzaniem zakresu. Traktuj ten etap 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ń. Utrzymuj stan grafu w prostej formie i określonej typowości. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji po przerwach.

Idea: klasyfikacja zero-shot

Idea etapu klasyfikacji zero-shot działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przykład transkrypcji, jeden przypadek awarii oraz notatkę o cofnięciu działań, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w formie prostych, spójnie skategoryzowanych elementów. Wplecione struktury ukrywają informację o tym, który węzeł zapisał dane w danym polu, i powodują przerwę w kontynuacji pracy po zakłóceniach.

TAG_VOCABULARY = [
    "sunset", "sunrise", "beach", "ocean", "mountain", "forest", "city",
    "night", "snow", "rain", "fog", "sunny", "cloudy",
    "dog", "cat", "bird", "people", "crowd", "portrait", "selfie",
    "food", "coffee", "restaurant", "travel", "architecture",
    "car", "road", "nature", "flowers", "trees",
    "indoor", "outdoor", "party", "celebration", "sport",
    "screenshot", "document", "text", "map",
]
# tools/tag.py

from pipeline.embedder import embed_image, embed_text
from config import TAG_THRESHOLD
import numpy as np

TAG_LABELS = [...]  # full list as above

# Pre-compute label embeddings once at module load —
# no point re-embedding the same 50 words on every photo
_label_vectors = {
    label: embed_text(label)
    for label in TAG_LABELS
}

def generate_tags(image_path: str) -> list[str]:
    """
    Run zero-shot classification on an image.
    Returns a list of tags whose similarity to the image
    exceeds TAG_THRESHOLD (default: 0.20).
    """
    image_vector = embed_image(image_path)

    tags = []
    for label, label_vector in _label_vectors.items():
        similarity = np.dot(image_vector, label_vector)  # cosine sim on normalised vectors
        if similarity >= TAG_THRESHOLD:
            tags.append(label)

    return tags

Tagi w czasie indeksowania vs czasie wyszukiwania

The Tags at index time stage works best when treated jako mierzalna powierzchnia. Należy zarejestrować jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzy się zakres badania. Konfigurację należy przechowywać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, aby operatorzy mogli je sprawdzić bez konieczności przeglądania całej struktury. Stan grafu powinien być prosty i typowany. Zagłębione struktury danych utrudniają określenie, który węzeł zapisał dane w danym polu, oraz powodują przerwę w kontynuacji działania po zakłóceniach. The Tags at index time stage works best when treated jako mierzalna powierzchnia. Należy zarejestrować jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzy się zakres badania. Lepiej używać małych, testowalnych jednostek niż rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.

def generate_tags_from_vector(img_vec: np.ndarray, threshold: float = 0.20, max_tags: int = 6) -> list[str]:
    """
    Generate tags for an image vector using zero-shot CLIP classification.
    Tags with cosine similarity above threshold are included (up to max_tags).

    This is a utility function used for generating tags during indexing
    or when you already have an image vector.
    """
    tag_vecs = _get_tag_vectors()

    scores = {
        tag: float(np.dot(img_vec, vec))   # both normalized → cosine similarity
        for tag, vec in tag_vecs.items()
    }

    tags = sorted(
        [t for t, s in scores.items() if s >= threshold],
        key=lambda t: scores[t],
        reverse=True,
    )[:max_tags]

    return tags

Wykorzystywanie tagów jako filtrów

W etapie wykorzystywania tagów jako filtrów należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Wymagaj ludzkiej aprobaty w przypadkach, gdy dochodzi do wydawania pieniędzy lub zmiany danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się pełnej kompletności biznesowej.

def search_photos(query: str, top_k: int = TOP_K, tags: list[str] = None) -> list[dict]:
    #search photo library with a Natural language query
    #takes in query, no of results to be displayed, and a list of tags
    # returns list of dicts with photo metadata and relevance score
    print(f"[search] Received query='{query}' with tags={tags} and top_k={top_k}")
    shard = get_shard()
    query_vector = embed_text(query)

    # Over-fetch when tag filtering is requested to have enough candidates
    # after post-filtering by tags
    over_fetch_multiplier = 5 if tags else 1
    fetch_limit = top_k * over_fetch_multiplier

    results = shard.query(
        QueryRequest(
            query=Query.Nearest(query_vector.tolist(), using=VECTOR_NAME),
            limit=fetch_limit,
            with_vector=False,
            with_payload=True,
        )
    )
    print(f"[search] Found {len(results)} initial hits for query='{query}' with tags={tags}")

    hits = []
    untagged_hits = []  # Fallback results for images without tags

    for point in results:
        payload = point.payload or {}
        point_tags = payload.get("tags", [])

        result_dict = {
            "path": payload.get("path"),
            "filename": payload.get("filename"),
            "tags": point_tags,
            "timestamp": payload.get("timestamp"),
            "score": round(point.score, 4)
        }

        # Post-filter by tags if specified
        # (EdgeShard doesn't support complex filters, so we filter in Python
        # after over-fetching more results than needed)
        if tags:
            if point_tags and any(t in point_tags for t in tags):
                # Has tags and matches the filter
                hits.append(result_dict)
            elif not point_tags:
                # No tags yet (images not auto-tagged), save as fallback
                untagged_hits.append(result_dict)
        else:
            # No tag filter specified, include all results
            hits.append(result_dict)

        # Stop if we have enough tagged results
        if len(hits) >= top_k:
            break

    # If we don't have enough tagged results, include untagged ones that match the query
    if tags and len(hits) < top_k:
        hits.extend(untagged_hits[:top_k - len(hits)])

    return hits[:top_k]

Budowanie agenta wyszukiwania

W fazie tworzenia agenta wyszukiwawczego 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. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Konieczne jest ludzkie zatwierdzenie dla przypadków, w których wydawane są pieniądze lub zmieniane są dane produkcyjne. Połączenia realizowane w czasie kompilacji nie równają się pełnej kompletności rozwiązania biznesowego.

curl --location 'http://localhost:8000/search' \
--header 'Content-Type: application/json' \
--data '{"query": "eiffel tower from rooftop","tags":[], "top_k": 1}'
{
    "query": "eiffel tower from rooftop",
    "results": [
        {
            "path": "/Users/vatsalasingh/Documents/Datasets/tag_phot/photo-1638051017225-0d9fcca18cf4.jpg",
            "filename": "photo-1638051017225-0d9fcca18cf4.jpg",
            "tags": [
                "cloudy",
                "city",
                "rain",
                "screenshot",
                "architecture",
                "travel"
            ],
            "timestamp": "2021-12-09T17:27:56",
            "score": 0.2864
        }
    ]
}

Eleganckie radzenie sobie z przypadkami krawędziowymi

Aby w sposób uporządkowany radzić sobie z przypadkami krawędziowymi, należy przed modyfikacją kodu zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić dany krok od 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ć bez konieczności czytania całej struktury. Wprowadź ludzką aprobatę dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia skompilowane w czasie kompilacji nie równają się pełności funkcjonalności biznesowej. Aby w sposób uporządkowany radzić sobie z przypadkami krawędziowymi, należy przed modyfikacją kodu zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie

splątana sieć przetwarzania.

Koordynacja wszystkiego za pomocą OpenClaw

Gdy przechodzisz przez etap koordynacji wszystkiego, 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ń. Ustaw punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą próbę wywołania LLM, gdy operator ponawia działanie późniejszego węzła.

Jak działa OpenClaw

Gdy przechodzisz przez etap wyjaśniania, jak działa OpenClaw, 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. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy przechodzi się od środowiska demonstracyjnego do współdzielonych środowisk. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji nie powinno ponownie naliczać opłat za tę samą funkcję LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

@app.post("/chat")
def chat(req: ChatRequest):
    """
    Conversational endpoint. Accepts user message and conversation history,
    returns agent's reply after processing with tools.
    """
    # Define tool functions that the agent can call
    def search_tool(query: str, top_k: int = 10, tag_filter: list = None):
        """Search photos by natural language query"""
        return search_photos(query=query, top_k=top_k, tags=tag_filter)

    def duplicates_tool(threshold: float = 0.97):
        """Find duplicate or near-duplicate photos"""
        return find_duplicates(threshold=threshold)

    def tag_tool(image_path: str):
        """Generate and update tags for a specific photo"""
        return generate_tags_from_vector(image_path=image_path)

    # Create the agent with tools
    agent = Agent(
        tools=[search_tool, duplicates_tool, tag_tool],
        system_prompt="""
        You are a personal photo assistant. You help users search, organize,
        and understand their local photo library. You have access to tools
        for semantic search, duplicate detection, and tagging.

        When helping users:
        - Use the search tool to find photos by describing their content
        - Use duplicates tool to find and clean up duplicate shots
        - Use tag tool to inspect or update tags for specific photos

        Always be concise and helpful. When returning photo results,
        format them clearly with filenames, similarity scores, and tags.
        Use emojis sparingly but helpfully.
        """
    )

    # Run the agent conversation
    response = agent.chat(
        message=req.message,
        history=req.history
    )
    return {"response": response}

Prawdziwe strumienie interakcji

Gdy przechodzisz przez etap rzeczywistych przepływów interakcji, 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. Trzymaj 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. Ustaw punkty kontrolne po kosztownych krokach. System powinien unikać ponownego pobierania opłat za tę samą funkcję LLM, gdy operator próbuje ponownie uruchomić późniejszy element procesu. Gdy przechodzisz przez etap rzeczywistych przepływów interakcji, 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.

Dlaczego warstwa agenta jest ważna

Faza warstwy agenta działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przepis 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ń. Utrzymuj stan grafu w prostej formie i określ jego typ. Wkładki nawiasowe ukrywają informację o tym, który węzeł zapisał dane do którego pola, co powoduje przerwanie kontynuacji po zakłóceniach.

Rozwijanie wszystkiego lokalnie — i dlaczego to ma znaczenie

Rozwiązanie Running Everything Locally And stage funkcjonuje najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.

Nic nie opuszcza twojego urządzenia. Koniec tematu.

The Nothing leaves your device stage funkcjonuje najlepiej, gdy jest traktowany jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcji powinny znajdować się w jednym miejscu, aby operatorzy mogli je sprawdzić bez konieczności czytania całego grafu. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione struktury ukrywają informację o tym, który węzeł zapisał dane do którego pola, co utrudnia kontynuację działania po przerwach. The Nothing leaves your device stage funkcjonuje najlepiej, gdy jest traktowany jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.

Czego naprawdę potrzeba, aby to uruchomić

Na etapie określania tego, co faktycznie jest potrzebne, 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. Traktuj ten etap 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 zadań. Wymagaj ludzkiej aprobaty w przypadkach, gdy dochodzi do wydawania pieniędzy lub modyfikacji danych produkcyjnych. Połączenia realizowane w czasie kompilacji nie równają się pełnej kompletności procesu biznesowego.

Kolejne kroki — rozwijanie systemu

W fazie rozszerzania What’s Next 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. Zapisuj czas trwania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Konieczne jest ludzkie zatwierdzenie dla operacji, które generują wydatki lub zmieniają dane produkcyjne. Połączenia realizowane w czasie kompilacji nie gwarantują pełnej kompletności biznesowej.

Klastrowanie twarzy

W fazie klasterowania twarzy 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. 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ć bez konieczności czytania całej struktury. Zastosuj ludzką aprobatę dla operacji, które wiążą się z wydawaniem pieniędzy lub modyfikacją danych produkcyjnych. Połączenia skonstruowane w czasie kompilacji nie gwarantują kompletności biznesowej. W fazie klasterowania twarzy 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 nad rozbudowanymi skryptami. Gdy jakiś etap zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną ścieżkę przetwarzania.

OCR dla zrzutów ekranu i dokumentów

Pracując nad OCR dla zrzutów ekranu i kolejnych etapów, 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ć przypadki cichego, częściowego ukończenia zadania. Ustaw punkty kontrolne po kosztownych krokach. System powrotu nie powinien ponownie naliczać opłat za tę samą operację LLM, gdy operator próbuje ponownie wykonać późniejszy element.

Indeksowanie klatek wideo

Gdy przechodzisz przez etap indeksowania klatek wideo, 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. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy ścieżka przechodzi z wersji demonstracyjnej do środowisk współdzielonych. Ustal punkty kontrolne po kosztownych krokach. Narzędzie do kontynuacji pracy nie powinno ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.

Szukanie hybrydowe: wektory + słowa kluczowe + metadane

Gdy pracujesz nad etapem słów kluczowych wektorów wyszukiwania hybrydowego, 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. Ustaw punkty kontrolne po kosztownych krokach. System powrotu do pracy nie powinien ponownie naliczać opłat za tę samą wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł. Gdy pracujesz nad etapem słów kluczowych wektorów wyszukiwania hybrydowego, 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 skomplikowaną sekwencję operacji.

Pobieranie z uwzględnieniem czasu i lokalizacji

Etap uwzględniający czas i lokalizację działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań przed rozszerzeniem zakresu. Traktuj ten etap 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ń. Oddziel zasady dzielenia na fragmenty od zasad pobierania danych. Zmiana jednych nie powinna zmuszać do przepisywania drugich, gdy zmieniają się metryki jakości.

Ostateczne uwagi

Etap Ostatecznych Uwag działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę o cofnięciu zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Utrzymuj stan grafu w prostej formie i z określonym typem danych. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, i powodują przerwanie kontynuacji po przerwach.

Odnośniki i dalsza lektura

Etap „Referencje i dalsza lektura” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Trzymaj 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. Utrzymuj stan struktury w prostym formacie i z określonym typem danych. Wplecione bloki danych ukrywają informację o tym, który węzeł zapisał dane w danym polu, co powoduje przerwę w kontynuacji działania po zakłóceniach. Etap „Referencje i dalsza lektura” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.

Listwa kontrolna operacyjna

Etap listy kontrolnej operacyjnej działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres.

Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę przywracania. Próby ponowne, kontrolne punkty ludzkie oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później.

Zachowaj prostą i typowaną strukturę stanu grafu. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, i utrudniają kontynuację działania po przerwach.

Gdy budżet na to pozwala, dodaj test wstępny, który sprawdza kluczową ścieżkę działania w środowisku CI przy użyciu fixitów, a nie rzeczywistych, płatnych API.

Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakaś etap zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.

Zachowaj prostą i typowaną strukturę stanu grafu. Wplecione elementy ukrywają informację o tym, który węzeł zapisał dane do którego pola, i utrudniają kontynuację działania po przerwach.

Zanim uruchomisz cały system, 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 b7b9768f8acd: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy obok plików testowych, aby późniejsze zmiany modeli pozostawały porównywalne.