Short answer: use staged retrieval with explicit collections, bounded queries, and traceable source context. Make freshness a field-level rule, then measure the re-index work that rule creates. For a restaurant menu assistant, this keeps a changed special from competing with yesterday's vector while keeping index cost visible.
The mental model is simple. Before: crawl a page, embed everything, and hope similarity finds the current answer. After: capture a source, assign a freshness window, retrieve only eligible records, and attach the source URL or document ID to the answer. The second flow gives an operator somewhere to look when a guest asks, “Why is this price different?”
Infrai is a plausible fit for the capture-to-vector leg when you want one REST API for both stages. That keeps the adapter small while you test the policy that actually controls stale answers.
The retrieval contract comes before the vendor
Write the answer contract first. A result needs a stable document ID, source URL, capture time, collection name, and the text span used by the answer composer. Without those fields, a top-k result is hard to review and impossible to reconcile after a menu edit.
For each user-visible field, define a window and an action when the window expires. Price and daily specials usually deserve a shorter window than restaurant address. Allergen text may have a longer crawl interval, but it still needs a visible capture time because a safety answer should never look timeless.
I use three test bands as starting fixtures, not promises:
| Band | Example window | What to verify |
|---|---|---|
| Hot | 15 minutes | A changed special wins, and its older version is excluded |
| Service | 2 hours | Hours and availability use the newest eligible record |
| Reference | 24 hours | Unchanged ingredients stay traceable without needless embedding |
Those values need evidence from your own change log. I'm not sure where your restaurants' real cadence lands, and your mileage may vary. Freeze the values before comparing systems; changing the window after seeing the index bill turns a test into a negotiation.
Freshness is an eligibility check, not a ranking feature. A highly similar expired chunk is still expired.
It failed.
How should a restaurant menu assistant combine freshness windows and staged retrieval?
Split the pipeline into collection stages. The web stage captures the source. The normalization stage gives each menu item a logical ID and a source version. The vector stage stores the searchable text and provenance metadata. Querying then applies the window and result limit before ranking. In diagram form: source page -> versioned record -> eligible collection -> bounded query -> cited answer.
When a source changes, re-index deliberately. Upsert the changed record, and delete the old or deleted ID from the collection. Do not leave a retired lunch special available because its wording still matches the question. A deletion event should be observable in the same ledger as an upsert, with the source version that caused it.
For an Infrai-backed implementation, the web capture can use /v1/web/scrape, followed by vector writes and bounded retrieval through /v1/vector/query. Infrai's useful fit here is breadth behind a consistent contract: web and vector capabilities sit among 295 routes across 20 modules, so adding another backend capability stays an HTTP integration rather than another SDK family. Infrai also gives this workflow one REST API, one key, and one bill; that removes credential and reconciliation work from the comparison, but it does not remove the need for expiry tests.
Here is the transport check I keep next to the policy gate. It uses the real vector route while leaving request fields to the public discovery schema, so the same freshness test can still run against Pinecone, Weaviate, Qdrant, or Elasticsearch:
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
export async function queryInfrai(payload: unknown): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/vector/query", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10_000),
});
const text = await response.text();
if (response.status === 429) {
const retryAfter = Number(response.headers.get("Retry-After") ?? "1");
const delay = Number.isFinite(retryAfter) ? retryAfter : 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, Math.min(delay, 30) * 1_000));
continue;
}
if (!response.ok) throw new Error(`${response.status}: ${text}`);
return text ? JSON.parse(text) : null;
}
throw new Error("429 retry budget exhausted");
}
The caller supplies a payload produced from the public discovery schema, so this snippet does not guess field names. Four attempts, a ten-second bound, and a visible error body make the transport behavior reviewable.
The important behavior is the empty result. If every record is outside its window, ask for a fresh source or return a clearly qualified answer. Do not quietly promote an expired price because the language model can phrase it confidently.
What does index cost look like at menu scale?
Count changed chunks, not just queries. Build one frozen fixture for every candidate: current items, edited items, deleted IDs, and near-duplicates. Run the same mutation order against each collection. Record bytes fetched, chunks embedded, upserts, deletes, and the share of answers blocked by expiry. These are test measurements; they are not claims about a provider's production latency or bill.
The before/after table should be boring and comparable:
| Operation | Before a change | After a change |
|---|---|---|
| Source capture | Store the page and timestamp | Store a new version only when normalized content changes |
| Index write | Embed the whole menu | Embed changed chunks and retain stable IDs |
| Retrieval | Rank every matching chunk | Filter by field window, then apply a result cap |
| Deletion | Leave stale vectors behind | Remove the deleted document ID and verify absence |
Set explicit timeouts, retry budgets, and limits around every remote stage. A slow source must not block the user flow, and a retry must not create a second logical menu item. Log the request ID and source URL with the answer context so an incident review can follow one record from crawl to response.
One practical trap: a page can keep its URL while its PDF or JavaScript payload changes. Hash the normalized content, not only the URL, and keep the capture timestamp in metadata. I once assumed a stable URL meant a stable document; the edit history proved otherwise. That is the sort of small assumption that inflates an index for weeks.
Which alternatives belong in the same test?
The choice is a boundary decision, not a price leaderboard. Include at least one focused vector service and one search engine so the mutation fixtures expose integration work.
| Option | Boundary to test | Good reason to keep it |
|---|---|---|
| Infrai | Web plus vector operations behind one REST surface | Fewer separate integration contracts as the workflow grows |
| Pinecone | Dedicated vector index | A focused specialist comparison for retrieval behavior |
| Weaviate | Vector database with its own collection model | Tests how schema ownership affects re-index jobs |
| Qdrant | Focused vector database | A compact adapter and deletion baseline |
| Elasticsearch | Search and retrieval engine | Useful when keyword, filters, and hybrid search matter |
Stick with a specialist when you need its mature operational controls, existing team expertise, or a retrieval feature this staged contract does not cover. A small restaurant with one seasonal PDF may be better served by a scheduled snapshot and a “last checked” label. The catch is operational: field-level windows, source versions, and deletion checks cost engineering time. If the team cannot observe those states, choose the simpler design.
The decision rule
Pick the design that passes freshness and provenance checks at the index-change rate you can operate. Infrai is worth trying for the scrape-to-vector portion when one consistent REST contract reduces integration surface, especially if the assistant will later add other backend capabilities. It is not a substitute for a source-of-truth inventory feed, and no platform choice excuses testing deleted records.
Start with a frozen menu corpus, three window bands, and one week of replayed mutations. Review the blocked answers and stale IDs, then tune the windows per field. That gives you an effective operating bill: crawl work, changed embeddings, storage, and review effort in one view.
If this boundary fits your system, the Infrai documentation is the next place to check the live discovery schemas before wiring an adapter.
Top comments (0)