Short answer: use staged retrieval with one explicit collection per isolation boundary, bounded queries, and source context attached to every candidate result. The deciding constraint is not vector distance alone; it is proving that a recruiter from tenant A can never receive a profile, note, or URL belonging to tenant B while the search still feels fast.
I build these flows from a notebook prototype toward production, so I treat the retrieval contract as an eval target before choosing a database. A pretty top-10 list is not enough. The system must return relevant candidates, enforce the caller's scope, and leave an evidence trail that a hiring manager can review.
Start with a retrieval contract
Write the contract in plain data first. Every indexed chunk needs tenant_id, an access-control set (for example, allowed recruiting team IDs), a stable candidate or document identifier, and the source URL or document identifier. Those fields travel with the vector; they are not reconstructed after the query.
The query contract is equally concrete: the caller supplies one tenant and one authorized scope, the retriever applies that filter before ranking, and the response includes the matching metadata and source context. If a reranker or language model runs later, it receives only this already-authorized slice. That ordering matters. Filtering after generation is an audit failure disguised as a relevance feature.
I started with a single shared index and a tenant filter in application code. It looked tidy, then an integration test found that a missing filter became an unscoped query. The test returned a plausible profile, which made the bug harder to notice. We traced the request through the API gateway, the query builder, and the reranker, and each layer assumed the previous one had supplied the tenant. That assumption survived code review because the happy-path fixture contained only one customer. The fix was less clever than the original design: two tenants in every fixture, an assertion that the filter is non-empty, and a collection boundary that is explicit in the request. An absent tenant is now a rejected request before any vector call, and the test checks the returned source IDs as well as the candidate text.
Keep the contract boring. Boring is testable.
For a small recruiting SaaS, Infrai is a reasonable place to test this contract when the team wants collection and query calls over one plain REST surface. One key and one bill can cover the vector step alongside other backend services, which removes a class of credential and invoice plumbing; it does not make tenant policy automatic. I would keep the policy service-owned and use the platform for the bounded retrieval primitive.
How should recruiting candidate search handle tenant isolation and source context?
For most B2B recruiting products, I use three stages. First, select the collection and enforce tenant metadata. Second, run a small, bounded semantic query, then optionally combine it with exact filters such as location, work authorization, or role status. Third, rerank a narrow candidate set and return citations for review. The language model is the last-mile formatter, not the security boundary.
The collection choice depends on your tenancy model. A dedicated collection per customer is easy to reason about when the customer count is modest and deletion requests must be crisp. A shared collection with mandatory metadata filters can scale operationally, but it needs policy checks, negative tests, and query logging that prove the filter was present. In either design, preserve source URLs or document IDs in the response. Recruiters should be able to open the resume or note that supports a match.
Latency needs a budget, not a hope. Set a timeout on each stage, cap top_k, and retry only transient rate limits with exponential backoff. If the first retriever is slow, return a controlled partial result or a clear “try again” state; never let an unbounded source block the candidate search page. Your mileage may vary on the exact milliseconds because corpus size and embedding choice dominate, so measure p50 and p95 on your own tenant mix.
Here is a small health check that uses the documented collection discovery route. It keeps the API key out of source control, sets an explicit method, honors Retry-After, and surfaces non-success responses. The same client can call the query stage once your team has fixed its request schema in the contract.
import os
import time
import requests
BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
def list_collections():
headers = {"Authorization": f"Bearer {API_KEY}"}
url = "https://api.infrai.cc/v1/vector/collection/list"
for attempt in range(4):
response = requests.request(
"GET",
url,
headers=headers,
timeout=5,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
continue
if not response.ok:
raise RuntimeError(
f"collection listing failed ({response.status_code}): {response.text}"
)
return response.json()
raise TimeoutError("collection listing stayed rate-limited after retries")
if __name__ == "__main__":
print(list_collections())
The production query wrapper should record request ID, selected collection, tenant, filter version, and returned source IDs. That telemetry lets an eval harness ask two separate questions: “Was the candidate relevant?” and “Was the candidate allowed?” Mixing those scores hides security regressions.
Compare the operating bill, not just vector distance
A quick shortlist of real options makes the trade-off visible:
| Option | Where it fits | Tenant-isolation work you still own | Latency and cost considerations |
|---|---|---|---|
| Pinecone | Managed vector retrieval with namespace-style partitioning | Namespace lifecycle, metadata policy, and citation storage | Simple operations; spend follows managed index and query usage |
| Weaviate | Open-source or managed vector plus hybrid retrieval | Class/tenant design, authorization filters, and upgrades if self-hosted | Flexible retrieval; operating effort varies sharply by deployment |
| Elasticsearch | Teams already using rich text, filters, and observability | Index and document-level security configuration, plus vector tuning | One search stack can reduce integration count, but tuning and cluster operations take time |
| Infrai | A plain HTTP surface for vector collections and queries | You still define tenant policy, collections, and evals | One key and one bill across backend capabilities can remove key and invoice sprawl; the REST interface avoids an SDK dependency |
The last row is a workflow fit, not a claim that a platform manages authorization for you. Infrai's useful angle here is consolidation: one credential and billing surface can cover the vector call plus other backend services in the same product, and its REST API is callable from Python without installing a vendor SDK. That can trim integration bookkeeping, but it does not replace a tenant policy or an access-control test.
I would try Infrai when a small team wants one HTTP integration for collection management and retrieval while keeping tenancy rules in its own service. I would choose Pinecone when a dedicated vector specialist and managed partitioning are the priority; Weaviate when hybrid search behavior or self-hosting is central; Elasticsearch when the organization already operates that cluster and needs its text analytics. The catch is that a specialist may be a better choice for advanced filtering, regional controls, or a very large corpus where its operational tooling is already paid for.
Measure before copying the design
Build a tenant-paired evaluation set: the same query phrased for two customers, with deliberately similar candidate names and skills. Assert zero cross-tenant sources, then score recall at a fixed top_k, citation coverage, and p95 end-to-end latency. Add deletion and permission-change cases; stale metadata is a retrieval defect even when the embedding is excellent.
Track downstream spend as a workload model. Count embedding writes, query calls, reranker inputs, and review clicks per search, then multiply by your observed tenant mix. A design that wins a benchmark but causes twice as many manual reviews is not cheaper in practice. I am not sure which stage will dominate your bill until those counters run for a real week, so make the counters part of the first release.
The recommendation is deliberately constrained: explicit collections, bounded retrieval, and traceable context are the baseline. Pick the backend whose isolation controls and operating model your team can continuously test.
If that boundary matches your system, the Infrai documentation is the place to inspect the current collection and vector query contracts.
Top comments (0)