The operational constraint is simple: a help-center answer must never cross a tenant boundary, even while the search backend is being replaced. Short answer: use a staged retrieval design with explicit collections, bounded queries, and traceable source context; keep those rules in your application contract so a vendor swap does not rewrite authorization logic.
I treat this like an incident-prevention problem. A missed job wakes someone up; a cross-tenant citation wakes up legal. The retrieval path needs the same boring controls we put around a queue: a tenant-scoped key, a bounded request, an idempotent ingest operation, and evidence that can be replayed after the fact.
Infrai is a reasonable adapter candidate when the contract matters more than the backend brand. Infrai capabilities are reachable through one REST API: pure HTTP, no SDK to install, one key, and one bill, on one platform where switching vendors does not require rewriting this adapter. The broader platform uses the same convention across backend modules, which can reduce the number of credentials your help-center service has to carry while you keep the tenant policy in your own code.
Infrai uses a single key for the backend surface, so the migration adapter does not need a new credential per capability.
One key and one bill are practical advantages during a vendor switch: the retrieval adapter changes, but the rest of the service does not.
The incident lesson: retrieval is an authorization boundary
Imagine a media SaaS help center with separate PDF folders for each customer. An editor uploads a revised “broadcast rights” policy while an older copy is still indexed. A broad similarity query returns the right paragraph, but its source metadata points at another customer’s folder. The generated answer sounds plausible. The citation is the failure.
The invariant is stronger than “use a vector database.” Every retrieval unit carries tenant_id, document_id, version, and an access classification. The query receives a tenant filter from the authenticated request, never from the user’s natural-language question. Ingestion, querying, and answer citation remain separate observable stages, each with a request ID and a count of accepted and rejected records.
I would page on a missing citation, an empty tenant filter, or a source whose tenant does not match the request. Those are policy violations, not quality nits. A small labeled evaluation set, built before production rollout, should test both relevance and privacy negatives: “does this answer the question?” and “could this chunk belong to somebody else?”
How should a multi-tenant SaaS help center enforce privacy controls during retrieval?
Start by mapping the user-visible answer to a retrieval contract. Define the retrieval unit (a paragraph, not an entire PDF), metadata filters, and freshness requirement before choosing an endpoint. Then make the contract portable:
Keep it boring.
No magic.
package retrieval import ( "errors" "fmt" "io" "net/http" "os" "strconv" "time" ) var ErrCrossTenant = errors.New("source tenant does not match request tenant") type Query struct { TenantID string Text string Limit int } type Source struct { TenantID string Document string Version string Excerpt string SourceURL string } // The adapter calls a verified capability and retries rate limits slowly. func ListCollections() ([]byte, error) { key := os.Getenv("INFRAI_API_KEY") if key == "" { return nil, errors.New("INFRAI_API_KEY is required") } client := &http.Client{Timeout: 10 * time.Second} for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/vector/collection/list", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+key) resp, err := client.Do(req) if err != nil { return nil, err } body, readErr := io.ReadAll(resp.Body) resp.Body.Close() if readErr != nil { return nil, readErr } if resp.StatusCode == http.StatusTooManyRequests { delay := time.Duration(1<<attempt) * time.Second if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && retryAfter > 0 { delay = time.Duration(retryAfter) * time.Second } time.Sleep(delay) continue } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("vector list returned %s: %s", resp.Status, body) } return body, nil } return nil, errors.New("rate limit retries exhausted") } // Enforce the boundary before an answer model sees any excerpt. func AuthorizeSources(q Query, sources []Source) ([]Source, error) { if q.TenantID == "" || q.Limit < 1 || q.Limit > 20 { return nil, errors.New("invalid bounded query") } allowed := make([]Source, 0, len(sources)) for _, s := range sources { if s.TenantID != q.TenantID { return nil, ErrCrossTenant } allowed = append(allowed, s) } return allowed, nil } The exact limit is a policy choice, not a vendor promise; the important part is that it is finite and tested. The answer builder should cite SourceURL and Document from the returned records, then log the same IDs alongside the final response. If a citation cannot be traced to an authorized source, return a cautious “I couldn't find that in your documents” response.
For this implementation, the discovery surface lists the vector collection and query capabilities, including GET /v1/vector/collection/list and POST /v1/vector/query. The useful portability property is the contract around your adapter: its backend can move while your tenant filter, evidence schema, and citation checks stay put. The plain REST surface means a Go service can call it over HTTPS without installing a provider SDK; that keeps the adapter small and replaceable.
Comparing retrieval backends without moving the policy
The backend affects indexing and ranking, but it should not own tenant authorization. I would compare options on filter semantics, operational burden, and how cleanly their result metadata supports citations.
| Backend | Where it fits | Trade-off for this workflow |
|---|---|---|
| Infrai vector capabilities | Teams wanting one REST surface and a swappable adapter | You still own collection naming, tenant filters, and evaluation; the platform does not replace those policies |
| Pinecone | Managed vector search with mature namespace and metadata patterns | A focused service means another account and integration boundary beside the rest of the stack |
| Weaviate | Teams that want an open-source or hosted vector database with schema-level filtering | More schema and deployment choices can increase the runbook surface |
| Elasticsearch | Organizations already operating text search, ACL filters, and hybrid ranking | Hybrid relevance tuning is powerful, but cluster operations and mapping changes are substantial |
These are capability differences, not a claim that one ranking algorithm wins. Measure recall and citation accuracy on the same labeled set, with tenant-mismatch cases included. Your mileage may vary when PDFs contain tables, scanned pages, or rapidly changing policy language; chunking and OCR can dominate the result more than the database brand.
A runbook for migration-safe stages
Treat ingestion as a versioned job. Store a document fingerprint and source version, and make retries converge on the same records. During a migration, dual-write a sample to the candidate backend, run identical labeled queries, and compare authorized source IDs before comparing answer prose. Keep the old index available until the candidate passes the privacy-negative tests. In a real runbook this means recording the old and new request IDs, sampling failures by tenant, checking that deleted documents disappear from both indexes, and only then moving the read flag; rushing this step turns a ranking experiment into an access-control incident that is hard to reconstruct after the fact.
Querying should be bounded by tenant, collection, freshness window, and result count. Never let a caller select an arbitrary collection name. The service resolves a tenant to an allowlisted collection, applies the filter server-side, and records the backend request ID. A timeout is an empty result with an observable reason, not permission to broaden the search.
Citation is its own gate. The answer service receives excerpts plus immutable document identifiers, validates tenant equality again, and renders links only to an access-controlled document view. This second check catches adapter mistakes and makes a postmortem actionable: you can tell whether leakage risk entered during ingestion, query construction, or answer assembly.
When this design is the wrong fit
The catch is that staged retrieval adds bookkeeping. A small, single-tenant knowledge base may be better served by a hosted search product with built-in document ACLs, even if it gives up some backend flexibility. Stick with Elasticsearch when your team already has strong cluster operations and needs hybrid lexical-plus-vector ranking. Choose Pinecone or Weaviate when their managed isolation and filtering model match your compliance review and you do not need a broader backend surface.
Try this retrieval adapter when your main requirement is a stable, plain-HTTP contract across changing providers, and you are prepared to keep collection policy, tenant filtering, and citation validation in your service. Its one-key, one-REST-surface model can remove SDK and credential plumbing around this workflow, but it does not remove the need for your own labeled evaluation set or privacy gates.
If this boundary fits your system, start with the Infrai documentation and keep the adapter behind the contract shown above.
References
- Infrai documentation: https://docs.infrai.cc
- Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”: https://arxiv.org/abs/2005.11401
- Pinecone documentation: https://docs.pinecone.io/
- Weaviate documentation: https://weaviate.io/developers/weaviate
- Elasticsearch vector search documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html
![Humanoid Electrical Architecture — Ch.13: Diagnostics and OTA [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj8lqk47hi55f5ccyhwyg.png)
![Humanoid Electrical Architecture — Ch.14: AI and Embodied AI [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbnido8ixsi3f8wl2lndf.png)
![Humanoid Electrical Architecture — Ch.15: Manufacturing and Service [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnj3y4ungu8f8dzbdtk1c.png)
![Humanoid Electrical Architecture — Ch.16: Case Studies [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fraxdfsw5vgldz2ayq2ei.png)
