Eksamens post#
Elevator pitch / kort beskrivelse#
Hvad har I bygget? En applikation til EG til brug for deres eventplanlægning og reminder system. Ved fuldendelse af programmet, skal det erstatte deres nuværende arbejdsgang med Trello. Samt afhjælpe J’s copy-paste arbejde.
Hvem er løsningen til? Løsningen er til EG, som er en kunde, vi har haft besøg af i valgfagets forløb.
Hvilket problem eller behov adresserer den? Det addressere problematikken med at reminde og huske datoer, samt J’s massive arbejde med copypaste indhold fra A til B og C og D. Samt holde oplysninger opdateret automatisk, hvor det kun er i særlige tilfælde hvor AI-agenten er usikker på informationens korrekthed.
Projekt og værdiskabelse#
Hvilket konkret behov/problem hos kunden adresserer løsningen? Se ovenstående Hvem er brugeren? Primært J fra EG, men også B derfra og andre ansatte. Hvad ville kunden få ud af løsningen, hvis den blev realiseret? Et program hvor de nemt kan skabe sig et overblik, samt reminders på manglende oplysninger til kunden. Et program som derudover kan afhjælpe J’s arbejdsbyrde i form af copypaste oplysninger og skabeloner fra diverse steder. Hvilke antagelser byggede projektet på? Hvilke frister og opgaver der var, i forhold til reminders. Endvidere antog vi også at de ville være åbne overfor et nyt program som på sigt kan tale sammen med e-conomics, og erstatte trello. Dette kræver at EG vil afsætte tiden til at lære det nye system at kende.
Hvad blev bevidst fravalgt? Vi fravalgte både logistik-delen, sommerhus-bookning og julemarkedet. Selvom at overnatninger som minimum vil kunne implementeres, især da dette er tæt tilknyttet nogle bryllupper, og der er en problematik i forhold til de forskellige trello’er de bruger. Endvidere fravalgte vi at inkorporere trello direkte, men i stedet for at erstatte det, således en mulig besparelse på en udgift kunne undgåes. Derudover har vi heller på nuværende tidspunkt inkorporeret hverken e-mailsystem eller e-conomics. Både e-mail system og e-conomics inkorporation kunne være fremragende features til senere, som virkelig kunne skabe værdi ved at samle deres arbejde til en platform.
I og med at EG var lidt bekymrede for at AI skulle være inde over kundekontakt og de så ville miste det personlige touch, har vi valgt at implementere AI i et bagvedliggende system hvor det kun er EG selv der benytter sig af det.
AI-delen#
Hvor indgår AI i løsningen? Vi benytter os af en LLM-api, dvs. en ekstern api for en Large-language-model, navnligt OpenAIs. Det indgår ved aflæsning af dokumenter og indsættelse af oplysningerne i de korrekt felter og i oprettelse af frister og opgaver. Endvidere skal den kunne opdatere frister og opgaver ved ændring af oplysninger manuelt. Den skal kunne sortere informationer fra dokumenter den bliver givet, og vi har derfor valgt at den skal have en sikkerhed på 90% før den bruger en information, således at fejloplysninger ikke bliver udfyldt. Den skal nemlig selv kunne ligge informationerne i de rigtige mapper.
Hvorfor giver AI mening netop dér? Det kan gøre så J ikke skal gøre det manuelt. J bruger på nuværende tidspunkt meget tid på at copy paste oplysninger fra et sted til et andet, og ændre oplysninger ved f.eks. tillægskontrakter. Ved brug af vores program, bør J kun skulle gøre dette ved informationer hvor sikkerheden for accuracy er under 90 %. (Sidenote: de virkede ret nervøse for at AI’en ville skrive de forkerte oplysninger, hvorfor denne accuracy guardrail(?) kan anses som en sikkerheds feature, der kan være med til at berolige dem. Særligt i begyndelsen). Endvidere skal han på nuværende tidspunkt selv huske diverse frister, og holde øje med dem, hvor systemet vil kunne gå ind og påminde når en kommende frist nærmer sig. Det vil endvidere tage noget af arbejdsbyrden fra ham. Planen er også at systemet automatisk opdaterer frister/opgaver hvis han skriver de oplysninger det vedrører.
Hvad er input og output? Input
- Brugerinput (frontend): brugeren uploader et dokument (typisk PDF) under Dokumenter/noter.
- Dokumentmetadata (backend):
documentType,filename,weddingId,documentId. - Dokumentindhold: enten udtrukket tekst fra PDF eller sidebilleder (base64) til vision.
- Prompt-kontekst: systemprompt + user prompt med dokumenttype, filnavn og dokumenttekst.
I koden sker det sådan:
AiDocumentAnalyzerService.analyze(...)læser filen viaPdfTextExtractor.- Hvis der er brugbar tekst:
OpenAiClient.extractFromText(documentType, filename, documentText). - Hvis ikke:
OpenAiClient.extractFromImages(documentType, filename, pageImagesBase64).
Output Der er to niveauer af output:
Output fra modellen (struktureret JSON), som parses til
ExtractionResult:fieldssuggested_taskssuggested_deadlinessuggested_pricing_selections
Output fra backend-processen efter mapping/merge/gemning:
- forslag gemmes i DB-tabellen
extracted_information - høj-confidence forslag anvendes automatisk (
applied=true) - status på dokument sættes til
processing/completed/failed - API returnerer
AnalyzeResultmed:- opdateret dokument
- liste af extracted information
autoAppliedCountpendingReviewCount
- forslag gemmes i DB-tabellen
Hvad kan AI’en gøre godt? Aflæse dokumenter udfra parametre vi sætter, lave automatiske frister ud fra parametre vi sætter.
Hvor kan AI’en fejle? Hvis de parametre vi sætter ikke er konkrete nok, eller oplysninger mangler fra dokumentet.
Hvor bør der være menneskelig godkendelse? Vi har valgt en accuracy under 90 % skal have menneskelig godkendelse, da fejl kan opstå og skabe forvirring i informationer.
Hvilke værktøjer, modeller eller metoder brugte I? Vi har benyttet systemskitse ved begyndelsen, projektbeskrivelse, aftalt techstack.
Vi har brugt nogle af principperne fra Spec-Driven-Dev, såsom begrænsninger, formål og …
Endvidere har vi også talt/diskusteret meget om hvordan vi ville have programmet til at virke, og undervejs delt de erfaringer vi har gjort med vores kodeagenter. Vi har kunne gennem projektet se relationen mellem programmet og virkeligheden, i forhold til planlægning af et bryllup.
Vi har arbejdet ud fra en blanding af spec-anchored og spec-as-source, da vi i løbet af projektet også har skabt os erfaringer i forhold til dokumentet, og hvilke behov der er. Undervejs, da vi ikke havde alle oplysninger fra start af, er der kommet ændringer, som jo har gjort at vi har skulle tilpasse os, men har brugt specifikationerne som en støtte.
I forhold til spec-as-source, er det jo specifikationer som vi har givet til Codex/Cursor, som derefter har generet koden. Hvor specifikationerne så har været kilden til det implementeret.
Vi har på en måde arbejde i flows, hvor vi har taget små opgaver og arbejdet med disse per projektgang.
Vi har særligt benyttet os af plan-mode så vi kunne gennemgå hvad kodeagenter planlagde inden implementering, og vi har også i stor grad benyttet os af pair-programming
Hvordan har I arbejdet med prompts, kontekst eller struktureret output?
Prompt-opbygning
Vores bruger en system + user prompt struktur:
- Systemprompt ligger i
backend/src/main/resources/ai/system-prompt.md - User prompt bygges i
WeddingExtractionPrompt.userMessage(...)
User-prompten indeholder:
- dokumenttype
- filnavn
- evt. ekstra kontrakt-hints (når
documentType=contract) - selve dokumentteksten (og den kalder på system-prompten)
Det giver modellen både generelle regler og konkrete, runtime-specifikke data.
System-prompten er den faste styring af modellens adfærd på tværs af alle dokumenter. Den fungerer som en kontrakt for, hvad modellen må returnere, og hvordan den skal tolke bryllupsdomænet.
Konkret bruges system-prompten til at:
- fastholde det præcise JSON-format (
fields,suggested_tasks,suggested_deadlines,suggested_pricing_selections) - begrænse modellen til tilladte keys (både field keys og pricing item keys)
- sikre at pakkevalg placeres i
suggested_pricing_selectionsog ikke som almindelige fields - indbygge domæneregler (fx mapping af “natmad”, “bryllupskage”, “overnatning”, reception kun ved eksplicit nævnelse)
- kræve, at svaret er ren JSON uden forklarende tekst
User-prompten giver dokumentets konkrete indhold, men system-prompten holder svaret inden for de strukturelle og faglige rammer.
Kontekst
Kontekst er styret på tre niveauer:
Domænekontekst i systemprompten
- tilladte field keys
- tilladte pricing keys (item_key)
- mappings for typiske kontraktord (fx natmad →
addon_late_night_food) - regler for confidence, quantity og selected
Dokumentkontekst i user prompten
- dokumenttype, filnavn og fuld tekst
- ekstra kontrakt-instruktioner for tilkøb/reception/overnatning
Post-processing kontekst i backend
- heuristikker (
ContractFieldHeuristics,ContractPricingHeuristics) - merge-logik (
ExtractionResultMerger) som fylder huller i LLM-svaret
- heuristikker (
Struktureret output
Vi har arbejdet med robust strukturering som betyder at vi har haft regler for opsætning og hvordan svaret blev returneret.
- OpenAI-kaldet bruger
response_format: { "type": "json_object" }. temperatureer sat lavt (0.1) for mere stabilt, deterministisk output.- Temperature er en parameter til sprogmodeller, der styrer hvor “kreative” eller tilfældige svarene er.
- Lav temperature (fx 0.1): modellen vælger de mest sandsynlige næste ord → mere forudsigeligt, ensartet output.
- Den sættes i baseRequestBody() i OpenAiClient.java (linje 64)
- Svaret parses til typed records i
ExtractionResult. - Ukendte felter ignoreres med
@JsonIgnoreProperties(ignoreUnknown = true).
Det reducerer risikoen for fri tekst-svar og gør output direkte anvendeligt i kode/DB.
Validering og robusthed
Efter modelsvaret sker der flere sikkerhedsled:
- tomt/ugyldigt svar giver fejl
- hvis LLM ikke finder noget, bruges heuristik-resultat som fallback
- LLM + heuristik merges, så kontraktens tilkøb ikke tabes
- forslag mappes til interne felter/sektioner før gemning
- auto-apply styres af confidence-tærskler
Praktisk betydning
Denne tilgang betyder, at AI-delen ikke kun er “et enkelt prompt-kald”, men en fuld pipeline:
- kontrolleret input
- stram prompt og nøgleliste
- struktureret JSON-output
- typed parsing
- heuristisk backup
- domæne-specifik anvendelse i systemet
Det giver højere stabilitet end en ren “fri prompt → fri tekst”-løsning.
Hvordan har i styret projektet? Spec-driven development etc. Vi har som tidligere nævnt tildels benyttet os af spec-anchored og spec-as-source delene fra spec-driven dev.
Teknisk løsning og arkitektur#
Hvordan er løsningen bygget op? En 3 lags MVP i forhold til model, view, protokol.
Vi har fået kodeagenten til at skrive i java og react.
Hvilke dele ligger i frontend, backend og eventuel database? Forretningslogikken ligger i backenden sammen med AI’en, prisberegningen og fristreglerne. Forbindelsen til databasen er også i backenden. Frontenden håndtere udelukkende view delen, ved at kalde på backendens oplysninger.
Hvordan bevæger data sig gennem systemet?
Det mest typiske flow:
Brugeren uploader en pdf via frontend, som sender den via endpoint til backenden der gemmer det. Når det er sendt, bliver analysen triggered ved et post endpoint, hvorefter AiDocumentAnalyzerService (metode i service-lag) sættes igang. Denne metode ændre først status, så udtrækker den tekst og billeder, så kalder den OpenAI for struktureret JSON (teksten der er udtrukket, hvor AI finder oplysninger og returnere som JSON), så merger den med heuristikke svar, i tilfælde hvor confidence ikke er over 0.9. Dernæst gemmer den forslagene i extracted_information som er en staging tabel for vores AI-forslag. Dernæst tjekkes om oplysningerne er over confidence grænsen på 0.9, hvorefter der oprettes frister og opgaver. Til slut fore AiDocumentAnalyzerService sættes status som færdig. Frontenden henter bryllupsdetaljerne via et get-endpoint.
- Bruger uploader PDF i frontend →
POST /api/weddings/{id}/documents - Backend gemmer fil på disk + metadata i
documents - Bruger (eller auto ved oprettelse) trigger analyse →
POST .../analyze AiDocumentAnalyzerService:- Sætter
ai_status = processing - Udtrækker tekst/billeder med Apache PDFBox (
PdfTextExtractor) - Kalder OpenAI for struktureret JSON
- Merger med regex-heuristikker (
ContractPricingHeuristics) for tilkøb - Gemmer forslag i
extracted_information - Auto-anvender felter over confidence-tærskel (tærskel sættes i .env)
DeadlinePlannerServiceopretter opgaver/frister- Sætter
ai_status = completed
- Sætter
- Frontend henter bryllupsdetaljer →
GET /api/weddings/{id}og viser felter, AI-forslag, pris osv.
Hvor kaldes AI-modellen/API’et? Den kaldes i backenden men mere specifik i AiDocumentAnalyzerService.analyze(), hvor man enten kalder den som tekst eller billede. OG selve forbindelsen er i filen: OpenAiClient.java. Vores configurationer kommer fra vores .env fil som bliver sat sammen i AIconfig, der ligger i AI mappen.
Hvordan håndteres fejl, svartider eller usikre svar? Vi har nogle enkelte fejlkoder hvis filen er tom eller ikke findes. Der er også en enkelt fejlhåndtering med errorbesked til frontenden.
Vi har ikke selv arbejdet med svartiderne, så det er bare de standarder der er med. OpenAI kald har en 3-minutters timeout. HTTP connect har en timeout på 30 sekunder.
Usikre svar bliver set bort fra baseret på vores confidence-tærskel som sættes i .env filen. Til resultater med lavere confidence end 0.9 har vi gjort så forslagene kommer frem under fanen AI-forslag med en knap for anvend.
Det er et semi-automatisk system, så brugeren har altid mulighed for at godkende eller ændre usikre forslag.
Hvordan er løsningen deployed eller tænkt deployed? Lokalt (men semi klar med docker-compose.yml), hvis man ville benytte den skal man nok sørge mere sikkerhed, i form af login. Men vil kunne deployes på en hjemmeside. Man skal også huske en database forbindelse, hvor vi pt. benytter docker.
Vi har på nuværende tidspunkt ikke tilpasset den til en mobilskærm.
Hvilke tekniske valg er du mest tilfreds med?¨
- Opsætning, med en klar techstack, så der er en separation of concerns.
- Semi-automatisk system, hvor brugeren selv kan tilrette ting, og at der confidence-tærskel.
Som er de specifikationer vi har været særligt opmærksomme på, og som vi har sat.
Hvad ville du ændre, hvis du skulle bygge det igen?
Vores prompts, vi glemte tit at skrive hvis du har spørgsmål så spørg. Den kom med nogle spørgsmål, men vi har også skulle ændre og lave flere prompts, fordi vi ikke var specifikke nok.
Vi ville nok også gerne have noget mere struktur og gennemgang af den kode der blev lavet løbende, i stedet for den bare smed på med ting, såsom heuristik. Men dette kræver mere tid.
Jeg ville bruge deres oplysninger fra start, hvis man havde dem. Da ændringen af dette også skabte nogle udfordringer.
Generelt ville jeg ønske at vi havde flere oplysninger fra dem, da vi fortsat har manglet informationer.
Feedback fra dem undervejs ville også være rart.
Jeg ville nok også ændre arkitekturen af vores system- og userprompts, så de er placeret sammen i stedet for hhv. resources/ai og java/ai.
Blogposts / refleksion#
Hvilke faglige pointer fra dine blogposts blev vigtigst i projektet?
- Spec-driven dev
- Userprompt og systemprompt
- LLM-api
Er der noget, du ser anderledes nu end tidligere i forløbet?
- Cursor - cursor er langsom i forhold til codex.
- Men kodeagenter generelt og noget af deres sikkerhed.
Hvordan har dine refleksioner påvirket jeres løsning?
- At vi ikke har turde deploye det, fordi vi ikke ved nok om hvordan gør det sikkert nok når AI er indenover udviklingen.
- Ikke at lave det til en boble fra start af, sørge for at der kan bygges videre på det. Ikke afgrænse sig for meget.
Hvad lærte du om at bygge AI-drevne applikationer?
- Inkorporering af OpenAI, system-prompt, user-prompt
- Den digitale forestilling er særlig vigtigt. Så man har en idé om hvad der skal bygges, så man faktisk får hvad man har brug for
- Man skal være specifik i sin formulering til prompts
- Tænk fremad, så den ikke bliver afgrænset. Så det ikke bare er en boble, og når man skal bygge videre skal man lave en ny.
- Man skal have kunden med og de skal være klar til AI-delen, f.eks. ville en chatbot eller automatiseret svar ikke være relevant her, da det er et særligt ønske fra kunden at have det personlige touch.
- Sikkerhed, at AI nok har adgang til hele ens pc. Så man skal være påpasselig
- RAG retrieval augmented generation - ofte chatbots
- Tokens brug ser vildt ud, men ofte “billige”
- Versioner af LLM’er kan variere i pris og effektivitet. GPT-4o-mini
Individuelt bidrag#
Vi har arbejdet meget tæt sammen Hvad har du konkret bidraget med i projektet? Kommunikation og samarbejde. Vi har udarbejdet projektet i samhørighed, og aftalt specifikationer sammen for dokumentationen. Hvor derefter fået AI til at lave koden. Hvilke dele af løsningen forstår du særligt godt? Den digitale forestillingsevne. Grundprincipperne bag problemstillingen og de ønsker fra kunden der har været. Hvilke tekniske eller faglige valg har du selv været med til at træffe?
- Techstacken
- Udseenede af brugergrænsefladen.
- Sætte grænser for AI’en såsom confidence og IKKE FRI-TEKST Hvad har du lært gennem dit eget arbejde?
- Cursor er langsom
- Claude var lidt hurtigere, men kom med dårligere resultater.
- Man skal være specifik, og gode prompts med kontekst kan hjælpe.
- En forarbejdet plan er godt at have, så man kan vurdere arbejdet mere grundigt, og undgå bobler.
- Man skal huske at være kodekritisk
Afsluttende perspektivering#
Hvad ville næste skridt være, hvis kunden ville videre?
- Skalering til mobil-side
- Eksempel på kontrakt, så vi kan optimere LLM’en til at finde oplysninger udfra deres specifikke opsættelse
- Login funktion for sikkerhed
- Opgave optimering
- E-conomics integration (priser)
- Funktion til automatisk oprettelse af tilbud, med brug af LLM og e-conomics.
- Mail funktion til reminders, og eventuelt så bruger kan sende e-mails direkte fra programmet Hvad skulle testes, før løsningen kunne bruges rigtigt?
- Om deres kontrakter kan aflæses af vores AI.
- Generelt lave nogle fake documents, både gode og dårlige kontrakter, så man kan se om den fanger oplysninger eller mangel på sammen. Hvad er den største risiko ved løsningen? Manglende login funktion Hvad er din vigtigste læring fra forløbet?
- Mie er god at arbejde sammen med
- Sikkerhed
- Afgrænsning af LLM
Noter#
Vi har Heuristik som et sikkerhedsnet til vores AI, ved aflæsning af dokumenter.
- Heuristik = faste mønster-regler i Java (regex/søgestrenge), ikke AI
- heuristicResult = det strukturerede resultat fra de regler — samme datatype som AI-svaret (ExtractionResult)
- Merge = AI + heuristik kombineres, så tilkøb og kontraktfelter ikke går tabt
- Heuristik er et sikkerhedsnet for jeres specifikke kontraktformat, især under “Tilkøb”