Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > Szóval kísérleteztem kicsit lokális LLM-mel

Szűrő megjelenítése

Szóval kísérleteztem kicsit lokális LLM-mel

Tehát miről is lesz ma itt szó? Az elképzelés a következő volt:

  • A Domino dokumentációja kezd egy kevésbé könnyed nyári olvasmányra hasonlítani, így arra gondoltam, egy Domino-asszisztenst fogok építeni.
  • Ehhez szükségem lesz egy lokálisan futó LLM-re;
  • Illetve egy AI flow futtató eszközre, hogy a dokumentációt egyrészt megértessem az LLM-mel, másrészt pedig kikényszerítsem, hogy az általam támasztott szabályok szerint működjön.

Mivel a Langflow-val már korábban is találkoztam, úgy döntöttem maradok annál az AI flow építésre. Modell tekintetében már egyszerre volt kevésbé és közben mégis nagyjából teljesen egyértelmű a választás: lokális LLM futtatásához ugyanis sok VRAM kell, amiből az idén 5 éves AMD Radeon RX 6750 XT-m nem sokkal rendelkezik (12 GB-tal, egészen pontosan). Az egészen kicsi modellek már egy ekkora dokumentáció kezelésére nem lennének alkalmasak (ezt sajnos sikerült is a kísérletek alkalmával bizonyítani), a nagyobb modellek pedig fizikailag nem férnek el a rendelkezésre álló VRAM-ban, így végül két modellre szűkítettem a lehetőségeket, a Gemma 4 modell 12 milliárd (gemma4:12b) és "effektív" 4 milliárd (gemma4:e4b) paraméteres változataira.

A modell futtatásához a legtriviálisabb eszköz az Ollama, tökéletesen elegendő a lokális modellekkel való kísérletezéshez, a modelleket is az Ollama modell-registryjéből szereztem be. A kísérlet egyébként már majdnem azelőtt befejeződött, hogy elkezdődött volna, eleinte ugyanis a Windows 11 WSL alrendszerében futó Docker alatt szerettem volna a modellt futtatni (illetve később Ubuntuval is megpróbáltam), de mivel a GPU-m hivatalosan nem is támogatott, meg az LLM-ek eleve jobban szeretik az NVidia-t (bár az Ollama önmagában elég sok AMD GPU-t is támogat), sehogy sem sikerült szóra bírni. Mint kiderült, az Ollama-t natívan futtatva Windowson felismeri a GPU-t, és a 12b illetve az e4b modellek teljes 7.6 GB illetve 9.6 GB-ját be tudja tölteni a VRAM-ba. Szóval innentől sínen volt a kísérlet. (Spoiler: nem.)

Következő lépésben összeraktam Docker alá egy Langflow-t egy PostgreSQL adatbázissal és kezdődhetett a flow létrehozása. Az első akadály rögtön az volt, hogy a Langflow nem hajlandó beszélgetni olyan modell kiszolgálókkal, amik nem a saját domainja alatt futnak. Windows-on futtatott Docker esetén ismerős lehet a fejlesztőkollegáknak a host.docker.internal cím, nyilván a Langflow-ból ezen keresztül tudtam elérni az Ollama-t, viszont ez addig nem működik, míg a LANGFLOW_SSRF_ALLOWED_HOSTS környezeti változóba fel nem vettem a címet. Ez a kapcsoló a CSRF-hez hasonló védelmi faktort ad szerver-szerver kommunikáció esetére, hasonlóan ahhoz, csak az érintett távoli szerver címét (több esetén vesszővel elválasztva) kell megadni, aztán jöhet a containernek egy újraindítás és kezdhetünk építeni.

A Terv

A nagybetűs Terv az volt, hogy egy tudásbázist építek a dokumentációból, amit megkap a modell, aztán kérdezek tőle. Addig szerettem volna eljutni, hogy egy komplett Domino deployment definíciót össze tudjon rakni, illetve meg tudja nekem mondani, hogy a Domino CLI segítégével hogyan importálom a Domino rendszerébe, aztán hogyan indítom el az alkalmazást, szintén a CLI-n keresztül. (Később még valószínűleg vissza fogok térni ehhez a kísérlethez, mert szeretném MCP szerveren keresztül elérni azt is, hogy az említett műveleteket el is tudja végezni az agent). Amire nem számítottam - nyilván a saját "tudásbázisom" hiányosságai miatt -, hogy a tudásbázis építéséhez egy második modellre is szükségem lesz, mégpedig egy úgynevezett "embedding" modellre. Az embedding modell lényege, hogy a tudásbázis szövegét vektorizálja, aztán egy vektor adatbázisban (egyébként ezt is ad a Langflow, a "Chroma Local" vektor adatbázis motor elérhető alapból) tárolva azt az asszisztens agent külső eszköz (MCP) hívással el tudja érni (ez esetben kényszerítettem rá, hogy más "tudást" ne is használhasson). Az embedding modellek nem ugyanazok, mint az agent modellek, végül a Qwen 3 Embedding modell 0.6b paraméteres (qwen3-embedding:0.6b) változatát használtam.

