When Zero Means Zero: An LLM Safety Net That Ate Real Queries
Ballpark Genius’s search bar takes plain English, “players with 30 or more home runs,” “who’s on
pace for 40 steals,” and an LLM turns it into a structured filter against Postgres. LLMs being
LLMs, they occasionally hallucinate a filter nobody asked for, a stray
{ stat: 'plateAppearances', value: 0, direction: 'above' } bolted onto an otherwise fine parse.
“0 or more” is meaningless as a filter (everyone has 0 or more plate appearances, that’s the
whole species) and it can null out an entire result set through the JSON-path it compiles into.
So there’s a stripper that deletes any parsed threshold where the value’s exactly 0.
Nothing comes from nothing, King Lear says, except apparently a search stripper doesn’t know the difference between “nothing” and “the user typed zero on purpose,” and it ate both.

“Players with 0 or more three baggers,” a real query that used to come back empty for no good reason.
Type “players with 0 or more three baggers” and the LLM parses it to the exact same shape as the
hallucinated placeholder: { stat: 'triples', direction: 'at_least', value: 0 }. Nothing in that
object says which one it is. The stripper had no second signal, so it deleted both, every time.
“1 or more” always worked fine, since value === 0 never matched, which is exactly why nobody
noticed, a “0 of something” query feels like a null query even when it very much isn’t one.
Losing the threshold broke more than the threshold, too: sortBy downstream gets inferred from
whichever *_min/*_max key survived the parse, so strip the threshold and there’s nothing left
to sort by, a request for the top 20 quietly stopped showing the top 20 with zero errors anywhere
to say so.
1 | buildFallbackToolArgs("players with 0 or more three baggers", 'player') |
Fix gave the stripper a second source of truth: before deleting a zero threshold, re-check that
stat’s own regex (THRESHOLD_PATTERNS, already used earlier in the same pipeline to parse the
query in the first place) against the raw text the user typed. An explicit “0” in the sentence
survives. No textual basis anywhere, it gets stripped as the hallucination it almost certainly
is. Same parsed shape either way, but the decision now depends on what the person actually wrote
instead of a blanket rule about a single number, which is a pretty good general policy, folks,
for distrusting anything an LLM hands you unprompted, including its own generated placeholders.