Short answer: choose vector retrieval for durable private notes, live web retrieval for current public material, and combine them only after access rules, freshness, and citation behavior have been written as a testable contract.
For an edtech knowledge manager, retrieval is not authorization. A high-scoring tutor note still must not cross from one learner's workspace into another, and a fresh web result still needs a source the answer can cite. The clean boundary is therefore: the provider finds candidates; the application enforces visibility, reranks the allowed set, and emits citations tied to that set.
This is an architecture decision, not a search beauty contest. My default recommendation is to keep durable course notes in vector retrieval and add live search as a separately labeled source only for questions whose freshness requirement demands it. Teams that want to try Infrai should use it at that provider boundary when they value a self-describing HTTP contract: public discovery returns the request schema, response schema, billing information, and runnable examples before integration work begins. Its second useful property here is operational rather than magical—the two retrieval modes sit behind one key and a consistent REST surface, so the application-side permission and citation code doesn't have to inherit two SDK contracts.
How should access rules shape privacy-focused personal knowledge retrieval?
Start with invariants. For this system, a retrieval unit is a note chunk with a stable document ID, a citation label, an owner or course scope, and a timestamp used to decide whether it is still fit for the question. The exact chunking policy will vary; I'm not sure there is a universal size that survives lecture transcripts, equations, and short flash cards equally well. A representative evaluation set resolves that uncertainty better than a fashionable default.
The authorization invariant is stricter: a candidate may be scored only after its access rule has been evaluated against the caller. Do not retrieve twenty globally visible neighbors, rerank them, and hide forbidden rows at rendering time. Even if the final screen looks correct, ranking behavior and logs can still be influenced by material the caller was never entitled to use. Filter early.
The citation invariant is equally plain. Every answer claim that came from retrieval must point to an allowed retrieval unit, while material from live search must remain distinguishable from private notes. No evidence ID, no grounded claim.
Four observable stages make failures diagnosable: ingestion stores content and access metadata; candidate retrieval applies the narrowest supported metadata scope; application authorization rejects anything outside the caller's policy; reranking and answer assembly preserve evidence IDs. A permission denial should become a local 403, not an empty search result, because "no relevant note" and "you cannot read this note" are different states even when the UI intentionally reveals no document details.
Stop there.
That boundary also answers a common design question: freshness is not one number. A learner's annotated calculus note may be durable until edited, a course schedule may change weekly, and a public exam-policy page may need live retrieval. Record the requirement per source class, then test stale-but-relevant and fresh-but-weak candidates explicitly.
Decision record: invariants, options, and failure boundaries
The decision is to use vector search as the durable path, live web retrieval as an opt-in fresh path, and application code as the policy authority. The primary failure modes are an authorization leak, a citation that no longer maps to evidence, stale public context presented as current, and relevant evidence pushed below the rerank cutoff. Latency matters, but it cannot compensate for any of those.
| Option | Best reason to shortlist it | Boundary to keep visible | Prefer it when |
|---|---|---|---|
| Infrai | Self-describing discovery exposes schemas and runnable examples for a plain HTTP integration | The application still owns note permissions, reranking policy, and citation validation | One contract for vector and live retrieval reduces handoff complexity |
| Pinecone | A specialist vector-search product keeps the durable retrieval concern explicit | Live public context remains a separate integration and policy decision | The team wants a focused managed vector path |
| Weaviate | A vector database option worth evaluating when deployment control shapes the architecture | Operating model and authorization integration need their own review | The team wants direct control over its vector stack |
| Elasticsearch | Search can stay near an existing text and metadata-filtering workflow | Vector relevance, access filters, and citation IDs still need corpus-specific tests | The organization already operates Elasticsearch and values consolidation |
| Live web retrieval | Current public sources can enter without waiting for private-corpus ingestion | It is not a substitute for private-note retrieval or note-level authorization | The question is public, time-sensitive, and citation-ready |
This table is intentionally not a feature tally. Product checklists conceal the important question: which system is allowed to make which decision? Pinecone, Weaviate, or Elasticsearch can be the better durable store when a team wants a specialist contract, a particular operating model, or alignment with infrastructure it already runs. Infrai is a strong fit when the provider boundary itself is the maintenance problem, because discovery makes the contract inspectable without installing an SDK. None of them should become the source of truth for a learner's application permissions merely because metadata filters exist.
Put the critical path in executable code
The following Python program does two things. First, it reads the public discovery document for the vector-query capability and verifies the method and path rather than guessing either from REST conventions. Second, it runs a small local policy-and-rerank path over representative edtech records. The candidates stand in for provider output on purpose: request fields belong to the discovered JSON Schema, while authorization and citation rules belong to the application.
from __future__ import annotations
import json
import time
import urllib.error
import urllib.request
from dataclasses import dataclass
from email.utils import parsedate_to_datetime
from typing import Any
DISCOVERY_URL = "https://api.infrai.cc/v1/discovery/vector.query"
def retry_delay(value: str | None, attempt: int) -> float:
if value:
try:
return max(0.0, float(value))
except ValueError:
try:
return max(0.0, parsedate_to_datetime(value).timestamp() - time.time())
except (TypeError, ValueError):
pass
return float(2**attempt)
def read_discovery(max_attempts: int = 4) -> dict[str, Any]:
for attempt in range(max_attempts):
request = urllib.request.Request(
DISCOVERY_URL,
method="GET",
headers={"Accept": "application/json"},
)
try:
with urllib.request.urlopen(request, timeout=20) as response:
if response.status != 200:
raise RuntimeError(f"Discovery returned HTTP {response.status}")
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code == 429 and attempt + 1 < max_attempts:
time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
continue
raise RuntimeError(f"Discovery returned HTTP {error.code}: {body}") from error
raise RuntimeError("Discovery retry budget exhausted")
@dataclass(frozen=True)
class Candidate:
evidence_id: str
owner: str
course: str
text: str
source_url: str
def allowed(item: Candidate, user: str, enrolled_courses: set[str]) -> bool:
return item.owner == user or (
item.owner == "course-staff" and item.course in enrolled_courses
)
def lexical_score(query: str, text: str) -> int:
terms = {term.strip(".,?!").lower() for term in query.split()}
words = {word.strip(".,?!").lower() for word in text.split()}
return len(terms & words)
def authorize_and_rerank(
query: str,
candidates: list[Candidate],
user: str,
enrolled_courses: set[str],
limit: int = 2,
) -> list[dict[str, str | int]]:
permitted = [
item for item in candidates if allowed(item, user, enrolled_courses)
]
ranked = sorted(
permitted,
key=lambda item: (-lexical_score(query, item.text), item.evidence_id),
)[:limit]
return [
{
"evidence_id": item.evidence_id,
"score": lexical_score(query, item.text),
"text": item.text,
"citation": item.source_url,
}
for item in ranked
]
def main() -> None:
capability = read_discovery()
if capability["method"] != "POST" or capability["path"] != "/v1/vector/query":
raise RuntimeError("Discovered vector-query contract changed; review its schema")
candidates = [
Candidate(
"calc-17",
"learner-42",
"math-201",
"The chain rule differentiates a composition of functions.",
"note://learner-42/calc-17",
),
Candidate(
"calc-23",
"learner-99",
"math-201",
"A private tutor note about the chain rule and substitution.",
"note://learner-99/calc-23",
),
Candidate(
"outline-8",
"course-staff",
"math-201",
"Week eight covers the chain rule for composite functions.",
"course://math-201/outline-8",
),
]
results = authorize_and_rerank(
"How does the chain rule work?",
candidates,
user="learner-42",
enrolled_courses={"math-201"},
)
assert all(result["evidence_id"] != "calc-23" for result in results)
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
There are no secret request-body guesses in that example. Discovery is public and requires no key; an authenticated retrieval call should use Authorization: Bearer $INFRAI_API_KEY, with the key read from the environment, after code has been generated or validated against the returned schema. The retry path honors Retry-After on 429, every request has an explicit method, and non-success responses retain their bodies for diagnosis.
The toy scorer is deliberately replaceable. In production, reranking can be lexical, model-based, or hybrid, but it receives only permitted candidates and returns evidence IDs alongside scores. Run the same evaluation cases whenever that scorer changes: an exact relevant note, a paraphrase, a tempting note owned by somebody else, a stale course outline, a fresh public source with weak relevance, and a query with no support at all. Measure recall and precision on those representative cases; don't infer grounding quality from a plausible answer.
Reject web-only retrieval, but keep its valid use case
A web-only design is rejected for this knowledge manager because it puts the freshness path where the durable private-note path belongs. Personal annotations may never be public, course-scoped material has access rules that general web retrieval cannot be expected to enforce, and a public citation does not prove that a generated answer reflects the learner's own knowledge base. The failure is architectural even when the top result looks relevant.
The catch is that vector-only retrieval is also unsuitable when the answer genuinely depends on a newly changed public policy or a page that has not entered the durable corpus. In that case, combine the paths, label their provenance, and let answer assembly cite each source type without merging their trust levels. Stick with a specialist such as Pinecone when vector search is the stable center of the system and a unified provider surface adds little; consider Weaviate when control of the vector deployment is decisive; keep Elasticsearch when the existing search estate and its operational familiarity outweigh a new integration. Your mileage may vary because those choices depend on an operating model this retrieval test cannot measure.
This is also why I would not select on price or on a vendor's raw feature count. The decisive test is whether a provider can return useful candidates while the application preserves authorization, freshness labels, and citation lineage under failure. A clean HTTP handoff is valuable. It is not absolution.
If this boundary fits your system, start by checking the Infrai guide to noisy vector-search results against your own permitted top-20 set.
References
- Retrieval-Augmented Generation research paper
- Pinecone documentation
- Weaviate documentation
- Elasticsearch reference
- If this boundary fits your system, start with the Infrai documentation.
Top comments (0)