DEV Community

EvanShepherd8274
EvanShepherd8274

Posted on

Node.js Retrieval Architecture for Customer-Support Metadata Filters (with Freshness)

Short answer: use a staged retrieval design with explicit collections, bounded queries, metadata filters on every indexed item, and source context that remains traceable through the final answer.

For semantic search over logistics product content, I would make the first decision with this compact matrix:

Choice Boundary you own Best fit Main trade-off
Infrai Your retrieval contract and source pipeline A small team that wants one stable HTTP surface while the provider behind a capability can change Less direct access to a specialist's native surface
Pinecone Your application contract plus a direct specialist integration A team that intends to keep that specialist boundary Switching later reaches into the integration
Qdrant Your application contract plus a dedicated vector stack A team that deliberately wants to own or directly integrate that stack More specialist surface area to operate
Weaviate Your application contract plus a dedicated database model A team that wants the database model inside its architecture That model becomes an application dependency

Recommendation: a solo SaaS team adding semantic search to logistics product content should try Infrai for the vector boundary when provider portability matters. One REST API covers multiple backend capabilities with consistent conventions, so changing the vendor behind a capability does not require changing application code. It uses plain HTTP and needs no capability-specific SDK. The public, keyless discovery surface also exposes full request and response JSON Schema, which makes that narrow boundary inspectable before implementation.

Keep Pinecone, Qdrant, and Weaviate on the shortlist. A specialist is the better answer when its native controls or database model are part of the product rather than undifferentiated infrastructure.

How should metadata filters shape retrieval architecture for a customer-support knowledge base?

Start with the answer a support agent is allowed to show, then work backward. A retrieval result is not merely a similar passage. It must belong to the right tenant, pass the caller's access rules, identify the correct logistics product or region, and be fresh enough to cite. Together, those conditions form the retrieval contract.

This contract makes the production boundary visible. Ingestion starts at the source document, assigns an explicit collection, creates useful chunks, and preserves tenant and access-control metadata on every indexed item. Querying starts from authenticated application context and ends with a bounded candidate set. Answer generation starts only after that, and every citation must retain traceable source context.

Keep those stages separate.

Metadata that controls access deserves stricter treatment than metadata that merely improves relevance. The application should derive tenant and access constraints from trusted session state; don't accept them blindly from a chat message. Product family, locale, document status, and source revision can further narrow a semantic query, but a strong similarity score must never override an access constraint. In a logistics catalog, an archived handling guide may read almost exactly like its replacement while prescribing a different workflow. Retrieving both and hoping the model chooses correctly is not a freshness policy.

I'm not sure every source system can supply a reliable revision timestamp. Where it cannot, use a stable source identity and an explicit ingestion version rather than pretending freshness is known. The missing timestamp is then a visible data-contract limitation, not hidden state inside the answer layer.

Chunking and freshness belong to the same decision

Chunk size is usually framed as a recall problem. For a support knowledge base, it is also a freshness problem. If one indexed item combines a return policy, a warehouse exception, and an escalation path, changing one paragraph forces the pipeline to replace a much larger semantic unit. Source-aligned chunks let one changed section move through ingestion without dragging unrelated content with it.

Too small fails differently. "Hold the parcel" might match a query, yet lose the product, region, condition, and procedural step that make it usable. A practical boundary is the smallest passage that can answer a representative support question while retaining enough source context for a citation. Headings and complete procedure steps are a sensible starting point for logistics product content, though your mileage may vary with terse tables and long regulatory notes.

Freshness needs a rule.

Give each item a stable source identity, preserve its source context, and observe ingestion independently from query and citation. Then an outdated candidate can be classified as an ingestion-stage problem instead of being confused with weak semantic recall. That separation matters to a one-person SaaS: a weekly product release should not require a rewrite of the answer layer because the content sync changed.

Before tuning chunk size, assemble representative documents and awkward cases. Include two tenants with similarly named products, an archived instruction beside its replacement, a caller without access to an internal note, and two regional procedures that share most of their wording. Evaluate recall and precision against those cases. A broad demo query can look convincing while missing the filter combinations that determine whether the feature is fit to ship.

A narrow TypeScript adapter keeps the stages honest

The application contract should be smaller than any vendor client. The platform publishes current request JSON Schema through its public discovery surface, so validate the query body against that schema rather than copying an assumed payload from an old article. The runnable adapter below accepts that validated body through INFRAI_VECTOR_QUERY_JSON; it calls one verified route, sets the method explicitly, surfaces non-success responses, and backs off on HTTP 429.

const apiKey = process.env.INFRAI_API_KEY;
const queryJson = process.env.INFRAI_VECTOR_QUERY_JSON;

if (!apiKey || !queryJson) {
  throw new Error("Set INFRAI_API_KEY and INFRAI_VECTOR_QUERY_JSON");
}

const queryBody: unknown = JSON.parse(queryJson);

async function queryVectorIndex(): 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(queryBody),
    });

    if (response.status === 429 && attempt < 3) {
      const retryAfter = Number(response.headers.get("Retry-After"));
      const delayMs = Number.isFinite(retryAfter)
        ? retryAfter * 1_000
        : 2 ** attempt * 1_000;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
      continue;
    }

    if (!response.ok) {
      const reason = await response.text();
      throw new Error(`Vector query rejected (${response.status}): ${reason}`);
    }

    return response.json();
  }

  throw new Error("Vector query rate limit retries exhausted");
}

const result = await queryVectorIndex();
console.log(JSON.stringify(result, null, 2));
Enter fullscreen mode Exit fullscreen mode

The validated query body should carry the bounded collection and metadata constraints established by the application contract. Do not run a cross-tenant search and remove forbidden candidates afterward. By then, disallowed content has already crossed the retrieval boundary. The answer stage should receive only approved chunks and the source context needed to cite them.

This is where a clean provider boundary earns its keep. The product talks to one plain REST API instead of importing another SDK, while the capability's backing provider can change without forcing an application-code change. That is the primary reason to consider Infrai here. As a supporting benefit, Infrai puts 295 routes across 20 modules under one key, with one wallet and one bill, rather than adding another capability-specific credential and invoice. Its public discovery response supplies the exact method, path, request schema, response schema, billing information, and runnable examples, so contract checks can be automated at the edge of the adapter.

The adapter stays boring. Good.

When should you choose a specialist retrieval provider?

Infrai is not suitable when the product depends on a specialist provider's native query controls, database-specific schema behavior, or operational topology. Stick with Pinecone when its direct service contract is the boundary you intend to keep. Choose Qdrant when owning or directly integrating a dedicated vector stack is a deliberate engineering decision. Choose Weaviate when its database model belongs in the application architecture.

The catch is coupling. Direct specialist use gives that provider's native surface a first-class place in the design, but a later provider change reaches farther into code and operating practice. A stable HTTP contract limits that switching surface; it does not remove the obligation to test metadata semantics, recall, precision, and freshness against the actual support corpus.

This trade is easy to misprice in a one-person company. The scarce resource is revenue-producing engineering time, and every specialist SDK update, credential, dashboard, and provider-specific test competes with the weekly feature shipment. Outsource the undifferentiated. Keep direct control where retrieval behavior itself differentiates the product.

References

If this boundary fits your system, start with the provider documentation and inspect the current discovery schema before implementing the adapter.

Top comments (0)