{"record":{"id":"349e8005b33c449d","repo":"datawhalechina/hello-agents","slug":"error-349e80","errorCode":null,"errorMessage":"议题不能为空","messagePattern":"议题不能为空","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/web/app.py","lineNumber":80,"sourceCode":"\n@app.get(\"/\")\nasync def index_page() -> FileResponse:\n    html = _STATIC / \"index.html\"\n    if not html.is_file():\n        raise HTTPException(status_code=500, detail=\"前端文件缺失，请检查 historical_review/web/static/\")\n    return FileResponse(html)\n\n\n@app.get(\"/api/health\")\nasync def health() -> dict[str, str]:\n    return {\"status\": \"ok\"}\n\n\n@app.post(\"/api/debate\", response_model=DebateResponse)\nasync def run_debate(req: DebateRequest) -> DebateResponse:\n    topic = req.topic.strip()\n    if not topic:\n        raise HTTPException(status_code=400, detail=\"议题不能为空\")\n\n    key_err = _api_key_error(req)\n    if key_err:\n        return DebateResponse(ok=False, error=key_err)\n\n    def _work() -> str:\n        return run_historical_debate(\n            topic,\n            use_evidence_bundle=req.use_evidence_bundle,\n            debate_temperature=req.debate_temperature,\n            synthesizer_temperature=req.synthesizer_temperature,\n            llm_api_key=req.api_key.strip() if req.api_key else None,\n            llm_base_url=req.base_url.strip() if req.base_url else None,\n            llm_model=req.model.strip() if req.model else None,\n            llm_max_tokens=req.max_tokens,\n            llm_timeout=req.timeout,\n        )\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/web/app.py#L62-L98","documentation":"The POST /api/debate handler in historical_review/web/app.py strips req.topic and raises HTTPException(400, '议题不能为空') when the result is empty. This is the web-layer twin of the orchestrator's ValueError (error 247): the endpoint validates the DebateRequest body before spending LLM tokens. A 400 here means the JSON body's topic field was missing, null, empty, or whitespace-only.","triggerScenarios":"curl -X POST /api/debate with {\"topic\": \"\"} or omitting topic; frontend submitting the form before the user types anything; a client sending topic: null; whitespace/punctuation-only input that strips to empty.","commonSituations":"Missing required-field check in the frontend form; automated scripts probing the API with empty payloads; textarea default value being empty string and validation living only server-side.","solutions":["Send a real topic in the request body: curl -X POST /api/debate -H 'Content-Type: application/json' -d '{\"topic\":\"评价隋炀帝\", \"api_key\":\"sk-or-...\"}'.","Add client-side validation: disable submit until topic.trim().length > 0.","Make the Pydantic model enforce it — topic: str = Field(min_length=1) with a validator rejecting whitespace-only strings — so the 400 message is consistent for all callers.","Handle HTTP 400 in the client and show the detail message to the user."],"exampleFix":"# before\nclass DebateRequest(BaseModel):\n    topic: str\n# after\nfrom pydantic import field_validator\nclass DebateRequest(BaseModel):\n    topic: str\n    @field_validator(\"topic\")\n    @classmethod\n    def topic_not_blank(cls, v: str) -> str:\n        if not v.strip():\n            raise ValueError(\"议题不能为空\")\n        return v.strip()","handlingStrategy":"validation","validationCode":"topic = (req.topic or \"\").strip()\nif not topic:\n    return JSONResponse(status_code=400, content={\"detail\": \"议题不能为空\"})","typeGuard":"def is_valid_debate_request(req) -> bool:\n    return isinstance(getattr(req, \"topic\", None), str) and bool(req.topic.strip())","tryCatchPattern":"# client side\nresp = requests.post(url, json=payload, timeout=60)\nif resp.status_code == 400:\n    show_user(resp.json()[\"detail\"])  # display '议题不能为空' instead of crashing\nelse:\n    resp.raise_for_status()","preventionTips":["Add Pydantic validators so blank topics are rejected with a 400 at the model layer","Disable submit buttons until topic.trim() is non-empty","Handle 400 responses explicitly in every API client"],"tags":["fastapi","http-400","input-validation","python","rest-api"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}