A megvalósítás

Mivel eddigi AI-os """tapasztalataimmal""" úgy tudtam, hogy érdemes az agent-nek szánt System Promptot (az agent alapvető, elsődleges viselkedés jegyeit leíró direktíva-készletet) is AI-jal megíratni, így leírtam a Gemma modellnek, hogy mik lennének a céljaim az agenttel, hogyan várom el a működését, stb, az pedig egy korrektnek látszó, viszonylag terjedelmes System Promptot rakott össze. Ezen a ponton már minden rendelkezésemre állt, hogy felépítsem a flow-t és sikerült is a következőt összerakni:

  • A kiindulási alap egy Read File komponens "Raw content" módban. Ez nem csinál semmit, csupán betölti a dokumentációt tartalmazó Markdown fájlok (gyakorlatilag a Domino README.md fájlainak) tartalmát és Message objektummá konvertálja őket.
  • A Read File kimenetét egy Split Text komponensre kötöttem. Ez maximum 1000 karakteres chunkokat készít az input szövegből, maximum 200 karakteres átfedésekkel (ez az alapbeállítás). Azért szükséges ez, hogy később a vektor adatbázisba kisebb, de ne túl kicsi szeletek kerüljenek, így az agent jobban tud majd vele dolgozni.
  • A Split Text kimenetét egy Knowledge komponens kapja. Ez lesz maga a tudásbázist reprezentáló komponens, benne létrehoztam a korábban említett Qwen 3 Embedding modellel és Chroma Local vektor adatbázissal a tudásbázist. Ingest módban hagyva ekkor lefuttattam a flow eddig meglevő részét, ami néhány percnyi számolgatás után (a videókártyám nyilván nem élvezte ezt a folyamatot) el is készült. A komponenst ezután Retrieve és Tool módra állítottam, így külső eszközként tovább lehet kötni az Agent-re.

alt

  • Majd jött az Agent komponens, gemma4:e4b modellel és egy ugyanezzel a modellel íratott System Prompttal. Az Agentre csak a "szokásos" Chat Input és Chat Output komponenseket kötöttem.

Mivel a tudásbázis már kész volt, kezdhettem is az első teszteket.

Az első eredmények

... katasztrofálisak voltak. A System Prompt annyira "jól" sikerült, hogy az első néhány kérdésemre teljesen hihető, sőt, hiteles válaszokat kaptam. Mivel én fejlesztettem a Domino-t, nyilván tudtam milyen válaszokat várok és legnagyobb meglepetésemre minden a helyén volt. Aztán rákérdeztem a konfigurációra és ekkor teljesen szétesett az egész, konkrétan a Domino deployment definíciós formátuma helyett olyan Kubernetes (!) resource definíciókat épített nekem aminek az apiVersion értéke domino/v1 volt (?!). Egyszerűen ötletem nem volt, ez honnan jött neki, de folytattam a beszélgetést az asszisztenssel, próbálva belőle kicsikarni egy az elvártra csak kicsit is hasonlító választ, ám ekkor feltűnt egy fontos részlet: az asszisztens soha nem indított kérést a tudásbázis felé. Ez egy nagyjából egyértelmű indikátora a hibás, vagy legalábbis nem elég szigorú System Prompt-nak, így jött az újratervezés...

Sokadik nekifutás

Nem fogok hazudni, órákon át ültem a Langflow felett és gondolkodtam, miket kéne javítani rajta, és több iteráció is teljesen sikertelen volt. Míg nem eljutottam egy újragondolt felépítésre, amivel kapcsolatban már bizakodó voltam. Az asszisztens flow elé bekerült egy "dokumentáció-feljavítás": az ötlet az volt, hogy nem közvetlenül a README.md-ket használom, hanem azokat átfuttatom egy kisebb flow-n, ami kiegészíti az összes dokumentumot fejezetenként egy-egy összefoglalóval. Az új flow nagyon egyszerűen épült fel, mindössze egy Read File, egy Agent és egy Write File komponensből áll (meg egy Chat Output-ból, ami nélkül a Langflow nem hajlandó elindítani a flow-t). Az Agent maradt gemma4:e4b modellen 16k context window mérettel és kapott egy generált System Promptot.

