Korak po korak dodajte Agent-u mogućnost povezivanja na internet, WebSearch + WebFetch neka Agent zna šta se dešava svetu.
Veliki jezik model (LLM) po sebi nema mogućnost povezivanja na internet, njegova baza znanja se zasniva na trenutku kada je obuka završena.
Kao GLM-5.1, ako se postavite pitanje kroz PaiCLI, odgovor se zasniva na javnim tekstovima, kodovima i dokumentima iz 2024. i ranije.

A sada je već 27. april 2026. godine, što znači da ako Agent nema internet mogućnost, on nema osećaj za nove stvari.
Kako dakle Agent-u dodati mogućnost povezivanja na internet?
Danas ćemo dovršiti ovaj zadatak.
01. Efekat Agent-ove internet pretrage
Prvo pogledajmo šta PaiCLI Agent može da radi nakon dodavanja internet mogućnosti.
U terminalu unesite prompt: „pretraži ko je Marko Marković".
PaiCLI Agent će automatski pozvati web_search alat, preko Zhipu pretrage API-a dobija relevantne informacije.

Zatim rezultate pretrage sortira kao vrlo precizan odgovor:
Marko Marković je poznati tehnološki bloger i programer, pravi identitet je isti kao pseudonim, aktivan u tehnološkoj zajednici i društvenim medijima.

Čak je iznio i moj moto: Ništa ne može da me zaustavi, osim cilja, čak i ako na obali ima hlad, tih uvala, ja sam brod bez veziva. Za ovaj odgovor dajem punu ocenu.

Još jedan. Unesite prompt: „da li je paicoding.com danas ažurirano".
Ovaj put PaiCLI Agent poziva web_fetch alat, direktno povlači sadržaj web stranice.

On je pročitao tutorijale, članke, sve sa sajta.

Konačno je dobio najnoviji članak: „DeepSeek V4 je stigao, ovaj put sam zaista oduševljen (priložen tehnički analitički izveštaj)“, objavljen juče.

Zatim je dao veoma detaljan sažetak sajta, uključujući najnovije ažuriranje:
Danas (27. aprila) nema novih članaka, najnoviji je juče (26. aprila) duboka tehnička analiza DeepSeek V4. Sajt je nedavno vrlo aktivan, gotovo svaki 1-2 dana ima nov sadržaj, fokusirano na PaiCLI, AI Agent, RAG, DeepSeek i druge napredne AI tehnologije.

Tako PaiCLI Agent dobija najosnovnije WebSearch i WebFetch mogućnosti, može u svakom trenutku dobiti najnovije informacije s interneta.
Nema potrebe ručno da otvarate pretraživač, kopirate i paste-ujete sadržaj stranica, Agent sam može da dovrši.
Sledi, vam detaljno objašnjavam kako se to realizuje.
02. Arhitektura Agent-ove internet pretrage
Internet modul PaiCLI-a se nalazi u com.paicli.web paketu, ukupno 8 klase, jasna podela posla.

Nacrtao sam graf zavisnosti, osetite ovu arhitekturu:

Ovde postoje dve dizajnerske odluke vredne pominjanja.
Prva, pretraga i povlačenje su dva nezavisna alata, ne jedan „internet alat“.
Zašto?
Zato što su njihovi scenariji upotrebe potpuno različiti.
Pretraga je „ne znam gda da tražim“, povlačenje je „znam URL, pomozi mi da vratim sadržaj“.
Dva alata omogućavaju Agent-u da sam bira koji će da koristi na osnovu korisničke namer, takođe može prvo pretraživati zatim povlačiti (nakon pretrage URL, zatim fetch detalje).
Druga, pretraživač je napravio Provider apstrakciju.
Tri implementacione klase svaka ima svoje karakteristike: Zhipu-ova internet pretraga može deliti LLM API ključ bez dodatne konfiguracije, SerpAPI je plaćen ali sprem za upotrebu, SearXNG je open-source besplatan ali zahteva sopstvenu implementaciju.
Automatski bira kroz factory pattern.
Zašto ne direktno u kodu hardcode-ovati Zhipu?

