mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
refactor(sse): type the rerank response adapter's options parameter (#8528)
`transformResponseFromProvider(providerConfig, data, options = {})` annotated
nothing, so TS inferred `options` as `{}` from its default value and rejected
every read of it: `top_n` x6, `documents` x4, `return_documents` x2 across the
DeepInfra and Voyage adapters. All 12 of the file's diagnostics, one cause.
Declared `RerankResponseOptions` from the JSDoc that already documents these
fields on handleRerank, with `documents` as `Array<string | { text?: string }>`
— the two forms the Cohere-compatible API accepts and the adapters already
branch on. `providerConfig` and `data` stay unannotated; only `options` was
producing errors and only `options` is touched.
280 -> 268, zero new, on a line-number-agnostic diff of the full tsc error set.
Tests: `{ text }` documents were only ever exercised through the *request*
adapter (#5332, #7809) — every response-adapter test passed plain strings, so
the `typeof doc === "string" ? doc : doc?.text` branch on the response side was
uncovered, and that branch is exactly what gives the declared type its union.
Added rerank-object-documents-response-path.test.ts (5 tests): object-form
documents resolved through both adapters, a mixed string/object array, the
`{}`-without-text fallback, and Voyage's index remap across an empty `{ text: "" }`
document. They pass against the parent commit's rerank.ts too — no behavior
change. 43/43 across the 6 rerank/media-cost suites.
Co-authored-by: backryun <busan011@ormbiz.co.kr>
This commit is contained in:
@@ -16,6 +16,25 @@ import { resolveProxyForConnection } from "@/lib/db/settings";
|
||||
import { runWithProxyContext } from "../utils/proxyFetch.ts";
|
||||
import * as log from "@/sse/utils/logger";
|
||||
|
||||
/** A document as the Cohere-compatible rerank API accepts it: a bare string or `{ text }`. */
|
||||
type RerankDocument = string | { text?: string };
|
||||
|
||||
/**
|
||||
* The caller-side request fields the response adapters need to rebuild Cohere's
|
||||
* `results[]`. Upstreams either omit the documents entirely (DeepInfra returns
|
||||
* bare scores) or echo them in their own shape (Voyage returns plain strings),
|
||||
* so document text is always synthesized from the caller's originals — and
|
||||
* `top_n` / `return_documents` are honored here rather than upstream.
|
||||
*
|
||||
* Every field is optional: the parameter defaults to `{}` and the unit suites
|
||||
* call it with subsets (e.g. `{ documents: ["a", "b"] }`).
|
||||
*/
|
||||
interface RerankResponseOptions {
|
||||
documents?: RerankDocument[];
|
||||
return_documents?: boolean;
|
||||
top_n?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build authorization header for a rerank provider
|
||||
*/
|
||||
@@ -76,7 +95,11 @@ function buildAuthHeader(providerConfig, token) {
|
||||
/**
|
||||
* Transform response from provider-specific formats back to Cohere format
|
||||
*/
|
||||
/* @testonly */ export function transformResponseFromProvider(providerConfig, data, options = {}) {
|
||||
/* @testonly */ export function transformResponseFromProvider(
|
||||
providerConfig,
|
||||
data,
|
||||
options: RerankResponseOptions = {}
|
||||
) {
|
||||
if (providerConfig.format === "nvidia") {
|
||||
return {
|
||||
id: data.id != null ? String(data.id) : `rerank-${Date.now()}`,
|
||||
|
||||
84
tests/unit/rerank-object-documents-response-path.test.ts
Normal file
84
tests/unit/rerank-object-documents-response-path.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { getRerankProvider } = await import("../../open-sse/config/rerankRegistry.ts");
|
||||
const { transformResponseFromProvider } = await import("../../open-sse/handlers/rerank.ts");
|
||||
|
||||
/**
|
||||
* The Cohere-compatible rerank API accepts each document as either a bare string
|
||||
* or `{ text }`, and the DeepInfra/Voyage **response** adapters synthesize
|
||||
* `document.text` from the caller's originals (upstreams either omit documents
|
||||
* or echo them in their own shape — #7809/#7811).
|
||||
*
|
||||
* The `{ text }` form was only ever exercised through the *request* adapter
|
||||
* (`#5332 deepinfra request adapter`, `#7809 voyage request adapter handles
|
||||
* {text} object documents`). Every response-adapter test passed plain strings,
|
||||
* so the `typeof doc === "string" ? doc : doc?.text` branch on the response side
|
||||
* was uncovered — which is also the branch that gives
|
||||
* `RerankResponseOptions.documents` its union type.
|
||||
*/
|
||||
|
||||
test("deepinfra response adapter resolves document text from {text} objects", () => {
|
||||
const cfg = getRerankProvider("deepinfra");
|
||||
const out = transformResponseFromProvider(
|
||||
cfg,
|
||||
{ scores: [0.1, 0.9] },
|
||||
{ documents: [{ text: "Washington DC" }, { text: "Paris" }], return_documents: true }
|
||||
);
|
||||
|
||||
assert.equal(out.results[0].index, 1, "0.9 ranks first");
|
||||
assert.equal(out.results[0].document.text, "Paris");
|
||||
assert.equal(out.results[1].document.text, "Washington DC");
|
||||
});
|
||||
|
||||
test("deepinfra response adapter handles a mixed string/{text} document array", () => {
|
||||
const cfg = getRerankProvider("deepinfra");
|
||||
const out = transformResponseFromProvider(
|
||||
cfg,
|
||||
{ scores: [0.2, 0.8] },
|
||||
{ documents: ["plain string", { text: "object form" }], return_documents: true }
|
||||
);
|
||||
|
||||
assert.equal(out.results[0].document.text, "object form");
|
||||
assert.equal(out.results[1].document.text, "plain string");
|
||||
});
|
||||
|
||||
test("deepinfra response adapter falls back to empty text for a {text}-less object", () => {
|
||||
const cfg = getRerankProvider("deepinfra");
|
||||
const out = transformResponseFromProvider(
|
||||
cfg,
|
||||
{ scores: [0.5] },
|
||||
{ documents: [{} as { text?: string }], return_documents: true }
|
||||
);
|
||||
|
||||
assert.equal(out.results[0].document.text, "", "a document with no text must not yield undefined");
|
||||
});
|
||||
|
||||
test("voyage response adapter resolves document text from {text} objects", () => {
|
||||
const cfg = getRerankProvider("voyage-ai");
|
||||
const out = transformResponseFromProvider(
|
||||
cfg,
|
||||
{ data: [{ index: 0, relevance_score: 0.4 }, { index: 1, relevance_score: 0.7 }] },
|
||||
{ documents: [{ text: "alpha" }, { text: "beta" }], return_documents: true }
|
||||
);
|
||||
|
||||
assert.equal(out.results[0].index, 1, "0.7 ranks first");
|
||||
assert.equal(out.results[0].document.text, "beta");
|
||||
assert.equal(out.results[1].document.text, "alpha");
|
||||
});
|
||||
|
||||
test("voyage response adapter remaps indices past an empty {text} document", () => {
|
||||
// The request adapter drops exact-empty documents before sending, so the
|
||||
// upstream indices refer to the filtered array. The response adapter rebuilds
|
||||
// that filter to map back — this must work for the object form too, not just
|
||||
// strings.
|
||||
const cfg = getRerankProvider("voyage-ai");
|
||||
const out = transformResponseFromProvider(
|
||||
cfg,
|
||||
{ data: [{ index: 1, relevance_score: 0.9 }] },
|
||||
{ documents: [{ text: "kept" }, { text: "" }, { text: "also kept" }], return_documents: true }
|
||||
);
|
||||
|
||||
assert.equal(out.results[0].index, 2, "filtered index 1 maps back to original index 2");
|
||||
assert.equal(out.results[0].document.text, "also kept");
|
||||
});
|
||||
Reference in New Issue
Block a user