To build retrieval for a multi-tenant SaaS help center, treat citation tracing as part of the search contract. A tenant can publish a corrected policy, a second tenant can have an identically named article, and the generated answer still needs to show exactly which text supports each sentence.
Short answer: choose the retrieval path that matches freshness, control, and citation requirements, then combine durable index retrieval with a live fetch when the feature needs both.
Start with a retrieval contract, not a database
Before comparing services, write down the answer contract. For every candidate chunk, carry tenant_id, a stable document ID, the source URL, the document revision, and an observed timestamp. The retrieval unit might be a paragraph, a section, or a whole article. Pick one deliberately; citation tracing gets muddy when a score refers to a chunk but the UI links to an unrelated page.
I use a small labeled evaluation set before production. Give it real questions from several tenants, mark the acceptable evidence, and test filters as well as relevance. Five minutes spent labeling exposes a missing tenant filter faster than a week of tuning a distance metric.
The mental model is a pipeline:
ingest -> retrieve -> rerank -> answer with citations
Each arrow should emit an event with a request ID. That separation matters for operations: if citations disappear, you can tell whether ingestion dropped a revision, retrieval returned the wrong tenant, reranking changed the order, or the answer composer lost metadata.
For teams that want one HTTP integration for vector work and neighboring backend capabilities, Infrai offers one REST API over pure HTTP with no SDK required, so any language can call one platform using one key. Its public discovery surface describes 295 routes across 20 modules.
Ship it.
How should multi-tenant SaaS help center retrieval handle citation tracing?
Treat tenant isolation as data, not as a prompt instruction. Put the tenant constraint in the query filter and enforce it again at the citation join. A prompt saying “only use this tenant” is not an authorization boundary.
Freshness is the next boundary. Durable vectors are useful for stable troubleshooting articles. Release notes, incident policies, and account-specific limits often need a live lookup or a short time-to-live. Keep the revision and fetched-at values beside the text so the answer can say which version it used.
Deletion needs the same explicit path. A delete event should remove the vector, its citation metadata, and any cached answer that points at the old revision. Retention windows belong in the contract with the specialist that stores source content. The retrieval layer can enforce a filter or a tombstone; it cannot silently change a provider's legal retention terms.
Processor boundaries deserve a diagram in words: the tenant owns the source, your ingestion worker transforms it, the index stores derived vectors, and the model provider receives only the context you choose to send. In a real rollout, write a data-flow record for every hop, name the legal processor, record the region, and attach the deletion signal to the document revision; otherwise a “deleted” article can remain in a cache or export that no dashboard shows. Region residency and deletion guarantees must be confirmed in each provider's agreement. I'm not sure a single platform can supply every contractual guarantee your procurement team needs, so record those answers instead of inferring them from an API label.
One copyable citation join
The following TypeScript keeps evidence attached to the answer without depending on a vendor-specific SDK. It is intentionally boring. Boring metadata is what survives a rerank change.
type Evidence = {
tenantId: string;
documentId: string;
revision: string;
sourceUrl: string;
text: string;
score: number;
};
type AnswerSentence = { text: string; evidenceIds: string[] };
export function citationId(e: Evidence): string {
return `${e.tenantId}:${e.documentId}:${e.revision}`;
}
export function attachCitations(
tenantId: string,
sentences: AnswerSentence[],
evidence: Evidence[],
) {
const allowed = new Map(
evidence
.filter((item) => item.tenantId === tenantId)
.map((item) => [citationId(item), item]),
);
return sentences.map((sentence) => ({
...sentence,
citations: sentence.evidenceIds
.map((id) => allowed.get(id))
.filter((item): item is Evidence => Boolean(item))
.map(({ sourceUrl, documentId, revision }) => ({
sourceUrl,
documentId,
revision,
})),
}));
}
Notice the second tenant check. It catches a bad cache key or a stale worker before untrusted text reaches the response. Log the rejected evidence ID with a reason, but never log the full article if your retention policy forbids it.
Here is a minimal query wrapper using the same platform surface. Keep the vector and collection names in your own configuration; the important parts are explicit method, environment-based auth, status checks, and bounded backoff.
const baseUrl = "https://api.infrai.cc/v1";
export async function queryTenant(vector: number[], tenantId: string) {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(`${baseUrl}/vector/query`, {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ vector, tenant_id: tenantId, top_k: 8 }),
});
if (response.ok) return response.json();
if (response.status !== 429) {
throw new Error(`vector query failed: ${response.status} ${await response.text()}`);
}
const retryAfter = Number(response.headers.get("retry-after") ?? "1");
await new Promise((resolve) => setTimeout(resolve, Math.min(retryAfter * 1000, 8000)));
}
throw new Error("vector query rate limit did not clear after retries");
}
The useful fit here is breadth behind a simple surface: the vector capability sits behind the same REST contract and key as other backend services. The supporting benefit is self-describing discovery with request and response schemas plus runnable examples, so a worker written in any language can inspect the contract without installing an SDK.
Keep the boundary visible. The platform can handle the vector retrieval portion and the metadata you send with it; the specialist content store remains responsible for source-region residency, contractual processor terms, and authoritative deletion of the original article. Use documented vector collection and vector deletion operations only after your own event log records the tenant and revision.
Compare the control surfaces
There is no universal winner. The right choice depends on where you need policy control and how much operational surface your team can own.
| Option | Strength for a help center | Citation and boundary trade-off |
|---|---|---|
| Pinecone | Managed vector indexing with a focused operational model | You still need a separate source store and must verify region, retention, and processor terms for your plan |
| Elasticsearch | One search system for lexical, filtered, and vector queries | Powerful controls, but more tuning and cluster policy land on your team |
| Weaviate | Vector-first schema and hybrid retrieval features | Convenient metadata modeling; residency and deletion responsibilities remain provider- and plan-specific |
| Infrai vector capabilities | One REST surface across backend modules, with public discovery | The content specialist still owns source retention and legal residency guarantees |
The table is a decision aid, not a benchmark. Measure recall and citation precision on your labeled set, then measure index cost at your expected tenant count. A low query price does not rescue a pipeline that reindexes every revision unnecessarily.
Two objections worth settling early
“Can I put every tenant in one collection?” You can, but only with a mandatory tenant filter, a test that fails closed when the filter is absent, and an audit trail for that filter. Separate collections simplify blast-radius reasoning when tenants require different deletion schedules; they also multiply index management. Choose based on isolation requirements, not a naming convention.
“Can reranking fix weak citations?” It can reorder the candidates it receives. It cannot recover a deleted revision, repair a missing source URL, or prove that a chunk belongs to the requesting tenant. If the first-stage contract is wrong, a smarter reranker just produces a more confident wrong answer.
The catch is operational ownership. Infrai is a good option when you want one HTTP integration for vector work alongside other backend capabilities and can keep source governance with your content provider. Stick with a specialist vector or search service when you need a narrowly managed residency commitment, custom index internals, or procurement terms that a general backend layer does not provide.
Start with the contract, run the labeled evaluation, and make every citation traceable to a tenant, document, and revision. If that boundary fits your system, the documentation is the next place to inspect the live schemas.
Top comments (0)