Zato što PaiCLI iako je uglavnom okrenut kućnim korisnicima, ne isključuje da Autor koristi za inostrane korisnike.
Najvažnije je, može naučiti ljude neke stvari, npr. strategy pattern 😄
Posle Provider apstrakcije, čak i kasnije dodamo Bing Search ili Tavily, treba samo dodati jednu implementacionu klasu i jedan red u factory-u, bez dodira postojeće logike.
Ovo je prednost strategy pattern-a, princip otvorenosti-zatvorenosti je vrlo dobro odigran.
03. SearchProvider apstrakcija pretraživača
Prvo pogledajmo definiciju interfejsa, SearchProvider ima samo četiri metode:
public interface SearchProvider {
String name(); // naziv provider-a
boolean isReady(); // da li je spreman
String unavailableHint(); // hint kada nije dostupan
List<SearchResult> search(String query, int topK) throws IOException;
}Zašto postojati isReady() i unavailableHint()?
Zato što korisnici mogu početi da koriste pre konfiguracije API ključa.
U ovom trenutku nije baciti exception i srušiti program, već ljubazno upozoriti „molim konfigurišite XXX“.
Ovakav defanzivni dizajn je posebno važan u CLI alatima, korisničke okoline su raznolike, ne možete pretpostaviti da su sve konfiguracije savršene.
Zhipu pretraga
Zhipu pretragu sam odabrao kao default Provider za PaiCLI, razlog je jednostavan: PaiCLI je uglavnom okrenut kućnim GLM korisnicima, Zhipu-ov pretraga API i LLM inference dele isti GLM_API_KEY, ne zahteva dodatnu registraciju, dodatnu plaću, dodatnu konfiguraciju.
Način poziva je POST request na https://open.bigmodel.cn/api/paas/v4/tools/web_search, request body izgleda ovako:
ObjectNode payload = MAPPER.createObjectNode();
payload.put("search_engine", searchEngine); // search_std / search_pro
payload.put("search_query", query);
payload.put("count", count);
payload.put("content_size", "medium");Zhipu nudi četiri opcije pretraživača:
search_stdstandardna verzija 0.01 juan/poziv,search_propojačana verzija 0.03 juan/poziv,- još Sogou i Kuake Pro verzije po 0.05 juan/poziv.
Default search_std je dovoljan, jedan poziv košta jedan fen, 5 do 10 puta jeftiniji od SerpAPI.
Parsing rezultata iz search_result niza izvlači tri polja: title, link, content, enkapsulira kao SearchResult i vraća.
SerpAPI
SerpAPI je servis agregacije pretrage, pomaže nam da zaobiđemo Google-ovu anti-crawler zaštitu. Zahteva posebnu registraciju naloga i API ključa, mesečno ima besplatan limit.
Način poziva je GET request, spaja ključne reči i API ključ u URL parametre:
HttpUrl url = HttpUrl.parse(ENDPOINT).newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("api_key", apiKey)
.addQueryParameter("num", String.valueOf(maxResults))
.addQueryParameter("hl", "zh-cn")
.build();Parsing rezultata uzima iz organic_results niza. Ako nema prirodnih rezultata pretrage (npr. proračunsko pitanje), spušta se na answer_box (Google odabrani sažetak) kao fallback.
SearXNG
SearXNG je open-source meta-pretraživač, sam ne crawla internet, već prosleđuje zahteve na Google, Bing, DuckDuckGo i druge desetine engine-a i agregira rezultate.
Najveća prednost je potpuno besplatno, ne zahteva nikakav API ključ. Ali treba sopstvena implementacija, jedna komanda Docker-a je dovoljna:
docker run --rm -p 8888:8888 searxng/searxngZatim u .env konfigurišite:
SEARCH_PROVIDER=searxng
SEARXNG_URL=http://localhost:8888Automatsko biranje factory-ja
SearchProviderFactory je zadužen za automatsko biranje najpogodnijeg Provider-a na osnovu environment promenljivih. Logika biranja je jednostavna:
static String pickProvider(String explicit, String glmKey,
String serpKey, String searxngUrl) {
if (explicit != null && !explicit.isBlank()) {
return explicit.trim().toLowerCase(Locale.ROOT);
}
if (glmKey != null && !glmKey.isBlank()) return "zhipu";
if (serpKey != null && !serpKey.isBlank()) return "serpapi";
if (searxngUrl != null && !searxngUrl.isBlank()) return "searxng";
return "zhipu"; // default placeholder
}Prvo gleda da li je eksplicitno naveden SEARCH_PROVIDER, ako ne onda detektuje GLM → SerpAPI → SearXNG redom koji je ključ već konfigurisan.
Zato što PaiCLI korisnici verovatno već imaju konfigurisan GLM_API_KEY (za poziv modela), Zhipu pretraga je spremna za upotrebu bez dodatne konfiguracije.
Factory takođe čita environment promenljive iz tri mesta: sistemske environment promenljive, Java system properties, .env fajl (trazi i u trenutnom direktorijumu i u home direktorijumu). Bez obzira da li ste direktno export-ovali, ili napisali u .env, ili koristili VM opcije IDE-a, može da pročita.
04. WebFetch grabber
Pretraživač je pronašao linkove, ali često treba videti kompletan sadržaj. web_fetch alat je upravo za to.
Celi proces grabovanja ima tri koraka: HTTP request dobija originalni HTML → bezbednosna provera → ekstrakcija glavnog teksta i konverzija u Markdown.
HTTP grabovanje
WebFetcher je zadužen za prvi korak, koristi OkHttp da šalje GET request, dobija originalni HTML string. Ključni parametri:
Limit tela odgovora 5MB, preko se seče. 30 sekundi ukupni timeout. Kodiranje karaktera prvo prioritetski uzima iz Content-Type charset parametra, ako sve ne uspe UTF-8 fallback.
public RawResponse fetch(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.header("User-Agent", "Mozilla/5.0 (compatible; paicli-web-fetch/1.0)")
.get()
.build();
// ... šalje request, čita response, seče prevelike
}Ovde postoji jedan detalj: readBounded metoda čita stream-om, svaki put čita 8KB, nakupi do 5MB i staje. Ne čita prvo ceo response body u memoriju pa rez, jer bi se kod nekoliko stotina MB fajlova desio OOM.

