diff --git a/AGENTS.md b/AGENTS.md index 2c7c326..ffc1fcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -824,6 +824,8 @@ mark_x = answer_bbox[2] + 10 # 紧贴答案框 - ✅ 添加 URL 可访问性验证和 HTTP Headers 支持 - ✅ 实现超时保护机制(单任务120秒,总任务按图片数量计算) - ✅ 删除未使用的 S3 存储模块(src/storage/s3/) +- ✅ 修复 LLM 认证错误,使用 new_context() 初始化 Context +- ✅ 修复 Docker 环境空请求体错误,添加请求体验证 - 提供真实的多页作业图片进行完整流程测试 - 优化HTML报告的图片展示布局 - 支持PDF格式答案文档 @@ -965,3 +967,97 @@ for future in concurrent.futures.as_completed( - ✅ 兼容阿里云 CDN 等需要特殊 Headers 的服务 - ✅ 超时任务自动跳过,避免长时间阻塞 - ✅ 线上部署环境测试通过 +## Docker 环境请求验证修复(2026-03-30) +### 问题诊断 +在 Docker 环境中运行时报错: +``` +Invalid JSON format: Expecting value: line 1 column 1 (char 0) +``` + +**原因分析**: +1. 请求体为空(例如,使用 GET 请求而不是 POST) +2. Content-Type 不是 application/json +3. 负载均衡器或代理可能过滤了请求体 + +### 解决方案 +在 `http_run` 和 `http_stream_run` 函数中添加请求体验证: + +```python +# 检查请求体是否为空 +if not body_text or body_text.strip() == "": + logger.error(f"Empty request body for run_id={ctx.run_id}") + raise HTTPException( + status_code=400, + detail="Request body is empty. Please provide a valid JSON payload." + ) + +# 检查 Content-Type +content_type = request.headers.get("content-type", "") +if "application/json" not in content_type.lower(): + logger.error(f"Invalid Content-Type: {content_type}") + raise HTTPException( + status_code=400, + detail=f"Content-Type must be 'application/json', got: {content_type}" + ) +``` + +### 修改文件 +- `src/main.py` + - `http_run` 函数:添加请求体验证 + - `http_stream_run` 函数:添加请求体验证 + +### 正确的请求格式 + +**使用 curl**: +```bash +curl -X POST http://localhost:8000/stream_run \ + -H "Content-Type: application/json" \ + -d '{ + "student_homework": [ + { + "student_id": 1, + "student_name": "张三", + "homework_images": ["https://example.com/image.jpg"] + } + ], + "answer_doc_url": "", + "comment_max_length": 50, + "max_concurrent": 5 + }' +``` + +**使用 Python requests**: +```python +import requests + +url = "http://localhost:8000/stream_run" +headers = {"Content-Type": "application/json"} +data = { + "student_homework": [ + { + "student_id": 1, + "student_name": "张三", + "homework_images": ["https://example.com/image.jpg"] + } + ], + "answer_doc_url": "", + "comment_max_length": 50, + "max_concurrent": 5 +} + +response = requests.post(url, json=data, headers=headers) +print(response.json()) +``` + +**注意事项**: +- ✅ 必须使用 POST 请求 +- ✅ Content-Type 必须是 `application/json` +- ✅ 请求体必须是非空的 JSON 字符串 +- ❌ 不要使用 GET 请求 +- ❌ 不要发送空请求体 +- ❌ 不要省略 Content-Type 头 + +### 效果 +- ✅ 空请求体返回友好的错误信息 +- ✅ 错误的 Content-Type 返回明确的错误提示 +- ✅ 测试通过 diff --git a/src/main.py b/src/main.py index dd86f2d..67f567b 100644 --- a/src/main.py +++ b/src/main.py @@ -251,6 +251,23 @@ async def http_run(request: Request) -> Dict[str, Any]: body_text = str(raw_body) raise HTTPException(status_code=400, detail=f"Invalid JSON format: {body_text}, traceback: {traceback.format_exc()}, error: {e}") + + # 检查请求体是否为空 + if not body_text or body_text.strip() == "": + logger.error(f"Empty request body in http_run for run_id={ctx.run_id}") + raise HTTPException( + status_code=400, + detail="Request body is empty. Please provide a valid JSON payload." + ) + + # 检查 Content-Type + content_type = request.headers.get("content-type", "") + if "application/json" not in content_type.lower(): + logger.error(f"Invalid Content-Type in http_run: {content_type}") + raise HTTPException( + status_code=400, + detail=f"Content-Type must be 'application/json', got: {content_type}" + ) ctx = new_context(method="run", headers=request.headers) # 优先使用上游指定的 run_id,保证 cancel 能精确匹配 @@ -346,6 +363,24 @@ async def http_stream_run(request: Request): body_text = str(raw_body) raise HTTPException(status_code=400, detail=f"Invalid JSON format: {body_text}, traceback: {extract_core_stack()}, error: {e}") + + # 检查请求体是否为空 + if not body_text or body_text.strip() == "": + logger.error(f"Empty request body in http_stream_run for run_id={ctx.run_id}") + raise HTTPException( + status_code=400, + detail="Request body is empty. Please provide a valid JSON payload." + ) + + # 检查 Content-Type + content_type = request.headers.get("content-type", "") + if "application/json" not in content_type.lower(): + logger.error(f"Invalid Content-Type in http_stream_run: {content_type}") + raise HTTPException( + status_code=400, + detail=f"Content-Type must be 'application/json', got: {content_type}" + ) + run_id = ctx.run_id is_agent = graph_helper.is_agent_proj() logger.info(