Fundable Query Planner
Turns a plain-English question into a validated Fundable API query, live.
Python 3OpenRouter (OpenAI-compatible client)Gemini 3.5 Flash-LiteStrict JSON-schema structured outputPyYAML configThreadPoolExecutor parallel resolversstdlib http.server + urllibFundable REST API
Problem
Fundable's API has four search endpoints with nested filter blocks, enum-typed funding rounds, and permalink IDs for every location and industry, so writing a correct query by hand means knowing the whole contract. The team wanted anyone to ask in plain English and get back a query the API actually accepts, with no guessed IDs or invented numbers. The lookup endpoints made that harder than expected: they match names literally with no synonyms, relevance score, or prominence order, so a naive lookup for "New York" resolves to a town in Mexico and "healthcare" returns nothing.
What I built
A two-phase Python pipeline. Phase 1 is a single LLM call with strict structured output that picks the endpoint, extracts filters as display names, and explains its reasoning; it is forbidden from emitting any permalink or ID. Phase 2 is deterministic: regex extractors parse money, dates, and round ranges ("Series C+" becomes C through M), resolvers look up locations, industries, and named entities against the API in parallel, a curated alias table rewrites literal search terms and pins a known permalink only when it appears in the live results, and homonyms become a clarification prompt instead of a silent guess. A compiler assembles the request body and a schema gate validates it against a hand-mapped contract of all four endpoints so an invalid body is never sent; exclusions like "everywhere but America", which the API cannot express, are handled client-side by exact count-by-subtraction. A local four-tab tester shows the model's reasoning, what was looked up, the built query, and the live result, and a 117-case labeled test set plus a report harness produce the acceptance scorecard.
Highlights
- The model never touches an ID: it emits display names only, and a schema gate built from a hand-mapped contract of all four endpoints rejects unsupported fields and bad enums before anything is sent. In the 117-case live dry run, 100% of compiled bodies were accepted.
- Aliases are evidence-gated, not fabricated: the curated permalink for "London" or "healthcare" is used only if that exact permalink appears in the live search results, which fixes the API's literal, unranked lookups without inventing anything.
- Bakeoff of ten models and three architectures (single model, confidence router, orchestrator/executor), then shipped the simplest: the router never fired and the split only added latency, so production is one flash-lite model at about 1s p50. The priciest model was not the most accurate.
- Calibration finding that shaped the design: "exactly 100%" model confidence was right 58/58 times while the misses sat in the 90-99% band, so a reflect-and-retry loop was measured (+46% cost, +40% latency, no accuracy gain) and dropped; clickable disambiguation handles the ambiguous cases instead.
- A filterless query is never allowed to look successful: if nothing resolves, confidence is capped at 0.3 and a warning fires instead of silently matching the whole database, and "not in X" is honored client-side by exact subtraction because the API has no negation.
Numbers
- 99% route accuracy on 117 labeled cases across all four endpoints
- 100% of compiled request bodies accepted by the live API in dry-run
- Resolver-backed fields F1 99%
- $0.90 per 1,000 queries, measured on the 117-case run at July 2026 OpenRouter list prices
- Model latency p50 1.0s / p95 1.7s; end-to-end including parallel lookups p50 1.9s / p95 2.6s
- Five lookups take 1,482 ms in parallel vs 5,528 ms sequential (3.7x)
- 11/11 acceptance criteria pass
- 29 offline unit tests pass with no API keys (18 production-logic + 11 schema/pipeline)
- Ten models compared on the planning task
- Model confidence of exactly 100% was right 58/58; reflect-and-retry cost +46% and +40% latency for no accuracy gain
- 90 city nickname/abbreviation aliases
Screenshots