alt

Az asszisztens flow-ban csak a beállításokon változtattam:

  • Váltottam egy nagyobb embedding modellre. Eredetileg a qwen3-embedding:4b modellt akartam használni, de az önmaga 2.5 GB, így gyanús volt, hogy nem fog elférni az Agent modell mellett. Persze, az csak utólag esett le, hogy ez igazából nem is számít, mert az embedding modell csak a tudásbázis építés idején fut, az eredménye (a vektorizált tudásbázis szöveg) már a vektoradatbázisból van kiszolgálva. Szóval végül az 1.2 GB-os bge-m3:latest modellel építettem újra a tudásbázist, a korábban "feldúsított" dokumentációk alapján.
  • Kicseréltem az Agent modellt is, gemma4:12b-re váltottam, illetve újrageneráltattam (szintén a Gemmával) a System Promptot (ami egyébként leszólt azért, hogy a korábbi System Prompt nem volt elég szigorú az eszközhasználat tekintetében, amit szintén a Gemma generált...). A context window méretét itt még nem emeltem meg, maradt 16k-n (később egyébként megpróbáltam, de csak iszonyatosan megnövelte a válaszgenerálási időket...). A végleges flow így nézett ki:

alt

Tudásbázis újragenerálás, tudásbázis átállítva Retrieve/Tool módra, Agent indít és nézzük... Az első teszthez hasonlóan megint egyszerű kérdésekkel kezdtem, eleinte szándékosan olyan dolgokat kérdezve, amit még a tudásbázis nélkül is meg tud válaszolni. Már itt látszott, hogy rengeteg dolgot "hozzágondol", de legalább a válaszok rendben voltak. A kísérlet lényege persze az volt, hogy a tudásbázis alapján tud-e komplexebb dolgokban is segíteni. És a válasz: igen. Nem volt 100%-ig pontos, de nem volt rossz, 1-2 esetben rávezettem a helyes "megfejtésre", de összességében teljesen használható volt (a teljes chatlogot egyébként a cikk végén le lehet majd tölteni, ha valakit érdekelne). Valószínűleg a dokumentációk "feldúsítását" megpróbálhatnám még egyszer, egy szigorúbb prompttal, nagyobb context window-val, és nagyobb modellel, esetleg a context window méretét megnövelhetném picit (32k vagy 64k méretre), talán még egy hosszútávú memóriát is ráköthetnék (hogy ha kijavítom amikor butaságokat beszél, azokat "meg is jegyezze"). Egyelőre viszont már így is elégedett vagyok az eredménnyel, bár mindenképp folytatni szeretném a kísérletet, olyan egyedi MCP szerver bekötésével, ami ténylegesen képes végrehajtani a változtatásokat a Dominoban.

A bónuszkör

Gondoltam, még játszadozok kicsit tovább, így építettem egy harmadik flow-t is. Szerettem volna a teljes chatlogot formázva exportálni a Langflow-ból, de az nem igazán adott rá lehetőséget, így végül kimentettem az adatbázisból a chatlog rekordjait, IntelliJ IDEA-val JSON-ként formázva aztán gondoltam egy Read File, Agent, Write File flow itt is elég lesz. Maradva a gemma4:12b modellnél, és egy megfelelő System Promptot generálva neki is álltam tesztelni az ötletet. Aztán meglepődve tapasztaltam, hogy az Agent több percnyi gondolkodás után, egyszerűen üres válasszal jött vissza. Valószínűleg kifutott a context window-ból és/vagy a VRAM-ból. Akárhogy és akárhányszor is próbáltam, nem sikerült a generálás.

Rendben, új stratégia, az az ötletem támadt, hogy mi lenne, ha az üzeneteket egyenként formázom le, aztán az így létrejött kis "message box"-okat egy másik agent összefűzi és hozzáadja a szükséges CSS formázást. Az elképzelés nagyon lassan, de formát kezdett ölteni:

  • Kezdünk egy Read File komponenssel, ami a JSON fájlt (benne a chatloggal) betölti.
  • Ezt megkapja egy Type Convert komponens, Table módban, ami a JSON fájlban levő tömbből táblázatot készít.
  • A kimenetet Batch Run agentre kötöttem. Ez a komponens a táblázat soraira egyenként indít egy-egy modellt és végzi el a prompt által előírt transzformációkat. Természetesen itt is generáltattam a System Promptot, az elképzeléseim alapján.
  • Az eredményeket egy Parser komponens összefűzi. Ennek az eredménye gyakorlatilag egy csomó <div><span>itt az időbélyegző</span>itt az üzenet</div> összefűzve.
  • Majd jön egy Agent ami befejezi a helyes HTML szerkezet kialakítását, illetve hozzáadja a stíluslapot style blokkban. (Ez is generált System Prompttal futott.)
  • A kimenet pedig Write File komponensen keresztül egy .html fájlba kerül.

