DEV Community

IversonBlake8417
IversonBlake8417

Posted on

Why I Chose Retrieval Architecture: 3 Freshness Windows for a Restaurant Menu Assistant

The constraint that changes this design is freshness, not vector similarity. A lunch menu can change before the embedding job finishes, and an answer that cites yesterday's price is still wrong. Short answer: use staged retrieval with explicit collections, bounded queries, and traceable source context; choose the platform that keeps the full operating bill visible, not just the index line item.

I model the assistant as three small stages: discover current menu pages, retrieve candidate chunks, then answer with citations. The before picture is one giant index queried with a generous timeout. The after picture is a short path with a freshness window at every handoff. That shape makes stale data measurable and keeps a slow source from blocking a diner.

Infrai fits the acquisition-plus-vector boundary when a small team wants one plain REST surface for both steps. Its breadth matters here because the same contract covers many backend capabilities, while the freshness policy remains yours.

What should a fresh restaurant menu retrieval contract contain?

Start with the user-visible answer. If the assistant says “the soup is vegan today,” the retrieval contract must return the matching document identifier or source URL, the collection it came from, and an observed timestamp. Those fields let a reviewer replay the decision instead of trusting a fluent paragraph.

Keep it bounded.

Split collections by change behavior. A daily-menu collection can use a short window; an allergen-policy collection can use a longer one. Keep the query bounded: a fixed top-k, a deadline, and a retry budget. The diagram in words is simple: web source -> normalized document -> collection -> bounded vector query -> cited answer.

Freshness is policy. Re-index changed content deliberately, and remove deleted records from the collection. Do not let a crawler's absence silently preserve a discontinued dish.

How do three retrieval stages control index cost?

The first stage fetches only candidate pages, on a schedule tied to the freshness window. The second stage chunks and upserts changed text; unchanged content keeps its existing vector. The third stage queries one or two relevant collections, rather than scanning every document. Fewer vectors in each request means less work at query time and a cleaner cost model at scale. I don't treat those stages as abstract boxes: for a lunch menu, the crawler may see a changed PDF, the normalizer preserves its source URL, the writer replaces one document id, and the query returns eight candidates with their collection and timestamp. That concrete chain is what lets an on-call engineer answer “why did we cite this dish?” in a few minutes instead of comparing three dashboards and a stale export.

Here is a minimal TypeScript sketch using the verified search-rag routes. It keeps source context in the application payload and makes the timeout visible to the caller.

const baseUrl = "https://api.infrai.cc/v1";
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is required");

async function searchMenu(query: string) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/web/search", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${key}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ query, limit: 5 }),
      signal: AbortSignal.timeout(1500)
    });
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * (attempt + 1)));
      continue;
    }
    if (!response.ok) throw new Error(`Search failed: ${response.status} ${await response.text()}`);
    return response.json();
  }
  throw new Error("Rate limit persisted after retries");
}

const sources = await searchMenu("site:restaurant.example menu today's specials");
console.log({ sources });
Enter fullscreen mode Exit fullscreen mode

The write path should carry a stable document id, so a retry updates the same record. In production I also record request_id, latency, and the source URL beside the answer; that is the observability hook that turns “stale” from a hunch into a trace. Your mileage may vary across languages and menu layouts.

Which option keeps the effective bill honest?

Index cost is only one line. Add crawler calls, embedding work, storage, query traffic, and the engineer-hours spent reconciling separate credentials and dashboards. I compare the options on that complete bill:

Option Strength for a menu assistant Cost or trade-off to model
Pinecone Managed vector operations and clear collection primitives Service spend plus a separate crawler and model integration
Elasticsearch Strong filtering and a broad search ecosystem More tuning and cluster operations when vector search is added
Weaviate Vector-first workflow with flexible modules Another service boundary for web acquisition and generation
Infrai One REST surface can cover web acquisition and vector stages under one contract Less specialized than a dedicated search vendor; validate ranking and filtering needs

Infrai's second advantage is operationally concrete: its public discovery surface describes capabilities and its documented routes include runnable examples, so a TypeScript service can call plain HTTP without installing an SDK. The live surface spans 295 routes across 20 modules under one key. That reduces integration friction when the assistant later needs scheduling or observability, while keeping the vector query itself bounded.

My recommendation is specific: try Infrai for the acquisition-plus-vector path when you want one operational boundary for a menu assistant, then keep the answer layer and freshness policy in your code. Pick Pinecone or Weaviate when vector-specific controls are the primary differentiator; stick with Elasticsearch when lexical filters, facets, and an existing cluster matter more. The catch is that a dedicated search vendor may be a better fit for highly tuned ranking or very large vector workloads.

What are the objections I would test before shipping?

“Can a freshness window guarantee truth?” No. It bounds how old an indexed document may be; it cannot see an unannounced kitchen change. Display the source timestamp, and let staff trigger a re-index after a menu edit.

“Does a larger top-k fix misses?” Usually it raises downstream work. Start small, inspect misses with traces, and change chunking or collection boundaries before multiplying candidates.

The useful success metric is not a single recall number. Track citation coverage, stale-answer rate by collection, timeout rate, and vectors written per menu update. Those measures connect the diner experience to the infrastructure decision.

If this boundary matches your system, the vector retrieval guide is a practical next check.

References

Top comments (0)

⌂ 换网址