You have a query and a list of candidate documents — chunks from a vector search, rows from a keyword query, FAQ entries — and you need them ordered by how well they actually answer the query. Vector similarity gets you close, but a dedicated reranking model is far more precise. By the end of this guide you'll have that list reordered, with a relevance score per document.
rerank() is the precision step in a retrieval pipeline: retrieve a broad set of candidates cheaply, then rerank to surface the few that matter.
Reranking is available from two adapters today:
Both implement the same rerank() activity — swap the adapter, keep the call.
Cohere:
npm i @tanstack/ai-coherepnpm add @tanstack/ai-cohereyarn add @tanstack/ai-coherebun add @tanstack/ai-cohereOpenRouter:
npm i @tanstack/ai-openrouterpnpm add @tanstack/ai-openrouteryarn add @tanstack/ai-openrouterbun add @tanstack/ai-openrouterPeer dependency:
npm i @tanstack/aipnpm add @tanstack/aiyarn add @tanstack/aibun add @tanstack/aiPass a query and an array of documents. The result's rerankedDocuments are ordered most-relevant first, and ranking carries the relevance score and the original index of each.
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
const { ranking, rerankedDocuments } = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'talk about rain',
documents: ['sunny day at the beach', 'rainy afternoon in the city'],
topN: 2,
})
console.log(rerankedDocuments[0]) // 'rainy afternoon in the city'
console.log(ranking[0]) // { index: 1, score: 0.98, document: 'rainy afternoon in the city' }The adapter reads COHERE_API_KEY from the environment. To pass a key explicitly, use createCohereRerank('rerank-v3.5', 'co-...').
To rerank through OpenRouter instead, swap the adapter — everything else stays the same:
import { rerank } from '@tanstack/ai'
import { openRouterRerank } from '@tanstack/ai-openrouter'
const { rerankedDocuments } = await rerank({
adapter: openRouterRerank('cohere/rerank-v3.5'),
query: 'talk about rain',
documents: ['sunny day at the beach', 'rainy afternoon in the city'],
topN: 2,
})
console.log(rerankedDocuments[0]) // 'rainy afternoon in the city'openRouterRerank reads OPENROUTER_API_KEY from the environment.
Documents don't have to be strings. Pass JSON-serializable objects and the original object is returned in the result — fully typed — so you can carry an id or metadata through the rerank and read it back off the ranked results.
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
const chunks = [
{ id: 'doc-1', text: 'A heavy gaming desktop with an RTX card.' },
{ id: 'doc-2', text: 'A lightweight ultrabook with all-day battery.' },
]
const { ranking } = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'best laptop for travel',
documents: chunks,
})
// `document` is the original object — `id` is available and type-safe.
console.log(ranking[0]?.document.id) // 'doc-2'Object documents are serialized to JSON before being sent to the provider; the ranking is mapped back to your original elements by index.
| Option | Type | Description |
|---|---|---|
| adapter | RerankAdapter | A rerank adapter created with a model (e.g. cohereRerank('rerank-v3.5')) |
| query | string | The search query documents are scored against — required |
| documents | Array<string | object> | Candidate documents to rerank — required |
| topN | number | Return only the top N results |
| abortSignal | AbortSignal | Cancel the in-flight request |
| modelOptions | provider options | Provider-specific options (see below) |
| middleware | Array<GenerationMiddleware> | Observe-only lifecycle hooks (usage, finish, error, abort) |
Cohere rerank accepts:
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
const { ranking } = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'refund policy',
documents: ['Returns accepted within 30 days.', 'Free shipping over $50.'],
modelOptions: {
// Cap tokens kept per document when chunking long inputs (Cohere default: 4096).
maxTokensPerDoc: 512,
},
})
console.log(ranking)import type { TokenUsage } from '@tanstack/ai'
interface RerankResult<TDocument = string> {
id: string
model: string
// Scored results, most relevant first.
ranking: Array<{ index: number; score: number; document: TDocument }>
// The documents reordered by relevance (ranking.map(r => r.document)).
rerankedDocuments: Array<TDocument>
// Rerank typically bills in provider "search units"
// (usage.billed = { quantity, unit: 'units' }). Some providers (for example
// OpenRouter) also report totalTokens and cost. Cohere reports only search
// units and leaves token counts at 0.
usage: TokenUsage
}Reranking runs on the server (it needs your API key). Wrap it in an API route and call it from the client over fetch:
// routes/api/rerank.ts
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/rerank')({
server: {
handlers: {
POST: async ({ request }) => {
const body: unknown = await request.json()
if (
typeof body !== 'object' ||
body === null ||
!('query' in body) ||
typeof body.query !== 'string' ||
!('documents' in body) ||
!Array.isArray(body.documents)
) {
return new Response('Invalid request body', { status: 400 })
}
const { query, documents } = body
const topN = 'topN' in body && typeof body.topN === 'number'
? body.topN
: undefined
const result = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query,
documents,
topN,
})
return Response.json(result)
},
},
},
})// client.ts — call the endpoint and use the reordered documents
async function rerankDocuments(query: string, documents: Array<string>) {
const res = await fetch('/api/rerank', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, documents, topN: 3 }),
})
const result = await res.json()
return result.rerankedDocuments
}Reranking shines as the second stage after a cheap, broad retrieval. Over-fetch candidates with vector search, then rerank to keep only the most relevant few for the prompt:
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
import { vectorSearch } from './my-vector-store'
async function retrieveContext(query: string) {
// 1. Over-fetch candidates cheaply.
const candidates = await vectorSearch(query, { limit: 50 })
// 2. Rerank and keep the most relevant handful for the prompt.
const { rerankedDocuments } = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query,
documents: candidates.map((c) => c.text),
topN: 5,
})
return rerankedDocuments
}Pass an abortSignal to cancel an in-flight request:
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
const controller = new AbortController()
setTimeout(() => controller.abort(), 5000)
const result = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'q',
documents: ['a', 'b'],
abortSignal: controller.signal,
})
console.log(result.rerankedDocuments)Attach observe-only middleware to track usage, completion, errors, and cancellation — the same GenerationMiddleware contract the media activities use:
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
const result = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'q',
documents: ['a', 'b'],
middleware: [
{
name: 'usage-logger',
onUsage: (_ctx, usage) => {
if (usage.billed) {
console.log(
`search units billed: ${usage.billed.quantity} ${usage.billed.unit}`,
)
}
},
},
],
})
console.log(result.rerankedDocuments)Tip: Pass otelMiddleware() to emit OpenTelemetry spans for rerank calls. See OpenTelemetry.
The Cohere rerank adapter uses:
import { rerank } from '@tanstack/ai'
import { cohereRerank } from '@tanstack/ai-cohere'
try {
const result = await rerank({
adapter: cohereRerank('rerank-v3.5'),
query: 'q',
documents: ['a', 'b'],
})
console.log(result.rerankedDocuments)
} catch (error) {
if (error instanceof Error) {
console.error('Rerank failed:', error.message)
}
}Passing an empty documents array throws before any request is made.
examples/ts-react-rerank is a small TanStack Start app that runs everything on this page: a fixed corpus of support articles listed newest-first, a query box, and a side-by-side view of the original order against the reranked order with scores. The provider dropdown switches between the Cohere and OpenRouter adapters over the same rerank() call.
cd examples/ts-react-rerank
pnpm install
cp .env.example .env # add COHERE_API_KEY and/or OPENROUTER_API_KEY
pnpm dev