Šef dao čvrstu naredbu: rok jedan dan, preobrazi RAG u Agent, bunio sam se bez uspeha, samo da izvadim Claude Code + DeepSeek i energično radim
Odmah posle raspusta, šef dao čvrstu naredbu: "PaiSmart trenutno radi samo RAG pitanja, sada, odmah, nadogradi ga u Agent-a — da sam radi tool use, da sam radi ReAct. Rok ti je jedan dan."
Jedan dan? Odmah sam se nadurio: jedan dan? Zar misliš da je Agent stvar koja se rešava dodavanjem plagina?
Ali bunjenje nije pomoglo (jao).
Šef ne poznaje tehnologiju, samo zna da je na tržištu "Agent" svuda, svi projekti moraju da vežu za Agent-a.
Zaista sam se nasmejao.
Srećom, ranije sam istrgnuo Agent projekat sličan Claude Code-u, po imenu PaiCLI. Od prve epizode sa ReAct petljom, do desete epizode gde se povezuje na Chrome Devtools MCP.

Već je kompletan Agent CLI proizvod, sa svim pokrenutim pozivima alata, petljom za odlučivanje, upravljanjem memorijom i MCP integracijom.
Vadim Claude Code + DeepSeek V4, krećem.
Kolege koji prate rad, pripremite Claude Code + Opus 4.7 (ili Codex + GPT-5.5, ili Claude Code + GLM-5.1).
PaiSmart RAG-u dodajemo ReAct + tool use, kod je već komitovan u gitcode i kolege ga mogu direktno koristiti.

01. Šta je PaiSmart RAG?
PaiSmart je RAG baza znanja: Spring Boot 3.4 + Elasticsearch 8.10 + DeepSeek API. Jezgro procesa u četiri koraka: korisnik pita → hibridna pretraga → montiranje konteksta → veliki model generiše odgovor.

Ceo ulaz u pitanja je u ChatHandler.java, ključna metoda je processMessage:
public void processMessage(String userId, String userMessage,
WebSocketSession session) {
// 1. Uzmi istoriju razgovora
List<Map<String, String>> history = getConversationHistory(userId);
// 2. Hibridna pretraga: vektorska sličnost + BM25 ključne reči
List<SearchResult> results = hybridSearchService
.searchWithPermission(userMessage, userId, 5);
// 3. Od rezultata pretrage sačini kontekst
String context = buildContext(results);
// 4. Pozovi DeepSeek da generiše odgovor, strimuj nazad
deepSeekClient.streamResponse(userId, userMessage, context,
history, chunk -> sendToWebSocket(session, chunk));
}
Pretraga se radi u HybridSearchService, strategija je da se KNN vektorska pretraga prvo izvuče 30-struki kandidatski skup, zatim BM25 prebodi i konačno radi filtriranje prava:
public List<SearchResult> searchWithPermission(String query,
String userId, int topK) {
// KNN vektorska pretraga (30x kandidatski prozor) + BM25 prebodiranje
SearchResponse response = elasticClient.search(s -> s
.index("knowledge_chunks")
.knn(k -> k.field("embedding").queryVector(embedQuery(query))
.numCandidates(topK * 30).k(topK * 5))
.query(q -> q.bool(b -> b
.should(textMatch(query))
.filter(permissionFilter(userId))
)),
SearchResult.class
);
return parseResults(response, topK);
}Pre nadogradnje na Agent-a, kad korisnik kaže "pomozi mi da u bazi znanja sredim dokumenta o Spring AI u jedan sažetak", PaiSmart može samo da radi jednu pretragu i vrati originalne isečke, bez sposobnosti da dalje prosuđuje "koliko je dokumenata pronađeno, da li da ih objedini u strukturirani sažetak".
Tu je suštinska razlika između RAG-a i Agent-a: RAG je pasivno odgovaranje; Agent je aktivno odlučivanje plus izvršenje, ume sam da radi.
02. Dodavanje poziva alata RAG-u
Prvi korak nadogradnje — omogućiti sistemu da poziva alate radi izvršenja operacija.
Princip je jednostavan: veliki model se opskrbi skupom Tool-ova, gde svaki Tool ima definisano ime, opis funkcije i ulazne parametre. Pošto veliki model primi pitanje korisnika, prvo ocenjuje "da li na ovo pitanje treba odgovoriti pretragom baze znanja, ili pozivom nekog alata za izvršenje".
PaiCLI-jev ToolRegistry je već proveo ovaj patern, evo kako se registrovanje radi:

