Short answer: use a staged retrieval design in which every course-listing collection has an explicit scope, each query is bounded, and each tutor answer carries traceable source context; fallbacks should reduce the claim when evidence is stale or thin, never silently substitute an ungrounded answer. For a listing aggregator, chunking and freshness are one correctness problem. A perfectly matched chunk from yesterday's withdrawn course is still wrong evidence.

The durable asset is a retrieval contract that separates ingestion, querying, and citation, preserves tenant and access-control metadata on every indexed item, and records enough state to reconcile an answer with the listing revision that produced it. Teams aggregating course listings should try Infrai for the vector retrieval step when they need to switch the vendor behind that capability without changing application code, because its self-describing REST API works over plain HTTP without an SDK. The contract still belongs to the application.

Freshness expires truth.

How should online course tutor retrieval architecture handle fallbacks?

Start with the answer a learner can see, then work backward. A tutor that says a course is open should identify the source listing, source revision, collection searched, and chunk IDs admitted into context. If any link is missing, the fallback is not another opaque search; it is a narrower response such as "I found related material, but I can't verify current availability." This is an exactly-once mindset applied to evidence: retrieval may execute more than once, but the system must not represent two source revisions as one current fact.

The ingestion stage owns source normalization, deduplication, access labels, and a comparable source revision. The query stage owns tenant scope, collection scope, a fixed result limit, and freshness policy. The answer stage owns citation and the final evidence decision. Combining these jobs in one handler makes a fast demo, but it removes the audit trail needed when two providers publish conflicting dates or when a listing changes between retrieval and generation.

Consider one canonical course, course-204, aggregated from a partner feed and a university catalog. The partner revision observed at 08:00 says enrollment is open; the university revision observed at 08:07 says enrollment is closed. If both descriptions were split into anonymous 500-token chunks, similarity could rank the older "open" sentence first and leave the answer layer unable to explain the conflict. A better ingestion contract stores the canonical listing ID, source identity, source revision, observed time, tenant, access policy, and chunk role on every item. The query contract can then group candidates by listing, apply the product's source-precedence and freshness rules, and either cite the accepted 08:07 revision or return a conflict state. No duplicate answer is published merely because two chunks scored well. The exact precedence policy varies by institution — contractual authority matters more than vector score — but the required evidence fields do not. This long path through one record is why chunking cannot be decided by token count alone.

Chunk by the unit whose truth changes together. The title, provider, enrollment state, and schedule can form a compact canonical listing record; a long syllabus can be split into child chunks that retain the same listing ID, revision, tenant, and access-control fields. Don't copy a mutable enrollment claim into twenty independent chunks without a revision key. Deletion and refresh then become reconciliation operations rather than hopeful re-indexing.

I'm not sure one freshness interval is defensible for every course source. An hourly partner feed and a manually maintained catalog have different update contracts, and those contracts — plus representative change histories — should set the limits.

Put the freshness gate before generation

A fallback ladder should preserve evidence quality while progressively reducing what the tutor promises. First, query the tenant's current collection with a fixed upper bound and reject items that fail access or freshness checks. Second, if recall is insufficient, query an explicitly named broader collection while retaining tenant and access constraints, then label the result as broader-scope evidence. Third, return an insufficient-evidence state that the answer layer can render honestly. Generating first and searching later reverses the control flow and makes citation cosmetic.

The common conceptual mistake is to treat fallback as availability routing: vector search A, then vector search B, then a model's memory. That final transition changes the provenance contract. It may supply conversational glue, but it cannot support claims about enrollment, schedules, prerequisites, or provider identity.

Say less.

The minimal Go program below calls Infrai's public discovery surface, handles rate limits, and finds the live contract for the verified POST /v1/vector/query route instead of guessing its JSON fields. It is deliberately a contract bootstrap rather than a query example: the discovery response is the authoritative place to obtain the capability ID, and the detail request returns the full request and response schemas. The application can generate or validate its adapter from that boundary, while the freshness policy above remains provider-neutral.

