CherryHQ/cherry-studio · error · Error

OpenAI-compatible reranking model requires baseURL

Error message

OpenAI-compatible reranking model requires baseURL

What it means

Final fallback in the Vertex listing throwOnError path, reached only when a publisher group is null (failed) but `firstPublisherError` is still `undefined`. Normally every null group records its error in the catch, so this branch is a defensive guard against a logic gap where a group became null without setting the error variable. It exists so throwOnError can never silently pass a partial failure; it never carries the real cause.

Source

Thrown at packages/ai-sdk-provider/src/openai-compatible-reranking-model.ts:114

    if (typeof result.index !== 'number' || typeof result.relevance_score !== 'number') {
      throw new Error('Rerank response results must contain numeric index and relevance_score')
    }

    if (!Number.isInteger(result.index) || result.index < 0 || result.index >= documentCount) {
      throw new Error('Rerank response results must reference a valid document index')
    }

    return { index: result.index, relevanceScore: result.relevance_score }
  })
}

export function createOpenAICompatibleRerankingModel(
  modelId: string,
  settings: OpenAICompatibleRerankingModelSettings
): RerankingModelV3 {
  const baseURL = withoutTrailingSlash(settings.baseURL)
  if (!baseURL) {
    throw new Error('OpenAI-compatible reranking model requires baseURL')
  }

  return new OpenAICompatibleRerankingModel(modelId, {
    provider: `${settings.name}.rerank`,
    url: ({ path }) => {
      const url = new URL(`${baseURL}${path}`)
      if (settings.queryParams) {
        url.search = new URLSearchParams(settings.queryParams).toString()
      }
      return url.toString()
    },
    headers: () => ({
      ...(settings.apiKey ? { Authorization: `Bearer ${settings.apiKey}` } : {}),
      ...settings.headers
    }),
    fetch: settings.fetch
  })
}

View on GitHub (pinned to 726446b54c)

Solutions

  1. Treat this error as a bug report: it means a publisher group failed without recording its cause — inspect the vertexFetcher implementation for a null-returning path outside the try/catch.
  2. Temporarily run listing without throwOnError and watch the warn logs; if a publisher still shows no models and no error log, that is the path missing the firstPublisherError assignment.
  3. Ensure any new null-returning branch in the per-publisher loop assigns firstPublisherError before returning null.
  4. File/fix an issue in the listModels vertexFetcher so every null result carries a recorded error.

Example fix

// before: a branch returns null without recording the error
const group = await (async () => {
  if (!shouldFetch(publisher)) return null   // firstPublisherError never set -> fallback message
  return await fetchPublisher(publisher)
})()
// after: record the reason before returning null
if (!shouldFetch(publisher)) {
  if (firstPublisherError === undefined) firstPublisherError = new Error(`publisher ${publisher} skipped`)
  return null
}
Defensive patterns

Strategy: validation

Validate before calling

// Defensive: before throwing throwOnError, ensure a cause was recorded.
// (This guard belongs in vertexFetcher internals, not at call sites.)
if (options?.throwOnError && publisherModelGroups.some(g => g === null) && firstPublisherError === undefined) {
  firstPublisherError = new Error('publisher group failed without a recorded cause (internal bug)')
}

Try / catch

try {
  await listModels(provider, signal, { throwOnError: true })
} catch (e) {
  if (e instanceof Error && /One or more Vertex AI publisher requests failed/.test(e.message)) {
    // invariant gap — report as a bug, then degrade to non-strict listing
    reportBug('vertexFetcher returned a null group without recording firstPublisherError')
    await listModels(provider, signal)
  } else throw e
}

Prevention

When it happens

Trigger: A code path inside the per-publisher map returns null without going through the catch (e.g. an early `return null` added outside the try, or a Promise.all rejection path that bypasses the per-publisher try/catch). In practice this is an internal invariant violation, not an operator-triggerable condition.

Common situations: A future refactor of vertexFetcher that adds a null-returning branch outside the try/catch; an await on a helper that rejects in a way Promise.all surfaces as a rejected group rather than a caught null. Should not occur in normal operation with valid/invalid auth.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/ae3890a00f7ee35c. Report an issue: GitHub.