datawhalechina/hello-agents · error · ApiError

换一批失败

Error message

换一批失败

What it means

'换一批失败' (change-batch failed) is thrown in beep-YingQian's ResultPage when postRecommend() resolves with success=false or missing data during a 'next batch' request. The ResultPage deliberately grows exclude_ids with everything already shown and passes the accumulated taste_profile, so each successive '换一批' shrinks the backend's candidate set until the recommender gives up.

Source

Thrown at Co-creation-projects/aatanxiao12-beep-YingQian/frontend/src/pages/ResultPage.tsx:75

    setStageIndex(1)
    const timers: number[] = []
    PROGRESS_STAGES.forEach((_, i) => {
      if (i <= 1) return
      timers.push(window.setTimeout(() => setStageIndex(i), 4_500 * (i - 1)))
    })

    const request = {
      ...session.request,
      exclude_ids: [
        ...new Set([...session.request.exclude_ids, ...exclude_ids]),
      ],
      taste_profile,
    }

    try {
      const res = await postRecommend(request)
      if (!res.success || !res.data) {
        throw new ApiError(res.message || '换一批失败')
      }
      const next: SessionPayload = {
        request: {
          ...request,
          taste_profile: undefined,
        },
        result: {
          ...res.data,
          taste_profile: res.data.taste_profile ?? taste_profile,
        },
        message: res.message,
      }
      saveSession(next)
      setSession(next)
      setStageIndex(PROGRESS_STAGES.length - 1)
    } catch (err) {
      setError(err instanceof Error ? err.message : '换一批失败')
    } finally {

View on GitHub (pinned to 606a07d341)

Solutions

  1. Check the network response body — success:false with a message shows the real reason; this generic string appears only when message is empty.
  2. If the pool is exhausted, cap exclude_ids (e.g. keep last K) or offer a 'start over' action that clears exclusions.
  3. Verify taste_profile serialization (undefined vs missing key) matches what the backend schema accepts.
  4. Have the backend return an explicit 'no more candidates' message and the UI render a friendly empty state instead of an error.
Defensive patterns

Strategy: validation

Validate before calling

const MAX_EXCLUDE = 200;
const exclude_ids = [...new Set([...session.request.exclude_ids, ...exclude_idsShown])].slice(-MAX_EXCLUDE);
if (exclude_ids.length >= MAX_EXCLUDE) {
  offerFreshStart(); // pool likely drained
}

Type guard

function isRecommendSuccess(res: unknown): res is { success: true; data: unknown; message?: string } {
  return typeof res === 'object' && res !== null
    && (res as { success?: unknown }).success === true
    && 'data' in (res as object);
}

Try / catch

try {
  const res = await postRecommend(request);
  if (!isRecommendSuccess(res)) {
    if (/没有更多|no more|empty/i.test(res?.message || '')) {
      renderNoMoreCandidates();
    } else {
      showError(res?.message || '换一批失败');
    }
    return;
  }
  // success path
} catch (err) {
  showError(err instanceof Error ? err.message : '换一批失败');
}

Prevention

When it happens

Trigger: POST recommend with the merged request ({...session.request, exclude_ids: prior + shown, taste_profile}) returns 200 with success=false: candidate pool exhausted after N batch refreshes, taste_profile incompatible with remaining candidates, or the backend recommender errored. Fires with the generic text only when res.message is also empty.

Common situations: User refreshes the batch many times in one session (typical for narrow genres); long-persisted session with a huge exclude_ids list; backend message omitted on failure.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/5638ea0ce93d90af. Report an issue: GitHub.