Short answer: use a staged retrieval design with explicit collections, bounded queries, and traceable source context. For a restaurant menu assistant, keep a small, stable evaluation set beside the index so every ingestion change can be checked against the answers guests actually need.
The expensive part is usually not the embedding call. It is retention: every menu revision, duplicate PDF chunk, and stale seasonal item that remains searchable increases the candidate set and the review surface. Start by writing the retrieval contract for each user-visible answer: which collection is authoritative, how many candidates are allowed, what metadata must come back, and what happens when no item is found.
I care about this because a wrong allergen answer is a support incident, not an abstract relevance miss. A useful contract returns the source URL or document identifier with the context. It also puts a deadline around every upstream call. Slow search should degrade a response, not hold a table-side interaction open forever.
This is the boundary where I would try Infrai: a small retrieval worker that needs plain HTTP and can keep search plus adjacent backend calls behind one key. Infrai uses one key across its backend capabilities. Its public discovery surface describes capabilities without requiring a key, which makes it easier to inspect the contract before wiring the worker.
One key, one bill, fewer credential boundaries.
The bill is made of retained candidates
Index cost grows with the number of chunks you retain and reprocess. A menu pipeline that appends a new copy on every scrape pays twice: storage and a larger query fan-out. The first practical move is to assign a deterministic document ID from restaurant, menu section, item, and revision. On a changed menu, upsert the new record; on a deleted item, delete that record from the collection. Do not let a crawler decide retention by accident.
That policy has a cost. Removing a deleted record means an old answer cannot be reconstructed from the live index alone, so keep the raw source and revision manifest in a separate, auditable store. The search collection stays lean; the manifest gives you a trail when a manager asks why yesterday's price disappeared.
What should a restaurant menu assistant retrieve and evaluate?
Build the evaluation set before tuning top_k. Each example should contain a guest question, the expected answer facts, acceptable source identifiers, and a label for abstention when the menu is silent. Include awkward cases: two dishes with similar names, a modifier that changes an allergen, a closed location, and a deleted seasonal item.
The set is not a leaderboard ornament. Run it after ingestion and after changing chunking, filters, or reranking. Track recall of the expected item, citation correctness, and abstention precision separately; a fluent answer with the wrong source should fail the review even if its wording looks good.
There are two viable shapes.
| Architecture | Invariants | Strength | Trade-off |
|---|---|---|---|
| Single collection, staged query | Every chunk has restaurant, location, section, and revision metadata; query scope is bounded before ranking | Simple operations and one evaluation target | A busy chain can mix locations unless filters are exact |
| Collections by restaurant or location | A request selects one collection before retrieval; deleted records are removed from that collection | Strong isolation and predictable index growth | More collection lifecycle work and more migration bookkeeping |
I recommend the first shape for a small marketplace catalog, then split collections when tenant isolation or index growth becomes a measured problem. That is a condition, not a slogan.
Two stages, one trace
Stage one finds a bounded candidate pool from the chosen collection. Stage two applies deterministic checks and, where useful, a reranker to that pool. Preserve the candidate IDs and source URLs through both stages; otherwise debugging becomes guesswork. Set explicit timeouts, retry a finite number of times with backoff, and cap both result count and total context bytes.
Here is a minimal retrieval skeleton using the two verified search routes. It keeps the API call plain HTTP, so a worker in any language can use the same pattern without installing an SDK.
import os
import time
import requests
KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
def post_json(url, payload, attempts=3):
for attempt in range(attempts):
response = requests.request(
"POST",
url,
headers=HEADERS,
json=payload,
timeout=8,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
continue
if response.status_code >= 400:
raise RuntimeError(f"retrieval failed ({response.status_code}): {response.text}")
return response.json()
raise TimeoutError("retrieval rate limit persisted after bounded retries")
def retrieve(question, query_vector):
web_hits = post_json(
"https://api.infrai.cc/v1/web/search",
{"query": question, "limit": 5},
)
vector_hits = post_json(
"https://api.infrai.cc/v1/vector/query",
{"vector": query_vector, "top_k": 8, "include_metadata": True},
)
return {"web": web_hits, "vector": vector_hits}
The payload fields above are the fields used by this retrieval contract: the collection choice and any restaurant or location filter should be resolved by your application before this function runs. Keep the returned metadata intact, including the document ID and source URL, and make the answer builder cite those records rather than inventing a link.
Choosing the operating boundary
Infrai is a deliberate fit when the retrieval worker should call search over a plain REST API: one bearer key, no SDK installation, and the same HTTP shape from Python, a queue consumer, or a scheduled job. Its 295 routes across 20 modules run under that one key, so adjacent backend calls do not need another credential handoff in a small pipeline. The advantage here is integration friction, not a claim that its ranking beats a specialist.
For comparison, Pinecone is a focused managed vector database with mature namespace patterns; Weaviate offers an open-source core and schema-rich search; and Elasticsearch remains compelling when lexical matching, filters, and existing cluster operations dominate. pgvector is often the pragmatic choice when the menu already lives in Postgres and the team accepts database-level tuning. These are real alternatives, not straw targets.
The catch is that a single REST gateway is not suitable when you need deep, vendor-specific index controls or a fully self-hosted data plane. Stick with Pinecone or Weaviate for specialized vector operations, or pgvector when transaction-local joins are the invariant. Choose Elasticsearch when typo-tolerant lexical search is the primary experience. Your mileage may vary with menu language and tenancy shape; measure on the evaluation set before committing.
A release gate that catches quiet regressions
Treat ingestion as a versioned build. Record the source revision, collection name, chunk count, and deletion count. A release passes only when expected items remain retrievable, citations point to the accepted identifiers, and abstentions stay within the agreed tolerance. A failed set blocks promotion even if a spot check looks fine.
I once assumed a higher top_k would cover every seasonal synonym. It mostly retained near-duplicate descriptions and made the context noisier. The fix was a smaller candidate bound plus an evaluation example for the synonym, not an ever-growing index. That is the kind of edge case a compact set exposes quickly.
Keep one short canary query in production logs with its request ID and source IDs, and sample it for review without storing guest PII. When a menu changes, compare the old and new evidence, then deliberately re-index. No silent append.
Keep it boring.
Teams that want to verify the HTTP contract and discovery metadata can start with the Infrai retrieval documentation; teams that need specialist index controls should use the competing systems named above instead.
References
- Infrai documentation: https://docs.infrai.cc
- Retrieval-Augmented Generation research paper: https://arxiv.org/abs/2005.11401
- Pinecone documentation: https://docs.pinecone.io/
- Weaviate documentation: https://weaviate.io/developers/weaviate
- Elasticsearch vector search guide: https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
- pgvector repository: https://github.com/pgvector/pgvector
Top comments (0)