[{"data":1,"prerenderedAt":328},["ShallowReactive",2],{"werk-list-nl":3},[4,104,186,277],{"id":5,"title":6,"body":7,"date":91,"description":92,"draft":93,"extension":94,"meta":95,"metric":96,"metricLabel":97,"navigation":98,"order":99,"path":100,"seo":101,"stem":102,"__hash__":103},"nl\u002Fwerk\u002Frag.md","RAG in productie",{"type":8,"value":9,"toc":83},"minimark",[10,17,22,25,29,51,55,62,65,68,72],[11,12,13],"blockquote",{},[14,15,16],"p",{},"Deze case is anoniem beschreven: geen klantnaam, geen code en geen screenshots.",[18,19,21],"h2",{"id":20},"probleem","Probleem",[14,23,24],{},"Een zoekfunctie die antwoord geeft op basis van eigen documenten is snel gebouwd, maar lastig te beoordelen. Zonder vaste meting weet je na elke wijziging niet of de antwoorden beter of slechter zijn geworden. Je test dan op gevoel.",[18,26,28],{"id":27},"wat-ik-deed","Wat ik deed",[30,31,32,36,39,42],"ul",{},[33,34,35],"li",{},"Documenten opgeknipt in passages en per passage een embedding opgeslagen, zodat een vraag de passages vindt die er qua betekenis het dichtst bij liggen.",[33,37,38],{},"Een reranker toegevoegd die de gevonden passages opnieuw beoordeelt tegen de vraag.",[33,40,41],{},"Een evaluatie gebouwd met een vaste set vragen waarvan de juiste passage bekend is. Elke wijziging krijgt daardoor een score.",[33,43,44,45,50],{},"Een samenvattingsindex getest als alternatief en weer weggegooid. Dat verhaal staat in ",[46,47,49],"a",{"href":48},"\u002Fschrijven\u002Fsummary-index","een apart artikel",".",[18,52,54],{"id":53},"wat-ik-mat","Wat ik mat",[14,56,57,58,50],{},"De evaluatie gebruikt MRR (mean reciprocal rank). Staat de juiste passage op plek 1, dan telt dat als 1. Op plek 2 als 0,5, op plek 3 als 0,33. Het gemiddelde over alle vragen is de score. De score kwam uit op ",[59,60,61],"strong",{},"0,950",[14,63,64],{},"Eerlijk erbij: die score was gelijk met en zonder reranker. De meting keek naar de plek van de passage nadat de passages opnieuw waren gesorteerd, en zag de volgorde van de reranker dus niet. Het cijfer zegt daarom iets over het vinden van passages, niet over de reranker.",[14,66,67],{},"De samenvattingsindex kostte 7,7 seconde per bestand, tegen 0,25 seconde voor embedden. Dat is ongeveer 30 keer trager.",[18,69,71],{"id":70},"wat-ik-leerde","Wat ik leerde",[30,73,74,77,80],{},[33,75,76],{},"Meet eerst, bouw daarna. Een vaste score maakt een discussie over \"voelt beter\" overbodig.",[33,78,79],{},"Een meting kan blind zijn voor precies het onderdeel dat je wilt beoordelen. Controleer wat het cijfer wel en niet ziet.",[33,81,82],{},"Iets weggooien na een meting is ook een resultaat.",{"title":84,"searchDepth":85,"depth":85,"links":86},"",2,[87,88,89,90],{"id":20,"depth":85,"text":21},{"id":27,"depth":85,"text":28},{"id":53,"depth":85,"text":54},{"id":70,"depth":85,"text":71},null,"Zoeken in documenten met embeddings en een reranker, en een evaluatie met vaste scores in plaats van testen op gevoel.",false,"md",{},"MRR 0,950","op een vaste set evaluatievragen",true,1,"\u002Fwerk\u002Frag",{"title":6,"description":92},"werk\u002Frag","g-H66OV7GD8OiMYpEDLyc4gNU1wypDuMp2baftg5B4A",{"id":105,"title":106,"body":107,"date":91,"description":180,"draft":93,"extension":94,"meta":181,"metric":91,"metricLabel":91,"navigation":98,"order":85,"path":182,"seo":183,"stem":184,"__hash__":185},"nl\u002Fwerk\u002Fai-agents.md","Werken met AI-agents in een team",{"type":8,"value":108,"toc":174},[109,111,114,116,148,154,156,161,163],[18,110,21],{"id":20},[14,112,113],{},"Een AI-agent die code schrijft is snel, maar doet ook snel iets wat je niet wilt: committen zonder dat het gevraagd is, bestanden weggooien of een eigen stijl aanhouden. In een team komt daar bij dat iedereen zijn eigen afspraken met de agent maakt, waardoor het resultaat per developer verschilt.",[18,115,28],{"id":27},[30,117,118,124,130,136,142],{},[33,119,120,123],{},[59,121,122],{},"Gedeelde regels."," De afspraken van het team staan in één repository die iedere developer inlaadt. Persoonlijke voorkeuren staan daar los van en gaan voor als ze botsen.",[33,125,126,129],{},[59,127,128],{},"Een hook als vangnet."," Een regel in tekst kan een agent negeren. Daarom blokkeert een hook gevaarlijke git-commando's voordat ze worden uitgevoerd.",[33,131,132,135],{},[59,133,134],{},"Een model per taak."," Mechanisch werk gaat naar een klein model, gewoon onderzoek naar een middelgroot model en alleen keuzes waar oordeel de uitkomst bepaalt naar het grootste model.",[33,137,138,141],{},[59,139,140],{},"Meerdere issues parallel."," Eén sessie verdeelt het werk en start per issue een eigen agent.",[33,143,144,147],{},[59,145,146],{},"Een vaste controlestap."," Het werk van een agent is pas klaar als het is nagekeken: lint, de volledige testsuite en een controle van de diff tegen de regels.",[14,149,150,153],{},[59,151,152],{},"TODO:"," voorbeeld toevoegen van wat misging en welke regel er daarna bij kwam.",[18,155,54],{"id":53},[14,157,158,160],{},[59,159,152],{}," hier staan nog geen cijfers. Vul aan met iets controleerbaars, of laat deze kop weg.",[18,162,71],{"id":70},[30,164,165,168,171],{},[33,166,167],{},"Een regel die alleen in tekst staat is een verzoek. Wat echt niet mag gebeuren, hoort in een hook.",[33,169,170],{},"Regels groeien uit fouten. Elke correctie krijgt een vaste plek, zodat dezelfde fout niet terugkomt.",[33,172,173],{},"Parallel werken levert alleen tijd op als de controle aan het eind net zo strak is als bij één issue.",{"title":84,"searchDepth":85,"depth":85,"links":175},[176,177,178,179],{"id":20,"depth":85,"text":21},{"id":27,"depth":85,"text":28},{"id":53,"depth":85,"text":54},{"id":70,"depth":85,"text":71},"Gedeelde regels, een hook die gevaarlijke git-commando's blokkeert, een model per taak en een vaste controlestap.",{},"\u002Fwerk\u002Fai-agents",{"title":106,"description":180},"werk\u002Fai-agents","UT0ApY7wTjMmGtBOyL9EvyOYfOUGQCt3z7DcO5bTDeI",{"id":187,"title":188,"body":189,"date":91,"description":270,"draft":93,"extension":94,"meta":271,"metric":91,"metricLabel":91,"navigation":98,"order":272,"path":273,"seo":274,"stem":275,"__hash__":276},"nl\u002Fwerk\u002Fself-hosting.md","Self-hosting op een NAS",{"type":8,"value":190,"toc":263},[191,193,201,203,224,227,231,238,241,243,250,252],[18,192,21],{"id":20},[14,194,195,196,200],{},"Ik wilde mijn eigen apps zelf hosten: zelf weten waar de data staat en zelf begrijpen wat er tussen een ",[197,198,199],"code",{},"git push"," en een werkende site gebeurt. Wie zelf host, is ook zelf verantwoordelijk voor wat er aan het internet hangt.",[18,202,28],{"id":27},[30,204,205,208,215,218],{},[33,206,207],{},"Apps draaien als Docker-containers op een NAS.",[33,209,210,211,214],{},"De code staat op een eigen Forgejo. Deployen is een ",[197,212,213],{},"git pull"," op de NAS en de container opnieuw bouwen.",[33,216,217],{},"Een reverse proxy (Nginx Proxy Manager) verzorgt de domeinen en de certificaten van Let's Encrypt.",[33,219,220,221,223],{},"Daarnaast heb ik wat ervaring met Kubernetes en ArgoCD. ",[59,222,152],{}," in één of twee zinnen beschrijven waar en waarvoor.",[14,225,226],{},"Deze site draait op dezelfde manier.",[18,228,230],{"id":229},"de-beveiligingsfout","De beveiligingsfout",[14,232,233,234,237],{},"Een van mijn eigen sites werd geserveerd door een nginx-container met de standaardconfiguratie, met de hele repository als webroot. Daardoor waren de ",[197,235,236],{},".git","-map, de README en het compose-bestand voor iedereen te downloaden. Ik vond het zelf bij het nalopen van een melding over een omleidingsfout.",[14,239,240],{},"De oplossing: een eigen nginx-configuratie buiten de webroot, die voor dotfiles en andere niet-sitebestanden een 404 geeft. De omleidingsfout zat in dezelfde standaardconfiguratie en ging in dezelfde wijziging mee.",[18,242,54],{"id":53},[14,244,245,246,249],{},"Na de wijziging van buitenaf gecontroleerd: een verzoek naar ",[197,247,248],{},"\u002F.git\u002Fconfig"," geeft een 404 en een omleiding blijft op https. Bij deze site gaat het een stap verder: de container bevat alleen de build-output, dus de repository staat niet eens op de plek die geserveerd wordt.",[18,251,71],{"id":70},[30,253,254,257,260],{},[33,255,256],{},"Een standaardconfiguratie is gemaakt om te werken, niet om veilig te zijn.",[33,258,259],{},"Serveer de build, niet de repository.",[33,261,262],{},"Controleer van buitenaf wat bereikbaar is. Van binnenuit ziet alles er goed uit.",{"title":84,"searchDepth":85,"depth":85,"links":264},[265,266,267,268,269],{"id":20,"depth":85,"text":21},{"id":27,"depth":85,"text":28},{"id":229,"depth":85,"text":230},{"id":53,"depth":85,"text":54},{"id":70,"depth":85,"text":71},"Eigen apps live met Docker, Forgejo en een reverse proxy, plus een beveiligingsfout die ik zelf vond en dichtte.",{},3,"\u002Fwerk\u002Fself-hosting",{"title":188,"description":270},"werk\u002Fself-hosting","dB0Q8xQD5hhYpDXsJ2lPKxOZ5gOcSduE5H5ekppLE3w",{"id":278,"title":279,"body":280,"date":91,"description":321,"draft":93,"extension":94,"meta":322,"metric":91,"metricLabel":91,"navigation":98,"order":323,"path":324,"seo":325,"stem":326,"__hash__":327},"nl\u002Fwerk\u002Flaravel-nuxt.md","Laravel API's met Nuxt-frontends",{"type":8,"value":281,"toc":315},[282,287,289,292,294,297,302,304,308,310],[11,283,284],{},[14,285,286],{},"Deze case is algemeen beschreven: geen klantnamen en geen code of data van mijn werkgever.",[18,288,21],{"id":20},[14,290,291],{},"Klanten hebben een applicatie nodig die past bij hun eigen proces. Dat vraagt om een backend die de regels bewaakt en een frontend die daar los van kan veranderen.",[18,293,28],{"id":27},[14,295,296],{},"Ik bouw de API in Laravel en werk mee aan de frontend in Nuxt. De twee zijn gescheiden: de frontend praat alleen via de API met de data.",[14,298,299,301],{},[59,300,152],{}," twee of drie concrete voorbeelden toevoegen van wat je bouwde, zonder klantnaam. Bijvoorbeeld een soort functionaliteit, een koppeling of een migratie.",[18,303,54],{"id":53},[14,305,306,160],{},[59,307,152],{},[18,309,71],{"id":70},[14,311,312,314],{},[59,313,152],{}," in je eigen woorden aanvullen.",{"title":84,"searchDepth":85,"depth":85,"links":316},[317,318,319,320],{"id":20,"depth":85,"text":21},{"id":27,"depth":85,"text":28},{"id":53,"depth":85,"text":54},{"id":70,"depth":85,"text":71},"Het dagelijkse werk, een API in Laravel met een frontend in Nuxt, algemeen beschreven en zonder klantnamen.",{},4,"\u002Fwerk\u002Flaravel-nuxt",{"title":279,"description":321},"werk\u002Flaravel-nuxt","hnHoOk6_08kwYFj4j-Ca0RFGRGRwYSAR40gA9qkunn0",1794307541443]