{"record":{"id":"a44d83acbb75c44b","repo":"datawhalechina/hello-agents","slug":"research-failed-a44d83","errorCode":null,"errorMessage":"Research failed","messagePattern":"Research failed","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"code/chapter14/helloagents-deepresearch/backend/src/main.py","lineNumber":127,"sourceCode":"            config.use_tool_calling,\n            config.strip_thinking_tokens,\n            _mask_secret(config.llm_api_key),\n        )\n\n    @app.get(\"/healthz\")\n    def health_check() -> Dict[str, str]:\n        return {\"status\": \"ok\"}\n\n    @app.post(\"/research\", response_model=ResearchResponse)\n    def run_research(payload: ResearchRequest) -> ResearchResponse:\n        try:\n            config = _build_config(payload)\n            agent = DeepResearchAgent(config=config)\n            result = agent.run(payload.topic)\n        except ValueError as exc:  # Likely due to unsupported configuration\n            raise HTTPException(status_code=400, detail=str(exc)) from exc\n        except Exception as exc:  # pragma: no cover - defensive guardrail\n            raise HTTPException(status_code=500, detail=\"Research failed\") from exc\n\n        todo_payload = [\n            {\n                \"id\": item.id,\n                \"title\": item.title,\n                \"intent\": item.intent,\n                \"query\": item.query,\n                \"status\": item.status,\n                \"summary\": item.summary,\n                \"sources_summary\": item.sources_summary,\n                \"note_id\": item.note_id,\n                \"note_path\": item.note_path,\n            }\n            for item in result.todo_items\n        ]\n\n        return ResearchResponse(\n            report_markdown=(result.report_markdown or result.running_summary or \"\"),","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/code/chapter14/helloagents-deepresearch/backend/src/main.py#L109-L145","documentation":"HTTPException(500) raised by POST /research as a defensive catch-all when the deep-research agent run fails with anything other than ValueError. The original exception is chained (`from exc`) but intentionally hidden from the client ('Research failed') to avoid leaking internals — the real stack trace lives in the server logs only.","triggerScenarios":"LLM API key invalid/expired or provider unreachable during agent.run(); web-search backend rate-limited or offline; a bug in an agent phase (parsing, note writing) raising a non-ValueError exception; long runs hitting timeouts.","commonSituations":"Missing LLM_API_KEY in the deployed backend; search-provider quota exhausted mid-run; transient network failures during multi-step research; disk/path errors writing notes.","solutions":["Check the server-side traceback (the exception is chained and logged) — the client message is intentionally opaque","Verify LLM credentials and network reachability of the LLM/search providers from the backend host","Retry once: many failures here are transient (rate limits, network blips) during long research runs","Add per-phase logging or shorter topics to narrow which agent stage failed"],"exampleFix":"# before\nr = requests.post(f'{BASE}/research', json={'topic': t})\nr.raise_for_status()  # only 'Research failed', no cause\n\n# after\nr = requests.post(f'{BASE}/research', json={'topic': t}, timeout=600)\nfor attempt in range(2):\n    if r.status_code == 500:\n        time.sleep(5)\n        r = requests.post(f'{BASE}/research', json={'topic': t}, timeout=600)\n    else:\n        break\n# meanwhile: inspect backend logs for the chained exception","handlingStrategy":"retry","validationCode":"import requests\n\n# precheck provider reachability before long research runs\nassert requests.get(f'{BASE}/healthz', timeout=5).status_code == 200, 'backend down'\n# ensure LLM/search provider keys are configured server-side before submitting","typeGuard":null,"tryCatchPattern":"r = None\nfor attempt in range(2):\n    r = requests.post(f'{BASE}/research', json={'topic': topic}, timeout=600)\n    if r.status_code == 500 and attempt == 0:\n        time.sleep(10)  # transient rate-limit/network; retry once\n        continue\n    r.raise_for_status()","preventionTips":["Remember the client message is masked — always correlate with server logs via request id","Retry once with backoff: many deep-research failures are transient provider issues","Pre-validate provider keys and quotas before long runs","Keep topics narrowly scoped; long multi-phase runs amplify transient failure chances"],"tags":["fastapi","http-500","agent","deep-research","error-masking"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}