Skip to content

rerank() can return undefined documents for invalid provider ranking indices #20421

Description

@canblmz1

Description

rerank() maps every provider-returned ranking[i].index straight back onto the
input documents array with a bare documents[index] lookup, and never validates
the index. The public result contract is non-optional:

// RerankResult<VALUE>
ranking: Array<{ originalIndex: number; score: number; document: VALUE }>;
rerankedDocuments: Array<VALUE>;

@ai-sdk/provider documents RerankingModelV4Result.ranking[].index as "the index
of the document in the original list of documents before reranking".

When a reranking model returns an index that is out of range or non-integer,
rerank() resolves successfully with document: undefined in ranking and
undefined entries in rerankedDocuments — no error, no retry, and the onEnd /
telemetry end event fires as if the call succeeded. The undefined then tends to
surface later as a TypeError far from the cause.

This is the same shape as #20355 (embed() resolves with an undefined embedding)
and #20351 (empty choices[]): a structurally valid but semantically invalid
successful provider response passed through as a value that violates the SDK's own
result type.

Actual behavior

documents = ['a', 'b', 'c'], one ranking entry, default maxRetries:

provider index outcome ranking[0].document rerankedDocuments doRerank calls onEnd fired
3 (=== length) resolves undefined [undefined] 1 yes
-1 resolves undefined [undefined] 1 yes
5 (> length) resolves undefined [undefined] 1 yes
1.5 resolves undefined [undefined] 1 yes

The malformed response is not retried (the provider is called once), so this is
deterministic rather than a transient failure.

Expected behavior

An invalid provider ranking index should not produce a successful RerankResult
containing undefined documents. Adjacent malformed-successful-response handling
in the same package surfaces this as InvalidResponseDataError
(embed-many.ts validateEmbeddingCount; #20358 / #20362) — but the exact error
type and whether the check belongs inside or outside the retry callback are for
maintainers to decide.

Control (unchanged, valid input)

ranking indices [2, 0]  ->  rerankedDocuments ['c', 'a']

Valid in-range rankings continue to work as before.

Source pointers (main @ c3c189c068baefe62808b4eca5432fe092dc37cb)

  • packages/ai/src/rerank/rerank.tsdocument: documents[ranking.index] in
    both the onEnd event payload and the DefaultRerankResult construction (both
    after retry() resolves); no Number.isInteger / 0 <= index < documents.length
    check.
  • packages/ai/src/rerank/rerank-result.tsranking[].document: VALUE,
    rerankedDocuments: Array<VALUE>.
  • packages/provider/src/reranking-model/v4/reranking-model-v4-result.ts
    index semantics.

I searched issues, PRs, and recent commits (rerank invalid index,
rerank out of range, rerank undefined document, ranking index validation,
InvalidResponseDataError rerank) and found nothing covering rerank index
validation.

Reproduction

Minimal, using the public API and the shipped mock (ai @ main,
c3c189c068baefe62808b4eca5432fe092dc37cb — current main at the time of
reproduction):

import { rerank } from 'ai';
import { MockRerankingModelV4 } from 'ai/test';

const result = await rerank({
  model: new MockRerankingModelV4({
    // documents.length === 3, so index 3 is out of range
    doRerank: async () => ({ ranking: [{ index: 3, relevanceScore: 0.9 }] }),
  }),
  documents: ['a', 'b', 'c'],
  query: 'q',
});

console.log(result.ranking[0].document); // => undefined
console.log(result.rerankedDocuments);   // => [ undefined ]

As a failing test in packages/ai/src/rerank/:

import { expect, it } from 'vitest';
import { MockRerankingModelV4 } from '../test/mock-reranking-model-v4';
import { rerank } from './rerank';

it('does not resolve with an undefined document for an out-of-range index', async () => {
  const model = new MockRerankingModelV4({
    doRerank: async () => ({ ranking: [{ index: 3, relevanceScore: 0.9 }] }),
  });

  await expect(
    rerank({ model, documents: ['a', 'b', 'c'], query: 'q' }),
  ).rejects.toThrow();
});

On current main this test fails: rerank() resolves with
DefaultRerankResult { ranking: [{ document: undefined, originalIndex: 3, score: 0.9 }] }.

AI SDK Version

  • ai: main @ c3c189c068baefe62808b4eca5432fe092dc37cb (also reproduces on published ai@7.0.77)
  • Node: v22.13+

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions