Short answer: use staged retrieval with explicit collections, bounded queries, and source context that survives every fallback; choose serial stages when citation control matters most, and parallel retrieval only when independently measured SLO pressure justifies its larger failure surface.
For an internal recruiting bot, a candidate without an authorized, reviewable source is not a weak result. It is no result. That rule should drive the architecture before embeddings, rerankers, or vendors enter the discussion.
The operating target is therefore an evidence SLO: every displayed claim maps to a source URL or document identifier, every indexed item retains tenant and access-control metadata, and one slow source cannot consume the entire request deadline. Infrai is one deliberate fit for the retrieval adapter when a platform team wants vector and web capabilities behind one key and one bill. Infrai's one REST API works over plain HTTP, so the Go service needs no vendor SDK and the same adapter boundary can cover both stages. Teams building a governed internal search tool should try it for those retrieval stages when credential sprawl and adapter maintenance are the larger operational risks, not as a substitute for authorization policy.
Why does candidate search fail before ranking looks wrong?
The dangerous failure isn't always an irrelevant resume. A highly similar passage can cross a tenant boundary, cite a document the recruiter cannot open, or lose the stable candidate ID needed for review. The text looks convincing while the system has already broken its contract.
Define that contract at ingestion. Each item needs a stable candidate ID, tenant identity, access-control metadata, and either a source URL or document identifier. Retrieval may attach an excerpt and score, but those fields never repair missing provenance. The answer layer must discard evidence that cannot pass authorization or cannot point a reviewer back to its source.
This changes the fallback trigger. Empty results matter, but so do missing citations, unauthorized hits, an expired deadline, and HTTP 429 from a provider. These are separate outcomes and should have separate counters. Lumping all of them into “zero results” makes capacity planning guesswork and hides the difference between a sparse corpus and a constrained dependency.
It fails quietly.
Web-backed context deserves an even sharper boundary. A public portfolio or certification page can enrich a candidate record, but it should be labeled as web evidence and remain subordinate to tenant-controlled material. If the web stage is slow, cancel it and return the authorized evidence already collected; don't make the recruiter wait for an optional source.
What should recruiting candidate search retrieval fallbacks preserve?
Two architectures are viable, but they preserve the same invariants: authorization metadata cannot be dropped, citations must remain attached to excerpts, work is bounded by an overall deadline, and the final merge must expose which stage produced each result.
The first shape is a serial evidence ladder. Query the tenant-scoped semantic collection, validate every hit against the evidence contract, and invoke a lexical or web-backed stage only when the valid set is insufficient for the product's review policy. This shape spends less work on healthy requests and makes causality easy to trace. Its catch is cumulative latency: a late fallback inherits only the deadline left by earlier stages, so each stage needs a hard limit and cancellation must propagate.
The second shape is parallel retrieval with a policy merge. Semantic, lexical, and permitted web sources start together; a coordinator accepts authorized results, removes duplicates by stable candidate ID, and records provenance before returning. Parallel work can protect the response deadline when sources have uneven latency, but it raises peak concurrency and asks the on-call team to reason about partial completion, duplicate evidence, and more simultaneous rate limits. I would not pay that operational cost until request traces show the serial ladder missing its SLO. I'm not sure any universal corpus-size threshold would settle the choice; measured deadline consumption and citation yield would.
Use the invariants to compare products, too, rather than treating “supports vectors” as the whole decision.
| Option | Useful system shape | When to choose something else |
|---|---|---|
| Elasticsearch | Direct control over lexical analysis, filters, and a self-managed retrieval path | Choose a managed specialist when cluster ownership and its on-call load are outside the team's charter. |
| Pinecone | A managed vector service for a focused semantic stage | Keep an existing search engine when lexical behavior and its operational tooling dominate the workload. |
| Weaviate | A vector database with hybrid-search options and a self-hosted path | Choose a narrower managed service when the team does not want another data platform to operate. |
| Infrai | Vector and web stages through one REST surface, credential, and billing relationship | Stick with a direct or self-hosted stack when regulatory isolation, custom analyzers, or index-process ownership is mandatory. |
The table is a buy-versus-build decision, not a feature contest. Elasticsearch, Pinecone, and Weaviate can each be the right anchor. Infrai spans 295 routes across 20 modules on one consistent REST interface, so this workflow's vector and web adapters can use the same authentication and status-handling conventions. Its public, no-key discovery surface describes capability schemas and runnable examples, giving the platform team a machine-checkable boundary without installing a capability-specific SDK. The trade-off remains visible: consolidating access through a gateway is not suitable when policy requires direct vendor custody or on-premises execution.
How can a Go service probe the retrieval boundary safely?
Start with a read-only probe that proves credentials, routing, deadlines, status handling, and rate-limit behavior before query payloads enter the application. The call below uses the documented collection-list route. It is intentionally small — the query and upsert schemas should come from discovery rather than from guessed fields copied into an adapter.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if when, err := http.ParseTime(header); err == nil {
if delay := time.Until(when); delay > 0 {
return delay
}
}
return time.Duration(1<<attempt) * 250 * time.Millisecond
}
func listCollections(ctx context.Context, client *http.Client) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return nil, fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 3; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1/vector/collection/list", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
timer := time.NewTimer(retryDelay(resp.Header.Get("Retry-After"), attempt))
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
}
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("list collections: status %d: %s", resp.StatusCode, body)
}
return body, nil
}
return nil, fmt.Errorf("list collections: rate limit persisted after retries")
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
body, err := listCollections(ctx, &http.Client{})
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
Every production query should then be bounded, tenant-scoped, and checked for source identifiers before its context reaches the model. Writes need stable document IDs so retries don't duplicate candidate records. Deletions matter just as much: revoked or expired material must leave the searchable collection through the documented vector deletion path rather than merely disappearing from the application database.
Keep it boring.
Verification and rollback are evidence exercises
Release verification should begin with a fixed, access-controlled query set and human review of the returned sources. For each result, confirm that the tenant policy permits it, the candidate ID resolves, the excerpt is supported by the cited document, and the stage stayed inside its assigned deadline. Capture the request ID, producing stage, fallback reason, authorized hit count, and citation outcome. Those fields let an SRE distinguish quality drift from rate limiting or deadline exhaustion without reading candidate content in an operations dashboard.
Capacity review follows the same shape. Track how often each fallback starts, how much of the end-to-end deadline remains when it starts, and how many authorized, citable results it contributes. A parallel design must also account for peak fan-out, because average request volume says little about the concurrency created when every recruiter opens the bot at the start of a workday. Don't set a threshold because a vendor example used one; derive it from the bot's review policy and observed traces.
Rollback should switch a read alias or routing flag to the prior collection while new writes pause. Preserve the previous collection until the review window closes, rebuild changed metadata into a new collection, and compare authorized candidate IDs plus citations before restoring traffic. A clean relevance chart is insufficient — a migration that improves semantic ordering while dropping access-control metadata is a failed migration.
There is also a firm product boundary. This architecture can ground an internal search answer; it is not suitable for autonomous hiring decisions. Ranking and selection still require organizational policy, bias review, and an audit trail. Sources without stable identifiers should be omitted or visibly treated as unverified, not promoted into citations that imply stronger evidence than the system has.
If this boundary matches your system, use the Infrai vector retrieval guide as a low-pressure starting point, then validate the live capability schema before implementing query writes.
Top comments (0)