alt

Egyszerűen hangzik, ugye? Az elképzelés betonstabil, pillanatok alatt elkészül majd a formázott chatlog, gondoltam. Aztán az első generálás sikertelen volt, végig sem futott. A következő sem volt sikeres, az üzenetek formázása még sikerült, de a végleges összefűzés mindig elhasalt, vagy botrányosan rossz minőségű lett a kimenet: megismételt üzenetek, széteső HTML szerkezet, az üzenetek felét kihagyta, és közben botrányos módon gyilkolta a GPU-t, még az asztal is laggolt a generálás közben, gyakorlatilag használhatatlanra redukálva a gépemet akár 10-15 percre. Csökkentettem az input fájl hosszát (~kevesebb üzenetet dolgoztattam fel vele egyszerre), modellt cseréltem, töröltem és újra letöltöttem a modellt, context window méretet váltottam, javítottam a System Promptokon, de semmi sem segített, sőt gyakorlatilag korábbi inputok eredményét generálta újra és újra, mindenféle változás nélkül, mintha egy erősen beakadt cache-ből dolgozna. Eleinte el is akartam engedni az egészet, gondoltam, másnap, a gépem újraindítása után javulni fog a helyzet, de ez sem volt igaz, ugyanúgy a korábbi eredményt köpte ki magából újra és újra.

Kezdett gyanússá válni a helyzet, utánaolvastam, mi okozhat ilyet a Langflow-nál és mint kiderült, valóban egy beakadt cache okozta. Töröltem a Langflow containében levő .cache mappát, újrakezdtem a flowt és a legnagyobb meglepetésemre, elsőre egy egészen konzisztens eredményt kaptam, ráadásul a korábbi futásidők töredékével. Visszaállítottam az eredeti chatlog input fájlt, mégegyszer lefuttattam a flowt és közel hibátlan lett az eredmény, bár a HTML szerkezet végére elfelejtette odabiggyeszteni a </body></html> tageket, illetve egy üres és egy megismételt üzenetet berakott a végefele, de ezeket már csak kézzel eltávolítottam, illetve a formázás egy korábbi generálásban jobban tetszett, így kicseréltem a style blokk tartalmát. Ezt a verziót lehet letölteni a cikk végén.

Konklúzió

