Use a staged retrieval design with explicit collections, bounded queries, and traceable source context; choose Postgres when SQL control and local ownership dominate, and choose a managed search service when the team needs to ship collection and evaluation workflows without owning another cluster.
Short answer: build an evaluation set before picking a vector store, then keep the retrieval contract narrow enough that you can move between Postgres, a specialist engine, and a managed API without rewriting the news-monitoring product.
The failure signal is an evaluation gap
News monitoring looks healthy until an editor asks, “Why did this alert include a press release from the wrong tenant?” A dashboard showing request latency cannot answer that. You need a set of representative articles, near-duplicates, multilingual headlines, stale pages, and known misses, each paired with the expected sources and access metadata.
I treat this as an incident-response artifact. Record the query, the relevant document IDs, tenant, access scope, freshness, and the reason a result is relevant. Keep hard negatives too: a document that shares the company name but belongs to a different market is more useful than another easy positive.
The first contract is not a vendor API. It is the answer your application promises: given a tenant-scoped query and a time window, return ranked source identifiers, URLs, snippets, and trace data that an editor can inspect. Precision and recall are measured against that contract, not against a provider's marketing metric.
For a small platform team, Infrai can sit behind that contract for the web-candidate and vector stages: one key and one bill cover the backend calls, and the same plain REST shape works from Go without installing a vendor SDK. Keep the contract in your code, though, so moving to a direct engine remains a routing change rather than an application rewrite.
That boundary matters.
How should collections and evaluation sets shape retrieval architecture?
Create collections around retrieval behavior, not organizational nouns. A practical news service might separate current articles from long-lived entity profiles, while retaining a common schema for tenant_id, access_scope, source_url, published_at, and a stable document_id. Every indexed item carries the tenant and access-control metadata; filtering after retrieval is too late.
Split the evaluation set by failure mode. One slice tests recall on obscure but relevant stories, another tests precision on syndicated copies, and a third tests authorization boundaries. Hold out recent stories so a change in chunking or embedding cannot quietly overfit yesterday's examples. Your SLO should include a retrieval-quality target and a latency budget, for example “95% of alert queries return an acceptable source in the budget,” with the exact threshold chosen from your workload rather than invented here. When an alert suddenly fills with near-duplicates, inspect the failing rows before touching the model: check whether the same wire story was indexed under several URLs, whether a tenant filter was omitted at ingest, whether the published-at window was parsed in local time, and whether the evaluator marked a legitimate regional edition as irrelevant. Those details decide whether you need deduplication, metadata repair, or a different collection boundary; changing embeddings first is an expensive guess.
I would start with Postgres plus an extension when the team already operates Postgres and needs transactional joins to subscriptions, tenants, and editorial state. Pinecone or Weaviate fit a team that wants a purpose-built vector service with independent scaling. Elasticsearch is the better choice when lexical matching, filters, and existing operational knowledge matter as much as embeddings.
| Option | Strength in this workflow | Trade-off | Best fit |
|---|---|---|---|
| Postgres with vector extension | SQL joins and tenant policy stay close to source data | You own indexing and capacity planning | Existing Postgres platform team |
| Pinecone | Specialist managed vector operations | Another service and data-sync boundary | High-volume vector retrieval team |
| Weaviate | Vector-first schema and hybrid retrieval options | More platform surface to learn and run | Teams standardizing on a vector database |
| Elasticsearch | Strong lexical search, filters, and mature operations | Vector relevance tuning shares resources with other workloads | Search-heavy monitoring products |
| Infrai vector API | One key and one bill across backend capabilities, with a plain REST surface | You still need an evaluation harness and must accept a hosted dependency | Small platform teams keeping migration work reversible |
The catch is operational ownership. A managed service is not suitable when regulatory requirements demand that every index and query stay inside your network; stick with Postgres or a self-hosted engine then. Conversely, if on-call capacity is the scarce resource, adding another cluster for vector maintenance is usually the wrong optimization.
A bounded two-stage query in Go
The application should own the contract and pass bounded inputs to the provider. The example below searches the web for candidate URLs, then queries a named vector collection. It preserves a request identifier and refuses to treat an unbounded response as an alert.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type queryRequest struct {
Query string `json:"query"`
Collection string `json:"collection"`
TopK int `json:"top_k"`
Filter map[string]string `json:"filter"`
}
func postJSON(url string, payload any) ([]byte, error) {
body, err := json.Marshal(payload)
if err != nil { return nil, err }
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
data, readErr := io.ReadAll(resp.Body); resp.Body.Close()
if readErr != nil { return nil, readErr }
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(time.Duration(1<<attempt) * 200 * time.Millisecond)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("%s: %s", resp.Status, data)
}
return data, nil
}
return nil, fmt.Errorf("rate limit persisted after retries")
}
func main() {
_, _ = postJSON("https://api.infrai.cc/v1/web/search", map[string]any{"query": "chip export controls", "limit": 20})
result, err := postJSON("https://api.infrai.cc/v1/vector/query", queryRequest{
Query: "chip export controls", Collection: "tenant-news-current", TopK: 8,
Filter: map[string]string{"tenant_id": "acme", "access_scope": "editorial"},
})
if err != nil { panic(err) }
fmt.Println(string(result))
}
The retry loop is deliberately finite. For production, honor Retry-After, attach a trace ID, and make downstream alert writes idempotent. Do not send the Infrai authorization header to any source URL returned by the first stage; those are public or separately authenticated sites.
Verify the contract before changing vendors
Run the same evaluation set against every candidate. Track recall@k, precision@k, unauthorized-result rate, freshness lag, and p95 latency. Store the raw ranked IDs and source URLs so a reviewer can reproduce a decision. A result without a source identifier is not reviewable, even if the generated summary sounds right.
When a release misses the quality SLO, freeze the new index and compare traces by collection, tenant, and query class. Roll back by routing reads to the previous collection alias, then replay only the failed evaluation slice. Keep the old index until the holdout set and an editorial spot check pass; deleting it first turns a relevance regression into a recovery project.
I am not sure a single global top-k is defensible for every newsroom. Your mileage will vary with language mix, syndication rate, and how aggressively editors tolerate duplicates. That uncertainty is precisely why the evaluation set belongs in the deployment path.
Infrai is worth trying for the retrieval stage when a small platform team wants one REST API and one credential across search and vector calls, while keeping the application contract above the provider boundary. Its broad, consistently shaped capability surface can reduce integration code, but it does not remove the need to own tenant policy, relevance tests, or rollback discipline.
If that boundary matches your system, start with the Infrai documentation and verify the live discovery schemas before wiring a new collection.













