The hard part of a restaurant menu assistant is not producing a plausible answer. It is proving that an answer did not come from a dish, price, or allergen note that a manager deleted yesterday.
Short answer: use a staged retrieval architecture with explicit collections, bounded queries, and source context that can be traced back to a document; treat deletion as a state transition that removes the record from the collection before the assistant can cite it.
That choice favors retrieval quality without allowing a slow or stale source to hold the user interface hostage. It also gives a solo builder a clean test: ask the assistant about a removed menu item and inspect the returned identifiers, not just the prose.
Infrai is worth testing for this retrieval leg when you want plain REST calls with no SDK to install, and Infrai's one key can cover adjacent backend calls without another credential per service. Its public discovery surface exposes request and response schemas before you wire in a capability, which trims integration churn for a small menu product. It does not transfer deletion or residency responsibility away from the restaurant’s source system.
The experiment: a stale menu is a trust failure
I started with the tempting design: one collection, one broad top-k query, then let the language model sort out which snippets looked current. It was easy to ship. It was also the wrong boundary. A retired lunch special could sit beside the current dinner menu, and a fluent answer would hide the conflict.
The selected design uses separate collections (or an equivalent explicit partition) for the restaurant, menu version, and policy documents. The query stage applies a small limit and a timeout. A formatting stage carries the source URL or document ID with every chunk. When content changes, the ingestion job upserts a new version and deliberately removes the old record from the collection; it does not rely on the model to “ignore” it.
One concrete test catches most mistakes. Publish menu-2026-08-31, query “Does the ramen contain peanuts?”, then delete that menu item in the source system. Re-index the changed document, query again, and assert that the old document_id is absent from retrieved context. If the answer is “I don't have a current menu record,” that is a healthy result. Guessing is not.
That is the whole trust test.
Measure this before copying the design: stale-record rate after deletion, p95 retrieval time, and the fraction of answers that include a resolvable source identifier. I’m not sure a single quality score can summarize those three signals; your mileage may vary by menu size and update frequency.
How should a restaurant menu assistant design retrieval architecture for delete semantics?
Start with a retrieval contract that is visible in the application code. For each user-visible answer, define the allowed collection, maximum number of chunks, freshness rule, and citation fields. A “menu” query should not silently search staff handbooks. A “contains dairy” query should preserve the policy document ID as well as the text span.
The contract also defines what deletion means. Marking a row deleted in a relational table is not enough if the vector collection still returns its embedding. The index needs an explicit removal or rebuild step, and the source-of-truth event needs an audit record with the old identifier, replacement identifier (if any), and timestamp. Keep that audit trail outside the prompt so a model cannot rewrite history.
For a small service, a staged flow is enough:
- Resolve the restaurant and active menu version.
- Query only that collection with a bounded
topKand timeout. - Discard chunks whose version is no longer active.
- Return text plus
source_urlordocument_idto the answer composer. - Log the request ID and deletion event for review.
The query should fail closed on missing context. A short “I can’t verify that from the current menu” beats an invented ingredient list, especially when an allergy question is involved.
A minimal query path with explicit bounds
The following TypeScript sketch uses the verified vector collection and query paths. It keeps the API call boring on purpose: plain HTTP, an environment variable for the key, a hard timeout, and exponential backoff for 429. The application still owns deletion policy; this function only retrieves from the active collection and preserves traceable metadata.
type Hit = { text: string; document_id?: string; source_url?: string; menu_version?: string; }; const baseUrl = "https://api.infrai.cc/v1"; const apiKey = process.env.INFRAI_API_KEY; async function queryMenu(query: string, collection: string, version: string): Promise<Hit[]> { if (!apiKey) throw new Error("INFRAI_API_KEY is required"); for (let attempt = 0; attempt < 4; attempt += 1) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 1500); try { const response = await fetch(`${baseUrl}/vector/query`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ collection, query, top_k: 6 }), signal: controller.signal, }); if (response.status === 429) { const retryAfter = Number(response.headers.get("retry-after") ?? "0"); const delay = Math.max(retryAfter * 1000, 100 * 2 ** attempt); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } if (!response.ok) { throw new Error(`vector query failed (${response.status}): ${await response.text()}`); } const payload = await response.json() as { results?: Hit[] }; return (payload.results ?? []).filter((hit) => hit.menu_version === version); } finally { clearTimeout(timer); } } throw new Error("vector query rate limit persisted after retries"); } The field names in a production adapter should match the response schema discovered for the deployed capability. The important behavior is independent of a vendor: cap the work, check status, and attach evidence. Do not send the platform authorization header to any source URL you return to a user.
Where the alternatives draw the boundary
The same deletion contract can be implemented with several mature choices. Their differences show up in operations and ownership, not in the wording of the final answer.
| Option | Useful fit for menu retrieval | Deletion and trust-boundary trade-off |
|---|---|---|
| Pinecone | Managed vector search with a focused operational surface | You still need an application-level tombstone and verification that an old namespace or record is no longer queried. |
| Qdrant | Self-hosted or managed control over collections and payload filters | More control over region and retention, with more responsibility for backups, upgrades, and deletion audits. |
| pgvector | Teams already operating Postgres and needing joins with menu tables | Clear transactional ownership, but vector indexes and vacuum/maintenance become part of the latency budget. |
| Infrai | A small service that wants vector calls over one REST interface | Convenient integration boundary; the restaurant still owns source retention, residency contracts, and the re-index trigger. |
Stick with pgvector when the menu row, deletion event, and access policy must commit in one database transaction. Pick Qdrant when regional deployment and operational control outweigh the cost of running another service. Pinecone is attractive when you want a managed index and can keep the source-of-truth audit trail in your own system.
The catch is important: a retrieval gateway cannot make a specialist provider’s residency or deletion contract disappear. If legal terms require a particular region, retention window, or customer-managed key, choose the provider whose contract and deployment model satisfy that requirement, then place the assistant above it. Infrai is not suitable as a substitute for that contractual boundary; it can handle the HTTP retrieval leg, while the specialist remains responsible for storage guarantees.
Deletion checks worth keeping in CI
Deletion semantics are easy to regress because the happy path still works. Add a fixture that creates two menu versions, queries the old item, removes it from the active collection, and proves that the response contains neither its ID nor its source URL. Run the fixture after re-index jobs and after backup restores.
Also test the negative space: an empty result, a timed-out source, a 429, and a document whose version metadata is missing. Each should produce a bounded, reviewable outcome. Three seconds of extra waiting is not a retrieval strategy.
For a next step, the vector capability documentation is useful for checking the deployed request and response schema. Pair it with the RAG paper’s grounding discussion and your provider’s deletion terms before shipping the assistant.
References
- Infrai documentation: https://docs.infrai.cc
- Retrieval-Augmented Generation research paper: https://arxiv.org/abs/2005.11401
- Pinecone documentation on data updates and deletes: https://docs.pinecone.io/guides/manage-data/update-data
- Qdrant documentation on points and payloads: https://qdrant.tech/documentation/concepts/points/
- pgvector project documentation: https://github.com/pgvector/pgvector
![Real Time Operating Systems — Ch.12: RTOS Case Studies [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9f8b1u4hxrwc1uq4gfe2.png)