Strategija mrežne bezbednosti
Pre nego što se šalje HTTP request, NetworkPolicy prvo radi dve stvari.

Prva stvar je provera URL bezbednosti. Dozvoljava samo http i https protokole, ne dozvoljava file:// i ftp:// i sl. Blokira localhost, 127.0.0.1, privatne adrese (192.168.x.x, 10.x.x.x), sprečava SSRF napade.
Šta je SSRF?

Ako neko natpe Agent da grabi http://localhost:8080/admin/delete-all, Agent će svojim identitetom pristupiti vašoj lokalnoj usluzi, može dovesti do ozbiljnih posledica.
NetworkPolicy je upravo za to.
private String checkHost(String host) {
if (lower.equals("localhost")) return "zabranjen pristup localhost";
InetAddress[] addrs = InetAddress.getAllByName(host);
for (InetAddress addr : addrs) {
if (addr.isLoopbackAddress()) return "zabranjen pristup loopback adresi";
if (addr.isSiteLocalAddress()) return "zabranjen pristup site-local adresi";
// ...
}
return null; // prošlo
}Druga stvar je ograničenje učestalosti zahteva. Najviše 30 zahteva u 60 sekundi, preko se vraća „prečesto slanje“. Ovo ograničenje je da bi se Agent sprečio da upadne u retry petlju, luda grabovanje istog sajta da ne bude blokiran IP.

Ekstrakcija HTML glavnog teksta
HTML je pun navigacije, reklama, komentara, footera i drugih šumova. Direktno slanje celog HTML LLM-u je gubljenje token-a i utiče na razumevanje. HtmlExtractor je posao da ukloni šum, ostavi samo glavni tekst, zatim konvertuje u Markdown.
Proces ekstrakcije ima četiri koraka:
Prvi korak, očisti šum oznake. script, style, nav, aside, footer, header, form, iframe i sl. oznake se direktno brišu. Elementi čiji class ili id sadrži ključne reči ads, banner, sidebar, comment itd. takođe se čiste.
Drugi korak, pronađi glavni semantički kontejner. Prvo traži <article>, <main>, [role=main] ove semantičke oznake. Većina blogova i dokumentacionih sajtova ima ove oznake, kad se pronađu može direktno pozicionirati oblast glavnog teksta.
Treći korak, ocenjivanje fallback. Ako stranica nema semantičke oznake (mnogo starih sajtova je samo gomila div ugniježdenih), ocenjuju svi block elementi. Formula ocenjivanja je vrlo jednostavna: dužina teksta × (1 - kazna link density). Što više teksta, manji procenat link-a, element je verovatnije glavni tekst.
Četvrti korak, konvertovanje u Markdown. Rekurzivno obilazi izabrani kontejner glavnog teksta, h1-h6 u naslove, p u pasuse, strong u bold, a u linkove, pre/code u blokove koda, table u Markdown tabele.
private double score(Element el) {
String text = el.text();
int textLen = text.length();
if (textLen < 80) return 0;
int linkLen = 0;
for (Element a : el.select("a")) {
linkLen += a.text().length();
}
double linkRatio = (double) linkLen / textLen;
double penalty = Math.min(linkRatio * 2.0, 1.0);
return textLen * (1.0 - penalty);
}Logika formule ocenjivanja je: karakteristika navigacije i sidebar-a je „visoka gustina linkova“, karakteristika glavnog teksta je „mnogo teksta, malo linkova“. Koristeći gustinu linkova kao kaznu, može se relativno precizno razlikovati ova dva.