Veliki model na osnovu opisa ocenjuje da li da pozove taj Tool, zato kvalitet opisa direktno određuje tačnost odlučivanja Agent-a. Ako je opis nejasan, veliki model može izabrati pogrešan alat.
Na PaiSmart-ovoj osnovi moramo registrovati sledeće Tool-ove:
Tool za pretragu dokumenata baze znanja: funkcija ista kao i trenutna RAG pretraga, ali je oblikovan kao Tool, da bi ga Agent po potrebi pozivao.
tools.put("search_knowledge", new Tool(
"search_knowledge",
"Pretraži bazu znanja za relevantnim isečcima dokumenata i vrati najbolje rezultate",
createParameters(
new Param("query", "string", "ključna reč za pretragu", true),
new Param("topK", "integer", "broj rezultata, podrazumevano 5", false)
),
args -> {
int topK = args.containsKey("topK") ?
Integer.parseInt(args.get("topK")) : 5;
List<SearchResult> results = hybridSearchService
.searchWithPermission(args.get("query"), currentUserId, topK);
return formatSearchResults(results);
}
));Tool za generisanje sažetka: generiše strukturirani sažetak preko više pronađenih isečaka — ovo je tipičan "višekoračni kolaborativni" Tool, prvo pretraga, zatim sumiranje.
Tool za povratnu informaciju o odgovoru: ocena korisnika o kvalitetu odgovora se čuva u Redis-u, kasnije se može koristiti za podešavanje težina pretrage i strategije prompt-a.
Tool za statistiku baze znanja ima sličnu logiku, neću lepiti kompletnu šifru.
Kad razumete princip, krenite direktno sa Claude Code/Codex. U korenu PaiSmart projekta izvršite:
U projektu PaiSmart kreiraj klasu AgentToolRegistry, registruj 4 Tool-a:
1. search_knowledge:
- Opis: "Pretraži bazu znanja za isečcima dokumenata relevantnim za pitanje korisnika, pozivaj samo kada korisnik izričito traži pretragu sadržaja baze"
- Parametri: query(string, obavezno), topK(integer, opciono, podrazumevano 5)
- Logika izvršenja: pozovi postojeći HybridSearchService.searchWithPermission(), formatiraj povratni rezultat
2. generate_summary:
- Opis: "Generiši strukturirani sažetak dokumenata baze znanja za zadatu temu, zgodno kada korisnik traži sređivanje, sumiranje, sintezu"
- Parametri: topic(string, obavezno), maxDocs(integer, opciono, podrazumevano 5)
- Logika izvršenja: prvo pozovi HybridSearchService da pretraži relevantne dokumente, zatim pozovi DeepSeekClient.summarize() da generiše sažetak
3. submit_feedback:
- Opis: "Pozovi kada korisnik izrazi zadovoljstvo ili nezadovoljstvo odgovorom, beleži povratnu informaciju za optimizaciju narednih odgovora"
- Parametri: rating(string, obavezno, good/bad), reason(string, opciono)
- Logika izvršenja: upiši u Redis Hash, ključ feedback:{userId}, polje vremenska oznaka, vrednost ocena+razlog
4. knowledge_stats:
- Opis: "Vrati statističke podatke o trenutnoj bazi znanja — ukupan broj dokumenata, ukupan broj isečaka, vreme poslednje izmene"
- Parametri: nema
- Logika izvršenja: upit Elasticsearch index stats + MySQL document count
Napomena:
- Opis Tool-a pišite iz ugla razumevanja velikog modela, izbegavajte nejasne opise koji dovedu do pogrešnog poziva
- generate_summary interno drugi put poziva veliki model radi sumiranja, obratite pažnju na razliku u odnosu na spoljašnju ReAct petlju

