[{"content":"Jeg vil i dette blog-post gennemgå, hvordan jeg har brugt en agent til at bygge et website ud fra kundekrav, samt sætte et par ord på hvad jeg har fået af faglig forståelse for ai-agenter.\nHvad Er En Agent # En agent er et ai-system, der opererer i en loop, hvor den har adgang til eksterne værktøjer, den kan kalde, og den bruger resultatet til at beslutte hvad den gør næste gang, da den fortsætter indtil det definerede mål er nået.\nHvordan Virker Agenter # En agent består af 4 centrale dele.\nLLM - er agentens hjerne.\nTools - giver den handlingsevne.\nContext window - er agentens hukommelse.\nMemory og state - agenten har ingen hukommelse på tværs af sessioner.\nAgentic loop - også kaldet ReAct-mønsteret (Reasoning + Acting). Alle agenter følger det samme grundlæggende loop.\nAgent VS Chatbot # Det der adskiller en agent fra et chat-system er tre ting.\nHandlingsevne - den påvirker sin omverden.\nMålorienterethed - er målet nået? Nej - ny strategi.\nMulti-step reasoning - løser komplekse opgaver ved at nedbryde dem i sekvenser af mindre handlinger.\nDen nemmeste måde at se forskellen på er ved at skrive til en chatbot og en agent. Den ene vil give dig et svar, hvorimod den anden vil give dig en færdig eksekveret løsning.\nNu Til Mit Projekt! # Min lærer har taget kunde-brillerne på og fremlagt et website, han har bygget. Det er en meditations-side, hvor man kan køre igennem et minikursus i form af at høre lydklip af meditation. Han ønsker i alt 5 quiz, en under hver lektion, for at se om lytterne også har hørt hvad der faktisk er blevet sagt.\nKrav til siden:\nMan skal ikke være logget ind. Det skal være muligt at huske tidligere svar fra quiz. Bruger skal kunne se tidligere svar, samt prøve igen. Første step var her at få lavet en god prompt til min agent! Jeg kunne allerede denne gang mærke, jeg var meget stærkere og teknisk i min prompting. Jeg brugte igen samme metode som sidst med at skrive prompten sammen med min chatbot.\nJeg blev denne gang også introduceret til plan-mode, som er en planlægningstilstand, hvor agenten ikke kører i det normale agentic loop, men planlægger uden at handle. Det fungerede super godt, da jeg satte min prompt ind og fik lukket de sidste huller og sørget for, at agenten og jeg er på samme bølgelængde, inden den går helt banjo!\nFor at starte simpelt valgte jeg at tilføje én quiz for at få et velfungerende site. Et problem vi så i mange demoer, var at quizzen blev gjort til én lang med de 29 spørgsmål. Det undgik jeg heldigvis ved at starte med den ene og sørge for via min prompt, at det ville være nemt at tilføje flere.\nJeg tog her også et valg, sammen med anbefaling fra chatbot, om at gemme quizzerne i localStorage for at opnå kundens krav om, at det skulle være muligt for brugeren at se tidligere svar.\nDerefter er der faktisk ikke meget mere at sige, end at det gik forfærdeligt smooth. Den ramte plet på det billede, jeg havde af designet, og sitet i sig selv. Jeg sidder igen tilbage og tænker waow, men faktisk også puha, hvordan kommer det til at se ud for mig på arbejdsmarkedet om et par år. Når et projekt, der ville tage mig flere uger i et team, tager den 10 minutter. Men i sidste ende tror jeg ikke, det handler om at blive erstattet, men at det handler om at lære at styre den rigtigt. Hvilket er præcis det, jeg er i gang med!\n","externalUrl":null,"permalink":"/Portfolio/posts/ai-agenter/","section":"Posts","summary":"","title":"AI-Agenter","type":"posts"},{"content":" Intro \u0026amp; Formål # Jeg har denne gang arbejdet med at udvikle en ai-drevet applikation, hvor jeg bruger en sprogmodel som ekstern service via et API.\nMere i dybden har jeg udviklet et system som kan give en første vurdering af en studenteropgave ved hjælp af en rubrik, som bliver vurderet af følgende materiale jeg har fået tildelt:\nstudenteropgaver Læringsmål Krav til rapport Dare, share, care Jeg vil igennem dette blog-post tage jer med igennem processen samt sætte et par ord på hvad der bare fungerede og hvad der fungerede mindre godt.\nFlow # Jeg valgte at opdele arbejdet i to adskilte faser: først at få rubrikken og promptdesignet på plads, og dernæst at bygge selve applikationen.\n1 fase startede jeg med at arbejde udelukkende i Claude chatbot. Her fortalte jeg hvilke materialer vurderingen skulle baseres på, vi arbejdede os frem mod en rubrik med fem kriterier og tre niveauer hver. Det tog tre iterationer, før jeg var tilfreds med både system-prompten og user-prompten. Det vigtige var at holde dette arbejde adskilt fra selve kodningen, så jeg ikke blandede to komplekse problemer sammen på én gang.\n2 fase var rubrikken klar, så jeg gik tilbage til Claude chatbot med en ny opgave: at generere en meget præcis og detaljeret prompt til at bygge hele monorepo-strukturen fra bunden. Jeg beskrev tech stack, mappestruktur, API-kontrakt, system-prompt, user-prompt og CI/CD-setup i én samlet specifikation, og fik her et komplet startpunkt ud af det:\npraktik-evaluator/ ├── 📁 backend/ │ ├── 📁 src/main/java/dk/ek/ │ │ ├── Main.java │ │ ├── 📁 controller/ │ │ │ └── EvaluationController.java │ │ ├── 📁 service/ │ │ │ └── OpenAiService.java │ │ └── 📁 model/ │ │ └── EvaluationRequest.java │ └── pom.xml │ ├── 📁 frontend/ │ ├── 📁 src/ │ │ ├── App.jsx │ │ └── main.jsx │ └── package.json │ └── 📁 .github/ └── 📁 workflows/ └── ci.yml 3 fase blev med strukturen på plads, implementationen af backend og frontend.\nBackend: skrevet i Java med Javalin. Rubrikken og system-prompten blev hardcodet som konstanter i OpenAiService, og user-prompten bygges dynamisk ved hvert kald. API-nøglen hentes udelukkende fra miljøvariablen ANTHROPIC_API_KEY – aldrig hardkodet.\nFrontend: bygget i React med Vite og plain CSS. Brugeren uploader en .txt eller .md fil via drag-and-drop, og resultatet vises struktureret med farvekodede niveauer, styrker, forbedringsområder og eksamensspørgsmål. Jeg tilføjede også en historikfunktion som gemmer i localstorage og en rubrikvisning direkte i appen.\n4 fase satte jeg et GitHub Actions-workflow op, der kører på hvert push til main. Backenden kompilerer med Maven og Java, frontend-jobbet kører npm install og npm run build.\nHvad Var Udfordrende # Jeg synes grundlæggende ikke at projektet som helhed var specielt udfordrende, Men det var selvfølgelig ikke helt smertefrit!\nJeg mærkede først og fremmest problemer, da jeg ikke havde arbejdet med et monorepo før, selvom mappestrukturen var enkel nok, syntes jeg det var svært at finde rundt, samt vide hvilke mapper man skulle stå i for at køre kommandoer.\nJeg oplevede også udfordringer da den prompt jeg fik genereret til monorepo-strukturen oprindeligt var skrevet til OpenAI\u0026rsquo;s API. Jeg valgte at bruge Anthropic i stedet, men vidste faktisk ikke at de to API\u0026rsquo;er fungerer på forskellige måder. Både i forhold til hvordan beskeder struktureres, samt hvilke endpoint og headers der bruges. Det gav en del forvirring og indsigt, da jeg pludselig ikke helt forstod hvorfor og hvilke fejl der opstod!\nOpsamling \u0026amp; Læring # Når jeg kigger tilbage på projektet, er det tydeligt at udfordringerne ikke kom fra selve AI-integrationen, men kom fra alt det omkring. At navigere i en ny projektstruktur, og at arve en konfiguration der ikke passede til det API jeg faktisk brugte, var begge situationer hvor jeg ikke bare kunne copypaste mig videre. Jeg var nødt til at forstå hvad der skete.\nHvilket egentlig er den vigtigste ting jeg tager med mig! AI kan generere et imponerende startpunkt på meget kort tid, men det erstatter ikke forståelsen. Fejlene jeg løb ind i opstod præcis der, hvor jeg faktisk troede at koden bare virkede, uden at have sat mig ind i hvad den rent faktisk gjorde. Det er en god påmindelse om at have hovedet med, også når værktøjerne er kraftfulde.\nTil gengæld har projektet givet mig en meget mere konkret forståelse af prompt engineering. Det at jeg isolerede rubrik- og promptarbejdet som sin egen fase, inden jeg rørte koden, var en beslutning der betalte sig.\nSystem-prompten sætter kontrakten User-prompten leverer data Da jeg fik de to prompts i den rigtige retning var outputtet overraskende stabilt og konsekvent, hvilket var ret fedt at opleve i praksis.\nMonorepo-strukturen tager jeg også med videre. Når frontend og backend er så tæt forbundne, er det en naturlig og overskuelig måde at organisere et projekt på, når man først har fundet rundt i det selvfølgelig!\nAlt i alt, har det været super lærerigt, med nye teknikker og udfordringer!\n","externalUrl":null,"permalink":"/Portfolio/posts/ai-applikationer/","section":"Posts","summary":"","title":"AI-Applikation","type":"posts"},{"content":"","externalUrl":null,"permalink":"/Portfolio/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":" Kontekst \u0026amp; Formål # I sidste uges blogindlæg legede jeg med at tilføje min første RAG til min portfolio, hvilket resulterede i at den kun havde knowledge om én enkelt side. Da jeg gennem semestret løbende vil opdatere min portfolio med nye blogindlæg og projekter, gav det mening at udvide denne knowledge og bygge et automatiseret workflow der følger med.\nI dette blogindlæg fortæller jeg om hvordan jeg har opnået mit resultat, samt styrket mine færdigheder inden for prompting og tidsoptimering. Da hele dette projekt er vibe-coded med hjælp af claude chatbot.\nArkitektur # Første trin er at programmet finder frem til den rigtige knowledge base i Dify. Det sker ved at sende min API-nøgle til Dify, som svarer tilbage med ID\u0026rsquo;et på den knowledge base nøglen tilhører — ingen manuel konfiguration nødvendig.\nAndet trin er at hente alle sider fra min portfolio. Det gør programmet ved at læse min sitemap.xml — en fil der automatisk genereres af GitHub Pages og indeholder en liste over alle URL\u0026rsquo;er på siden. Herfra ved programmet præcis hvilke sider der eksisterer.\nTredje trin er at spørge Dify hvad der allerede ligger i knowledge basen. Det returnerer en liste over eksisterende dokumenter, som programmet gemmer til sammenligning senere.\nFjerde trin er selve indhentningen af indhold. For hver URL i sitemappet kalder programmet Jina Reader, som scraper siden og returnerer indholdet som ren Markdown — altså kun den relevante tekst, uden navigation og HTML-rod. For at skåne Jina\u0026rsquo;s servere kører det tre sider ad gangen med en lille pause mellem hvert kald.\nFemte trin er synkroniseringen med Dify. For hver side sammenlignes URL\u0026rsquo;en med listen fra trin tre — hvis siden allerede eksisterer i knowledge basen opdateres den, hvis den er ny oprettes den. På den måde undgår vi dubletter og knowledge basen afspejler altid det aktuelle indhold på min portfolio.\nTeknologier # Dify er platformen jeg bruger til at hoste min RAG-chatbot. Deres Knowledge Base API gør det muligt at oprette og opdatere dokumenter programmatisk, uden at skulle gøre det manuelt via deres interface.\nGitHub-Pages hoster min portfolio og genererer automatisk en sitemap.xml — en fil der lister alle URL\u0026rsquo;er på siden, som programmet bruger som udgangspunkt for synkroniseringen.\nJina Reader er en gratis web-scraper der tager en URL og returnerer sidens indhold som ren Markdown. Det er ideelt til RAG, da ren tekst giver bedre resultater i knowledge basen end rå HTML.\nJava er sproget workflowet er skrevet i, og bruger udelukkende indbyggede biblioteker — ingen eksterne afhængigheder nødvendige.\nDiagram Af Flow # Hvad Har Jeg Lært # Det sværeste ved dette projekt har ikke været det tekniske, det har været at slippe kontrollen. Når man ikke selv skriver koden, er det svært at bevare overblikket, og det er let at ende i et dybt rabbithole, hvor man ikke får sat sig ordentligt ind i koden, og hurtigt kan tænke at den selvfølgelig har styr på det. Det er en balance jeg stadig skal finde.\nDenne opgave har dog taget mig et step tættere på at finde den balance. Jeg blev overrasket over hvor meget en god prompt egentlig betyder — jeg har tidligere bare smidt en opgavebeskrivelse ind i en chatbot og fået et svar tilbage, uden at tænke videre over det. At sidde med en chatbot og se hvor stor forskel en præcis og gennemtænkt prompt gør for det endelige resultat, har været en øjenåbner! Derudover har jeg fået en dybere forståelse for de teknologier jeg har arbejdet med, og hvordan man med relativt lidt kode kan forbinde helt forskellige systemer og få dem til at tale sammen.\nResultat # Jeg har nu et workflow der ved manuelt start går ind og opdaterer Dify\u0026rsquo;s knowledge base med det seneste indhold fra min portfolio. Workflowet er smart nok til at skelne mellem nye og eksisterende sider, hvilket gør, at der aldrig opstår dubletter. Det betyder at Dobby altid er opdateret, så længe jeg husker at køre scriptet.\nFor at gøre det hurtigere arbejder programmet på 3 sider samtidigt frem for én ad gangen. For at undgå at Jina Reader opfatter det som spam, er der en bevidst pause på 600ms mellem hvert kald, hvilket generelt er god stil, så vi undgår at overbelaste den service man bruger!\nDet næste naturlige skridt er at gøre det helt automatisk, så workflowet selv kører én gang om ugen og tjekker efter nyt indhold, uden at jeg behøver at gøre noget. Men det er en opgave til en anden dag!\n","externalUrl":null,"permalink":"/Portfolio/posts/automatiseret-workflow/","section":"Posts","summary":"","title":"Automatiseret Workflow","type":"posts"},{"content":"","externalUrl":null,"permalink":"/Portfolio/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":" Her er forsiden # ","externalUrl":null,"permalink":"/Portfolio/","section":"forside","summary":"","title":"forside","type":"page"},{"content":"","externalUrl":null,"permalink":"/Portfolio/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":" Hvad er RAG? # RAG er en arkitektur, man kan bruge til at designe sin AI og fodre den med selvvalgt materiale. Det sker via 3 steps:\nRetrieve - her går systemet ind og finder de relevante chunks tekst fra materialet, der passer til spørgsmålet.\nAugment - så lægger man de stykker tekst ind foran spørgsmålet, så ens AI kan se dem.\nGenerate - det er her AI\u0026rsquo;en svarer på spørgsmålet med de givne stykker tekst, frem for at gætte sig til et svar.\nDet er super smart, da vi ikke behøver at blive ved med at skulle træne vores AI forfra. Vi kan i stedet blive ved med at fodre den med ny viden, samtidig med at man kan vinkle den som ønsket.\nHvad har jeg brugt det til? # Jeg har valgt at lave en RAG-chatbot til min portfolio, Dobby. Jeg har fodret Dobby med mit CV, hvilket var super nemt at få på benene. Jeg har tilføjet en knowledge, hvor jeg har trukket mit CV ind og sat det på Dobby. Et tryk på publish, og så kunne jeg ellers bare begynde at spørge løs og få relevante svar tilbage.\nAt få den linket op til min portfolio har været en helt anden snak\u0026hellip; Mit første forsøg var at lave en knowledge, hvor den syncer fra website ved at bruge Jina, som jeg havde sat op med en API-nøgle. Jeg fandt frem til, at så lille som min portfolio er lige nu, skulle det ikke tage mange minutter, men efter flere forsøg på at den skulle crawle igennem siderne, måtte jeg give op.\nMit andet forsøg blev at prøve Firecrawl, hvilket jeg hurtigt fik op at køre med undersider og hele molevitten. Det var efter første forsøg en kæmpe sejr! Det gik dog hurtigt ned ad bakke, da mit gratis abonnement ikke dækker flere sider\u0026hellip;\nJeg gik herefter tilbage og legede med Jina igen for at se om den kunne hente en enkelt side, hvilket den godt kunne. Jeg valgte derfor at bruge Jina, da den er simpel og hurtig til enkle sider og god til statiske sider som lige netop min portfolio.\nHvad blev mit resultat, og hvad fik jeg ud af det? # Jeg har nu min RAG-bot Dobby, som med mine instruktioner kan svare på relevante spørgsmål vedrørende mit CV. Det kan være alt fra mine skills til hvilke teknologier jeg har erfaring med, og selvfølgelig lidt om mig. Det giver super god mening fremtidsmæssigt, hvis mulige arbejdspladser kommer ind og kigger på siden. Fordi jeg har valgt at indsætte Dobby på min portfolio, gav det virkelig god mening at Dobby selvfølgelig også skal kunne fortælle om mine blogindlæg.\nJeg vil arbejde videre på at finde en måde at få inkluderet min GitHub-profil til Dobby, så det på et tidspunkt vil være muligt at have knowledge om mine projekter, og at Dobby kan vokse sin viden om mig endnu mere.\nJeg har nu allerede fået en bedre forståelse for arkitekturen og flowet af, hvordan RAG deler dataen op i chunks, kategoriserer og sender kontekst til AI\u0026rsquo;en, som derfra kan vælge det mest relevante svar.\nAlt i alt meget sjovt at arbejde med!\n","externalUrl":null,"permalink":"/Portfolio/posts/rag/","section":"Posts","summary":"","title":"RAG","type":"posts"},{"content":"","externalUrl":null,"permalink":"/Portfolio/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/Portfolio/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Her er min første blogpost # I dag er vi kommet i gang.\n","externalUrl":null,"permalink":"/Portfolio/posts/velkommen/","section":"Posts","summary":"","title":"velkommen","type":"posts"}]