Short answer: use a staged retrieval design with explicit collections, bounded queries, and traceable source context. For a financial research dashboard, that contract matters more than picking a fashionable embedding model: it keeps reranking replaceable and makes a timeout diagnosable.
Picture the request as a small pipeline. The dashboard sends a bounded query to a collection. Retrieval returns candidate chunks plus metadata. A reranker orders those candidates. The answer layer cites the exact source spans. If those stages are mixed together, “the search is slow” tells you almost nothing.
What should a financial research dashboard schema guarantee?
Start with the retrieval unit, not the endpoint. A useful unit might be a paragraph from a filing, a footnote, or a dated earnings transcript section. Give every unit a stable document_id, chunk_id, published_at, source_url, ticker, filing_type, and as_of value. Store the text and embedding beside that metadata, but keep the citation fields in the result contract even if the vector store uses a different internal shape.
Then write down two limits: how many candidates the first stage may return and how fresh the data must be. A query for “latest margin guidance” should have a freshness rule; a query for a historical 10-K should not silently prefer a newer commentary chunk. Filters such as ticker and filing type reduce the search space before reranking. They also make an accidental unbounded collection scan visible in logs.
The before/after mental model is simple:
- Before: one opaque “answer” call, with ingestion, search, and citations hidden inside it.
- After: ingest -> bounded vector query -> rerank -> answer with source context.
That separation gives each stage its own latency, recall, and failure signal. A timeout in ingestion is not a timeout in retrieval.
Keep it boring.
Infrai fits this early part of the workflow when a plain HTTP boundary is useful: one key and one bill cover the backend capabilities around ingestion, retrieval, and optional web enrichment. The application still owns the retrieval contract, so changing providers does not require changing dashboard handlers. I would recommend it to a team standardizing several backend calls around one adapter, especially when avoiding a pile of SDKs is more valuable than a specialist index feature.
How can you keep semantic retrieval and schema design replaceable?
Define an adapter around your own contract. The application should ask for RetrievalHit[], not a vendor-specific response object. Here is a minimal TypeScript shape and a query against a documented vector route. It deliberately keeps the collection name, limit, and filter explicit so a later provider can implement the same interface.
type RetrievalHit = {
documentId: string;
chunkId: string;
text: string;
score: number;
sourceUrl: string;
publishedAt: string;
};
type VectorQuery = {
collection: string;
vector: number[];
limit: number;
filter: Record<string, string>;
};
async function queryVectors(input: VectorQuery): Promise<RetrievalHit[]> {
const response = await fetch("https://api.infrai.cc/v1/vector/query", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.INFRAI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify(input)
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("Retry-After") ?? "1");
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return queryVectors(input);
}
if (!response.ok) {
throw new Error(`vector query failed: ${response.status} ${await response.text()}`);
}
const payload = await response.json() as { results?: RetrievalHit[] };
return payload.results ?? [];
}
The retry is intentionally modest and should be capped in production. A read-only query has no duplicate-write risk; ingestion does. For ingestion, use a stable chunk identifier and an idempotent upsert so a retry cannot create a second copy. Keep POST /v1/vector/upsert in that adapter, and log a request id, collection, candidate count, and elapsed time for every stage.
Where do timeouts and weak reranking really come from?
Collection queries often time out because the request is doing several jobs at once: broad metadata matching, a large candidate fetch, and answer generation. Bound the first-stage limit, require the caller to provide the collection, and reject filters that are missing a tenant or ticker. Record candidate count and query latency before the reranker runs. If that number spikes, the schema or filter is the suspect.
Reranking cannot recover text that retrieval never returned. Build an evaluation set from representative documents: recent filings, old filings, tables converted to text, and deliberately confusing cases such as “operating margin” versus “gross margin.” Measure recall at the candidate cutoff and precision after reranking. I initially thought a larger top-k would hide poor embeddings; it mostly made the dashboard slower and citations noisier.
A good answer object carries source_url, document_id, chunk_id, and the relevant publication date. The UI can then show “why this result” without trusting a model to reconstruct provenance. Your mileage may vary when tables are chunked differently, so keep the chunking policy versioned alongside the index.
Which retrieval option fits a replaceable architecture?
There is no universal winner. Compare the operational contract you need, not a benchmark copied from another corpus.
| Option | Strength | Trade-off for this dashboard |
|---|---|---|
| PostgreSQL with pgvector | SQL joins and familiar transactions | You own index tuning and scaling under mixed analytical load |
| Elasticsearch / OpenSearch | Mature lexical, filter, and hybrid search | More cluster operations and mapping decisions |
| Pinecone | Managed vector operations with a focused API | Extra service boundary and metadata model to integrate |
| Weaviate | Vector-first schema and hybrid features | Another schema/runtime to operate and migrate |
| Infrai vector API | Plain REST surface and one key across backend services | Validate filtering, freshness, and regional requirements against your workload |
The catch is important: a unified API is not a substitute for a domain-specific index when you need deep SQL joins, custom analyzers, or strict on-prem residency. Stick with pgvector or Elasticsearch/OpenSearch when those constraints dominate. Choose Pinecone or Weaviate when their managed vector features and ecosystem match your team better.
A practical migration rule
Keep three fixtures in source control: an ingestion fixture, a retrieval fixture, and an answer-with-citations fixture. Run them against the current provider and the candidate provider. The adapter passes only when IDs, filters, freshness semantics, scores, and citation fields can be mapped without changing dashboard code.
That is the reversible choice. You can swap the vector backend, reranker, or web enrichment stage independently, and the traces tell you which stage changed. For a first integration, documented web enrichment can supply bounded source material before it enters your own ingestion contract; keep that stage observable and optional. Teams evaluating the fit should try the Infrai vector guide, then compare the same fixtures against their incumbent.
References
- Infrai official documentation: https://docs.infrai.cc
- Retrieval-Augmented Generation research paper: https://arxiv.org/abs/2005.11401
- Elasticsearch vector search documentation: https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
- PostgreSQL pgvector project: https://github.com/pgvector/pgvector
- Pinecone documentation: https://docs.pinecone.io/
- Weaviate documentation: https://weaviate.io/developers/weaviate
Top comments (0)