{"record":{"id":"3211946445dfeb6c","repo":"datawhalechina/hello-agents","slug":"error-321194","errorCode":null,"errorMessage":"生成旅行计划失败","messagePattern":"生成旅行计划失败","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"code/chapter13/helloagents-trip-planner/frontend/src/services/api.ts","lineNumber":47,"sourceCode":"    console.log('收到响应:', response.status, response.config.url)\n    return response\n  },\n  (error) => {\n    console.error('响应错误:', error.response?.status, error.message)\n    return Promise.reject(error)\n  }\n)\n\n/**\n * 生成旅行计划\n */\nexport async function generateTripPlan(formData: TripFormData): Promise<TripPlanResponse> {\n  try {\n    const response = await apiClient.post<TripPlanResponse>('/api/trip/plan', formData)\n    return response.data\n  } catch (error: any) {\n    console.error('生成旅行计划失败:', error)\n    throw new Error(error.response?.data?.detail || error.message || '生成旅行计划失败')\n  }\n}\n\n/**\n * 健康检查\n */\nexport async function healthCheck(): Promise<any> {\n  try {\n    const response = await apiClient.get('/health')\n    return response.data\n  } catch (error: any) {\n    console.error('健康检查失败:', error)\n    throw new Error(error.message || '健康检查失败')\n  }\n}\n\nexport default apiClient\n","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/code/chapter13/helloagents-trip-planner/frontend/src/services/api.ts#L29-L65","documentation":"The generic '生成旅行计划失败' (trip plan generation failed) is the last-resort message in generateTripPlan() (helloagents-trip-planner) when an axios POST to /api/trip/plan fails and neither error.response.data.detail nor error.message exists. The two richer variants (backend FastAPI `detail` or axios's own message) normally win; seeing the bare fallback means the failure carried no response body at all — typically a network-level or abort-level error.","triggerScenarios":"axios POST /api/trip/plan rejects: backend FastAPI returns 422/500 with a `detail` field (shown instead), request times out (axios ECONNABORTED with a message), backend down so there is no response object (fallback fires), or the request was cancelled. Also triggers when the backend plan generation exceeds any configured proxy timeout.","commonSituations":"Long-running LLM plan generation hitting axios/proxy timeouts; backend not running; Vite dev proxy missing for /api; CORS block producing a network error with no response.","solutions":["Check the console.error output right before the throw — it logs the full axios error with code (ECONNABORTED, ERR_NETWORK, ERR_BAD_REQUEST) that identifies the class of failure.","If it's a timeout, raise axios `timeout` and any proxy timeouts; trip-plan LLM calls are slow.","If it's ERR_NETWORK/none-response, verify the backend is up and the baseURL (apiClient) / dev proxy is configured for /api.","If the backend returned 422/500, fix the TripFormData payload or backend issue per the logged `detail`.","Type the catch as `unknown` and narrow with axios.isAxiosError to avoid the `error: any` smell."],"exampleFix":"// before\n} catch (error: any) {\n  console.error('生成旅行计划失败:', error)\n  throw new Error(error.response?.data?.detail || error.message || '生成旅行计划失败')\n}\n\n// after\nimport axios from 'axios'\n// ...\n} catch (error: unknown) {\n  if (axios.isAxiosError(error)) {\n    const detail = (error.response?.data as { detail?: string })?.detail\n    throw new Error(detail || error.code || error.message || '生成旅行计划失败')\n  }\n  throw new Error('生成旅行计划失败')\n}","handlingStrategy":"try-catch","validationCode":"const required: (keyof TripFormData)[] = ['destination', 'days'];\nconst missing = required.filter((k) => !formData[k]);\nif (missing.length) {\n  throw new Error(`缺少必填字段: ${missing.join(', ')}`);\n}","typeGuard":"import axios from 'axios';\n\nfunction isAxiosErrorWithDetail(e: unknown): e is axios.AxiosError<{ detail: string }> {\n  return axios.isAxiosError(e);\n}","tryCatchPattern":"try {\n  return await generateTripPlan(formData);\n} catch (error: unknown) {\n  if (axios.isAxiosError(error)) {\n    if (error.code === 'ECONNABORTED') throw new Error('请求超时，请重试');\n    const detail = (error.response?.data as { detail?: string })?.detail;\n    throw new Error(detail || error.message);\n  }\n  throw error;\n}","preventionTips":["Set a generous axios timeout (LLM plan generation is slow) and mirror it in the dev proxy.","Narrow catches with axios.isAxiosError instead of `error: any`.","Surface backend `detail` in the UI so 422/500 causes are visible."],"tags":["axios","http","llm-timeout","api-client"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}