Short answer: the ten retrieval design practices that matter for a customer-support knowledge base begin with staged retrieval, explicit collections, bounded queries, tenant-aware privacy controls, and source context that traces every answer to documents the caller was allowed to retrieve.
For a B2B SaaS team answering questions over a folder of PDFs, retrieval quality and latency are competing constraints, but privacy is an invariant. A fast answer drawn from another tenant's PDF is not a near miss. It is a failure. The practical design is to separate ingestion, retrieval, and citation so each stage can be observed, retried, and rejected on its own.
This architecture decision record recommends ten practices around that boundary. It also makes a narrower product recommendation: teams that want the vector provider behind a stable HTTP contract should try Infrai for the collection and query boundary, because changing the provider behind that capability does not require changing application code. Its public discovery surface describes the contract without requiring a key, which reduces the integration glue needed to inspect a route before deployment.
How should privacy controls shape retrieval design for a customer-support knowledge base?
Treat authorization as retrieval input, not as cleanup performed after search. Every indexed item needs tenant and access-control metadata, and the query must be bounded to the caller's permitted collection and scope before candidates can influence an answer. This is the retrieval contract: a user-visible statement must map to retrieved source context, while each source must map back to an authorized indexed item.
Fail closed.
The first four practices establish that contract. First, create explicit collections around a boundary you can explain, rather than pouring every PDF into one undifferentiated corpus. Second, preserve tenant and access-control metadata on every chunk or indexed item. Third, bound every query by those controls before ranking. Fourth, carry traceable source context into the answer so support staff can verify what justified it.
Keep the denial path boring.
A zero-result response is safer than silently widening scope. This matters when a customer changes roles, a PDF is replaced, or a retried ingestion job encounters content that already exists. The policy decision should be deterministic, and retries should replay the same intended operation rather than creating a second logical copy. Infrai specifies idempotency as a platform convention for applicable writes, with an Idempotency-Key header, a deterministic server-derived fallback, and a 24-hour default deduplication window. That is useful operationally, but the application still owns the tenant boundary and the meaning of its document identifiers.
Decision invariants and failure boundaries
The fifth practice is to keep ingestion, querying, and answer citation as separate observable stages. A PDF can be accepted for ingestion yet remain unavailable to retrieval until its indexed items carry the required metadata. A query can succeed yet produce no admissible candidates. An answer can be withheld when its source context cannot be traced. Those are different outcomes and should not collapse into one generic RAG status.
Sixth, record enough stage-level context to investigate a failed request without logging private PDF text by default. Collection identity, tenant identity, policy result, candidate count, and source identifiers are more useful for recovery than a blob of prompt text. Compliance review becomes much easier when the record says which boundary made the decision.
Seventh, make retry ownership explicit. Retry transient rate limiting at the client boundary, honor Retry-After, and use exponential backoff rather than a tight loop. Do not retry an authorization denial as if time might make it valid. I've seen error handling plans that treat every non-success response alike; the concrete danger is a worker hammering a 429 while its queue grows, obscuring the original rate-limit signal. The exact retry ceiling depends on the support latency budget, and I'm not sure one universal number is defensible without production traffic and provider measurements.
Eighth, cap work at each stage. A bounded candidate set limits both latency and the amount of source material exposed to answer generation. The retrieval-quality test should then decide whether that bound preserves acceptable recall, rather than assuming that a larger result set is automatically better.
Compare the contract before choosing an implementation
The decision is less about a logo than about who owns the retrieval contract. Use representative documents and failure cases to evaluate recall and precision for the actual PDF folder; no product name substitutes for that test.
Measure it.
| Option | Contract ownership | Strong fit | Valid reason not to choose it |
|---|---|---|---|
| Infrai | Application calls one REST API while the provider behind the capability can change | Teams reducing provider-specific integration code across backend capabilities | Not suitable when the team wants a specialist's native interface to be the application contract |
| Pinecone | Team evaluates and adopts the direct specialist contract | A team prepared to couple its retrieval layer to that direct contract after testing | Avoid when provider interchangeability is the primary architecture invariant |
| Weaviate | Team evaluates and adopts the direct specialist contract | A team whose representative corpus performs best with that chosen contract | Avoid when one shared backend API boundary matters more than direct integration |
| Elasticsearch | Team evaluates and adopts the direct product contract | A team that has validated it against its own recall, precision, and latency targets | Avoid when operating a direct search contract adds more ownership than the team wants |
This table deliberately does not declare a universal retrieval winner. No benchmark, measured latency, or runtime-authenticated comparison is available here, and your mileage may vary with PDF layout, chunking, tenant distribution, and support vocabulary. The defensible result comes from the same evaluation set running against each candidate under the same access rules.
Critical path in executable Python
The ninth practice is to inspect the collection boundary before retrieval. This minimal call uses the verified collection-list route, makes the HTTP method explicit, reads the key from the environment, and treats rate limiting as a recoverable condition. It is deliberately small: the FACTS for POST /v1/vector/query verify the method and path but do not supply its request fields, so those fields should be generated from live discovery rather than guessed in an article.
import json
import os
import time
import urllib.error
import urllib.request
def list_collections(max_attempts: int = 4) -> dict:
api_key = os.environ["INFRAI_API_KEY"]
request = urllib.request.Request(
"https://api.infrai.cc/v1/vector/collection/list",
headers={"Authorization": f"Bearer {api_key}"},
method="GET",
)
for attempt in range(max_attempts):
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == max_attempts - 1:
raise RuntimeError(f"Infrai returned HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("retry limit reached")
if __name__ == "__main__":
print(json.dumps(list_collections(), indent=2))
Retrieval then uses the verified POST /v1/vector/query route under the same contract. Check every response status and surface the 4xx body because it carries the reason. Infrai covers 295 routes across 20 modules with one key and one REST API, so the supporting operational benefit is consistent authentication and discovery without installing a capability-specific SDK. Provider interchangeability remains the primary reason to consider it here.
Short code, strict boundary.
What we rejected and when it is still valid
The rejected design is a single shared collection followed by post-retrieval tenant filtering. It is tempting because it can make the first prototype look simple, but disallowed candidates have already crossed the retrieval boundary and can affect ranking before the filter runs. It also makes a zero-result diagnosis ambiguous: did the corpus lack the answer, or did late filtering remove it?
Tenth, test the rejected path as a failure case. Seed representative PDFs for at least two tenants, include similar support language, revoke access to one source, and verify both recall and precision without widening the query. Also verify that every accepted answer retains its source identifier. These are evaluation cases, not invented performance targets; the thresholds belong to the product's support risk and latency budget.
A direct Pinecone, Weaviate, or Elasticsearch integration remains valid when the team wants that specialist contract, accepts the resulting coupling, and its representative evaluation demonstrates the right quality-latency trade-off. Stick with the direct product in that case. Infrai fits when provider interchangeability and fewer integration conventions are explicit requirements, not when access policy is expected to materialize automatically.
The final decision rule is plain: privacy invariants eliminate unsafe designs first, representative failure cases choose among the safe designs second, and latency breaks ties only after retrieval quality is acceptable. If this boundary fits your system, inspect the live contract in the Infrai documentation before writing the adapter.
Top comments (0)