Po završetku ovog koraka, PaiSmart postaje od "samo odgovara na pitanja" — "može sam da radi".
Kada korisnik kaže "pomozi mi da sredim i sumiram sadržaj o RAG-u u bazi znanja, sa najviše 3 relevantna isečka, izlaz u strukturiranom sažetku", Agent sam poziva generate_summary, prvo pretražuje 3 isečka, a zatim drugi put poziva model i generiše strukturirani sažetak, a na stranici se prikazuje "ključni zaključci / ključni dokazi / izvodljivi predlozi / pitanja za potvrdu".

Ako korisnik kaže "zadovoljan sam ovim rezultatom pretrage, razlog je što su broj i tema odgovarali", Agent poziva submit_feedback i beleži povratnu informaciju.

03. Dodavanje ReAct petlje za odlučivanje
ReAct je skraćenica od Reasoning + Acting.
Suština je uvesti Agent-a u petlju: razmisli šta treba uraditi → izvrši operaciju → posmatraj rezultat → ponovo razmisli o sledećem koraku, sve do završetka zadatka.

PaiCLI-jev Agent.java sadrži vrlo jasnu implementaciju ReAct petlje, ključna logika u oko 100 redova:

Ključ ovog koda je u while(true) petlji. U svakoj turi, veliki model na osnovu trenutne istorije razgovora (koja uključuje sve prethodne procese razmišljanja i rezultate alata) donosi odluku: da li da nastavi sa pozivom alata ili da direktno da konačan odgovor.
U PaiSmart-ovom scenariju, jedan tipičan višekoračni zadatak izgleda ovako:
Korisnik kaže: "Pretraži bazu znanja — ima li dokumentacije o RAG-u, ako ima, sredi u sažetak."

Proces odlučivanja Agent-a:
Prva tura — razmišlja: korisnik želi da zna ima li u bazi sadržaja o RAG-u, treba prvo da pretražim. Poziva alat search_knowledge sa parametrom query="RAG".
Druga tura — posmatra: pretraga je vratila 4 relevantna dokumenta. Razmišlja: sadržaja ima, korisnik traži sažetak, treba da pozovem alat generate_summary. Poziva generisanje sažetka sa parametrom topic="RAG".
Treća tura — posmatra: sažetak je gotov. Razmišlja: zadatak je završen, mogu vratiti sažetak korisniku. Vraća konačan odgovor, uključujući strukturirani sažetak znanja o RAG-u.
Tri ture petlje, bez ljudskog uplitanja — Agent sam dovršava ceo proces "pretraga → prosuđivanje → sumiranje".

Ovde postoji jedan veoma važan detalj: kontrola budžeta.
PaiCLI pomoću AgentBudget sprečava da Agent radi neograničeno — postavljena je maksimalna broj tura (na primer 10) i gornja granica token-a. Preko toga prinudno izlazi, izbegava se beskonačna petlja.
Drugi mehanizam osiguranja jeste u System Prompt-u gde se velikom modelu izričito kaže: ako nisi siguran kako da nastaviš, pitaj korisnika, ne nagađaj.
Ovo je ključno — sprečava da Agent u neizvesnim situacijama pozove pogrešan Tool (na primer greškom obriše dokument).
U PaiCLI-ju postoji još jedan vredan dizajn: izvršenje Tool-ova podržava paralelizam. ToolRegistry interno preko ExecutorService upravlja stepenom paralelnosti, najviše 4 Tool-a istovremeno, pojedinačni Tool timeout 90 sekundi pa se automatski prekida. Kad veliki model u jednoj turi istovremeno zatraži više poziva (na primer istovremeno pretražuje tri različite kategorije dokumenata), ti pozivi se izvršavaju paralelno, bez čekanja u redu.

