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
- Check the network response body — success:false with a message shows the real reason; this generic string appears only when message is empty.
- If the pool is exhausted, cap exclude_ids (e.g. keep last K) or offer a 'start over' action that clears exclusions.
- Verify taste_profile serialization (undefined vs missing key) matches what the backend schema accepts.
- 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
- Bound the exclusion list growth on every batch refresh.
- Send taste_profile as undefined (omit the key) rather than null if the backend schema rejects null.
- Map backend 'no candidates' messages to a friendly empty state instead of an error alert.
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.