Hackathon project · Allergen agent for restaurant servers
The model hears and speaks. The code decides.
Can this diner eat this dish? SafePlate is an allergen agent on Gemma 4 E2B. A server holds the phone, a diner asks in their own language, and the agent answers or refuses with a reason. It never calls a dish safe by default.
The demo replays real recorded runs step by step. Gemma 4 E2B is a 7.2 GB model that runs on a laptop; clone the repo to run the agent yourself.
SerpApi recognition award · Gemma 4 Hackathon Paris
Recorded run · rules mode
Pad thai
“I'm allergic to fish — can I have the pad thai without fish sauce?”
- Rules read the typed requestRule
- The dish table decides what can come outRule
- SerpApi — what the manufacturer declaresSerpApiForced
- Refuse rather than guessRuleForced
- Fixed sentences state the verdict, in EnglishRule
Do not serve
A risk was confirmed, or it could not be ruled out. I can't confirm this dish.
We cannot serve this as requested. Fish sauce is the salt and the backbone of pad thai.
A diner asks. The server holds the phone.
- 1
The diner asks in their own language, out loud or typed, whether they can eat a dish.
- 2
The agent checks the dish table and the EU-14 allergens, looks up packaged ingredients with SerpApi, and puts one question to the kitchen when the dish could be cleared.
- 3
The server reads back the answer in the diner's language, or a refusal with the reason. Most often: do not serve, I can't confirm.
Recorded run · Gemma 4 E2B
Diner, in French
“Je suis allergique au céleri. Les pâtes bolognaises, c'est possible ?”
Agent, back in French. The verdict was fixed by code first.
Nous ne pouvons pas servir ce plat comme demandé. Le céleri fait partie de la base de la sauce bolognaise et est donc essentiel à cette recette. Je peux vous proposer une autre option.
We cannot serve the pâtes bolognaises as requested. Celery is an essential part of the seasoning base for this dish. Because it is incorporated into the sauce, we cannot prepare it without it.
The model offered the dish the system had just refused.
In testing, Gemma was handed the verdict “do not serve” and the reason: fish sauce is structural to pad thai. It wrote the sentence below anyway. A stronger prompt did not stop it, so a deterministic check now reads every reply before the diner does.
What Gemma wrote, discarded
“We can offer you the Pad Thai without any added fish sauce instead.”
What the diner got, assembled from facts
We cannot serve this as requested. Fish sauce is the salt and the backbone of pad thai. Without it the dish has no seasoning base — it is not a garnish that can be left off. The fish sauce cannot be left out, so this dish is not possible for you. We can suggest something else from the menu.
def contradicts_refusal(text, run):
if run.verdict != "do_not_serve":
return False
lowered = text.lower()
if not any(p in lowered for p in OFFER_PATTERNS):
return False
return not any(p in lowered for p in ALTERNATIVE_PHRASES)E2B didn't escalate on its own, so escalation is control flow.
Given an ingredient it could not resolve, the model answered about what it did know and stopped. So the loop never asks it whether to look something up or call the kitchen. Plain code forces both, and the only path that clears a dish runs through the kitchen.
if assessment.unknown_dish:
return _forced_escalate(...) # needs_confirmation
disputed = packaged and _lookup(run, packaged[0], ...) # SerpApi
if assessment.blocking:
return _forced_escalate(...) # do_not_serve
if disputed:
return _forced_escalate(...) # do_not_serve
return _ask_kitchen(run) # the only way to clearThree layers. Only one of them decides.
The model is at both ends of the loop, never in the middle. Code makes every decision and forces the two calls a small model skips: the SerpApi lookup and the question to the kitchen.
1Hear
ModelTurns what the diner says into a dish and an allergen.
2Check
CodeThe dish table and the EU-14 allergen table.
3Look up
Code + SerpApiPackaged ingredient, such as fish sauce? SerpApi fetches what the manufacturer declares.
SerpApiForced
4Ask
HumanOne question to the chef, who taps No risk, Risk or Unsure.
Forced
5Decide
CodeDo not serve, needs confirmation, or can serve with confirmation.
6Speak
ModelWrites the answer in the diner's language. A reply that contradicts the verdict is discarded.
In rules mode the model lane is replaced by fixed rules and fixed English sentences, so the agent runs without Gemma. The code and human lanes are the same either way.
What was measured
Every safety decision is a table lookup, so it is fast and gives the same answer twice. The slow part is the model hearing and speaking, and that runs on a laptop.
- EU allergens in the table
- 14
- Regulation (EU) No 1169/2011, Annex II
- Time for a rule decision
- < 1 ms
- Every assess and escalate step in the recorded runs
- Recorded Gemma 4 E2B run
- ~55 s
- Median of six runs end to end on a laptop: most took 46 to 74 s, one took 128 s
- Dishes cleared without the kitchen
- 0
- The only path to a clear runs through ask_kitchen
If this were a product
The buyer would likely be a restaurant group's food-safety lead. EU Regulation 1169/2011 already requires restaurants to give allergen information when asked.
The job would be giving the same careful answer on every shift, in the diner's language, with a record of why.
None of this has been tested with a restaurant. It is a hackathon project.