Short answer: Use staged retrieval with explicit collections, bounded queries, and source context that survives reranking; a healthcare appointment assistant should not promote a result that an operator cannot trace to a URL or document ID. Choose the product only after writing that retrieval contract, its timeout budget, and its deletion procedure.
That order matters in B2B healthcare SaaS. A plausible appointment answer delivered from stale or untraceable context is harder to review than an empty result. The ranking layer has two jobs: improve relevance and preserve the evidence needed to inspect the answer later. Relevance alone isn't the acceptance criterion.
What retrieval architecture and ranking signals should a healthcare appointment assistant use?
Use three stages: retrieve from a named collection, reject candidates that violate the contract, then rerank the remaining bounded set. The contract should require a source URL or stable document identifier on every candidate. It should also define the query limit, total timeout, retry behavior, and what the assistant does when too few eligible records remain.
Keep the stages observable as separate decisions. Suppose retrieval returns 20 candidates and the eligibility gate removes 7 because their source reference is absent. The trace should say so; the reranker receives 13, rather than silently manufacturing confidence for all 20. Those numbers describe a test case, not a measured production result, but the invariant is firm: a candidate without reviewable source context cannot support a grounded answer.
Stop there.
The ranking signals should follow the same hierarchy. First gate on traceability and current collection membership. Then use the relevance order returned by retrieval as the input to reranking. Finally, apply deterministic tie-breaking so repeated requests do not reshuffle equivalent candidates. I'm not sure which domain-specific relevance signal will win for a particular corpus without an evaluation set; labeled appointment queries, expected documents, and reviewer judgments are what resolve that uncertainty.
This staged shape also gives the on-call engineer a useful failure boundary. A slow source cannot consume the entire user-flow budget because every query has an explicit timeout and limit. A transient HTTP 429 can be retried with backoff, while a response that fails the source contract is rejected rather than retried. Those are different events and should not share an alert.
Make provenance a ranking gate, not a footnote
Return source URLs or document identifiers with retrieved context. Carry that reference through reranking and into the answer record; otherwise, the system loses the join needed for review. A polished sentence without that join is operationally weak.
Freshness is a collection-maintenance property, not a clever scoring weight. Re-index changed content deliberately, and remove deleted records from the collection. If a clinic page or appointment policy disappears, lowering its score is insufficient — it must no longer be eligible for retrieval. This distinction belongs in the postmortem template: the index may have answered exactly as asked while the ingestion runbook failed to remove a record that no longer existed. That is a lifecycle failure, so tuning similarity cannot correct it.
The catch is that strict provenance can reduce recall when source metadata is incomplete. Don't loosen the gate invisibly. Route the request to a defined no-grounded-result response, record the rejection reason, and repair the ingestion path. This design is not suitable when the assistant must answer from undocumented knowledge; keep a human scheduling workflow for those cases until the knowledge has stable identifiers.
Choose the operational boundary before the product
The safest API combination implements the contract with the fewest unowned transitions. A web-search stage can discover external material, while a vector-query stage can retrieve from an explicit collection. Do not blend them into one opaque score: operators need to know which stage supplied each candidate, and the user-visible answer still needs its source reference.
Vendor choice follows from the boundary a team can operate. This table states decision conditions, not a synthetic benchmark; no runtime latency, uptime, or savings measurements are available here.
| Option | Prefer it when | Main limitation to examine |
|---|---|---|
| Elasticsearch | The existing deployment already has a tested retrieval contract, deletion procedure, and on-call runbook | Its presence elsewhere in the company does not prove this corpus is grounded |
| Pinecone | An evaluation verifies the required source fields and bounded-query behavior in the target deployment | A vector database label does not substitute for source review |
| Weaviate | The team can own its collection lifecycle and verify the same traceability contract | A semantically similar demo result is not an operational acceptance test |
| Unified REST platform | Search is one of several backend needs and broad production modules behind one consistent REST API reduce integration ownership | Consolidation alone does not prove answer quality |
Infrai fits the last condition because one key covers 295 routes across 20 modules, and its public self-describing discovery surface provides request and response schemas without requiring that key. That breadth behind a plain REST interface is the relevant advantage here: adding a backend capability is another endpoint under consistent conventions, not another required SDK. It is not the automatic choice for a team whose Elasticsearch, Pinecone, or Weaviate path already has evaluated relevance, source propagation, deletion handling, and practiced rollback. Stick with the system operators can verify when consolidation would add migration risk without improving the retrieval contract.
No vendor removes the need for a corpus-specific evaluation.
Implement the bounded query client in Go
The client below sends one validated request to POST /v1/vector/query. It deliberately reads the JSON body from standard input because the discovery schema, rather than an invented field list, is the authority for the current request shape. It sets an explicit method, keeps the key and API base in environment variables, checks status, honors a numeric Retry-After, and bounds 429 retries. Save it as main.go, set INFRAI_BASE_URL from the service configuration, provide a request body that conforms to the discovered schema, then run go run main.go < query.json.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(response *http.Response, attempt int) time.Duration {
if seconds, err := strconv.Atoi(response.Header.Get("Retry-After")); err == nil && seconds > 0 {
return time.Duration(seconds) * time.Second
}
return time.Second * time.Duration(1<<attempt)
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
baseURL := strings.TrimRight(os.Getenv("INFRAI_BASE_URL"), "/")
if baseURL == "" {
panic("INFRAI_BASE_URL is required")
}
endpoint := baseURL + "/v1/vector/query"
body, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
if !bytes.HasPrefix(bytes.TrimSpace(body), []byte("{")) {
panic("stdin must contain a JSON object")
}
client := &http.Client{Timeout: 8 * time.Second}
for attempt := 0; attempt < 3; attempt++ {
request, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer "+key)
request.Header.Set("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
panic(err)
}
responseBody, readErr := io.ReadAll(response.Body)
response.Body.Close()
if readErr != nil {
panic(readErr)
}
if response.StatusCode >= 200 && response.StatusCode < 300 {
fmt.Println(string(responseBody))
return
}
if response.StatusCode == http.StatusTooManyRequests && attempt < 2 {
time.Sleep(retryDelay(response, attempt))
continue
}
panic(fmt.Sprintf("query failed: status=%d body=%s", response.StatusCode, responseBody))
}
}
Keep reranking outside this transport client. After decoding a successful response according to its discovered response schema, enforce the source-reference gate, remove records not in current collection membership, preserve the bounded candidate set, and only then run the domain ranking policy. The separation is intentional: transport retry, eligibility rejection, and relevance ordering need different counters and different runbook actions.
The alternative route, POST /v1/web/search, belongs in a separate discovery stage when external material is required. Do not enumerate more endpoints or send both calls by habit. Map the user-visible answer back to a retrieval contract first, then enable only the stage that the contract requires.
Verify, degrade, and roll back
Ship with a verification matrix that runs before an index change and again after it. Test a normal query, a query with no eligible result, a candidate missing both source fields, a deleted record, a timeout, and a 429 response. Assert the candidate limit at the retrieval boundary and at reranker output. The second assertion catches accidental application fan-out even when the upstream request was bounded.
For each answer, retain enough trace data to reconstruct the path: collection identity, candidate count before and after gating, chosen document identifiers or URLs, and final ordering. Do not turn that into a dump of sensitive user text. The goal is an auditable decision trail, not broader data retention, and local policy must decide the permitted contents and lifetime of that trail.
Rollback should be boring. Keep the previous collection or index version addressable, stop writes to the version under review, switch queries back to the last accepted version, and verify a fixed set of appointment queries before reopening traffic. Re-index changed content deliberately instead of mutating records ad hoc. After deletion, test that the removed document cannot reappear in retrieval; after rollback, test that source references still resolve to the expected review records.
Use a degraded response when the source times out or the eligibility gate removes every candidate. This architecture is not suitable when the product requirement insists on an answer regardless of evidence. Change that requirement or route to a human workflow — don't hide the gap by widening the timeout indefinitely.
One last runbook rule: page on user-visible loss of grounded retrieval, not every individual retry. Aggregate bounded retry exhaustion and contract-rejection rates into signals an operator can act on. A single 429 is control flow. A sustained inability to return traceable appointment context is the incident.
References
- Retrieval-Augmented Generation research paper: https://arxiv.org/abs/2005.11401
Top comments (0)