{"record":{"id":"8e8ed14a385cb424","repo":"datawhalechina/hello-agents","slug":"response-status-8e8ed1","errorCode":null,"errorMessage":"研究请求失败，状态码：${response.status}","messagePattern":"研究请求失败，状态码：(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"code/chapter14/helloagents-deepresearch/frontend/src/services/api.ts","lineNumber":35,"sourceCode":"\nexport async function runResearchStream(\n  payload: ResearchRequest,\n  onEvent: (event: ResearchStreamEvent) => void,\n  options: StreamOptions = {}\n): Promise<void> {\n  const response = await fetch(`${baseURL}/research/stream`, {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      Accept: \"text/event-stream\"\n    },\n    body: JSON.stringify(payload),\n    signal: options.signal\n  });\n\n  if (!response.ok) {\n    const errorText = await response.text().catch(() => \"\");\n    throw new Error(\n      errorText || `研究请求失败，状态码：${response.status}`\n    );\n  }\n\n  const body = response.body;\n  if (!body) {\n    throw new Error(\"浏览器不支持流式响应，无法获取研究进度\");\n  }\n\n  const reader = body.getReader();\n  const decoder = new TextDecoder(\"utf-8\");\n  let buffer = \"\";\n\n  while (true) {\n    const { value, done } = await reader.read();\n    buffer += decoder.decode(value || new Uint8Array(), { stream: !done });\n\n    let boundary = buffer.indexOf(\"\\n\\n\");","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/code/chapter14/helloagents-deepresearch/frontend/src/services/api.ts#L17-L53","documentation":"Thrown by the deepresearch frontend's streaming helper (code/chapter14/helloagents-deepresearch) when POST {baseURL}/research/stream returns a non-OK status AND the response body is empty or unreadable (the body text is preferred as the message when present). It reports the numeric HTTP status, so the message doubles as the server's rejection code for the research-stream request that carries the user's research question.","triggerScenarios":"POST /research/stream with the research payload (and an optional abort signal) returns 404 (wrong baseURL or route), 422 (invalid research request payload), 429 (search/LLM rate limits upstream), 500 (deep-research pipeline crash), or the fetch was aborted and resolves unusually through the error path.","commonSituations":"baseURL env var pointing to the wrong origin; backend research pipeline failing on the first LLM/search call; upstream API keys missing on the server so it returns 500 with an empty body; reverse proxy rejecting long-lived SSE connections with 502.","solutions":["Read the status in the message: 404 → fix baseURL/route; 422 → fix payload shape; 500/502 → inspect backend logs for the research pipeline failure.","Verify the backend exposes POST /research/stream with SSE and that required env keys (search API, LLM key) are set server-side.","Check DevTools Network for this request — the empty-body case means the server gave no explanation, so server logs are the only source.","For 429s, add client-side throttling before starting a new research stream."],"exampleFix":"// before\nconst errorText = await response.text().catch(() => \"\");\nthrow new Error(errorText || `研究请求失败，状态码：${response.status}`);\n\n// after\nconst errorText = await response.text().catch(() => \"\");\nthrow new Error(errorText || `研究请求失败，状态码：${response.status} ${response.statusText}`);","handlingStrategy":"try-catch","validationCode":"if (!payload || !payload.question?.trim()) {\n  throw new Error('研究问题不能为空');\n}\nif (!('ReadableStream' in window) || !('body' in Response.prototype)) {\n  throw new Error('当前浏览器不支持流式研究进度');\n}","typeGuard":"function isResearchPayload(p: unknown): p is { question: string } {\n  return typeof p === 'object' && p !== null && typeof (p as { question?: unknown }).question === 'string';\n}","tryCatchPattern":"try {\n  await streamResearch(payload, onProgress, { signal });\n} catch (err) {\n  const m = (err as Error).message;\n  if (/429/.test(m)) showRateLimitNotice();\n  else if (/\\d{3}/.test(m)) showServerError(m);\n  else if ((err as Error).name === 'AbortError') return; // user cancelled\n  else showNetworkError();\n}","preventionTips":["Distinguish AbortError from real failures — the shared signal makes user cancels common.","Throttle client-side: don't fire a new research stream while one is active (429 avoidance).\n","Ensure the backend health endpoint and required API keys are verified before starting research."],"tags":["http","sse","streaming","fetch","research-pipeline"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}