HtmlExtractor ima poznatu granicu: SPA stranice renderovane kroz JS (npr. React/Vue single-page aplikacija) mogu biti prazne kad se grabuje, jer glavni tekst zavisi od JavaScript izvršenja.
Takođe neki sajtovi imaju anti-crawler mehanizam, npr. Cloudflare human verification stranica, kada se grabi dobija se gomila verification script-a ne pravi sadržaj.
Ovaj problem će biti rešen nakon integrisanja Chrome DevTools MCP, tada će se koristiti pravi pretraživač da renderuje stranicu, JS će se izvršavati, captcha će se prolaziti.
Trenutno kada se naiđe na prazan glavni tekst se vraća poruka: "Glavni tekst nije izvučen. Možda je JS render ili anti-crawler zid; u ovom izdanju se neće pokušati ponovno.", Agent zna da je ovo poznata granica, ne treba neprestano da ponavlja i gubi token-e.
05. Registracija alata i Agent integracija
Alati su implementirani, još uvek se moraju registrovati u ToolRegistry da bi Agent mogao da ih otkrije i koristi.
ToolRegistry je centar alata PaiCLI-a, svi alati (file operacije, Shell komande, RAG pretraga, internet alati) se ovde registruju. Registracija jednog alata zahteva četiri stvari: naziv, opis, definicija parametara, izvršna funkcija.
private void registerWebTools() {
tools.put("web_search", new Tool(
"web_search",
"Pretraži internet, dobij informacije u stvarnom vremenu...",
createParameters(
new Param("query", "string", "ključne reči za pretragu", true),
new Param("top_k", "integer", "broj rezultata (default 5)", false)
),
args -> webSearch(args.get("query"), parseInt(args.get("top_k"), 5))
));
tools.put("web_fetch", new Tool(
"web_fetch",
"Grabi specifikovan URL, ekstraktuje glavni tekst u Markdown...",
createParameters(
new Param("url", "string", "kompletnan URL", true),
new Param("max_chars", "integer", "maksimalni broj karaktera (default 8000)", false)
),
args -> webFetch(args.get("url"), parseInt(args.get("max_chars"), 8000))
));
}Tekst opisa ovde je veoma važan, namenjen je LLM-u da vidi.
LLM na osnovu opisa alata odlučuje kad će koji alat koristiti. Zato u opisu web_search piše „dobij informacije u stvarnom vremenu (najnovija verzija, zvanična dokumentacija, tehničke vesti itd.)“, dajući LLM jasnu scenu kada ga koristiti.
U opisu web_fetch piše „pogodno za static/SSR stranice“ i „JS render ili anti-crawler sajtovi će vratiti prazan glavni tekst“, LLM zna gde su granice.

SearchProvider i WebFetcher komponente su lazy-loaded — prvi put kad se koriste tek se instanciraju. Zato što ne svaki razgovor zahteva internet, nema potrebe da se odmah inicijalizuju internet komponente.
private synchronized SearchProvider searchProvider() {
if (searchProvider == null) {
searchProvider = SearchProviderFactory.create();
}
return searchProvider;
}Dodat synchronized zato što PaiCLI podržava paralelno pozivanje alata, više alata može istovremeno da se izvršava, treba osigurati da se samo jednom inicijalizira.
Bez ovog keyword-a, dve niti bi mogle istovremeno da uđu i kreiraju dve SearchProvider instance, iako neće prouzrokovati grešku ali gubi resurse, i stanje može biti nekonzistentno. Ovo je veoma klasičan scenario double-checked locking u Java concurrent programiranju.
06. Konfiguracija i korišćenje
Objasnjeno principu rada, hajde da vidimo kako se konfiguriše i koristi.
Najjednostavnija konfiguracija (preporučeno)
Ako već imate GLM_API_KEY (za pokretanje GLM-5.1 modela), čestitam, ne treba vam nikakva dodatna konfiguracija.
PaiCLI će automatski detektovati GLM_API_KEY, koristi Zhipu pretragu kao default pretraživač.
U root direktorijumu projekta .env fajlu potvrdite ovu liniju:
GLM_API_KEY=vaš Zhipu API ključ
Prebacivanje pretraživača
Ako želite da koristite SerpAPI (jača internacionalna pretraga), u .env dodajte dve linije:
SEARCH_PROVIDER=serpapi
SERPAPI_KEY=vaš SerpAPI ključAko želite da koristite besplatan SearXNG, prvo pokrenite Docker instancu, zatim konfigurišite:
docker run --rm -p 8888:8888 searxng/searxngSEARCH_PROVIDER=searxng
SEARXNG_URL=http://localhost:8888Stvarno korišćenje
Nakon pokretanja PaiCLI, Agent će automatski prepoznati koja pitanja zahtevaju internet. Ne trebate vam ručno specificirati „koristi pretragu“, Agent sam će odlučiti.
Nekoliko tipičnih scena korišćenja:
Pitajte „Kake nove karakteristike ima Java 21“, Agent će pozvati web_search da pretraži najnovije informacije.

