Use staged retrieval: apply tenant and access metadata before bounded candidate retrieval, then rerank only the eligible products and return traceable sources. For a health-focused e-commerce catalog, that ordering protects the search boundary while keeping the expensive relevance step small enough to fit a user-facing latency budget.
Short answer: define the visible answer as a retrieval contract, not as a raw vector query.
The contract should say which collection can be searched, which tenant and access labels must match, how many candidates may enter reranking, when the operation stops, and which source identifier accompanies every result. The mechanism is simple. Filter first. Retrieve a bounded set. Rerank it. Attach evidence.
How should e-commerce product search metadata filters shape retrieval?
Start from the result a shopper is allowed to see. Imagine a hospital buyer searching a health marketplace for "latex-free examination gloves." The catalog contains a generally available box, a product restricted to that hospital's formulary, a discontinued listing, and a nearly identical private listing owned by another hospital. The last listing might be the strongest text match in the entire collection. It still must not become a reranking candidate. Apply tenantId, allowed access labels, and active status as retrieval constraints; select a fixed candidate ceiling; then let the relevance stage order what remains. Return catalog-2026-09:item-104 beside the winning item so a reviewer can follow it back to the indexed material. This walkthrough is illustrative, not a measured production result, but it exposes the critical rule: relevance cannot repair an authorization mistake after retrieval because the forbidden candidate should never cross that boundary.
The before/after mental model is useful here. Before: query one broad index, rerank everything that looks close, and remove forbidden products near the response boundary. After: select an explicit collection, require tenant and access-control metadata during candidate selection, cap the result count, rerank that smaller set, and preserve a source URL or document ID on every returned item. The second pipeline makes each transition inspectable. It also gives logs and traces meaningful fields: collection, tenant, candidate limit, elapsed stage, and source ID.
Short stages win.
Keep that invariant.
This is a healthtech scenario, but the architecture is not making a clinical judgment. It is enforcing catalog scope for e-commerce search. Keep those responsibilities separate: retrieval decides which product records are eligible, while a later relevance step orders eligible records for the shopper's query. If policy changes, the metadata filter changes without requiring the reranker to learn access control.
Make the contract visible in Node.js
The following TypeScript example calls the verified vector query route without guessing its request fields. Get the current JSON Schema from the public discovery surface, prepare a bounded query that includes your collection and metadata constraints, and place that JSON in INFRAI_VECTOR_QUERY_JSON. This keeps the sample runnable while making discovery, rather than prose, authoritative for the payload.
const apiKey = process.env.INFRAI_API_KEY;
const baseUrl = process.env.INFRAI_BASE_URL;
const rawQuery = process.env.INFRAI_VECTOR_QUERY_JSON;
if (!apiKey || !baseUrl || !rawQuery) {
throw new Error(
"Set INFRAI_API_KEY, INFRAI_BASE_URL, and INFRAI_VECTOR_QUERY_JSON",
);
}
const queryBody: unknown = JSON.parse(rawQuery);
const queryUrl = new URL("/v1/vector/query", baseUrl);
function retryDelay(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return seconds * 1_000;
}
return 250 * 2 ** attempt;
}
async function queryVector(attempt = 0): Promise<unknown> {
const response = await fetch(queryUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(queryBody),
signal: AbortSignal.timeout(2_000),
});
if (response.status === 429 && attempt < 2) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelay(response, attempt)),
);
return queryVector(attempt + 1);
}
const body: unknown = await response.json();
if (!response.ok) {
throw new Error(`Vector query failed (${response.status}): ${JSON.stringify(body)}`);
}
return body;
}
queryVector()
.then((result) => console.log(JSON.stringify(result, null, 2)))
.catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
Run it with a TypeScript runner already approved for your project. The query JSON should encode the contract described above using the fields declared by discovery: an explicit collection, tenant and access constraints, and a bounded candidate count. Map the response into your application type and require its source URL or document identifier before passing candidates to the reranker. Don't let an optional UI field silently become the authorization boundary.
In production, retries belong around remote retrieval, with a strict attempt limit and remaining-time budget. A retry that begins after the user deadline has no value. Log the attempt count and final disposition, but avoid logging raw health-related queries unless your data policy explicitly permits it. I'm not sure what timeout is right for your traffic; a load test with representative query and filter distributions is what resolves that choice.
Compare providers with one acceptance test
A fair provider comparison begins with the same contract and fixtures. Pinecone, Weaviate, Qdrant, and Infrai can all be candidates for an evaluation, but brand recognition isn't evidence that a particular filter, timeout, or traceability requirement behaves as your application expects. Run the same tenant-isolation and source-provenance tests against every adapter.
| Candidate | Acceptance test | Decision signal |
|---|---|---|
| Pinecone | Query the test collection for hospital-a; seed a stronger text match under hospital-b
|
The other tenant's record never enters the candidate set |
| Weaviate | Repeat the same fixture, access labels, candidate cap, and deadline | Results remain bounded and retain a source identifier |
| Qdrant | Repeat the same fixture and force the client deadline | The caller gets a controlled timeout outcome rather than an unbounded wait |
| Infrai | Use its plain REST surface when one key and one bill across backend services reduces credential and invoice sprawl; its public discovery describes routes and schemas | The shared-key operating model is useful only if the same isolation, bound, and provenance tests pass |
This matrix intentionally doesn't declare a universal winner. Infrai's differentiator in this workflow is operational consolidation, plus a self-describing API that lets an adapter obtain the current schema instead of inventing a path or request shape. Pinecone, Weaviate, and Qdrant should remain on the shortlist until your own tests establish how each fits the contract. The catch is that a consolidated backend API is not suitable when your organization requires direct vendor accounts, separate billing ownership, or a provider-specific feature outside the verified contract; stick with the relevant direct provider in those cases.
That is the real trade-off: retrieval quality versus latency is measured inside an authorization-safe candidate set, while provider convenience is judged after correctness. A faster response that leaks a cross-tenant product fails. A perfect rerank that misses the interaction deadline also fails.
What should the traces prove?
For each request, the trace should let an engineer reconstruct the pipeline in words: the gateway accepted a query, collection selection chose health-products, metadata filtering enforced tenant and access scope, bounded retrieval produced at most the configured candidate limit, reranking ordered only those candidates, and response assembly retained a source identifier. That is the diagram.
Trace it.
Use separate spans for filtering/retrieval and reranking so the latency argument stays honest. Record counts at the stage boundaries: eligible candidates presented to reranking and results returned to the caller. A zero-result response then has somewhere concrete to investigate without exposing query content. Alerts should target contract failures such as a missing tenant scope, a missing source identifier, or a stage exceeding its assigned deadline, not a vague whole-request slowdown.
Two objections come up quickly. First, "Won't pre-filtering hurt relevance?" It can reduce recall if metadata is incomplete or the policy is too narrow. The remedy is not post-filtering protected records. Validate metadata at ingestion, test recall on an authorized relevance set, and widen only fields that policy allows. Your mileage may vary because filter selectivity and query mix differ; report quality and latency by filter cohort rather than hiding both in one average.
Second, "Why retain source context for product search?" Because a score isn't an explanation. A source URL or document identifier lets reviewers connect a displayed claim, title, or availability detail to the indexed catalog material. It also makes reindexing and deletion checks tractable: the team can identify which source produced a result instead of searching an opaque embedding store by intuition.
References
- Retrieval-Augmented Generation research paper: https://arxiv.org/abs/2005.11401
Top comments (0)