Nastavljamo sa Claude Code/Codex, prompt:
Dodaj ReAct petlju za odlučivanje:
Ključne tačke izmene:
1. Pri pozivu DeepSeekClient, definicije Tool-ova registrovane u AgentToolRegistry prevedi u format tools parametra DeepSeek API-ja (function calling format)
2. Proveri da li u odgovoru DeepSeek-a postoji polje tool_calls
3. Ako ima tool_calls: izvrši odgovarajući Tool → rezultat izvršenja dodaj kao tool message u conversationHistory → nastavi petlju
4. Ako nema tool_calls: veliki model smatra da je zadatak završen, izbaci konačan odgovor i izađi iz petlje
5. Dodaj kontrolu budžeta
6. Sačuvaj postojeće WebSocket strimovanje. Proces poziva Tool-a u svakoj turi takođe prosledi frontu preko WebSocket-a,
format JSON: {"type": "tool_call", "tool": "search_knowledge", "status": "executing"}
7. Pri izuzecima u Tool-u ne prekidaj petlju, vrati informaciju o izuzetku kao tool message velikom modelu, neka ponovo razmišlja
Ključna detalji implementacije:
- Format poruka u conversationHistory mora biti kompatibilan sa višeturasnim formatom razgovora DeepSeek-a:
system / user / assistant(sa tool_calls) / tool(sa tool_call_id + content)

04. Dodavanje upravljanja memorijom
Da bi Agent bio zaista upotrebljiv, potreban mu je i kapacitet memorije.
PaiSmart trenutno radi višeturasni razgovor tako što u Prompt nalepi poslednjih nekoliko tura — ovaj pristup ima dva problema: prozor konteksta je ograničen, kad istorija preduga mora da se odseče; memorija između sesija potpuno nestaje.

PaiCLI-jev sistem memorije je dvoslojan i vrlo jasno dizajniran:
Kratkoročna memorija: upravlja kontekstom izvršenja trenutnog zadatka. U višekoračnom zadatku Agent mora da pamti šta je uradio u prethodnim koracima i šta je dobio kao rezultat, da bi ispravno doneo odluku o sledećem.

Kada prekorači token budžet, ne odseca se najstariji razgovor prosto, već se rana memorija komprimuje u sažetak. Tako Agent i ne zaboravlja ključne informacije, i ne razbija prozor konteksta.
Dugoročna memorija: perzistira između sesija, čuva se u lokalnom JSON fajlu.

U PaiSmart-ovom scenariju, upravljanje memorijom može da radi sledeće:
Kratkoročna memorija za upravljanje konteksta izvršenja ReAct petlje.
Dugoročna memorija može da pamti korisničke preferencije i navike. Na primer, Agent zapamti da korisnik često pretražuje dokumente vezane za "uputstvo za proizvod", pa sledeći put kad taj korisnik pita, pretraga prvo ide iz kategorije uputstava — povećava pogodak. Dovoljno je u vektorskoj bazi čuvati sažetke istorijskih interakcija korisnika i pretraživati po semantičkoj relevantnosti.
Nastavljamo sa Claude Code, treći prompt:
PaiSmart-u dodaj upravljanje memorijom:
Kratkoročna memorija:
1. Ponovo iskoristi conversationHistory ReAct petlje kao kratkoročnu memoriju
2. Dodaj brojač token-a, pre svake ture proveri ukupan token u conversationHistory
3. Odredi kada prekoračenje ukupnog token-a konteksta okida kompresiju: sačuvaj poslednje 3 pune ture razgovora, a ranije pozovi veliki model da generiše sažetak kojim se zamenjuju
4. Kompresovani sažetak se umeće kao system message na početak conversationHistory
Dugoročna memorija (na osnovu Redis-a):
2. Kada korisnik eksplicitno traži pamćenje, iz conversationHistory trenutne sesije izdvoji ključne informacije:
- Koje tipove pitanja je korisnik postavljao
- Koje je Tool-ove Agent pozivao
- Koji je konačan rezultat
- Ili sadržaj koji je korisnik direktno tražio da zapamtite
3. Izdvojene informacije sačuvaj u Redis, format ključa: ltm:{userId}:{timestamp}, rok 30 dana
4. Na početku svake nove sesije, pretraži poslednjih 5 dugoročnih sećanja tog korisnika iz Redis-a
5. Dobijeni istorijski kontekst ubaci u System Prompt: "Evo sažetka istorijskih interakcija ovog korisnika: ..."05. Revizija i testiranje
Sledeća je faza verifikacije.