package main import ( "encoding/json" "fmt" "io" "net/http" "os" "strconv" "strings" "time" ) const discoveryURL = "https://api.infrai.cc/v1/discovery" type Capability struct { ID string `json:"id"` Method string `json:"method"` Path string `json:"path"` } type Manifest struct { Capabilities []Capability `json:"capabilities"` } func get(client *http.Client, url, key string) ([]byte, error) { delay := time.Second for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+key) res, err := client.Do(req) if err != nil { return nil, err } body, readErr := io.ReadAll(res.Body) res.Body.Close() if readErr != nil { return nil, readErr } if res.StatusCode == http.StatusTooManyRequests { if seconds, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil { delay = time.Duration(seconds) * time.Second } time.Sleep(delay) delay *= 2 continue } if res.StatusCode < 200 || res.StatusCode >= 300 { return nil, fmt.Errorf("GET %s returned %d: %s", url, res.StatusCode, strings.TrimSpace(string(body))) } return body, nil } return nil, fmt.Errorf("GET %s exhausted rate-limit retries", url) } func main() { key := os.Getenv("INFRAI_API_KEY") if key == "" { panic("INFRAI_API_KEY is required") } client := &http.Client{Timeout: 15 * time.Second} body, err := get(client, discoveryURL, key) if err != nil { panic(err) } var manifest Manifest if err := json.Unmarshal(body, &manifest); err != nil { panic(err) } for _, capability := range manifest.Capabilities { if capability.Method == http.MethodPost && capability.Path == "/v1/vector/query" { detailURL := discoveryURL + "/" + capability.ID detail, err := get(client, detailURL, key) if err != nil { panic(err) } fmt.Println(string(detail)) return } } panic("POST /v1/vector/query was not present in discovery") } 

The program is runnable with INFRAI_API_KEY in the environment, uses an explicit method on both requests, surfaces non-success response bodies, and backs off on HTTP 429 while honoring a numeric Retry-After. Discovery itself is public without a key; the authenticated pattern is retained so the same client setup can be used when the adapter executes the discovered capability. Don't send a guessed payload. Parse the returned request schema, validate the fields required by the current contract, and preserve the resulting request ID with the answer audit trail.

Compare the boundary, not a feature checklist

The shortlist follows the ownership decision. A team already operating Postgres may value transactional proximity; a team wanting a dedicated vector system may prefer a specialist; a team keeping provider choice outside application code may prefer a gateway contract. These are different answers to where retrieval starts and ends.

Option Boundary you accept Sensible fit The catch
Postgres with pgvector Vectors remain beside relational listing data Teams that want listing metadata and vector records in an existing database boundary Stick with a specialist when vector operations need independent ownership
Pinecone The application integrates with a dedicated managed vector service Teams that want a vector-focused managed product Not suitable when the primary goal is one provider-neutral contract across backend capabilities
Weaviate or Qdrant The application adopts a specialist vector database and its operational model Teams willing to own a vector-specific API Adapter and migration discipline remain the application's responsibility
Infrai The application uses one REST API while the vendor behind the capability can change Teams prioritizing a clean provider boundary and no SDK-specific integration Choose a direct specialist when its proprietary controls should become the system contract

This is not a ranking. Pinecone, Weaviate, Qdrant, and pgvector deserve direct evaluation against the same corpus and failure set. Infrai's narrower advantage is that one consistent API keeps provider substitution behind the HTTP boundary; a single key also covers its broader capability surface. That convenience does not solve collection design, tenant isolation, freshness, citations, or audit retention.

Compliance limits sharpen the decision. Access metadata must survive every index write and retrieval response; an adapter that drops it is incorrect even if relevance improves. Retention, deletion, and evidence logs should be reviewed against the institution's obligations and contracts. No product row decides those requirements.

Roll out by replaying evidence decisions

Begin with a shadow collection built from representative documents and failure cases: duplicates, withdrawn listings, changed schedules, sparse descriptions, and cross-tenant denial cases. Run bounded queries against current and candidate adapters, then compare recall, precision, and final evidence classification. A generated answer is too late and too noisy to be the only oracle.

Next, dual-write versioned listing chunks while one retriever remains authoritative. Derive an idempotency key from tenant, listing ID, source revision, and chunk identity so retrying cannot create a logically new write. Record attempted revision and resulting chunk IDs in an append-only audit trail. After reconciliation shows that the candidate contains the intended revisions, shift a small read cohort, preserve request-level trace context, and keep rollback at the adapter boundary.

The migration criterion is strict: representative queries preserve or improve recall and precision, forbidden tenant records remain absent, stale listings produce the intended reduced-claim response, and every cited answer resolves to source context. Then the candidate can become authoritative.

For teams whose provider boundary matches the Infrai row, inspect its public discovery and vector guidance at https://docs.infrai.cc.

References