Szóval, mit tanultam ebből a kísérletből? Abszolút semmit. Csak viccelek, valójában egészen sokat, alább össze is szedtem néhány észrevételt:

  • A kisebb modellek (maximum 2-3b paraméter) közel használhatatlanok, talán még egy ennél is kisebb feladatra elegendőek lehetnek, de nem sokat lehet tőlük várni. A közepes méretű (~12b-ig) modellek már jól használhatóak, de nagyon sokat kell a system prompttal és a context window méretével játszani, hogy elfogadható eredményeket kapjunk. Nagyjából ez az a határ, amit egy otthoni számítógépen még el lehet futtatni, hiszen az otthoni használatra szánt GPU-k túlnyomó többségén 8-12-16 GB VRAM található, ha pedig a modell VRAM igénye ennél nagyobb, akkor egy szelete rendszermemóriából és CPU-n fog futni, ami lényegesen lassabb és gyengébb minőségű eredményeket ad. A fenti flow-kkal való kísérletezés során nekem is volt olyan, hogy a modell 22%-a CPU-n, 78%-a GPU-n futott, pusztán azért mert a gemma4:12b 256k-s context window-val már nem fért el a rendelkezésre álló 12 GB VRAM-ban. Vannak még 17-20 GB körüli méretű modellek is, egy RTX 5090 24-32 GB VRAM-mal már elég lehet azokra, de aki egy 1,5-2 millió forintos videókártyán egy személyi számítógépen AI agent-eket futtat, annak lehet ideje újraértékelni a döntéseit. :) Az ennél nagyobb modellek már csak speciális célhardware-en fognak tudni futni, professzionális verziójú GPU-kkal és rengeteg rendszermemóriával.
  • Érdemes megválasztani az adott munkafolyamathoz a modellt. A chat asszisztens igényelte a 12b modellt, de kisebb context window méret is elég volt neki (16k), miközben az e4b modell használhatatlan eredményeket eredményezett. A chatlog formázás viszont sokkal jobb eredményekkel tért vissza az e4b modell használatával, de nagy (128k) context window-ra volt szükség, pusztán a bemeneti adatok mérete miatt. Viszont a 128k context window érezhetően sokkal jobban terhelte a GPU-t, közben a gépem szinte használhatatlan volt. Valószínűleg egy újabb szériájú Radeon (RX 9000) vagy NVidia (RTX 5000), 16 GB VRAM-mal jobban teljesített volna, de ezt persze nem volt lehetőségem tesztelni. A lényeg, hogy érdemes több kombinációt kipróbálni (modell paraméterszám vs context window méret tekintetében, úgy értem) és megfigyelni, melyik ad stabilabb, konzisztensebb eredményeket.
  • A beszédes system prompt fontos, érdemes megíratni egy nagyobb modellel (a gemma4:12b teljesen alkalmas volt rá), hogy a megfelelő megszorítások a modell számára érthető és kierőltetett formában jelenjenek meg benne. Persze, ha már megvan a tapasztalat és tudjuk, hogyan szóljunk az agent-hez, akkor meg lehet írni kézzel is, de kezdetben egyszerűbb csak leírni egy promptba, hogy mit szeretnénk elérni és rábízni a system prompt megírását is a modellre. Persze ez sem feltétlenül biztosítja azt, hogy mindig pontosan ugyanúgy fog viselkedni a modell. Például a chatlog formázás flowban az üzenetek dobozának formázására konkrét megszorítás volt a system promptban, az látszik is, hogy minden üzenetnél azonos. A tartalmára vonatkozóan azonban nem voltak konkrétumok (vagyis de, csak nem elég "erőszakosak"), így néhol beformázta a Markdown szövegrészleteket, máshol nem, néhol az időbélyegző még az ezredmásodperceket is tartalmazza, máshol csak másodpercig jutott, de van ahol egy rövidebb formátumú időbélyegző van csak.

Végül még egy észrevétel magával a Langflow-val kapcsolatban: elképesztően bugos, több órányi kínlódás után jöttem rá, hogy törölnöm kell a data cache mappáját, anélkül ugyanis már teljesen megváltozott input esetén is mindig ugyanazt generálta a modell a chatlog formázás flowban. Emellett a UI is hajlamos botrányosan lelassulni egy "komplexebb" flow esetén. Valahogy az egész nagyon törékenynek érződött, bár maga a Langflow csak egy UI a Python alapú LangChain framework fölött, szóval egyáltalán nem fontos Langflow-val nekiesni egy flow felépítésének, de kezdésnek nem rossz.

Összességében én mindenképpen sikeresnek ítélem meg ezt a kísérletet, úgy érzem rengeteget tanultam agentic flow-k építéséről, és tudva, hogy mennyire felkapottak mostanság az ilyenek, biztosan hasznomra lesz. Azonban továbbra sem mehetünk el szó nélkül a borzasztó erőforrásigénye és a továbbra is erősen jellemző nem-determinisztikus viselkedése miatt. Én továbbra sem vagyok arról meggyőzve, hogy egy üzleti-kritikus rendszert szívesen építenék AI alapokra, ahol a szoftverek determinisztikus viselkedése, a gyakran végrehajtott üzleti folyamatok gyors és megbízható működése a legfontosabb szempont. Illetve szerintem nagyon fontos jól megítélni, egyáltalán szükség van-e AI alapú adatfeldolgozásra, például a chatlog formázással annyi problémám volt, hogy közben már azon gondolkoztam, hogy inkább írok kézzel egy scriptet Pythonban vagy TypeScriptben, ami ugyanezt megoldja, sokkal gyorsabban és determinisztikusan. Visszatérve, a jelenlegi nagy AI vállalatok által üzemeltetett modellek használata bár lehet jobb eredményeket ad, de az adatvédelmi szempontok és a könnyen elszálló költségek visszahúzó ereje nagy, míg a házilag üzemeltetett modelleknek jelentős építési (célhardware), üzemeltetési és karbantartási költsége van. Szóval addig is marad a játszadozás, kísérletezés a használt videókártyákon, amiken inkább Baldur's Gate 3-at és Expedition 33-at kéne futtatni. :)

Csatolmányok
final_chatlog_fixed.html
Típus Egyéb

 

Domino asszisztens chatlog
all_system_prompts_for_blog.txt
Típus Egyéb

 

Az összes felhasznált system prompt (az azt generáló promptokkal)
Kommentek

Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.