Prvo Claude Code + Opus 4.7 da odradi Review:
Pregledaj sve dosadašnje izmene, sa naglaskom na:
1. Da li ReAct petlja ima rizik od beskonačne petlje
2. Da li izvršenje Tool-a ima obradu izuzetaka
3. Da li WebSocket push ima problema sa sigurnošću niti
4. Da li perzistencija memorije ima problema sa konzistentnošću podataka

Po završetku ispravki, pustite Codex/Claude Code da odradi krug integracionih testova, test slučajevi su sledeći:
Test scenario 1: korisnik pita "koliko dokumenata ima u bazi znanja"
Očekivano: Agent poziva knowledge_stats Tool, vraća statističke podatke
Test scenario 2: korisnik pita "pretraži mi RAG dokumente i sredi u sažetak"
Očekivano: Agent prvo poziva search_knowledge da pretraži, zatim poziva generate_summary da generiše sažetak
Test scenario 3: korisnik kaže "ovaj odgovor nije bio posebno tačan"
Očekivano: Agent poziva submit_feedback, beleži negativnu povratnu informaciju korisnika
Test scenario 4: korisnik uzastopno pita tri pitanja, treće se odnosi na rezultat prvog
Očekivano: Agent može iz kratkoročne memorije da dobavi kontekst prvog pitanja
06. Kako PaiSmart upisati u biografiju?
Ako radite sličan projekat nadogradnje RAG→Agent, u biografiji možete ovako da ga predstavite:
Naziv projekta: PaiSmart AI baza znanja (PaiSmart)
Kratak opis: Enterprise AI sistem baze znanja na osnovu Spring Boot 3.4 + Elasticsearch 8.10 + DeepSeek API, podržava multi-tenant upravljanje dokumentima i inteligentno odgovaranje, nadograđen sa čiste RAG arhitekture na Agent arhitekturu.
Tehnički stek: Spring Boot 3.4, Spring WebFlux, Elasticsearch 8.10, Redis 7.0, MinIO, DeepSeek V4 API, WebSocket.
Ključne odgovornosti:
- Na osnovu Spring Boot + Elasticsearch izgrađen engine hibridne pretrage, koristi KNN vektorsku pretragu + BM25 prebodiranje ključnih reči, tačnost priziva u pitanjima nad bazom znanja dostiže 92%.
- Dizajniran i implementiran okvir za registraciju Tool Calling alata, registrovano 4 ključna alata za Agent-a (pretraga dokumenata, generisanje sažetka, povratna informacija o odgovoru, statistika baze znanja).
- Implementiran engine ReAct petlje za odlučivanje, podržava Agent-a da samostalno dovršava višestepene kompozitne zadatke.
- Dizajnirana dvoslojna arhitektura upravljanja memorijom — kratkoročna memorija koristi strategiju kompresije token budžeta da kontroliše dužinu konteksta, dugoročna memorija perzistira korisničke interakcione sažetke u Redis.
- Na osnovu WebSocket-a realizovano slanje u realnom vremenu procesa Agent-ovog odlučivanja, korisnik vidi proces razmišljanja Agent-a i status poziva alata.
