Jello
en shoppingagent du skriver til
En AI-agent i iMessage og WhatsApp der søger i 36 butikker og markedspladser. Priser skrives af kode, aldrig af modellen
En startup med betalende brugere.
- 1stplads, Hackathon of Hackathon Winners
- 52,380linjer kode
- 36butiks- og markedspladsadaptere
- 585commits på ti dage
- 15 minhårdt loft på en dyb søgning
Prøv det
To rigtige søgninger, optaget 29. september 2026 og afspillet med komprimeret timing; fund, priser og antal er som optaget. Live søgning er slået fra på dette site, så udgifterne holder sig under loftet.
Arkitektur
Forbindelser
- Kunden på iMessage eller WhatsApp → Chatagent: forstår ønsket: tekst, foto eller link
- Chatagent: forstår ønsket → Planlægger: søgninger på fem sprog: struktureret ønske
- Planlægger: søgninger på fem sprog → Søgerunder: butikker, markedspladser, web: parallelle søgninger
- Søgerunder: butikker, markedspladser, web → Stopregler og hårde lofter (kode): må vi stoppe?
- Stopregler og hårde lofter (kode) → Søgerunder: butikker, markedspladser, web: manglende arbejde
- Stopregler og hårde lofter (kode) → Dommer-LLM og billedtjek: shortlist
- Dommer-LLM og billedtjek → Live tjek af hver produktside: relevante annoncer
- Live tjek af hver produktside → Svar: links og priser sat af koden: op til tre live forslag
- Svar: links og priser sat af koden → Kunden på iMessage eller WhatsApp: beskedbobler
En besked kommer ind på iMessage eller WhatsApp og når en chatagent, der laver den om til et struktureret ønske: hvad personen vil have, faste krav som størrelse, stand eller budget og, ved et ønske som “en gave til far, han kan lide kaffe og fiskeri”, ét aspekt pr. interesse.
Søgemotoren er en Python-pakke uden afhængigheder ud over standardbiblioteket. Den har to tilstande. En hurtig søgning tager cirka 15 til 25 sekunder: én side fra de relevante platforme, én websøgning, et relevanstjek, et live tjek af hver kandidat og så svaret. En dyb søgning kører i cirka fem minutter og aldrig over femten: en planlægger skriver søgevarianter på dansk, svensk, norsk, tysk og engelsk, så følger runder af parallelle søgninger med flere sider og prissorterede gennemløb, og en kritiker leder efter huller efter hver runde.
Hver markedsplads og butik er en adapter bag ét interface og findes automatisk. Alle tal, motoren bruger, står i én konfigurationsfil.
Før et svar sendes, vurderer en sprogmodel shortlisten for relevans, og ved tøj og møbler tjekker en billedmodel fotoene for den ønskede pasform, det ønskede mønster eller den ønskede farve. Hver tilbageværende kandidats produktside hentes live for at bekræfte pris og lager. Svaret sættes sammen af koden: links, titler og priser kommer kun fra verificerede kandidater, og skrivemodellen leverer kun en indledning, én kommentar pr. forslag og en afslutning.
En model må aldrig bestemme, hvornår en søgning er færdig
Begrænsning. Overladt til sig selv stopper en sprogmodel, når den føler sig færdig, og det er som regel for tidligt ved svære ønsker og for sent ved lette. En dyb søgning koster også penge for hvert kald.
Muligheder. Lad modellen kalde et “færdig”-værktøj, eller lad koden afgøre det ud fra, hvad der faktisk er søgt.
Beslutning. Koden afgør det. Et sæt rene funktioner over søgeloggen lister, hvad der mangler: en platform, der aldrig er spurgt, en primær platform med for få søgevarianter, en fuld resultatside, der stadig giver nye kandidater, et prissorteret gennemløb, der ikke er kørt, resultater, der stadig flytter sig mellem runderne, færre end tre verificerede live forslag. Hver mangel bliver næste rundes arbejde. Hårde lofter vinder over alt: 8 runder, 40 modelkald og 0,20 dollar pr. dyb søgning, plus et tidligt stop, når to runder i træk ikke har tilføjet noget, der lever op til kravene.
Resultat. En dyb søgning har et kendt loft over tid og pris, og den kan ikke give op, før de vigtige platforme er gennemsøgt.
Ingen opfundne produktfakta
Begrænsning. Et svar med en forkert pris eller et dødt link er værre end intet svar.
Beslutning. Hver kandidat verificeres live på sin produktside, før den kan blive et forslag. Links, titler og priser i svaret sættes af koden. En linje fra skrivemodellen med en URL eller en pris, der ikke er et af forslagenes, bliver afvist og erstattet af en fast linje. Det samme gælder en linje, der er for lang.
Resultat. Modellen vælger ordene omkring fakta, aldrig selve fakta.
Relevans er sværere end søgning
Begrænsning. Søgning på ord giver tilfældige træffere. “Mac” fandt en airfryer og LEGO. En fiskeblink med kaffesmag matcher “kaffe og fiskeri”.
Beslutning. Ønsker om et mærke eller en produktfamilie udvides til konkrete produkttyper inden for budgettet, og det løse ord søges aldrig. Dommeren fjerner tilfældige ordtræffere og generiske gaveartikler. Nævner personen et bestemt look, kan kun annoncer, hvis foto viser det, blive forslag. Færre rigtige træffere betyder færre forslag, aldrig fyld med noget, der næsten passer.
Resultat. Jello siger hellere, at den fandt ét match eller intet, end at den sender tre, der ikke passer.
Kode
De hårde lofter. Det første, der rammes, tvinger søgningen til at afslutte. Uredigeret.
brain/jello/gates.py
def hard_cap(led, cfg: dict) -> str | None:
"""Caps override everything and force the beast-mode finalise. Returns the reason or None."""
m = led.meter
checks = [(led.elapsed() >= cfg["finalise_s"], "time"),
(led.rounds >= cfg.get("max_rounds", 1), "rounds"),
(led.count("platform") >= cfg.get("max_platform_fetches", 10**9), "platform fetches"),
(led.count("web") >= cfg.get("max_web_searches", 10**9), "web searches"),
(m.get("llm_calls", 0) >= cfg.get("max_llm_calls", 10**9), "LLM calls"),
(m.get("cost_usd", 0) >= cfg.get("max_spend_usd", 10**9), "spend"),
(dry(led, cfg), "nothing new turning up")]
return next((why for hit, why in checks if hit), None)Stop tidligt, når et svært ønske er løbet tørt. Uredigeret.
brain/jello/gates.py
def dry(led, cfg: dict) -> bool:
"""Nothing turning up: the last K rounds added no qualifying candidate and there still aren't enough for the
picks. More rounds of the same won't fix a hard ask; stop early and answer with what there is."""
k = cfg.get("dry_rounds")
h = led.top_history
if not k or len(h) < k + 1 or led.rounds < cfg.get("min_rounds", 1):
return False
before = h[-k - 1].get("n_q", 0)
return all(x.get("n_q", 0) <= before for x in h[-k:]) and h[-1].get("n_q", 0) < cfg.get("verified_min", 3)Skrivemodellen må ikke finde på et link eller en pris. Uredigeret uddrag af en længere funktion.
brain/jello/loop.py
def foreign_money(s):
for m in _MONEY.finditer(s):
try:
if int(float(re.sub(r"[.,](?=\d{3}\b)", "", m.group(1) or m.group(3)).replace(",", "."))) not in allowed:
return True
except ValueError:
return True
return False
def clean(s, fallback, max_words=None):
s = re.sub(r"[*_#`>\[\]]", "", str(s or "")).strip()
# Too long counts as a miss too: a text reply, not an essay (see writer.md).
if _URL.search(s) or foreign_money(s) or not s or (max_words and len(s.split()) > max_words):
if s:
meta["rejected"].append(s[:120])
return fallback
return sBaggrund
Skal du købe noget bestemt online, skal du gennem brandbutikker, store forhandlere og genbrugsmarkeder én for én, på flere sprog, og du ved stadig ikke, om den billigste annonce er ægte, på lager eller overhovedet det rigtige produkt. Chatassistenter svarer med stor sikkerhed, men de finder på priser, links og produktdetaljer.
Jello er en gratis shoppingassistent, du skriver til på iMessage og WhatsApp. Med teamets egne ord fra partnersiden:
Who we are Jello won 1st place at the Hackathon of Hackathon Winners in September 2026. We built it there and have kept building since.
Jello is a free shopping assistant people text on iMessage and WhatsApp. They ask for a specific product, and Jello replies with a few matching picks and a direct link to the shop that sells them.
No made-up product facts. Price, stock and details come from your product page or feed.
Jello lanceres snart. Min del var størstedelen af søgemotoren: løkken, der bestemmer, hvad der skal søges på, hvornår der skal stoppes, hvad der er relevant, og hvad der er sikkert at sige.
Det ville jeg gøre anderledes
- Færre særtilfælde, mere data. Reglerne for størrelser, istandsatte varer og produktfamilier rettede hver en reel fejl, og samlet gør de søgeløkken til en fil på over 2.500 linjer. Flere af dem hører hjemme i data pr. kategori frem for i koden.
- Måle hele søgningens pris, ikke kun delene. Lofterne pr. kald holder, men den samlede pris for en gennemsnitlig dyb søgning er stadig et skøn.
Dybdegående
I den rækkefølge systemet var svært at bygge.