RAG z rozpoznawaniem domeny, wykorzystujący rejestracje MLflow oraz routowanie LLM
RAG z rozpoznawaniem domeny: klasyfikuje intencję, ładuje odpowiedni embedder z rejestru MLflow, wyszukuje odpowiednią kolekcję Qdrant, a następnie kieruje generację do LLM.
Źródła: RAG Beyond Retrieval: Model Registry, Vector Search and Intelligent LLM Routing
Większość demonstracji RAG łączy jeden narzędzie do embedowania, jedną kolekcję oraz jeden model LLM. To wystarcza, dopóki pytania dotyczące kredytów hipotecznych i medyczne nie wykorzystują tego samego procesu, albo dopóki niewielki model lokalny wystarczy do wstępnego oceniania sytuacji, podczas gdy większy model zdalny ma odpowiadać na szczegółowe kwestie kliniczne. Silniejszy wzorzec najpierw klasyfikuje intencję, wybiera odpowiadającą bazę wiedzy i model embedowania, pobiera kontekst, a następnie kieruje proces generowania do odpowiedniego modelu LLM.
To rozwiązanie realizuje tę koncepcję na skromnym sprzęcie: MLflow Model Registry na Raspberry Pi służy do embedowania danych, kolekcje Qdrant dla poszczególnych dziedzin, Ollama do lokalnej klasyfikacji, a wybrane połączenia z modelami LLM w chmurze dla dziedzin, które tego wymagają. Trudności związane z łączeniem Raspberry Pi z Postgresem również są istotne, ponieważ rejestratory modeli są przydatne tylko wtedy, gdy magazyn danych backendu jest dostępny.
Łączność z Postgresem z Raspberry Pi
Jeśli Postgres został zainstalowany za pomocą Homebrew na Macu w tej samej sieci LAN, sprawdź przed edycją, jaką konfigurację używa bieżąca instancja:
psql -U <your_user> -d postgres -c "SHOW config_file;"
Adresy słuchania oraz powiązane ustawienia znajdują się w pliku postgresql.conf:
cd /opt/homebrew/var/postgresql@15
sed -i '' "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" postgresql.conf
Sprawdź aktualną konfigurację:
grep -v "^#" postgresql.conf | grep listen_addresses
# listen_addresses = '*'
Dodaj IP adres Pi do listy białych w pliku pg_hba.conf, aby z tego hosta było możliwe uwierzytelnianie za pomocą hasła:
echo "host all all 192.168.1.9/32 md5" >> pg_hba.conf
Upewnij się, że reguły są ładowane zgodnie z oczekiwaniami:
grep "192.168.1.9" pg_hba.conf
Zrestartuj Postgres i sprawdź, czy słucha na interfejsach, które zamierzasz użyć:
brew services restart postgresql@15
lsof -i -P | grep LISTEN | grep postgres
Potwierdź, że istnieje rola z uprawnieniami do logowania:
psql -U <admin_user> -d postgres -c "\du"
Ustal lub sformatuj hasło, którego będzie używał MLflow:
psql -U <admin_user> -d postgres -c "ALTER USER root WITH PASSWORD 'root';"
Zapewnij się, że docelowa baza danych istnieje:
psql -U <admin_user> -d postgres -c "\l" | grep mlflow
psql -U <admin_user> -d postgres -c "CREATE DATABASE mlflow OWNER root;"
Przetestuj bezpośrednie połączenie z Pi przed włączeniem MLflow:
nc -zv 192.168.1.8 5432
Gdy ścieżka sieciowa i dane uwierzytelniające działają poprawnie, uruchom MLflow w odniesieniu do tego zdalnego magazynu backend:
mlflow server \
--backend-store-uri postgresql://root:root@192.168.1.8:5432/mlflow \
--host 0.0.0.0 \
--port 5000 \
--allowed-hosts "192.168.1.9:5000" \
--cors-allowed-origins "http://192.168.1.9:5000"
Architektura w dwóch etapach
Etap 1 — warstwa wiedzy. Pliki PDF z danej dziedziny są dzielone na fragmenty i wzbogacane modelami sentence-transformer dostosowanymi do tej dziedziny, a następnie przechowywane w oddzielnych kolekcjach Qdrant. MLflow śledzi, która wersja narzędzia do wzbogacania jest najlepsza dla danej dziedziny, dzięki czemu usługi nie muszą mieć w kodzie stałych ścieżek lokalnych.
Etap 2 — inteligentne kierowanie ruchem. Klasyfikator przypisuje każde pytanie użytkownika określoną kategorię (w tym demo: białaczka, hipoteka lub inne). Proces wyszukiwania wykorzystuje odpowiednią kolekcję i narzędzie do wzbogacania danych. Proces generowania tekstu wykorzystuje model LLM wybrany do danej kategorii — lokalny w niektórych przypadkach, zdalny w innych — dzięki czemu koszt i możliwości są dostosowane do rodzaju pytania.
Modyuły implementacyjne
model_registry.py rejestruje Arctic-embed do zastosowań w obszarze hipotek oraz MiniLM do leczenia białaczki (lub dowolną inną parę modeli wybraną przez użytkownika), zapisuje je w MLflow na urządzeniu Pi i tworzy aliasy dla najlepszych modeli:
import mlflow
import mlflow.sentence_transformers
from mlflow import MlflowClient
from mlflow.models import infer_signature
from sentence_transformers import SentenceTransformer
mlflow.set_tracking_uri("http://192.168.1.9:5000")
mlflow.set_experiment("embedding-models")
EMBEDDERS = {
"mortgage-embedder": {"source": "Snowflake/snowflake-arctic-embed-m-v1.5"},
"leukemia-embedder": {"source": "all-MiniLM-L6-v2"},
}
sample_texts = ["MLflow tracks embedding models", "Qdrant stores vectors"]
client = MlflowClient()
for name, cfg in EMBEDDERS.items():
model = SentenceTransformer(
cfg["source"],
trust_remote_code=cfg.get("trust_remote_code", False),
config_kwargs=cfg.get("config_kwargs"),
device=cfg.get("device"),
)
with mlflow.start_run(run_name=name):
info = mlflow.sentence_transformers.log_model(
model=model,
name="embedder",
signature=infer_signature(sample_texts, model.encode(sample_texts)),
input_example=sample_texts,
registered_model_name=name,
)
client.set_registered_model_alias(name, "champion", info.registered_model_version)
print(f"{name}: v{info.registered_model_version}, dim={model.get_embedding_dimension()}")
ingest_data.py odbudowuje kolekcje danych z danej dziedziny, dzieli pliki PDF na fragmenty o stałej szerokości okna tokenów i określonym stopniu nakładania się, embeduje je za pomocą najlepszego modelu dla danej dziedziny i wprowadza te dane do bazy Qdrant:
import os
from chonkie import Pipeline
from qdrant_client import QdrantClient
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
client = QdrantClient(url="http://localhost:6333", api_key=os.environ["QDRANT_API_KEY"])
client.delete_collection("leukemia_details") # this will delete and recreate the collection.
(Pipeline()
.fetch_from("file", dir="../", ext=[".pdf"])
.process_with("liteparse")
.chunk_with("recursive", chunk_size=512)
.refine_with("overlap", context_size=100)
.store_in("qdrant", collection_name="leukemia_details", client=client,
embedding_model="sentence-transformers/all-MiniLM-L6-v2")
.run())
count = client.count("leukemia_details", exact=True).count
print(f"Ingested {count} chunks into leukemia_details")
ai_gateway.py klasyfikuje intencje za pomocą ścisłych instrukcji lokalnych przekazywanych przez Ollamę, a następnie wybiera odpowiednie backendi LLM w zależności od klasyfikacji:
from openai import OpenAI
import mlflow
mlflow.set_tracking_uri("http://192.168.1.9:5000")
client = OpenAI(
base_url="http://192.168.1.9:5000/gateway/mlflow/v1",
api_key="no-api-key",
)
CLASSIFIER_PROMPT = (
"Classify the user query into exactly one label: LEUKEMIA, MORTGAGE, or OTHER.\n"
"LEUKEMIA = questions about leukemia (AML, CML, ALL, CLL), its symptoms, diagnosis, "
"treatment, prognosis, or related blood cancers.\n"
"MORTGAGE = questions about home loans, mortgage rates, refinancing, EMI, "
"eligibility, lenders, or mortgage documents.\n"
"OTHER = everything else, including general medical questions not about leukemia.\n"
"Reply with only the label."
)
ROUTES = {
"LEUKEMIA": "openai",
"MORTGAGE": "gemma",
}
REJECT_MESSAGE = (
"Sorry, this setup can only answer Leukemia-specific medical questions "
"and mortgage-related questions."
)
def classify(query: str) -> str:
resp = client.chat.completions.create(
model="ollama", # local Ollama endpoint does the classification
messages=[
{"role": "system", "content": CLASSIFIER_PROMPT},
{"role": "user", "content": query},
],
temperature=0,
)
label = resp.choices[0].message.content.strip().upper()
for intent in ROUTES:
if intent in label:
return intent
return "OTHER"
def route(query: str) -> str:
intent = classify(query)
target = ROUTES.get(intent)
if target is None:
print(f"[intent={intent} -> rejected]")
return REJECT_MESSAGE
resp = client.chat.completions.create(
model=target,
messages=[{"role": "user", "content": query}],
)
print(f"[intent={intent} -> model={target}]")
return resp.choices[0].message.content
#
# if __name__ == "__main__":
# print(route("What are the first-line treatments for acute myeloid leukemia?"))
# print(route("How does refinancing affect my monthly mortgage EMI?"))
# print(route("What are the side effects of metformin?")) # medical but not leukemia -> rejected
# print(route("Write a haiku about monsoon in Hyderabad.")) # rejected
smart_rag.py łączy ze sobą procesy klasyfikacji, wyboru kolekcji danych, wyszukiwania najważniejszych informacji oraz generowania odpowiedzi opartych na tych danych:
from qdrant_client import QdrantClient
import os
from ai_gateway import client, classify, ROUTES, REJECT_MESSAGE
from embedding_model_inference import EMBEDDERS
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
COLLECTIONS = {
"LEUKEMIA": {"collection": "leukemia_details", "embedder": "leukemia-embedder"},
"MORTGAGE": {"collection": "mortgage", "embedder": "mortgage-embedder"},
}
TEXT_FIELD = "text"
EXIT_WORDS = {"bye", "exit", "quit"}
class SmartRAG:
def __init__(self, qdrant_url="http://localhost:6333", top_k=5):
self.qdrant = QdrantClient(url=qdrant_url, api_key=os.environ.get("QDRANT_API_KEY"))
self.top_k = top_k
def retrieve(self, query, intent):
cfg = COLLECTIONS[intent]
embedder = EMBEDDERS[cfg["embedder"]]
prompt = "query" if "query" in embedder.prompts else None # arctic needs its query prefix
vec = embedder.encode(query, prompt_name=prompt, normalize_embeddings=True).tolist()
hits = self.qdrant.query_points(cfg["collection"], query=vec, limit=self.top_k, with_payload=True).points
return "\n\n".join(str(h.payload.get(TEXT_FIELD, h.payload)) for h in hits)
def answer(self, query):
intent = classify(query)
if intent not in ROUTES:
return REJECT_MESSAGE
context = self.retrieve(query, intent)
resp = client.chat.completions.create(
model=ROUTES[intent],
messages=[
{"role": "system", "content": f"Answer using only this context:\n{context}"},
{"role": "user", "content": query},
],
)
print(f"[intent={intent} -> {COLLECTIONS[intent]['collection']} -> {ROUTES[intent]}]")
return resp.choices[0].message.content
def run(self):
while True:
query = input("\nyou> ").strip()
if query.lower() in EXIT_WORDS:
break
if query:
print("\nassistant>", self.answer(query))
if __name__ == "__main__":
SmartRAG().run()
embedding_model_inference.py ładuje modele embedujące z MLflow za pomocą aliasów (models:/...@champion), dzięki czemu ścieżki dostępu w czasie rzeczywistym nie zależą od ścieżek na laptopie programisty:
import mlflow
import mlflow.sentence_transformers
mlflow.set_tracking_uri("http://192.168.1.9:5000")
EMBEDDERS = {
name: mlflow.sentence_transformers.load_model(f"models:/{name}@champion")
for name in ("leukemia-embedder", "mortgage-embedder")
}
QUERIES = {
"leukemia-embedder": "What are the first-line treatments for acute myeloid leukemia?",
"mortgage-embedder": "How does refinancing affect my monthly mortgage EMI?",
}
for name, embedder in EMBEDDERS.items():
vecs = embedder.encode([QUERIES[name]], normalize_embeddings=True)
print(f"{name}: shape={vecs.shape}, prompts={list(embedder.prompts) or None}")
Wyniki
Pytania dotyczące kredytów hipotecznych powinny być odwoływane z zbiorów danych o kredytach hipotecznych za pomocą narzędzi do włączania treści oraz odpowiadane zgodnie z zasadami modeli LLM specjalizujących się w kredytach hipotecznych. Pytania o białaczkę powinny być rozpatrywane zgodnie z procedurami klinicznymi. Pytania spoza danej dziedziny powinny być pomijane lub na nie odpowiadać bez udawania, że udało się uzyskać informacje z danej dziedziny.
/Users/pavanmantha/.local/bin/uv run /Users/pavanmantha/Pavans/PracticeExamples/DataScience_Practice/mlflow-tutorials/mlflow_on_edge/.venv/bin/python /Users/pavanmantha/Pavans/PracticeExamples/DataScience_Practice/mlflow-tutorials/mlflow_on_edge/src/smart_rag.py
Downloading artifacts: 100%|██████████| 16/16 [00:19<00:00, 1.19s/it]
2026/09/06 12:48:35 INFO mlflow.sentence_transformers: 'models:/leukemia-embedder@champion' resolved as 'mlflow-artifacts:/3/models/m-f568ee233dd748f997ba66763e41653b/artifacts'
Downloading artifacts: 100%|██████████| 1/1 [00:00<00:00, 76.69it/s]
Loading weights: 100%|██████████| 103/103 [00:00<00:00, 15587.15it/s]
Downloading artifacts: 100%|██████████| 16/16 [01:29<00:00, 5.60s/it]
2026/09/06 12:50:06 INFO mlflow.sentence_transformers: 'models:/mortgage-embedder@champion' resolved as 'mlflow-artifacts:/3/models/m-c4221c844dfb45bfa15c6a4ecde56920/artifacts'
Downloading artifacts: 100%|██████████| 1/1 [00:00<00:00, 27.17it/s]
Loading weights: 100%|██████████| 197/197 [00:00<00:00, 8855.57it/s]
[transformers] BertModel LOAD REPORT from: /var/folders/1x/36nz44_569501n4xs0px2_8m0000gn/T/tmpef4y6tj5/model.sentence_transformer
Key | Status |
--------------------+---------+-
pooler.dense.weight | MISSING |
pooler.dense.bias | MISSING |
Notes:
- MISSING: those params were newly initialized because missing from the checkpoint. Consider training on your downstream task.
leukemia-embedder: shape=(1, 384), prompts=['query', 'document']
mortgage-embedder: shape=(1, 768), prompts=['query', 'document']
/Users/pavanmantha/Pavans/PracticeExamples/DataScience_Practice/mlflow-tutorials/mlflow_on_edge/src/smart_rag.py:20: UserWarning: Api key is used with an insecure connection.
self.qdrant = QdrantClient(url=qdrant_url, api_key=os.environ.get("QDRANT_API_KEY"))
you> What are the phases of treatment for acute lymphoblastic leukemia?
[intent=LEUKEMIA -> leukemia_details -> openai]
assistant> The phases of treatment for acute lymphoblastic leukemia (ALL) are:
1. Induction - achieving remission.
2. Consolidation - eradicating residual disease with different agents to circumvent resistance.
3. Maintenance (in ALL) - prolonged low-dose therapy to suppress re-emergence of the clone.
you> What was 30-day Average SOFR referred to in Series issued before July 2023?
[intent=MORTGAGE -> mortgage -> gemma]
assistant> * Question: "What was 30-day Average SOFR referred to in Series issued before July 2023?"
* Constraint: Use *only* the provided context.
* Chunk 1: Discusses "Excess inclusion income", Residual Holders, taxable income, Trust REMIC taxable income, etc. No mention of SOFR or July 2023.
* Chunk 2: Discusses "Backup withholding", Non-U.S. Persons, Ginnie Mae Guaranty, State/Local/Foreign tax. No mention of SOFR or July 2023.
* Chunk 3: Discusses "Residual Holder's share of expenses", REITs, RICs, phantom income. No mention of SOFR or July 2023.
* Chunk 4: Discusses "Principal Distribution Amount", Ginnie Mae Project Loan Certificates, Underlying Certificates. No mention of SOFR or July 2023.
* Chunk 5: Discusses "OID on a Teaser Security", "Initial Distribution Period", "Excess Interest Payment", "Accrual of Original Issue Discount". No mention of SOFR or July 2023.
* I have scanned all five provided text nodes.
you>
Dlaczego ta struktura jest interesująca
Rejestratory umożliwiają wymianę narzędzi do włączania treści bez konieczności ponownego wdrażania rozwiązań u każdego klienta. Zbiory danych specyficzne dla danej dziedziny zapobiegają zanieczyszczeniu wynikami z innych dziedzin. Systemy kierowania intencjami chronią drogie modele przed obsługą prostych zapytań. Uruchamianie MLflow na urządzeniu typu Pi nie jest konieczne dla tego wzorca, ale pokazuje, że rejestratory mogą funkcjonować na kontrolowanym sprzęcie znajdującym się w pobliżu sieci laboratoriów.
Wskazówki operacyjne
Zabezpiecz Postgres przed udostępnieniem go w sieci LAN. Traktuj zmiany w pliku pg_hba jak zmiany produkcyjne. Ustal stałe wersje MLflow i sterowników. Monitoruj wskaźniki błędów klasyfikatora: błędna intencja może skierować zapytanie do niewłaściwej kolekcji i sprawić wrażenie „złego LLM”. Oceniaj proces wyszukiwania i kierowania zapytaniami oddzielnie. Gdy w rejestrze nastąpią zmiany, przeprowadź testy przed przeniesieniem ruchu produkcyjnego.
Zakończenie
RAG wykraczający poza pojedynczą ścieżkę wyszukiwania to przede wszystkim architektura produktu: trzeba znać dziedzinę, pobierać dane przy użyciu odpowiedniej metody oraz generować wyniki z uwzględnieniem określonych ograniczeń. MLflow, Qdrant oraz niewielki bramkarz sprawiają, że ta architektura jest na tyle konkretna, by mogła działać nawet na biurku, a jednocześnie na tyle zdyscyplinowana, by rozwijać się poza fazę demonstracyjną.
Zmocnienie ścieżki opartej na rejestrze
Nazwy alternatywne championów powinny przechodzić przez etap testowy. Zapisuj ID wersji modelu, który dostarcza każdą odpowiedź, aby móc odtworzyć incydenty po zmianie w rejestrze. Jeśli urządzenie Pi wyjdzie z sieci, klienci potrzebują trybu awaryjnego zamiast cichego powrotu do losowego lokalnego punktu kontrolnego.
Parametry dzielenia na fragmenty powinny znajdować się w metadanych rejestru obok elementu embedder. Nowy champion o innej preferowanej długości kontekstu nie powinien ponownie używać starych fragmentów bez flagi odbudowy.
Wskazówki dla klasyfikatora wymagają specjalnych przykładów: dziesięć sformułowań dotyczących kredytów hipotecznych, dziesięć sformułowań klinicznych oraz dziesięć adwersarialnych „innych” sformułowań. Zmierz dokładność i召回 przed dostosowaniem wyboru LLM. W przeciwnym razie będziesz dążyć do lepszej jakości generatora, podczas gdy prawdziwym problemem jest sposób routowania.
Przy wdrażaniu Raspberry Pi należy śledzić zużycie dysku spowodowane częstym zapisywaniem artefaktów oraz preferować dyski zewnętrzne jako lokalizację plików artefaktów MLflow. Zapasowe kopie Postgresa powinny znajdować się na innym komputerze niż Pi.
Wyjaśniając projekt interesariuszom, przedstawcie następującą sekwencję: klasyfikacja → wybór narzędzia do embedowania/kolekcji → pobieranie danych → wybór modelu LLM → udzielenie odpowiedzi z odniesieniami. Taki obraz jest skuteczniejszy niż lista narzędzi i jasno pokazuje, gdzie podejmowane są decyzje polityczne.
Rozszerzanie domen należy robić ostrożnie. Każda nowa kategoria wymaga kolekcji, osoby odpowiedzialnej za embedowanie, zadania do pobierania danych, przykładów do klasyfikatora oraz polityki dotyczącej modelu LLM. Brama wejściowa powinna odrzucać nieznane kategorie zamiast automatycznie używać największego modelu.
Na koniec należy zachować uczciwość w obsłudze ścieżki „INNE”. Odrzucenie prośby jest lepsze niż pobieranie danych z przypadkowego domeny, aby uniknąć niezręcznej ciszy. Użytkownicy ufają systemom, które wiedzą, kiedy wykraczają poza swoje zakresy. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora. Dokładność ścieżki oraz jakość pobierania danych muszą być mierzone jako oddzielne kryteria wypuszczenia na rynek, zanim dalsze dostosowania zostaną wprowadzone do poleceń generatora p
Prompty są dalej dostosowywane. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostosowywaniem promptów generatora. TrasaDokładność oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszą optymalizacją poleceń generatora. Dokładność trasy oraz jakość wyszukiwania mDokładność trasy oraz jakość odzyskiwania danych muszą być mierzone jako oddzielne punkty weryfikacyjne przed dalszym dostosowywaniem poleceń generatora.Bramy kontroli muszą zostać uruchomione przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne bramy wypuszczenia przed dalszym dostosowywaniem promptów generatora.Są one dalej dostrojane. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostrojeniem poleceń generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostrojeniem poleceń generatora. Dokładność trasy oraz jakość wydobywania informacji muszą być mierzone jako oddzielne kryteria wypuszczenia przed dalszym dostrojeniem poleceń generatora.