Uvod
Generiranje potpomognuto dohvatom podataka — ili jednostavno RAG (Retrieval-Augmented Generation) — postalo je nešto poput temeljnog kamena za svaki ozbiljniji sustav zasnovan na velikim jezičnim modelima. U 2026. godini, RAG više nije eksperimentalna igračka iz laboratorija. To je ključna infrastrukturna komponenta u produkcijskim sustavima širom industrije — od financijskih institucija koje kopaju po regulatornim dokumentima, preko zdravstvenih sustava koji pretražuju medicinsku literaturu, do korisničkih servisa koji automatiziraju odgovore na složene upite.
Ali evo jedne neugodne statistike: između 40% i 60% RAG implementacija nikada ne doživi produkciju. Razlog? Problemi s kvalitetom dohvata podataka. Modeli generiraju odgovore koji zvuče sjajno, ali su faktički netočni jer su se oslonili na irelevantne ili nepotpune informacije. I to nije problem jezičnog modela — to je problem vašeg cjevovoda za dohvat.
RAG je u posljednjih godinu dana prošao ozbiljnu evoluciju. Ono što je počelo kao jednostavan obrazac dohvati-pa-generiraj danas obuhvaća sofisticirane arhitekture s višestrukim fazama dohvata, samokorekcijskim mehanizmima, grafovskim strukturama znanja i agentičkim pristupima u kojima modeli sami odlučuju kada, što i kako dohvatiti. Zapravo, govorimo o prijelazu s jednostavnih cjevovodâ na prave sustave za inteligentno upravljanje znanjem.
Zašto bi vas to trebalo zanimati? Pa, pravilno dizajnirani cjevovodi za dohvat mogu smanjiti halucinacije za 70% do 90% u usporedbi s čistim LLM generiranjem. Razlika između nepouzdanog chatbota i robusnog poslovnog alata doslovno leži u kvaliteti RAG arhitekture.
Dakle, hajdemo zaroniti. U ovom ćemo vas članku provesti kroz sve aspekte izgradnje produkcijskog RAG sustava — od segmentiranja i pohrane dokumenata, preko hibridnog dohvata i ponovnog rangiranja, do naprednih arhitektura poput Self-RAG, Corrective RAG i agentičkog dohvata. Svaki koncept potkrijepljen je praktičnim Python kodom koji možete primijeniti u vlastitim projektima. Bilo da gradite svoj prvi prototip ili optimizirate postojeći sustav, ovdje ćete naći jasan putokaz od početka do produkcije.
Kako funkcionira RAG cjevovod
Prije nego što krenemo u napredne tehnike, trebamo razumjeti temeljnu mehaniku. Svaki RAG sustav, bez obzira koliko je složen, funkcionira kroz tri ključne faze: unos i indeksiranje podataka, dohvat relevantnog konteksta te generiranje odgovora.
Faza 1: Unos i indeksiranje (Ingestion)
Prva faza transformira sirove dokumente u pretraživu bazu znanja. Proces započinje učitavanjem dokumenata iz raznih izvora — PDF-ovi, web stranice, baze podataka, API-ji, interna dokumentacija u Confluenceu ili SharePointu. Zatim slijedi segmentiranje (chunking) — razbijanje velikih dokumenata na manje, semantički smislene dijelove. Svaki segment prolazi kroz model za ugrađivanje (embedding model), koji ga pretvara u visokodimenzionalni numerički vektor. Ti se vektori pohranjuju u vektorsku bazu podataka zajedno s originalnim tekstom i metapodacima, stvarajući indeks za brzu pretragu po sličnosti.
Faza 2: Dohvat (Retrieval)
Kad korisnik postavi upit, sustav ga najprije pretvara u vektor koristeći isti embedding model. Potom izvodi pretragu po sličnosti nad vektorskom bazom, pronalazeći segmente čiji su vektori najbliži vektoru upita. Rezultat je skup najrelevantnijih tekstualnih fragmenata — obično 3 do 10 segmenata — koji služe kao kontekst za generiranje odgovora. U naprednijim sustavima ova faza uključuje i hibridnu pretragu, filtriranje po metapodacima te ponovno rangiranje rezultata.
Faza 3: Generiranje (Generation)
U završnoj fazi dohvaćeni kontekst ubacuje se u prompt zajedno s korisničkim upitom. Jezični model tada generira odgovor utemeljen na stvarnim podacima iz vaše baze znanja, umjesto da se oslanja isključivo na znanje stečeno tijekom treniranja. Ova utemeljenost drastično smanjuje halucinacije i omogućuje rad s informacijama koje model nikad nije vidio tijekom treninga.
Vizualno, tok izgleda otprilike ovako: Korisnikov upit → Ugrađivanje upita → Vektorska pretraga → Dohvat top-K segmenata → Konstrukcija prompta (upit + kontekst) → LLM generiranje → Odgovor korisniku. Svaki od ovih koraka nudi priliku za optimizaciju, a razlika između prototipa i produkcijskog sustava često se krije upravo u detaljima implementacije.
Ono što RAG čini posebno moćnim jest mogućnost ažuriranja znanja bez ponovnog treniranja modela. Jednostavno dodajte nove dokumente u indeks i sustav ih odmah može koristiti. To je ogromna prednost nad fine-tuningom, pogotovo u domenama gdje se znanje brzo mijenja.
Strategije segmentiranja dokumenata
Ovo je jedan od najkritičnijih koraka u cijelom cjevovodu, a istovremeno jedan od najčešće podcijenjenih. Način na koji razbijete dokument izravno utječe na kvalitetu dohvata — i posljedično na točnost generiranog odgovora. Loše segmentiranje vodi do zagubljenog konteksta, irelevantnih rezultata i frustriranih korisnika.
Istraživanja pokazuju da optimizirane strategije segmentiranja smanjuju broj potrebnih poziva za dohvat za 30% do 40%. To znači bolja latencija i niži troškovi — dvostruka pobjeda.
Segmentiranje fiksne veličine
Najjednostavniji pristup. Tekst se dijeli na dijelove unaprijed određene veličine — tipično 256 do 512 tokena — s opcionalnim preklapanjem između susjednih segmenata. Preklapanje (obično 10-20% veličine segmenta) osigurava da se kontekst ne gubi na granicama. Prednost? Jednostavnost i predvidljivost. Nedostatak? Segmenti često presjecaju rečenice, pa čak i riječi, što narušava semantičku cjelovitost.
Semantičko segmentiranje
Ovdje stvari postaju pametnije. Semantičko segmentiranje koristi modele za ugrađivanje da odredi prirodne semantičke granice unutar teksta. Umjesto rezanja na fiksnim pozicijama, sustav analizira sličnost uzastopnih rečenica i postavlja granice tamo gdje dolazi do značajne promjene teme. Rezultat su koherentniji segmenti koji bolje odgovaraju na specifične upite. Cijena je, naravno, veća računalna složenost jer zahtijeva ugrađivanje svake rečenice tijekom indeksiranja.
Hijerarhijsko segmentiranje
Ovo je, po mom mišljenju, najelegantniji pristup. Hijerarhijsko segmentiranje kombinira prednosti malih i velikih segmenata putem strukture roditelj-dijete. Mali segmenti (100-256 tokena) koriste se za precizno semantičko podudaranje, dok se veći roditeljski segmenti (1024+ tokena) prosljeđuju jezičnom modelu za potpunije razumijevanje konteksta.
Ovo elegantno rješava temeljnu napetost u dizajnu RAG sustava: semantičko podudaranje traži male, fokusirane segmente, dok razumijevanje konteksta zahtijeva veće, cjelovitije tekstualne blokove.
Napetost između preciznosti i konteksta
Segment od 100 tokena može savršeno odgovarati na upit, ali jezičnom modelu nedostaje okolni kontekst za potpuni odgovor. S druge strane, segment od 2000 tokena pruža bogat kontekst, ali je manje precizan u semantičkom podudaranju. Nema univerzalnog rješenja — optimalna strategija ovisi o vrsti dokumenata, tipovima upita i zahtjevima vaše domene. I to je okej. Eksperimentirajte.
Evo praktičnog primjera semantičkog segmentiranja koristeći LangChain:
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
# Inicijalizacija modela za ugrađivanje
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# Kreiranje semantičkog segmentatora
# breakpoint_threshold_type kontrolira osjetljivost na promjenu teme
chunker = SemanticChunker(
embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=75, # Viši prag = veći segmenti
)
# Učitavanje i segmentiranje dokumenta
with open("dokument.txt", "r", encoding="utf-8") as f:
tekst = f.read()
segmenti = chunker.create_documents([tekst])
for i, segment in enumerate(segmenti):
print(f"Segment {i+1} ({len(segment.page_content)} znakova):")
print(segment.page_content[:150] + "...")
print("---")
# Za produkciju: dodajte metapodatke svakom segmentu
for i, segment in enumerate(segmenti):
segment.metadata.update({
"izvor": "dokument.txt",
"redni_broj_segmenta": i,
"ukupno_segmenata": len(segmenti),
"datum_indeksiranja": "2026-02-07"
})
Ovaj pristup automatski pronalazi semantičke granice analizirajući sličnost između uzastopnih rečenica. Parametar breakpoint_threshold_amount omogućuje fino podešavanje — niže vrijednosti stvaraju manje, preciznije segmente, dok više vrijednosti proizvode veće, kontekstualno bogatije blokove.
Vektorske baze podataka: Usporedba i odabir
Vektorska baza podataka srce je svakog RAG sustava. U njoj se pohranjuju ugrađivanja vaših dokumenata i izvodi pretraga po sličnosti. Odabir prave baze značajno utječe na performanse, troškove i operativnu složenost — i iskreno, to je odluka koju ne želite donijeti olako. U 2026. tržište nudi zrela rješenja za svaki scenarij, od laganog prototipiranja do enterprise implementacija s milijardama vektora.
Pregled ključnih opcija
Pinecone je potpuno upravljana serverless vektorska baza koja se ističe izuzetno niskim operativnim opterećenjem. S p99 latencijom od oko 30 milisekundi, Pinecone je idealan za timove koji žele eliminirati brigu o infrastrukturi. Podržava filtriranje po metapodacima, namespaceove za izolaciju podataka i automatsko skaliranje. Jedini veći nedostatak? Vendor lock-in i potencijalno viši troškovi pri velikim količinama podataka.
Weaviate je open-source rješenje s opcijom upravljane usluge u oblaku. Posebno se ističe snažnom hibridnom pretragom koja kombinira vektorsku i ključnu pretragu unutar jednog poziva. GraphQL API pruža fleksibilnost, a modularna arhitektura omogućuje integraciju raznih modela za ugrađivanje. Odličan izbor za timove kojima trebaju fleksibilnost i kontrola.
Qdrant, napisan u Rustu, poznat je po visokoj memorijskoj učinkovitosti i izvrsnim performansama. Podržava napredne opcije filtriranja, kvantizaciju za smanjenje memorijskog otiska i gRPC API za niske latencije. Idealan za sustave osjetljive na troškove s velikim brojem vektora.
Chroma je lagana, developer-friendly baza savršena za brzo prototipiranje i lokalni razvoj. Jednostavan Python API, minimalna konfiguracija — pokrenete se za par minuta. Ali za produkciju većeg opsega, iskreno, postoje bolje opcije.
pgvector je PostgreSQL ekstenzija koja dodaje vektorsku pretragu u vašu postojeću relacijsku bazu. Ako već koristite Postgres (a tko ne koristi?), pgvector eliminira potrebu za zasebnom bazom. Doduše, skalabilnost i performanse pri velikim kolekcijama zaostaju za specijaliziranim rješenjima.
Tablica usporedbe
| Baza | Tip | Hibridna pretraga | Latencija (p99) | Idealno za |
|---|---|---|---|---|
| Pinecone | Upravljana (serverless) | Da (sparse-dense) | ~30ms | Timove bez DevOps kapaciteta |
| Weaviate | OSS + upravljana | Da (ugrađena) | ~40ms | Fleksibilne enterprise sustave |
| Qdrant | OSS + cloud | Da (sparse vektori) | ~25ms | Troškovno osjetljive radne zadatke |
| Chroma | OSS | Ne | Varijabilna | Prototipiranje i razvoj |
| pgvector | PostgreSQL ekstenzija | Ručno (s FTS) | ~50-100ms | Postojeće Postgres sustave |
Evo primjera povezivanja s Qdrant bazom i umetanja vektora:
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct, Filter,
FieldCondition, MatchValue
)
import openai
import uuid
# Povezivanje s Qdrant instancom
klijent = QdrantClient(
url="https://vas-qdrant-cluster.cloud.qdrant.io",
api_key="vas-api-kljuc",
)
# Kreiranje kolekcije s vektorskim parametrima
klijent.recreate_collection(
collection_name="baza_znanja",
vectors_config=VectorParams(
size=3072, # Dimenzija za text-embedding-3-large
distance=Distance.COSINE,
),
)
# Generiranje ugrađivanja putem OpenAI
oai_klijent = openai.OpenAI()
def generiraj_ugradivanje(tekstovi: list[str]) -> list[list[float]]:
odgovor = oai_klijent.embeddings.create(
model="text-embedding-3-large",
input=tekstovi,
)
return [item.embedding for item in odgovor.data]
# Priprema segmenata za umetanje
segmenti = [
{"tekst": "RAG sustavi koriste vektorsku pretragu...", "izvor": "prirucnik.pdf", "stranica": 12},
{"tekst": "Hibridni dohvat kombinira BM25 i vektore...", "izvor": "prirucnik.pdf", "stranica": 34},
{"tekst": "Evaluacija koristi RAGAS metriku...", "izvor": "blog.md", "stranica": 1},
]
tekstovi = [s["tekst"] for s in segmenti]
ugradivanja = generiraj_ugradivanje(tekstovi)
# Umetanje točaka u kolekciju
tocke = [
PointStruct(
id=str(uuid.uuid4()),
vector=vektor,
payload={
"tekst": segment["tekst"],
"izvor": segment["izvor"],
"stranica": segment["stranica"],
},
)
for vektor, segment in zip(ugradivanja, segmenti)
]
klijent.upsert(collection_name="baza_znanja", points=tocke)
# Pretraga s filtriranjem po metapodacima
upit = "Kako funkcionira hibridni dohvat?"
vektor_upita = generiraj_ugradivanje([upit])[0]
rezultati = klijent.search(
collection_name="baza_znanja",
query_vector=vektor_upita,
query_filter=Filter(
must=[FieldCondition(key="izvor", match=MatchValue(value="prirucnik.pdf"))]
),
limit=5,
)
for rezultat in rezultati:
print(f"Ocjena: {rezultat.score:.4f} | {rezultat.payload['tekst'][:80]}...")
Hibridni dohvat i ponovno rangiranje
Čista vektorska pretraga, koliko god bila sofisticirana, ima svoja ograničenja. Embedding modeli izvrsno hvataju semantičku sličnost — razumiju da su "automobil" i "vozilo" povezani koncepti — ali mogu zakazati kod preciznih terminoloških upita, brojčanih vrijednosti ili specifičnih identifikatora. Korisnik koji traži "greška ERR-4052 u modulu za plaćanje" treba egzaktno podudaranje, ne semantičku interpretaciju.
Upravo zato hibridni dohvat postaje standard u produkcijskim RAG sustavima.
Kombinacija BM25 i vektorske pretrage
BM25 je klasični algoritam temeljen na frekvenciji termina — izvrsno radi za egzaktno i djelomično podudaranje ključnih riječi. Vektorska pretraga hvata semantičko značenje, razumije sinonime, parafraze i konceptualne veze. Kombinirajte ih i dobijete sustav koji pokriva oba scenarija — kad korisnik koristi precizne termine i kad opisuje koncept drugačijim riječima.
U praksi, korisnički upit se paralelno šalje na oba sustava pretrage. Svaki vraća rangirani popis rezultata, a zatim se ti popisi spajaju algoritmom za fuziju rangova.
Recipročna fuzija rangova (RRF)
Reciprocal Rank Fusion (RRF) elegantan je algoritam za spajanje rezultata iz različitih sustava. Za svaki dokument, ocjena se računa kao suma recipročnih vrijednosti njegova ranga: RRF(d) = Σ 1/(k + rang(d)), gdje je k konstanta (tipično 60). Ljepota RRF-a je u tome što ne ovisi o apsolutnim ocjenama — koristi samo relativni rang, čime izbjegava problem neusporedivosti ocjena između BM25 i vektorske pretrage.
Ponovno rangiranje s cross-encoder modelima
Nakon spajanja rezultata dolazi ponovno rangiranje (reranking) putem cross-encoder modela. Za razliku od bi-encodera koji kodiraju upit i dokument odvojeno, cross-encoder istovremeno procesira par upit-dokument, što mu daje mnogo finije razumijevanje relevantnosti. Ovo je računalno skuplje, pa se primjenjuje samo na top-N rezultata (obično 20-50) iz hibridne pretrage.
Reranking modeli poput Cohere Rerank, BGE Reranker ili Jina Reranker mogu dramatično poboljšati preciznost. Istraživanja pokazuju poboljšanje od 15% do 25% na metrici nDCG@10 u usporedbi s čistom vektorskom pretragom. To nije zanemarivo.
Pogledajmo implementaciju kompletnog hibridnog cjevovoda s Cohere ponovnim rangiranjem:
import cohere
from rank_bm25 import BM25Okapi
from qdrant_client import QdrantClient
import numpy as np
class HibridniDohvat:
def __init__(self, qdrant_url: str, qdrant_api_key: str, cohere_api_key: str):
self.qdrant = QdrantClient(url=qdrant_url, api_key=qdrant_api_key)
self.cohere = cohere.ClientV2(api_key=cohere_api_key)
self.kolekcija = "baza_znanja"
self.dokumenti = []
self.bm25 = None
def izgradi_bm25_indeks(self, dokumenti: list[dict]):
"""Izgradi BM25 indeks nad tekstualnim segmentima."""
self.dokumenti = dokumenti
tokenizirani = [doc["tekst"].lower().split() for doc in dokumenti]
self.bm25 = BM25Okapi(tokenizirani)
def bm25_pretraga(self, upit: str, top_k: int = 20) -> list[tuple[int, float]]:
"""Vrati top-K rezultata iz BM25 pretrage."""
tokeni_upita = upit.lower().split()
ocjene = self.bm25.get_scores(tokeni_upita)
top_indeksi = np.argsort(ocjene)[-top_k:][::-1]
return [(idx, ocjene[idx]) for idx in top_indeksi if ocjene[idx] > 0]
def vektorska_pretraga(self, vektor_upita: list[float], top_k: int = 20) -> list[dict]:
"""Vrati top-K rezultata iz vektorske pretrage."""
rezultati = self.qdrant.search(
collection_name=self.kolekcija,
query_vector=vektor_upita,
limit=top_k,
)
return rezultati
def rrf_fuzija(self, bm25_rezultati, vektorski_rezultati, k=60):
"""Spoji rezultate korištenjem Reciprocal Rank Fusion."""
rrf_ocjene = {}
for rang, (idx, _) in enumerate(bm25_rezultati):
doc_id = self.dokumenti[idx]["id"]
rrf_ocjene[doc_id] = rrf_ocjene.get(doc_id, 0) + 1 / (k + rang + 1)
for rang, rezultat in enumerate(vektorski_rezultati):
doc_id = rezultat.id
rrf_ocjene[doc_id] = rrf_ocjene.get(doc_id, 0) + 1 / (k + rang + 1)
sortirani = sorted(rrf_ocjene.items(), key=lambda x: x[1], reverse=True)
return sortirani
def ponovno_rangiraj(self, upit: str, dokumenti: list[str], top_n: int = 5):
"""Ponovno rangiraj korištenjem Cohere cross-encoder modela."""
odgovor = self.cohere.rerank(
model="rerank-v3.5",
query=upit,
documents=dokumenti,
top_n=top_n,
)
return odgovor.results
def dohvati(self, upit: str, vektor_upita: list[float], top_k: int = 5):
"""Kompletni hibridni dohvat s ponovnim rangiranjem."""
# Korak 1: Paralelna pretraga
bm25_rez = self.bm25_pretraga(upit, top_k=20)
vektorski_rez = self.vektorska_pretraga(vektor_upita, top_k=20)
# Korak 2: RRF fuzija
spojeni = self.rrf_fuzija(bm25_rez, vektorski_rez)
# Korak 3: Priprema dokumenata za reranking
kandidati = []
for doc_id, _ in spojeni[:30]: # Top 30 za reranking
doc = next((d for d in self.dokumenti if d["id"] == doc_id), None)
if doc:
kandidati.append(doc["tekst"])
# Korak 4: Ponovno rangiranje
rerankirani = self.ponovno_rangiraj(upit, kandidati, top_n=top_k)
konacni = []
for r in rerankirani:
konacni.append({
"tekst": kandidati[r.index],
"ocjena_relevantnosti": r.relevance_score,
})
return konacni
Ovaj cjevovod pokazuje kako se tri tehnike — ključna pretraga, vektorska pretraga i ponovno rangiranje — slažu u koherentni sustav. U produkciji biste još dodali asinkrono izvršavanje paralelnih pretraga, predmemoriranje čestih upita i bilježenje metrika za svaki korak.
Napredne RAG arhitekture
Osnovni RAG obrazac — dohvati-pa-generiraj — ima jedno fundamentalno ograničenje: tretira svaki upit jednako i slijepo ubacuje dohvaćeni kontekst u prompt bez provjere relevantnosti. To je otprilike kao da svaku bolest liječite istim lijekom. Napredne arhitekture uvode inteligenciju u sam proces dohvata, omogućujući sustavu da evaluira, korigira i prilagođava svoj pristup ovisno o situaciji.
U 2026., ove arhitekture više nisu istraživački koncepti — one su produkcijska stvarnost.
Self-RAG (Samoregulativni RAG)
Self-RAG uvodi koncept samorefleksije u RAG cjevovod. Model ne samo da generira odgovor, već u svakom koraku donosi ključne odluke: Trebam li uopće dohvatiti kontekst za ovaj upit? Ako da, jesu li dohvaćeni dokumenti zapravo relevantni? I konačno, je li moj odgovor podržan dohvaćenim dokazima?
Ova sposobnost samoevaluacije omogućuje modelu da izbjegne ubacivanje irelevantnog konteksta (što, vjerovali ili ne, može zapravo pogoršati kvalitetu odgovora) i da detektira kada njegov vlastiti odgovor nije dovoljno utemeljen.
U praksi, Self-RAG koristi posebne refleksijske tokene tijekom generiranja koji signaliziraju procjenu relevantnosti, podržanosti i korisnosti. Model uči generirati te tokene tijekom fine-tuninga, čime dobiva sposobnost kritičke samoevaluacije bez dodatnog vanjskog poziva.
Corrective RAG (CRAG) — Korektivni RAG
CRAG ide korak dalje fokusirajući se specifično na korekciju procesa dohvata. Sustav evaluira svaki dohvaćeni dokument pomoću laganog evaluatora i klasificira ga kao relevantan, djelomično relevantan ili irelevantan. Ako su svi dokumenti irelevantni, CRAG pokreće alternativne strategije — recimo preformulaciju upita ili čak web pretragu. Ako su samo djelomično relevantni, izdvaja korisne dijelove i odbacuje šum.
Ključna inovacija? Petlja povratne informacije. Sustav ne prihvaća prvi rezultat pretrage kao konačan, nego ga aktivno ispituje i po potrebi poboljšava. Posebno korisno za složene upite koji zahtijevaju informacije iz više izvora ili za domene s neujednačenom kvalitetom dokumentacije.
Graph RAG (Grafovski RAG)
Graph RAG koristi grafove znanja kao posrednu strukturu. Umjesto pretrage sirovih tekstualnih segmenata, sustav najprije gradi graf entiteta i njihovih odnosa iz korpusa dokumenata. Za odgovaranje na upite koristi zajednice unutar grafa za generiranje sažetaka na razini tema, a zatim ih kombinira za konačni odgovor.
Ova arhitektura posebno blista kod upita na razini teme — primjerice "Koji su glavni trendovi u regulativi financijskog sektora?" — gdje tradicionalni RAG zakazuje jer ne može spojiti informacije raspršene kroz stotine dokumenata. Također je iznimno koristan za pitanja o odnosima između entiteta, kauzalnim lancima i vremenskim sekvencama.
Agentički RAG
Ovo je najnapredniji oblik — sustav u kojem jezični model funkcionira kao autonomni agent s pristupom alatima za dohvat. Agent može planirati složene strategije dohvata, koristiti različite izvore podataka, reflektirati o kvaliteti dohvaćenih informacija i iterativno usavršavati svoj pristup.
Za razliku od statičnog cjevovoda, agentički sustav adaptivno reagira na svaki upit. Jednostavne upite rješava jednim dohvatom, dok za složene orchestrira višestruke pretrage, kombinira rezultate i sintetizira odgovor iz različitih perspektiva.
Agentički RAG tipično uključuje četiri komponente: planiranje (dekompozicija složenog upita na podupite), korištenje alata (vektorska pretraga, SQL upiti, API pozivi, web pretraga), refleksiju (provjera potpunosti i konzistentnosti) i suradnju više agenata (specijalizirani agenti za različite domene).
Pogledajmo implementaciju Corrective RAG-a koristeći LangGraph:
from langgraph.graph import StateGraph, END
from typing import TypedDict
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
class StanjeGrafa(TypedDict):
upit: str
dokumenti: list[str]
ocjene_relevantnosti: list[float]
preformulirani_upit: str | None
konacni_odgovor: str
korak: str
llm = ChatOpenAI(model="gpt-4o", temperature=0)
def dohvati_dokumente(stanje: StanjeGrafa) -> StanjeGrafa:
"""Dohvati dokumente iz vektorske baze."""
upit = stanje.get("preformulirani_upit") or stanje["upit"]
# Ovdje biste koristili stvarni sustav za dohvat
dokumenti = hibridni_dohvat.dohvati(upit, top_k=5)
stanje["dokumenti"] = [d["tekst"] for d in dokumenti]
stanje["korak"] = "evaluacija"
return stanje
def evaluiraj_relevantnost(stanje: StanjeGrafa) -> StanjeGrafa:
"""Evaluiraj relevantnost svakog dohvaćenog dokumenta."""
prompt = ChatPromptTemplate.from_template(
"Na skali 0-1, koliko je sljedeći dokument relevantan za upit?\n"
"Upit: {upit}\nDokument: {dokument}\n"
"Vrati SAMO broj između 0 i 1."
)
ocjene = []
for doc in stanje["dokumenti"]:
odgovor = llm.invoke(prompt.format(upit=stanje["upit"], dokument=doc))
try:
ocjena = float(odgovor.content.strip())
except ValueError:
ocjena = 0.0
ocjene.append(ocjena)
stanje["ocjene_relevantnosti"] = ocjene
return stanje
def odluci_o_akciji(stanje: StanjeGrafa) -> str:
"""Odluči sljedeći korak na temelju ocjena relevantnosti."""
ocjene = stanje["ocjene_relevantnosti"]
prosjecna = sum(ocjene) / len(ocjene) if ocjene else 0
if prosjecna >= 0.6:
return "generiraj" # Dokumenti su dovoljno relevantni
elif stanje.get("preformulirani_upit"):
return "generiraj" # Već smo preformulirali, koristi što imamo
else:
return "preformuliraj" # Pokušaj s drugačijim upitom
def preformuliraj_upit(stanje: StanjeGrafa) -> StanjeGrafa:
"""Preformuliraj upit za bolji dohvat."""
prompt = ChatPromptTemplate.from_template(
"Originalni upit nije dao dovoljno relevantne rezultate.\n"
"Originalni upit: {upit}\n"
"Preformuliraj upit tako da bude specifičniji i precizniji. "
"Vrati SAMO preformulirani upit."
)
odgovor = llm.invoke(prompt.format(upit=stanje["upit"]))
stanje["preformulirani_upit"] = odgovor.content.strip()
stanje["korak"] = "ponovni_dohvat"
return stanje
def generiraj_odgovor(stanje: StanjeGrafa) -> StanjeGrafa:
"""Generiraj konačni odgovor koristeći relevantne dokumente."""
# Filtriraj samo dovoljno relevantne dokumente
relevantni = [
doc for doc, ocjena in zip(stanje["dokumenti"], stanje["ocjene_relevantnosti"])
if ocjena >= 0.4
]
kontekst = "\n\n---\n\n".join(relevantni) if relevantni else "Nema relevantnih dokumenata."
prompt = ChatPromptTemplate.from_template(
"Odgovori na korisnikov upit koristeći ISKLJUČIVO informacije iz konteksta.\n"
"Ako kontekst ne sadrži dovoljno informacija, jasno to naznači.\n\n"
"Kontekst:\n{kontekst}\n\n"
"Upit: {upit}\n\nOdgovor:"
)
odgovor = llm.invoke(prompt.format(kontekst=kontekst, upit=stanje["upit"]))
stanje["konacni_odgovor"] = odgovor.content
return stanje
# Izgradnja grafa
graf = StateGraph(StanjeGrafa)
graf.add_node("dohvat", dohvati_dokumente)
graf.add_node("evaluacija", evaluiraj_relevantnost)
graf.add_node("preformulacija", preformuliraj_upit)
graf.add_node("generiranje", generiraj_odgovor)
graf.set_entry_point("dohvat")
graf.add_edge("dohvat", "evaluacija")
graf.add_conditional_edges("evaluacija", odluci_o_akciji, {
"generiraj": "generiranje",
"preformuliraj": "preformulacija",
})
graf.add_edge("preformulacija", "dohvat")
graf.add_edge("generiranje", END)
# Kompilacija i pokretanje
aplikacija = graf.compile()
rezultat = aplikacija.invoke({
"upit": "Koje su beste prakse za segmentiranje dokumenata u RAG sustavima?",
"dokumenti": [],
"ocjene_relevantnosti": [],
"preformulirani_upit": None,
"konacni_odgovor": "",
"korak": "početak",
})
print(rezultat["konacni_odgovor"])
Ovaj graf automatski detektira nekvalitetan dohvat i pokreće korektivni ciklus — preformulira upit i pokušava ponovno. U produkcijskoj verziji dodali biste ograničenje broja iteracija, logiranje svakog koraka i fallback mehanizme za slučaj kada ni preformulacija ne daje rezultate.
Evaluacija RAG sustava
Bez rigorozne evaluacije, svaka optimizacija RAG sustava je — budimo iskreni — čisto nagađanje. Mjerenje kvalitete RAG-a posebno je izazovno jer uključuje dva odvojena aspekta: kvalitetu dohvata i kvalitetu generiranja. I oba se moraju evaluirati zasebno i zajedno.
U 2026. godini, RAGAS okvir (Retrieval-Augmented Generation Assessment) etablirao se kao de facto standard za automatiziranu evaluaciju RAG sustava.
Ključne metrike
Preciznost konteksta (Context Precision) mjeri koliko su dohvaćeni dokumenti relevantni za postavljeni upit. Visoka preciznost znači da sustav ne ubacuje irelevantni šum u kontekst — a to je kritično za kvalitetu generiranja.
Odziv konteksta (Context Recall) mjeri koliko informacija potrebnih za odgovor zapravo postoji u dohvaćenom kontekstu. Nizak odziv znači da sustav propušta važne informacije, što vodi do nepotpunih odgovora. Posebno je bitan za upite koji zahtijevaju sintezu iz više izvora.
Vjernost (Faithfulness) evaluira koliko je generirani odgovor utemeljen u dohvaćenom kontekstu. Ova metrika direktno mjeri halucinacije — tvrdnje u odgovoru koje nisu podržane kontekstom. Vrijednost od 1.0 znači da je svaka tvrdnja podržana dokazima.
Relevantnost odgovora (Answer Relevancy) mjeri koliko generirani odgovor zapravo adresira postavljeno pitanje. Odgovor može biti istinit i utemeljen, ali svejedno irelevantan ako ne odgovara na ono što je korisnik zapravo pitao. I da, to se događa češće nego što biste očekivali.
Evaluacija bez referentnih odgovora
Jedan od najvećih izazova je stvaranje referentnih skupova podataka. LLM-as-judge pristup koristi sam jezični model za ocjenjivanje kvalitete, eliminirajući potrebu za skupim ljudskim anotatorima. Modeli poput GPT-4o ili Claude pokazali su visoku korelaciju s ljudskim procjenama, posebno za metrike poput vjernosti i relevantnosti. Ipak, bitno je koristiti drugi model za evaluaciju od onog koji generira odgovore — inače riskirate pristranost.
Detekcija halucinacija
Detekcija halucinacija temelji se na provjeri utemeljenosti tvrdnji. Svaka tvrdnja iz generiranog odgovora uspoređuje se s dohvaćenim kontekstom. Tvrdnje bez podrške klasificiraju se kao potencijalne halucinacije. Napredniji pristupi razlikuju intrinzične halucinacije (direktna kontradikcija kontekstu) i ekstrinzične halucinacije (informacije koje ni ne potvrđuju ni ne proturječe kontekstu).
Evo primjera evaluacije korištenjem RAGAS biblioteke:
from ragas import evaluate
from ragas.metrics import (
context_precision,
context_recall,
faithfulness,
answer_relevancy,
)
from ragas.dataset_schema import SingleTurnSample, EvaluationDataset
# Priprema evaluacijskog skupa podataka
primjeri = [
SingleTurnSample(
user_input="Što je hibridni dohvat u RAG sustavima?",
retrieved_contexts=[
"Hibridni dohvat kombinira BM25 pretragu ključnih riječi s vektorskom "
"pretragom po semantičkoj sličnosti. Ovo pokriva scenarije egzaktnog "
"podudaranja i semantičkog razumijevanja.",
"Reciprocal Rank Fusion (RRF) algoritam koristi se za spajanje rezultata "
"iz različitih sustava pretrage koristeći recipročne vrijednosti rangova.",
],
response=(
"Hibridni dohvat u RAG sustavima je tehnika koja kombinira dva pristupa "
"pretrazi: BM25 pretragu po ključnim riječima i vektorsku pretragu po "
"semantičkoj sličnosti. Rezultati se spajaju korištenjem RRF algoritma. "
"Ovo osigurava da sustav pokriva i egzaktno podudaranje termina i "
"semantičko razumijevanje upita."
),
reference=(
"Hibridni dohvat kombinira BM25 keyword pretragu s vektorskom semantičkom "
"pretragom, koristeći algoritme poput RRF za spajanje rezultata."
),
),
SingleTurnSample(
user_input="Kako funkcionira Corrective RAG?",
retrieved_contexts=[
"CRAG evaluira dohvaćene dokumente i klasificira ih kao relevantne, "
"djelomično relevantne ili irelevantne. Za irelevantne rezultate "
"sustav preformulira upit i pokušava ponovno.",
],
response=(
"Corrective RAG (CRAG) je napredna arhitektura koja aktivno evaluira "
"kvalitetu dohvaćenih dokumenata. Svaki dokument klasificira se po "
"relevantnosti. Ako su dokumenti irelevantni, sustav automatski "
"preformulira upit i ponovi pretragu, čime se značajno poboljšava "
"kvaliteta konačnog odgovora."
),
reference=(
"CRAG evaluira dohvaćene dokumente, klasificira ih po relevantnosti "
"i koristi korektivne mehanizme poput preformulacije upita za "
"poboljšanje kvalitete dohvata."
),
),
]
eval_dataset = EvaluationDataset(samples=primjeri)
# Pokretanje evaluacije
rezultati = evaluate(
dataset=eval_dataset,
metrics=[
context_precision,
context_recall,
faithfulness,
answer_relevancy,
],
)
# Ispis rezultata
print("=== Rezultati RAGAS evaluacije ===")
print(f"Preciznost konteksta: {rezultati['context_precision']:.4f}")
print(f"Odziv konteksta: {rezultati['context_recall']:.4f}")
print(f"Vjernost: {rezultati['faithfulness']:.4f}")
print(f"Relevantnost odg.: {rezultati['answer_relevancy']:.4f}")
# Detaljni rezultati po primjeru
df = rezultati.to_pandas()
print("\nDetaljni rezultati:")
print(df.to_string(index=False))
Preporučujem evaluaciju pokretati kao dio CI/CD cjevovoda. Svaka promjena u strategiji segmentiranja, modelu za ugrađivanje ili promptu trebala bi proći automatiziranu evaluaciju prije produkcije. Definirajte minimalne pragove za svaku metriku i automatski blokirajte implementacije koje ih ne zadovoljavaju. Zvuči strogo, ali vjerujte mi — spašava od neugodnih iznenađenja.
Produkcijske beste prakse
Prijelaz iz prototipa u produkcijski RAG sustav — to je ono gdje stvari postaju zanimljive (i ponekad bolne). Sustav koji odlično radi na evaluacijskom skupu od 50 upita može potpuno zakazati pod stvarnim opterećenjem, s dinamičnim podacima i raznolikim korisničkim očekivanjima. Evo ključnih praksi koje čine razliku između demonstracije i stvarnog proizvoda.
Svježina podataka i strategije sinkronizacije
Statični indeks brzo zastarijeva. Produkcijski sustavi trebaju inkrementalne strategije ažuriranja — praćenje promjena u izvornim dokumentima, dodavanje novih segmenata, ažuriranje modificiranih i brisanje uklonjenih. Implementirajte sustav za praćenje verzija dokumenata s vremenskim oznakama. Za kritične slučajeve, razmislite o streaming indeksiranju putem reda čekanja koji procesira nove dokumente u gotovo realnom vremenu.
Vodite računa o konzistentnosti — korisnik ne smije dobiti odgovor temeljen na zastarjelim podacima ako je ažurirana verzija dostupna.
Predmemoriranje za ponavljajuće upite
Analize produkcijskih sustava pokazuju da se 30-50% upita semantički ponavlja. To je prilika koju ne smijete propustiti. Implementacija semantičke predmemorije može drastično smanjiti latenciju i troškove. Pristup je jednostavan: za svaki dolazni upit provjerite postoji li semantički sličan upit u predmemoriji. Ako je sličnost iznad praga (tipično 0.95), vratite predmemorirani odgovor.
Koristite TTL (Time to Live) za automatsku invalidaciju zastarjelih zapisa i omogućite ručnu invalidaciju kad se baza znanja promijeni.
import hashlib
import json
import time
from qdrant_client import QdrantClient
class SemantickaPrememorija:
"""Semantička predmemorija za RAG odgovore."""
def __init__(self, qdrant: QdrantClient, ttl_sekundi: int = 3600):
self.qdrant = qdrant
self.kolekcija = "cache_upita"
self.ttl = ttl_sekundi
def dohvati(self, vektor_upita: list[float], prag: float = 0.95):
"""Provjeri postoji li semantički sličan upit u predmemoriji."""
rezultati = self.qdrant.search(
collection_name=self.kolekcija,
query_vector=vektor_upita,
limit=1,
score_threshold=prag,
)
if rezultati:
zapis = rezultati[0]
timestamp = zapis.payload.get("timestamp", 0)
if time.time() - timestamp < self.ttl:
return {
"odgovor": zapis.payload["odgovor"],
"iz_predmemorije": True,
"izvorna_ocjena": zapis.score,
}
return None
def spremi(self, vektor_upita: list[float], upit: str, odgovor: str):
"""Spremi odgovor u predmemoriju."""
from qdrant_client.models import PointStruct
import uuid
tocka = PointStruct(
id=str(uuid.uuid4()),
vector=vektor_upita,
payload={
"upit": upit,
"odgovor": odgovor,
"timestamp": time.time(),
},
)
self.qdrant.upsert(collection_name=self.kolekcija, points=[tocka])
Nadzor i opservabilnost
Produkcijski RAG sustav bez nadzora je crna kutija. I to nije metafora — doslovno ne znate što se događa unutra. Pratite ove metrike:
- Latencija po fazi — ugrađivanje upita, vektorska pretraga, ponovno rangiranje, LLM generiranje. Identificirajte uska grla.
- Kvaliteta dohvata — prosječna ocjena relevantnosti, postotak upita bez relevantnih rezultata (tzv. "prazni dohvati").
- Praćenje troškova — broj tokena po upitu, troškovi API poziva za ugrađivanje, reranking i generiranje. Postavite alarme za anomalije.
- Korisničke povratne informacije — palac gore/dolje, ocjene odgovora, prijave netočnosti. Dugoročno, ovo je najvažnija metrika.
Integrirajte alate poput LangSmith, Phoenix (Arize) ili Langfuse za praćenje svakog poziva kroz cijeli cjevovod. Svaki upit treba imati jedinstveni identifikator koji omogućuje reprodukciju i dijagnostiku problema.
Sigurnost i kontrola pristupa
RAG sustavi često pristupaju osjetljivim korporativnim dokumentima. Sigurnost nije opcionalna:
- Kontrola pristupa na razini dokumenta — osigurajte da korisnik vidi samo dokumente kojima ima pravo pristupa. Implementirajte ovo putem filtriranja metapodataka u vektorskoj bazi, ne naknadnim filtriranjem rezultata (to je kasno).
- PII filtriranje — detektirajte i maskirajte osobne podatke (OIB, IBAN, e-mail adrese) prije pohrane u vektorsku bazu i prije slanja konteksta LLM-u.
- Revizijski trag — bilježite tko je postavio koji upit, koji dokumenti su dohvaćeni i koji odgovor je generiran. U mnogim industrijama to je regulatorni zahtjev.
- Zaštita od ubacivanja prompta — validirajte korisničke upite i dohvaćeni kontekst protiv pokušaja prompt injectiona.
Skaliranje sustava
Kako sustav raste, pojavljuju se izazovi na svakoj razini:
- Asinkrono procesiranje — koristite redove čekanja (Celery, RabbitMQ, SQS) za pozadinske zadatke indeksiranja. Nikada ne blokirajte korisničke upite dugotrajnim operacijama.
- Skupno ugrađivanje (batch embedding) — grupiranje dokumenata za istovremeno ugrađivanje značajno smanjuje vrijeme i troškove.
- Dijeljenje indeksa (index sharding) — za kolekcije s više od 10 milijuna vektora, podijelite indeks po logičkim kriterijima (domena, jezik, vremensko razdoblje).
- Horizontalno skaliranje — višestruke replike vektorske baze za čitanje i balanser opterećenja za distribuciju upita.
Upravljanje greškama i rezervni mehanizmi
Robusni sustavi moraju graciozno podnijeti kvarove. Evo nekoliko scenarija na koje treba biti spreman:
- Vektorska baza ne odgovara? Koristite lokalni predmemorirani indeks ili BM25 pretragu kao rezervu.
- LLM API vraća grešku? Implementirajte fallback chain s alternativnim modelom.
- Dohvat ne vraća relevantne rezultate? Jasno komunicirajte korisniku umjesto generiranja nepotkrijepljenog odgovora. Ovo je važnije nego što se čini.
- Latencija je previsoka? Razmislite o manjem, bržem modelu za jednostavnije upite.
Implementirajte circuit breaker obrazac — ako komponenta kontinuirano zakazuje, privremeno je isključite i koristite rezervni put. Beskrajni ponovni pokušaji samo opterećuju neispravan servis.
Zaključak
RAG je u 2026. nesumnjivo temeljna tehnologija za enterprise AI. Od jednostavnog obrasca za smanjenje halucinacija, evoluirao je u sofisticirani ekosustav arhitektura, alata i praksi. Ali ta sofisticiranost ne bi trebala zastrašiti — upravo suprotno.
Ako iz ovog članka ponesete samo jednu stvar, neka bude ova: počnite jednostavno i dodajte složenost inkrementalno. Vaš prvi RAG sustav ne treba agentičku arhitekturu, grafove znanja ni petnaest faza obrade. Počnite s osnovnim cjevovodom — kvalitetno segmentiranje, pouzdana vektorska baza i jasno strukturirani promptovi. Mjerite performanse. Identificirajte uska grla. I tek tada dodajte hibridni dohvat, ponovno rangiranje ili korektivne mehanizme — tamo gdje podaci pokazuju da su potrebni.
Evo ključnih zaključaka:
- Kvaliteta dohvata važnija je od kvalitete generiranja — uložite 70% vremena u optimizaciju segmentiranja, indeksiranja i pretrage.
- Hibridni dohvat s ponovnim rangiranjem trebao bi biti standard, ne iznimka. Poboljšanje od 15-25% na nDCG@10 govori samo za sebe.
- Evaluacija nije opcionalna — integrirajte RAGAS u CI/CD od prvog dana. Bez mjerenja, ne znate idete li naprijed ili nazad.
- Produkcija zahtijeva opservabilnost — pratite latenciju, kvalitetu, troškove i korisničke povratne informacije.
- Sigurnost od prvog dana — kontrola pristupa, PII filtriranje i revizijski tragovi moraju biti ugrađeni u arhitekturu, ne dodani naknadno.
Gledajući prema budućnosti, tri trenda oblikovat će sljedeću generaciju RAG sustava. Multimodalni RAG proširuje dohvat na slike, tablice, dijagrame i audio. RAG u realnom vremenu sa streaming indeksiranjem omogućit će primjene u financijama i kriznom upravljanju. A dublja integracija s agentičkim sustavima pretvorit će RAG iz pasivnog cjevovoda u aktivnog sudionika u složenim poslovnim procesima — agenti koji ne samo dohvaćaju znanje, već ga sintetiziraju, validiraju i primjenjuju.
Bez obzira gradite li prvi prototip ili optimizirate enterprise sustav, osnove ostaju iste: kvalitetni podaci, pametno segmentiranje, robustan dohvat, rigorozna evaluacija i neprestano usavršavanje. RAG nije destinacija — to je putovanje. I svaka iteracija donosi mjerljivo poboljšanje.