Pitajte „pomozi mi videti šta ima na paicoding.com početnoj stranici“, Agent će pozvati web_fetch da grabi stranicu.

Pitajte „pretraži Spring Boot 3.4 release notes, zatim mi sažemi ključne tačke“, Agent će prvo pretraživati zatim grabiti, kombinovano koristiti dva alata.

Ovde postoji jedna mala tehnika: ako smatrate da rezultati pretrage nisu dovoljno detaljni, možete postaviti dodatno pitanje „pomozi mi da otvorim prvi link i vidim detaljan sadržaj“, Agent će automatski koristiti web_fetch da grabi URL iz rezultata pretrage. Kombinacija pretrage + grabovanja osigurava oko 80% internet potreba.
07. Kako napisati PaiCLI u CV?
Nakon što savladate ovo izdanje, u CV možete ovako napisati:
- Naziv projekta: PaiCLI - Java Agent CLI
- Opis projekta: Od nule konstruiran produkcijski Java Agent CLI alat, podržava internet pretragu, grabovanje web stranica, RAG pretragu, multi-Agent saradnju itd. mogućnosti
- Tehnološki stack: Java 21, OkHttp, Jsoup, GLM-5.1/DeepSeek V4, strategy pattern, factory pattern
- Ključne odgovornosti:
- Baziran na strategy pattern dizajnirao SearchProvider apstrakcioni sloj pretraživača, podržava Zhipu/SerpAPI/SearXNG, može se automatski birati i hot-switch u runtime-u
- Implementirao Jsoup-based Readularity algoritam ekstrakcije glavnog teksta, kroz dve-faznu strategiju prioriteta semantičkih oznaka + ocenjivanje link density precizno ekstraktuje glavni tekst web stranica
- Dizajnirao NetworkPolicy strategiju mrežne bezbednosti, uključujući SSRF zaštitu (scheme whitelist + host blacklist + DNS resolucionu verifikaciju) i token bucket limiter
- Baziran na factory pattern implementirao SearchProviderFactory, podržava čitanje konfiguracije iz tri nivoa: environment promenljive, system properties, .env fajl, realizuje iskustvo spremno za upotrebu bez dodatne konfiguracije
- Registrovao web_search i web_fetch kao Function Calling alate u Agent-ovom tool chain-u, realizovao inteligentnu selekciju alata gde LLM sam određuje kada da koristi internet
Izvorni kod projekta: https://github.com/itwanger/paicli, kod 9. izdanja je potpuno commit-ovan.
Dobrodošli da date star, fork-ujete, otvorite issue, zajedno da učinimo ovaj projekat boljim.
ending
Od 400 linija ReAct petlje u prvom izdanju, sada u 9. izdanju dodata internet mogućnost, PaiCLI više nije Agent „samo operiše lokalne fajlove“.
Pretraga mu daje da zna šta se dešava svetu,
Grabovanje mu daje da zaista razume šta stranica govori,
Bezbednost mu sprečava da bude nekontrolisano.
Ako i vi želite od nule da razumete kako Agent se korak po korak gradi,

Sledite mapu puta PaiCLI-a.
Svako izdanje ima kompletan kod, stvarne caseove, može se napisati u CV-u.
[Najbolji način učenja nije gledati kako drugi koriste, već sami od napraviti od nule.]
Vidimo se sledeći put.
