This commit is contained in:
zhangquan 2026-03-31 10:47:52 +08:00
parent 5cdea9140f
commit b2ad58e30c
17 changed files with 2036 additions and 17 deletions

View File

@ -1061,3 +1061,55 @@ print(response.json())
- ✅ 空请求体返回友好的错误信息 - ✅ 空请求体返回友好的错误信息
- ✅ 错误的 Content-Type 返回明确的错误提示 - ✅ 错误的 Content-Type 返回明确的错误提示
- ✅ 测试通过 - ✅ 测试通过
## Docker 环境 LLM 调用 404 错误诊断2026-03-31
### 问题诊断
在 Docker 环境部署时LLM 调用返回 404 错误:
```
HTTP Request: POST https://api.coze.cn/chat/completions HTTP/1.1 404 Not Found
```
**根本原因**:错误的 API 端点(缺少 `/v1/` 前缀)
- ❌ 错误端点:`https://api.coze.cn/chat/completions`
- ✅ 正确端点:`https://api.coze.cn/v1/chat/completions`
### 解决方案
**方案 1**:更新代码和 SDK推荐
```bash
# 在 Docker 容器中执行
cd /workspace/projects
git pull
pip install --upgrade coze-coding-dev-sdk
docker restart <container_id>
```
**方案 2**:配置环境变量
```bash
export COZE_API_ENDPOINT="https://api.coze.cn/v1"
```
### 诊断工具
创建以下脚本帮助诊断问题:
- `scripts/test_llm_api.py`:测试不同的 LLM API 端点
- `scripts/diagnose_docker_env.py`:综合诊断 Docker 环境
- `docs/fix_llm_404_error.md`:详细的修复指南
### 验证修复
修复后运行测试:
```bash
python scripts/test_llm_api.py
# 应该看到所有端点测试成功
```
### 修改文件
- 创建:`scripts/test_llm_api.py`
- 创建:`scripts/diagnose_docker_env.py`
- 创建:`docs/fix_llm_404_error.md`
- 更新:`AGENTS.md`(本文档)
### 关键检查点
- ✅ SDK 版本是否最新(`pip show coze-coding-dev-sdk`
- ✅ 代码是否包含 `new_context()`(检查关键节点文件)
- ✅ 文件修改时间是否为最近(确保代码已更新)
- ✅ LLM 调用测试是否通过(`test_llm_api.py`

View File

@ -45,7 +45,8 @@ ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
LLM_BASE_URL=https://api.coze.cn/v1 \ LLM_BASE_URL=https://api.coze.cn/v1 \
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \ LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \
COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \ COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
COZE_WORKSPACE_ID=7622238752642957347
# 升级 pip 和构建工具 # 升级 pip 和构建工具
RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja

61
assets/Dockerfile Normal file
View File

@ -0,0 +1,61 @@
FROM registry-vpc.cn-chengdu.aliyuncs.com/zhixueli-prod/python:3.12-slim
WORKDIR /app
# 设置国内镜像源
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
# 安装完整的系统依赖(包含所有 PyGObject 需要的库)
RUN apt-get update && apt-get install -y --no-install-recommends \
# 编译工具
gcc \
g++ \
make \
pkg-config \
cmake \
# D-Bus 相关
dbus \
dbus-x11 \
libdbus-1-dev \
libdbus-glib-1-dev \
# GLib 和 GObject 相关
libglib2.0-dev \
libgirepository1.0-dev \
gobject-introspection \
# Cairo 图形库
libcairo2-dev \
libcairo-gobject2 \
# GTK 相关(如果需要)
libgtk-3-dev \
# 其他常用库
libffi-dev \
libxml2-dev \
libxslt1-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 设置 pip 国内镜像源
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
PIP_TRUSTED_HOST=mirrors.aliyun.com \
COZE_INTEGRATION_BASE_URL=https://api.coze.cn/v1 \
COZE_INTEGRATION_MODEL_BASE_URL=https://api.coze.cn/v1 \
COZE_WORKLOAD_IDENTITY_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
LLM_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
LLM_BASE_URL=https://api.coze.cn/v1 \
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \
COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA"
# 升级 pip 和构建工具
RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja
# 复制并安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
EXPOSE 8000
CMD ["python", "src/main.py", "-m", "http", "-p", "8000"]

51
assets/Dockerfile.fixed Normal file
View File

@ -0,0 +1,51 @@
FROM registry-vpc.cn-chengdu.aliyuncs.com/zhixueli-prod/python:3.12-slim
WORKDIR /app
# 设置国内镜像源
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
# 安装系统依赖(精简版)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
make \
pkg-config \
libglib2.0-dev \
libcairo2-dev \
libxml2-dev \
libxslt1-dev \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 设置 pip 国内镜像源和 API 端点
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
PIP_TRUSTED_HOST=mirrors.aliyun.com \
# API 端点配置
COZE_INTEGRATION_BASE_URL=https://api.coze.cn/v1 \
COZE_INTEGRATION_MODEL_BASE_URL=https://api.coze.cn/v1 \
COZE_INTEGRATION_CHAT_BASE_URL=https://api.coze.cn/v1 \
# ✅ 关键修复:只保留一个 API_KEY使用环境变量注入
# COZE_API_KEY 将在运行时通过 docker-compose.yml 注入
# ✅ 关键修复:添加 WORKSPACE_ID必须
# COZE_WORKSPACE_ID 将在运行时通过 docker-compose.yml 注入
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215
# 升级 pip 和构建工具
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
# 复制并安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 添加健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
EXPOSE 8000
CMD ["python", "src/main.py", "-m", "http", "-p", "8000"]

139
assets/check_env.py Normal file
View File

@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
测试环境变量配置是否正确
"""
import os
import sys
from datetime import datetime
def check_env_var(var_name, required=True):
"""检查环境变量"""
value = os.environ.get(var_name)
if required and not value:
print(f"{var_name}: 未设置(必须)")
return False
elif value:
# 隐藏敏感信息
if "KEY" in var_name or "TOKEN" in var_name:
display_value = f"{value[:20]}...{value[-10:]}" if len(value) > 30 else "***"
else:
display_value = value
print(f"{var_name}: {display_value}")
return True
else:
print(f"⚠️ {var_name}: 未设置(可选)")
return True
def check_jwt_token(token):
"""检查 JWT token 是否包含必要字段"""
try:
import base64
import json
if token.startswith("Bearer "):
token = token[7:]
parts = token.split(".")
if len(parts) != 3:
print(f"❌ Token 格式错误:应该是 JWT 格式")
return False
# 解码 payload不验证签名
payload_b64 = parts[1]
# 添加 padding
padding = 4 - len(payload_b64) % 4
if padding != 4:
payload_b64 += "=" * padding
payload = json.loads(base64.b64decode(payload_b64))
print("\nJWT Token 信息:")
print(f" Issuer (iss): {payload.get('iss')}")
print(f" Subject (sub): {payload.get('sub')}")
print(f" Expires (exp): {datetime.fromtimestamp(payload.get('exp', 0))}")
print(f" Issued At (iat): {datetime.fromtimestamp(payload.get('iat', 0))}")
# 检查是否过期
import time
if payload.get('exp', 0) < time.time():
print(f"❌ Token 已过期")
return False
else:
print(f"✅ Token 未过期")
# 提取 workspace_id
sub = payload.get('sub', '')
if 'workload_identity/id:' in sub:
workspace_id = sub.split('workload_identity/id:')[-1]
print(f"\n✅ 从 Token 提取的 Workspace ID: {workspace_id}")
return workspace_id
else:
print(f"⚠️ 无法从 Token 提取 Workspace ID")
return True
except Exception as e:
print(f"❌ 解析 Token 失败: {str(e)}")
return False
def main():
print("=" * 80)
print("环境变量配置检查")
print("=" * 80)
print()
# 检查关键环境变量
print("1. 检查环境变量:")
print("-" * 80)
all_ok = True
all_ok &= check_env_var("COZE_API_KEY", required=True)
all_ok &= check_env_var("COZE_WORKSPACE_ID", required=True)
all_ok &= check_env_var("COZE_INTEGRATION_BASE_URL", required=True)
all_ok &= check_env_var("LLM_MODEL_NAME", required=True)
print()
# 检查 JWT token
print("2. 检查 JWT Token:")
print("-" * 80)
token = os.environ.get("COZE_API_KEY", "")
if token:
check_jwt_token(token)
print()
# 检查端点配置
print("3. 检查 API 端点配置:")
print("-" * 80)
base_url = os.environ.get("COZE_INTEGRATION_BASE_URL", "")
if base_url:
if "/v1" in base_url:
print(f"✅ API 端点包含 /v1 前缀: {base_url}")
else:
print(f"❌ API 端点缺少 /v1 前缀: {base_url}")
all_ok = False
else:
print(f"❌ API 端点未设置")
all_ok = False
print()
# 总结
print("=" * 80)
print("检查总结")
print("=" * 80)
if all_ok:
print("✅ 所有必需的环境变量都已正确配置")
print("\n下一步:")
print("1. 重新构建 Docker 镜像: docker-compose build")
print("2. 启动服务: docker-compose up -d")
print("3. 检查日志: docker-compose logs -f")
else:
print("❌ 存在配置问题,请修复后再试")
print("\n修复方法:")
print("1. 设置 COZE_API_KEY 环境变量")
print("2. 设置 COZE_WORKSPACE_ID 环境变量")
print("3. 确保 API 端点包含 /v1 前缀")
return 0 if all_ok else 1
if __name__ == "__main__":
sys.exit(main())

24
assets/docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
version: '3.8'
services:
math-grading:
build: .
container_name: math-grading-app
ports:
- "8000:8000"
environment:
# ✅ 关键:从环境变量文件或直接传入密钥
- COZE_API_KEY=Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA
# ✅ 关键:添加 WORKSPACE_ID从 token 中提取)
# 根据 JWT token 的 sub 字段spiffe://api.coze.cn/workload_identity/id:7622238752642957347
- COZE_WORKSPACE_ID=7622238752642957347
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
volumes:
- ./logs:/app/work/logs
- ./cache:/tmp/homework_cache

267
assets/fix_docker_404.md Normal file
View File

@ -0,0 +1,267 @@
# 修复 Docker 环境 404 错误
## 问题诊断
### 日志分析
`assets/physics-correction.log` 中发现以下关键错误:
```
❌ HTTP Request: POST https://api.coze.cn/v1/chat/completions "HTTP/1.1 404 Not Found"
❌ HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest "HTTP/1.1 401 Unauthorized"
```
**日志明确提示**
```
remote service error, authentication is invalid
[http_code=401 error_code=4100]
```
### 根本原因
1. **认证失败**:虽然端点已经正确添加了 `/v1/` 前缀,但由于认证失败导致返回 404 错误
2. **缺少必需环境变量**
- ❌ 没有 `COZE_WORKSPACE_ID`
- ❌ 密钥被硬编码在 Dockerfile 中(不安全且配置混乱)
## 修复步骤
### 步骤 1使用修复后的 Dockerfile
`assets/Dockerfile.fixed` 替换原来的 `Dockerfile`
```bash
# 备份原文件
mv Dockerfile Dockerfile.backup
# 使用修复后的文件
mv Dockerfile.fixed Dockerfile
```
**关键修改**
- ✅ 移除硬编码的 API_KEY 和密钥
- ✅ 添加 `COZE_INTEGRATION_CHAT_BASE_URL` 端点
- ✅ 保留 API 端点配置(`/v1` 前缀已正确)
- ✅ 添加健康检查
### 步骤 2创建 docker-compose.yml
使用提供的 `assets/docker-compose.yml` 文件,确保包含以下环境变量:
```yaml
environment:
# ✅ 从 JWT token 提取的值
- COZE_API_KEY=Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9...
# ✅ 从 token 的 sub 字段提取spiffe://api.coze.cn/workload_identity/id:7622238752642957347
- COZE_WORKSPACE_ID=7622238752642957347
```
### 步骤 3验证环境变量
在重新构建之前,运行检查脚本:
```bash
# 在宿主机上(确保环境变量已设置)
python assets/check_env.py
```
应该看到类似输出:
```
✅ COZE_API_KEY: Bearer eyJhbGciOiJSUzI1...
✅ COZE_WORKSPACE_ID: 7622238752642957347
✅ COZE_INTEGRATION_BASE_URL: https://api.coze.cn/v1
✅ LLM_MODEL_NAME: doubao-seed-2-0-pro-260215
✅ Token 未过期
✅ API 端点包含 /v1 前缀
✅ 从 Token 提取的 Workspace ID: 7622238752642957347
```
### 步骤 4重新构建并启动
```bash
# 停止旧容器
docker-compose down
# 重新构建镜像
docker-compose build --no-cache
# 启动服务
docker-compose up -d
# 查看日志
docker-compose logs -f
```
### 步骤 5测试 LLM 调用
```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": 1
}'
```
## 验证修复成功的标志
### 日志应该显示:
```
✅ HTTP Request: POST https://api.coze.cn/v1/chat/completions "HTTP/1.1 200 OK"
✅ Workflow doc_extract started
✅ Node 'doc_extract_node' started
✅ LLM 调用成功
```
**不应该再出现**
```
❌ 404 Not Found
❌ 401 Unauthorized
❌ authentication is invalid
```
## 环境变量说明
### 必需的环境变量
| 变量名 | 说明 | 示例值 |
|--------|------|--------|
| `COZE_API_KEY` | Coze API 认证密钥 | `Bearer eyJhbGci...` |
| `COZE_WORKSPACE_ID` | 工作区 ID | `7622238752642957347` |
### 可选的环境变量(已在 Dockerfile 中设置)
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| `COZE_INTEGRATION_BASE_URL` | API 基础 URL | `https://api.coze.cn/v1` |
| `COZE_INTEGRATION_MODEL_BASE_URL` | 模型 API 基础 URL | `https://api.coze.cn/v1` |
| `LLM_MODEL_NAME` | LLM 模型名称 | `doubao-seed-2-0-pro-260215` |
### 提取 Workspace ID 的方法
从 JWT Token 的 `sub` 字段提取:
```python
# Token 示例
token = "Bearer eyJhbGci..."
# 解码 JWT payload
import json, base64
payload = json.loads(base64.b64decode(token.split('.')[1]))
# sub 字段spiffe://api.coze.cn/workload_identity/id:7622238752642957347
sub = payload['sub']
workspace_id = sub.split('workload_identity/id:')[-1]
print(workspace_id) # 7622238752642957347
```
## 安全建议
### ✅ 推荐:使用环境变量文件
创建 `.env` 文件(不要提交到 Git
```bash
# .env
COZE_API_KEY=Bearer eyJhbGci...
COZE_WORKSPACE_ID=7622238752642957347
```
修改 `docker-compose.yml`
```yaml
env_file:
- .env
```
添加到 `.gitignore`
```
.env
```
### ❌ 避免:硬编码密钥
**不要**在 Dockerfile 中硬编码密钥:
```dockerfile
# ❌ 错误示例
ENV COZE_API_KEY="Bearer eyJhbGci..."
```
## 故障排查
### 问题 1仍然出现 401 错误
**检查**
```bash
# 检查环境变量是否正确注入
docker exec -it math-grading-app env | grep COZE
# 应该看到 COZE_API_KEY 和 COZE_WORKSPACE_ID
```
**修复**
```bash
# 重新启动容器
docker-compose down
docker-compose up -d
```
### 问题 2仍然出现 404 错误
**检查**
```bash
# 检查端点配置
docker exec -it math-grading-app env | grep BASE_URL
# 应该包含 /v1
# COZE_INTEGRATION_BASE_URL=https://api.coze.cn/v1
```
**修复**
```bash
# 重新构建镜像(确保使用最新的 Dockerfile
docker-compose build --no-cache
docker-compose up -d
```
### 问题 3Token 过期
**检查**
```bash
# 运行检查脚本
python assets/check_env.py
```
**修复**
```bash
# 获取新的 Token
# 1. 登录 Coze 平台
# 2. 进入个人中心
# 3. 生成新的 API Key
# 4. 更新 docker-compose.yml 中的 COZE_API_KEY
```
## 总结
**关键修复点**
1. ✅ 添加 `COZE_WORKSPACE_ID` 环境变量
2. ✅ 使用运行时环境变量注入密钥(不硬编码)
3. ✅ 确保 API 端点包含 `/v1` 前缀
4. ✅ 添加健康检查
**预期结果**
- ✅ LLM 调用成功200 OK
- ✅ 不再出现 401/404 错误
- ✅ 工作流正常运行

View File

@ -0,0 +1,43 @@
Warning: Using fallback log directory: /tmp/work/logs/bypass, due to error: [Errno 2] No such file or directory: '/tmp/app/work/logs/bypass/app.log'
{"message": "Logging configured: file=/tmp/work/logs/bypass/app.log, max_bytes=104857600, backup_count=5", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "root", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 178, "funcName": "setup_logging", "taskName": null}
{"message": "Adding job tentatively -- it will be properly scheduled when the scheduler starts", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "apscheduler.scheduler", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 507, "funcName": "add_job", "taskName": null}
{"message": "Added job \"PromptCache._refresh_all_prompts\" to job store \"default\"", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "apscheduler.scheduler", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 1090, "funcName": "_real_add_job", "taskName": null}
{"message": "Scheduler started", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "apscheduler.scheduler", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 214, "funcName": "start", "taskName": null}
{"message": "Start HTTP Server, Port: 8000, Workers: 1", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "__main__", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 545, "funcName": "start_http_server", "taskName": null}
{"message": "Logging configured: file=/tmp/work/logs/bypass/app.log, max_bytes=104857600, backup_count=5", "timestamp": "2026-03-31 10:10:54", "level": "INFO", "logger": "root", "log_id": "", "run_id": "", "space_id": "", "project_id": "", "method": "", "x_tt_env": "", "lineno": 178, "funcName": "setup_logging", "taskName": "Task-1"}
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
{"message": "Received request for /stream_run: run_id=9ccf13d1-5850-4c4e-aa8d-3525424edd76, is_agent_project=False, query={}, body={\r\n \"student_homework\": [\r\n {\r\n \"student_id\": 1,\r\n \"student_name\": \"张三呀\",\r\n \"homework_images\": [\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/18/69baa4f5-4826-4901-00e1-c6e66f02947f.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6ff235a12b2.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c7055a396422.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fe7f9c07ef.jpg?x-oss-process=image/resize,w_1000\"\r\n ]\r\n },\r\n {\r\n \"student_id\": 2,\r\n \"student_name\": \"李四呀\",\r\n \"homework_images\": [\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c70052d895e3.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fc6ca7c4bf.png?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c33f31-4826-4901-00e1-c6fb50697e06.png?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f614bc06d9.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fd479e0a0e.jpg?x-oss-process=image/resize,w_1000\",\r\n \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f569b31a14.jpeg?x-oss-process=image/resize,w_1000\"\r\n ]\r\n }\r\n ],\r\n \"answer_doc_url\": \"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c353d0-4826-4901-00e1-c7081bcab988.docx\",\r\n \"comment_max_length\": 50,\r\n \"max_concurrent\": 5,\r\n \"grade_standards\": {}\r\n}", "timestamp": "2026-03-31 10:11:55", "level": "INFO", "logger": "main", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 386, "funcName": "http_stream_run", "taskName": "Task-5"}
INFO: 10.221.187.31:4212 - "POST /stream_run HTTP/1.1" 200 OK
{"message": "Registered workflow streaming task for run_id: 9ccf13d1-5850-4c4e-aa8d-3525424edd76", "timestamp": "2026-03-31 10:11:55", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 534, "funcName": "workflow_stream_handler", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "Starting stream with run_id: 9ccf13d1-5850-4c4e-aa8d-3525424edd76", "timestamp": "2026-03-31 10:11:55", "level": "INFO", "logger": "main", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 130, "funcName": "stream_sse", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "CacheManager initialized: math_answer_doc, dir=/tmp/homework_cache/math_answer_doc", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "utils.cache_manager", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 60, "funcName": "__init__", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "CacheManager initialized: grade_standards, dir=/tmp/homework_cache/grade_standards", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "utils.cache_manager", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 60, "funcName": "__init__", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "Stream mode: updates", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 335, "funcName": "astream", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "Workflow started - Run", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
{"message": "Node 'doc_extract_node' started", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
{"message": "Cache miss for answer doc: https://dpcclass.oss-cn-beijing.aliyuncs.com/umsup...", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 335, "funcName": "doc_extract_node", "taskName": null}
{"message": "Downloading Word document from: https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c353d0-4826-4901-00e1-c7081bcab988.docx", "timestamp": "2026-03-31 10:11:57", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 116, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Extracted Word document text length: 5267", "timestamp": "2026-03-31 10:11:58", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 168, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Word document content preview: 第14讲 实数专题\n【重难点知识讲解】\n实数章节常用的解题思想方法有特殊值思想、数形结合思想、归纳推理思想、迁移转化思想等。数学思想方法是对数学知识的深度提炼它兼具指导性与普适性掌握了这些思想方法就相当于握住了破解实数各类题的 “核心密码”.\n【考点一·比较大小】\n例1.已知实数a在数轴上的位置如图所示则 $ \\sqrt[3]{a} $ $- a$ $ \\frac{1}{a} $ $ a^{2} $ 的大小关系是( \nA $ \\sqrt[3]{a} < - a < \\frac{1}{a} < a^{2} $ B $ \\frac{1}{a} < \\sqrt[3]{a} < a^{2} < - a$ C $- a < \\frac{1}{a} < \\sqrt[3]{a} < a^{2} $ \t D $ \\frac{1}{a} < a^{2} < \\sqrt[3]{a} < - a$\n【答案】B\n【考点二·利用数轴解决化简问题】\n例2.已知a、b、c在数轴上的位置如图化简 $ \\sq", "timestamp": "2026-03-31 10:11:58", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 207, "funcName": "parse_answer_doc_with_llm", "taskName": null}
{"message": "HTTP Request: POST https://api.coze.cn/v1/chat/completions \"HTTP/1.1 404 Not Found\"", "timestamp": "2026-03-31 10:11:58", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 1025, "funcName": "_send_single_request", "taskName": null}
2026-03-31 10:11:58,631 cozeloop.internal.trace.span span.py:434 [WARNING] [cozeloop] field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size
{"message": "field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size", "timestamp": "2026-03-31 10:11:58", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:11:58,631"}
{"message": "Workflow doc_extract ended with error", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 10:11:58,642 cozeloop.internal.trace.span span.py:434 [WARNING] [cozeloop] field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size
{"message": "field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size", "timestamp": "2026-03-31 10:11:58", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:11:58,642"}
{"message": "Workflow ended with error", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 10:11:58,648 cozeloop.internal.trace.span span.py:434 [WARNING] [cozeloop] field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size
{"message": "field value [error_trace] is too long, and opt.EnableLongReport is false, so value has been truncated to 1024 size", "timestamp": "2026-03-31 10:11:58", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:11:58,648"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 10:11:58", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 1025, "funcName": "_send_single_request", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
2026-03-31 10:11:58,765 cozeloop.internal.httpclient.http_client http_client.py:65 [ERROR] [cozeloop] Call remote service failed. Path: https://api.coze.cn/v1/loop/traces/ingest, remote service error, authentication is invalid [http_code=401 error_code=4100 logid=202603311011584FB283DDFA5BA3A6DD6C], log id: 202603311011584FB283DDFA5BA3A6DD6C.
{"message": "Call remote service failed. Path: https://api.coze.cn/v1/loop/traces/ingest, remote service error, authentication is invalid [http_code=401 error_code=4100 logid=202603311011584FB283DDFA5BA3A6DD6C], log id: 202603311011584FB283DDFA5BA3A6DD6C.", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 65, "funcName": "parse_response", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap", "asctime": "2026-03-31 10:11:58,765"}
2026-03-31 10:11:58,765 cozeloop.internal.trace.trace trace.py:162 [ERROR] [cozeloop] finish_event[exporter.span_flush.rate] fail, msg: export spans fail, err:[remote service error, authentication is invalid [http_code=401 error_code=4100 logid=202603311011584FB283DDFA5BA3A6DD6C]], retry later
{"message": "finish_event[exporter.span_flush.rate] fail, msg: export spans fail, err:[remote service error, authentication is invalid [http_code=401 error_code=4100 logid=202603311011584FB283DDFA5BA3A6DD6C]], retry later", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 162, "funcName": "default_finish_event_processor", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap", "asctime": "2026-03-31 10:11:58,765"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 10:11:58", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 1025, "funcName": "_send_single_request", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
2026-03-31 10:11:58,866 cozeloop.internal.httpclient.http_client http_client.py:65 [ERROR] [cozeloop] Call remote service failed. Path: https://api.coze.cn/v1/loop/traces/ingest, remote service error, authentication is invalid [http_code=401 error_code=4100 logid=20260331101158372AA269460C8D9AE0BA], log id: 20260331101158372AA269460C8D9AE0BA.
{"message": "Call remote service failed. Path: https://api.coze.cn/v1/loop/traces/ingest, remote service error, authentication is invalid [http_code=401 error_code=4100 logid=20260331101158372AA269460C8D9AE0BA], log id: 20260331101158372AA269460C8D9AE0BA.", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 65, "funcName": "parse_response", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap", "asctime": "2026-03-31 10:11:58,866"}
2026-03-31 10:11:58,866 cozeloop.internal.trace.trace trace.py:162 [ERROR] [cozeloop] finish_event[exporter.span_flush.rate] fail, msg: export spans fail, err:[remote service error, authentication is invalid [http_code=401 error_code=4100 logid=20260331101158372AA269460C8D9AE0BA]], retry second time failed
{"message": "finish_event[exporter.span_flush.rate] fail, msg: export spans fail, err:[remote service error, authentication is invalid [http_code=401 error_code=4100 logid=20260331101158372AA269460C8D9AE0BA]], retry second time failed", "timestamp": "2026-03-31 10:11:58", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "9ccf13d1-5850-4c4e-aa8d-3525424edd76", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 162, "funcName": "default_finish_event_processor", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap", "asctime": "2026-03-31 10:11:58,866"}

View File

@ -0,0 +1,210 @@
# Docker 部署问题排查指南
## 问题描述
在 Docker 环境中部署工作流时,出现以下错误:
```
S3对象不存在: <!DOCTYPE HTML><html><head>...<title>404 Not Found</title>...
```
## 诊断步骤
### 1. 运行 URL 诊断脚本
在 Docker 容器内运行:
```bash
python scripts/test_urls.py
```
**预期结果**:所有 URL 应该显示 `✅ 有效`
**如果显示 `❌ 无效`**
- 检查 Docker 容器网络配置
- 检查 DNS 解析
- 检查防火墙规则
### 2. 检查 Docker 网络配置
```bash
# 检查容器是否可以访问外网
docker exec <container_id> ping -c 3 8.8.8.8
# 检查 DNS 解析
docker exec <container_id> nslookup dpcclass.oss-cn-beijing.aliyuncs.com
# 检查是否能访问 OSS
docker exec <container_id> curl -I https://dpcclass.oss-cn-beijing.aliyuncs.com
```
### 3. 检查 LLM 服务网络
LLM 服务可能在隔离的网络环境中运行,无法直接访问外部 URL。
**验证方法**
- 查看 LLM 服务的网络配置
- 确认 LLM 服务可以访问外部 URL
- 联系 LLM 服务提供商确认网络策略
## 解决方案
### 方案 1配置 Docker 网络使用宿主机网络
```bash
docker run --network host ...
```
**优点**
- 容器使用宿主机的网络栈
- 可以直接访问宿主机的网络资源
**缺点**
- 端口可能冲突
- 安全性降低
### 方案 2配置 DNS 解析
在 Dockerfile 中:
```dockerfile
RUN echo "nameserver 8.8.8.8" > /etc/resolv.conf
```
或在 docker-compose.yml 中:
```yaml
services:
app:
dns:
- 8.8.8.8
- 114.114.114.114
```
### 方案 3添加 HTTP 代理
如果需要通过代理访问外网:
```bash
# 在容器内设置环境变量
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
```
或在 Dockerfile 中:
```dockerfile
ENV HTTP_PROXY=http://proxy.example.com:8080
ENV HTTPS_PROXY=http://proxy.example.com:8080
```
### 方案 4下载图片到本地推荐
如果 LLM 服务无法访问外部 URL可以在预处理阶段下载图片
```python
import requests
import tempfile
import os
from pathlib import Path
def download_image_to_local(image_url: str) -> str:
"""
下载图片到本地临时目录
返回本地文件路径
"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(image_url, headers=headers, timeout=30)
response.raise_for_status()
# 保存到临时目录
tmp_dir = Path("/tmp/homework_images")
tmp_dir.mkdir(parents=True, exist_ok=True)
# 生成文件名
import hashlib
filename = hashlib.md5(image_url.encode()).hexdigest() + ".jpg"
local_path = tmp_dir / filename
# 写入文件
with open(local_path, "wb") as f:
f.write(response.content)
return str(local_path)
```
### 方案 5使用内网 OSS最佳
如果有内网 OSS可以
1. 将图片从公网 OSS 复制到内网 OSS
2. 使用内网 OSS URL
3. 配置内网 OSS 访问权限
## 代码改进
我已经增强了 URL 验证逻辑:
### 1. 改进的图片 URL 验证(`recognize_and_correct_node.py`
```python
# 检查 HTTP 状态码(只接受 200 和 206
if status_code not in (200, 206):
raise ValueError(f"HTTP status code: {status_code}")
# 检查 Content-Type
content_type = response.headers.get('Content-Type', '')
if not content_type.startswith('image/'):
logger.warning(f"Unexpected Content-Type: {content_type}")
# 检查是否为 HTML 错误页面
if b'<html' in preview_lower:
# 提取具体错误信息404/403/500
raise ValueError(f"URL returned HTML page ({error_msg})")
```
### 2. 改进的 Word 文档下载验证(`doc_extract_node.py`
```python
# 验证 URL 格式
if not url.startswith(('http://', 'https://')):
raise ValueError(f"Invalid URL format")
# 检查 HTTP 状态码
if status_code != 200:
raise ValueError(f"HTTP Error {status_code}: {url}")
# 检查是否为有效的 docx
if not response.content.startswith(b'PK'):
raise ValueError("Not a valid docx file")
```
## 临时解决方案
如果遇到无法解决的访问问题,可以:
### 1. 使用本地图片测试
```json
{
"student_homework": [
{
"student_id": 1,
"student_name": "测试",
"homework_images": ["file:///tmp/test_image.jpg"]
}
],
"answer_doc_url": ""
}
```
### 2. 禁用答案文档
```json
{
"answer_doc_url": "",
...
}
```
系统会自动切换到"专业老师批改模式",不依赖标准答案。
## 联系支持
如果以上方案都无法解决问题,请提供:
1. Docker 容器网络配置
2. `python scripts/test_urls.py` 的输出
3. Docker 容器日志
4. LLM 服务网络配置

216
docs/fix_llm_404_error.md Normal file
View File

@ -0,0 +1,216 @@
# Docker 环境 LLM 调用 404 错误修复指南
## 问题描述
在 Docker 环境部署时LLM 调用返回 404 错误:
```
HTTP Request: POST https://api.coze.cn/chat/completions HTTP/1.1 404 Not Found
```
## 根本原因
错误的 API 端点:
- **错误端点**: `https://api.coze.cn/chat/completions`(缺少 `/v1/`
- **正确端点**: `https://api.coze.cn/v1/chat/completions``https://api.coze.cn/v1`
## 诊断步骤
### 1. 在 Docker 容器中运行诊断脚本
```bash
# 进入容器
docker exec -it <container_id> /bin/bash
# 运行诊断脚本
cd /workspace/projects
python scripts/diagnose_docker_env.py
```
### 2. 检查关键内容
诊断脚本会检查以下内容:
- ✅ SDK 版本
- ✅ 关键文件修改时间
- ✅ 代码是否包含 `new_context()`
- ✅ LLM 调用测试
## 修复方案
### 方案 1更新代码和 SDK推荐
这是最彻底的解决方案,确保使用最新的代码和依赖。
```bash
# 在 Docker 容器中执行
cd /workspace/projects
# 1. 更新代码
git pull
# 2. 更新依赖
pip install --upgrade coze-coding-dev-sdk
# 3. 验证更新
pip show coze-coding-dev-sdk
# 4. 重启服务
exit
docker restart <container_id>
```
### 方案 2设置环境变量
如果无法更新代码,可以通过环境变量指定正确的端点:
```bash
# 在 Docker 容器中
export COZE_API_ENDPOINT="https://api.coze.cn/v1"
```
或者在 `docker-compose.yml` 中添加:
```yaml
environment:
- COZE_API_ENDPOINT=https://api.coze.cn/v1
```
### 方案 3重新构建 Docker 镜像
如果以上方案都不行,重新构建镜像:
```bash
# 1. 拉取最新代码
git pull
# 2. 重新构建镜像
docker-compose build
# 3. 重启服务
docker-compose down
docker-compose up -d
```
## 验证修复
修复后,运行以下命令验证:
```bash
# 测试 LLM 调用
python scripts/test_llm_api.py
# 应该看到类似输出:
# ✅ 成功 - 默认
# ✅ 成功 - https://integration.coze.cn/api
```
## 关键代码检查
确保以下关键文件包含正确的代码:
### 1. `src/graphs/nodes/recognize_and_correct_node.py`
```python
# 必须包含
from coze_coding_utils.runtime_ctx.context import new_context
# 创建 LLM 客户端时使用
ctx = new_context(method="invoke") # ✅ 正确
client = LLMClient(ctx=ctx)
# ❌ 不要使用旧的方式
# client = LLMClient(runtime.context) # 可能导致认证错误
```
### 2. `src/graphs/nodes/doc_extract_node.py`
```python
# 必须包含
from coze_coding_utils.runtime_ctx.context import new_context
# 创建 LLM 客户端时使用
ctx = new_context(method="invoke")
client = LLMClient(ctx=ctx)
```
### 3. `src/main.py`
确保包含请求体验证:
```python
# 验证请求体不为空
if not body:
return create_error_response(400, "请求体为空")
```
## 常见问题
### Q1: 更新代码后仍然失败?
**A**: 可能是缓存问题,尝试:
```bash
# 清理 pip 缓存
pip cache purge
# 重新安装
pip install --force-reinstall --no-cache-dir coze-coding-dev-sdk
# 重启服务
docker restart <container_id>
```
### Q2: 如何确认代码已更新?
**A**: 检查文件修改时间:
```bash
ls -l src/graphs/nodes/recognize_and_correct_node.py
ls -l src/graphs/nodes/doc_extract_node.py
```
修改时间应该是最近的。
### Q3: SDK 版本是多少?
**A**: 检查 SDK 版本:
```bash
pip show coze-coding-dev-sdk
```
确保版本是最新的(建议 >= 1.0.0
## 技术支持
如果以上方案都无法解决问题,请收集以下信息联系技术支持:
1. SDK 版本号:
```bash
pip show coze-coding-dev-sdk
```
2. 完整的错误日志:
```bash
tail -100 /app/work/logs/bypass/app.log
```
3. Docker 镜像信息:
```bash
docker images
```
4. 诊断脚本输出:
```bash
python scripts/diagnose_docker_env.py > diagnose_output.txt
```
## 总结
**根本原因**:错误的 API 端点(缺少 `/v1/` 前缀)
**推荐解决方案**:更新代码和 SDK
**快速验证**:运行诊断脚本 `scripts/diagnose_docker_env.py`
**预期结果**LLM 调用成功,返回 200 状态码

149
scripts/check_env_vars.py Normal file
View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
环境变量检查脚本
检查工作流运行所需的环境变量
"""
import os
from typing import List, Dict, Tuple
def check_env_vars() -> List[Tuple[str, str, str]]:
"""
检查所有相关的环境变量
Returns:
List of (variable_name, status, value_preview)
"""
# 需要检查的环境变量列表
required_vars = [
"COZE_WORKSPACE_PATH",
"COZE_BUCKET_ENDPOINT_URL",
"COZE_BUCKET_NAME",
]
optional_vars = [
"COZE_API_KEY",
"COZE_BASE_URL",
"COZE_API_ENDPOINT",
"PYTHONPATH",
"PATH",
]
results = []
# 检查必需的环境变量
for var in required_vars:
value = os.environ.get(var, "")
if value:
status = "✅ 已设置"
preview = f"{value[:50]}..." if len(value) > 50 else value
else:
status = "❌ 未设置"
preview = "N/A"
results.append((var, status, preview))
# 检查可选的环境变量
for var in optional_vars:
value = os.environ.get(var, "")
if value:
status = "✅ 已设置"
preview = f"{value[:50]}..." if len(value) > 50 else value
else:
status = "⚠️ 未设置(可选)"
preview = "N/A"
results.append((var, status, preview))
return results
def check_llm_client():
"""检查 LLM 客户端配置"""
try:
from coze_coding_dev_sdk import LLMClient
from coze_coding_utils.runtime_ctx.context import new_context
print("\n" + "=" * 80)
print("LLM 客户端测试")
print("=" * 80)
# 尝试创建客户端
try:
ctx = new_context(method="invoke")
client = LLMClient(ctx=ctx)
print("✅ LLM 客户端创建成功")
# 检查客户端配置
if hasattr(client, '_client') and client._client:
print(f"✅ LLM 客户端已初始化")
if hasattr(client._client, 'base_url'):
print(f" Base URL: {client._client.base_url}")
if hasattr(client._client, 'timeout'):
print(f" Timeout: {client._client.timeout}")
return True
except Exception as e:
print(f"❌ LLM 客户端创建失败: {str(e)[:200]}")
return False
except ImportError as e:
print(f"❌ 无法导入 LLM 客户端: {str(e)}")
return False
except Exception as e:
print(f"❌ 检查 LLM 客户端时发生错误: {str(e)[:200]}")
return False
def main():
print("=" * 80)
print("环境变量检查")
print("=" * 80)
print()
results = check_env_vars()
print(f"{'变量名':<40} {'状态':<20} {'值预览'}")
print("-" * 100)
for var, status, preview in results:
print(f"{var:<40} {status:<20} {preview}")
print()
print("=" * 80)
print("总结")
print("=" * 80)
required_missing = [r for r in results if "" in r[1] and "COZE_WORKSPACE_PATH" in r[0]]
if required_missing:
print("\n⚠️ 缺少必需的环境变量:")
for var, _, _ in required_missing:
print(f" - {var}")
print("\n请设置这些环境变量后再运行工作流。")
else:
print("\n✅ 所有必需的环境变量都已设置。")
# 检查 LLM 客户端
check_llm_client()
print()
print("=" * 80)
print("配置建议")
print("=" * 80)
print()
print("如果 LLM 调用失败,请检查以下配置:")
print("1. 确保在 Coze 平台中正确配置了 LLM 服务")
print("2. 检查是否需要设置 API 密钥或认证信息")
print("3. 确认 LLM 服务的端点 URL 是否正确")
print("4. 检查网络连接是否可以访问 LLM 服务")
print()
print("在 Docker 容器中运行时,可能需要添加以下环境变量:")
print(" - COZE_WORKSPACE_PATH: 工作空间路径")
print(" - 其他必要的认证和配置信息")
print()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""
Docker 环境问题诊断脚本
帮助用户诊断和修复 Docker 环境中的问题
"""
import os
import sys
import subprocess
from datetime import datetime
def check_version():
"""检查关键组件的版本"""
print("=" * 80)
print("版本检查")
print("=" * 80)
print()
packages = [
"coze-coding-dev-sdk",
"langchain",
"langgraph",
"coze-coding-utils",
]
for pkg in packages:
try:
result = subprocess.run(
["pip", "show", pkg],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
version = None
for line in result.stdout.split('\n'):
if line.startswith('Version:'):
version = line.split(':', 1)[1].strip()
break
if version:
print(f"{pkg:<30} {version}")
else:
print(f"⚠️ {pkg:<30} 版本未知")
else:
print(f"{pkg:<30} 未安装")
except Exception as e:
print(f"{pkg:<30} 检查失败: {str(e)[:50]}")
print()
def check_file_modification():
"""检查关键文件的修改时间"""
print("=" * 80)
print("文件检查")
print("=" * 80)
print()
critical_files = [
"src/graphs/nodes/recognize_and_correct_node.py",
"src/graphs/nodes/doc_extract_node.py",
"src/main.py",
]
for file_path in critical_files:
try:
if os.path.exists(file_path):
mtime = os.path.getmtime(file_path)
mtime_str = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
print(f"{file_path:<50} {mtime_str}")
# 检查是否包含 new_context
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
if 'new_context' in content:
print(f" ✓ 包含 new_context()")
else:
print(f" ✗ 不包含 new_context()(需要更新代码)")
else:
print(f"{file_path:<50} 文件不存在")
except Exception as e:
print(f"{file_path:<50} 检查失败")
print()
def test_llm_with_correct_endpoint():
"""测试使用正确端点的 LLM 调用"""
print("=" * 80)
print("LLM 调用测试")
print("=" * 80)
print()
try:
from coze_coding_dev_sdk import LLMClient, Config
from coze_coding_utils.runtime_ctx.context import new_context
from langchain_core.messages import HumanMessage
# 使用正确的端点
ctx = new_context(method="invoke")
# 尝试不同的配置
configs = [
("默认配置", None),
("集成端点", Config(base_url="https://integration.coze.cn/api")),
("V1 端点", Config(base_url="https://api.coze.cn/v1")),
]
for name, config in configs:
try:
if config:
client = LLMClient(config=config, ctx=ctx)
else:
client = LLMClient(ctx=ctx)
messages = [HumanMessage(content="测试")]
print(f"正在测试: {name}...")
response = client.invoke(
messages=messages,
model="doubao-seed-2-0-lite-260215",
temperature=0.1,
max_completion_tokens=10
)
print(f" ✅ 成功!")
except Exception as e:
error_str = str(e)
if "404" in error_str:
print(f" ❌ 失败404 Not Found端点错误")
elif "401" in error_str:
print(f" ❌ 失败401 Unauthorized认证失败")
else:
print(f" ❌ 失败:{error_str[:100]}")
except ImportError as e:
print(f"❌ 无法导入 LLM 客户端: {str(e)}")
print()
def provide_fix_suggestions():
"""提供修复建议"""
print("=" * 80)
print("修复建议")
print("=" * 80)
print()
print("如果 LLM 调用失败,请按以下步骤修复:")
print()
print("1. 更新代码(推荐)")
print(" git pull")
print()
print("2. 更新 SDK")
print(" pip install --upgrade coze-coding-dev-sdk")
print()
print("3. 检查代码是否包含 new_context()")
print(" grep -r 'new_context' src/graphs/nodes/")
print()
print("4. 如果代码已更新但仍有问题,尝试重启服务")
print(" docker restart <container_id>")
print()
print("5. 如果仍然失败,请联系技术支持并提供:")
print(" - SDK 版本号")
print(" - 完整的错误日志")
print(" - Docker 镜像信息")
def main():
print()
print("=" * 80)
print("Docker 环境问题诊断")
print("=" * 80)
print()
print("此脚本将诊断 Docker 环境中的问题并提供修复建议")
print()
check_version()
check_file_modification()
test_llm_with_correct_endpoint()
provide_fix_suggestions()
print("=" * 80)
print("诊断完成")
print("=" * 80)
print()
print("请根据上面的诊断结果采取相应的修复措施")
print()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""
完整错误信息诊断脚本
运行工作流并捕获完整的错误信息
"""
import sys
import os
import json
import traceback
from datetime import datetime
# 添加项目路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
sys.path.insert(0, os.path.join(project_root, 'src'))
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context, new_context
def run_test_with_full_error_capture():
"""运行测试并捕获完整错误信息"""
# 准备测试数据
test_data = {
"student_homework": [
{
"student_id": 1,
"student_name": "张三呀",
"homework_images": [
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/18/69baa4f5-4826-4901-00e1-c6e66f02947f.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6ff235a12b2.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c7055a396422.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fe7f9c07ef.jpg?x-oss-process=image/resize,w_1000"
]
},
{
"student_id": 2,
"student_name": "李四呀",
"homework_images": [
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c70052d895e3.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fc6ca7c4bf.png?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c33f31-4826-4901-00e1-c6fb50697e06.png?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f614bc06d9.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fd479e0a0e.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f569b31a14.jpeg?x-oss-process=image/resize,w_1000"
]
}
],
"answer_doc_url": "https://dpc-oss.23544.com/umsupload/2026/03/25/69c353d0-4826-4901-00e1-c7081bcab988.docx",
"comment_max_length": 50,
"max_concurrent": 5,
"grade_standards": {}
}
print("=" * 80)
print("开始运行完整错误诊断测试")
print("=" * 80)
print(f"测试数据: {len(test_data['student_homework'])} 个学生")
total_images = sum(len(s['homework_images']) for s in test_data['student_homework'])
print(f"总图片数: {total_images}")
print(f"答案文档: {test_data['answer_doc_url'][:50]}...")
print("=" * 80)
print()
# 初始化上下文
ctx = new_context(method="run")
try:
# 导入主图
from graphs.graph import main_graph
print("✓ 成功导入主图")
print()
# 运行测试
print("开始运行工作流...")
print("-" * 80)
result = main_graph.invoke(test_data, config={"recursion_limit": 100})
print("-" * 80)
print()
print("=" * 80)
print("✓ 测试成功!")
print("=" * 80)
print()
# 输出结果摘要
if "student_results" in result:
for student_result in result["student_results"]:
# StudentResult 是 Pydantic 对象,使用属性访问而不是 .get()
student_name = getattr(student_result, "student_name", "未知")
total_images = getattr(student_result, "total_images", 0)
total_score = getattr(student_result, "total_score", 0)
full_score = getattr(student_result, "full_score", 0)
grade = getattr(student_result, "grade", "N/A")
print(f"学生: {student_name}")
print(f" 图片数: {total_images}")
print(f" 得分: {total_score}/{full_score}")
print(f" 等级: {grade}")
print()
# 保存完整结果到文件(先转换为 dict
result_dict = result.model_dump() if hasattr(result, 'model_dump') else result
result_file = f"/tmp/diagnostic_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(result_file, 'w', encoding='utf-8') as f:
json.dump(result_dict, f, ensure_ascii=False, indent=2)
print(f"完整结果已保存到: {result_file}")
return True
except Exception as e:
print("-" * 80)
print()
print("=" * 80)
print("✗ 测试失败!")
print("=" * 80)
print()
print("错误类型:", type(e).__name__)
print("错误消息:", str(e))
print()
print("完整堆栈跟踪:")
print("-" * 80)
traceback.print_exc()
print("-" * 80)
print()
# 保存错误信息到文件
error_file = f"/tmp/diagnostic_error_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
with open(error_file, 'w', encoding='utf-8') as f:
f.write(f"错误类型: {type(e).__name__}\n")
f.write(f"错误消息: {str(e)}\n")
f.write("\n完整堆栈跟踪:\n")
f.write("=" * 80 + "\n")
traceback.print_exc(file=f)
print(f"错误信息已保存到: {error_file}")
print()
# 分析错误类型
print("错误分析:")
print("-" * 80)
error_str = str(e).lower()
if "404" in error_str or "not found" in error_str:
print("⚠️ 404 错误:资源不存在")
print(" 可能原因:")
print(" 1. URL 已过期(签名 URL 有时效性)")
print(" 2. 文件已被删除或移动")
print(" 3. URL 拼写错误")
elif "403" in error_str or "forbidden" in error_str:
print("⚠️ 403 错误:禁止访问")
print(" 可能原因:")
print(" 1. 访问权限不足")
print(" 2. 需要 IP 白名单")
print(" 3. 需要认证")
elif "401" in error_str or "unauthorized" in error_str:
print("⚠️ 401 错误:未授权")
print(" 可能原因:")
print(" 1. API 密钥无效")
print(" 2. 认证失败")
print(" 3. Token 过期")
elif "timeout" in error_str:
print("⚠️ 超时错误")
print(" 可能原因:")
print(" 1. 网络连接慢")
print(" 2. 服务器响应慢")
print(" 3. 图片或文档太大")
elif "connection" in error_str or "network" in error_str:
print("⚠️ 网络连接错误")
print(" 可能原因:")
print(" 1. 无法连接到外部网络")
print(" 2. DNS 解析失败")
print(" 3. 防火墙阻止")
elif "html" in error_str:
print("⚠️ HTML 错误页面")
print(" 可能原因:")
print(" 1. URL 返回了 404/403/500 错误页面")
print(" 2. 网络重定向失败")
print(" 3. CDN 或代理返回错误")
elif "json" in error_str:
print("⚠️ JSON 解析错误")
print(" 可能原因:")
print(" 1. LLM 返回的不是有效的 JSON")
print(" 2. JSON 格式错误")
print(" 3. 缺少必需字段")
elif "llm" in error_str or "model" in error_str:
print("⚠️ LLM 调用错误")
print(" 可能原因:")
print(" 1. LLM 服务不可用")
print(" 2. 模型配置错误")
print(" 3. API 配额不足")
else:
print("⚠️ 未知错误类型")
print(" 请查看上面的错误详情和堆栈跟踪")
print()
return False
if __name__ == "__main__":
print()
print("=" * 80)
print("初中数学作业批改工作流 - 完整错误诊断")
print("=" * 80)
print()
success = run_test_with_full_error_capture()
print()
print("=" * 80)
if success:
print("诊断完成:工作流运行正常")
sys.exit(0)
else:
print("诊断完成:发现错误,请查看上面的错误详情")
sys.exit(1)

147
scripts/test_llm_api.py Normal file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""
LLM API 端点测试脚本
测试不同的 LLM API 端点配置
"""
import os
from typing import List, Dict, Any
from coze_coding_dev_sdk import LLMClient, Config
from coze_coding_utils.runtime_ctx.context import new_context
from langchain_core.messages import HumanMessage
def test_llm_endpoint(base_url: str = None) -> Dict[str, Any]:
"""
测试 LLM 端点
Args:
base_url: 自定义的 base URL
Returns:
测试结果
"""
result = {
"base_url": base_url or "默认",
"success": False,
"error": None,
"response": None
}
try:
# 创建上下文
ctx = new_context(method="invoke")
# 创建配置
if base_url:
config = Config(
api_key=os.environ.get("COZE_API_KEY", ""),
base_url=base_url,
timeout=30
)
client = LLMClient(config=config, ctx=ctx)
else:
client = LLMClient(ctx=ctx)
# 尝试调用 LLM
messages = [
HumanMessage(content="你好,请回复'测试成功'")
]
print(f"正在测试端点: {base_url or '默认'}...")
response = client.invoke(
messages=messages,
model="doubao-seed-2-0-lite-260215", # 使用轻量模型快速测试
temperature=0.1,
max_completion_tokens=100
)
result["success"] = True
result["response"] = str(response.content)[:100]
print(f"✅ 成功!响应: {result['response']}")
except Exception as e:
result["error"] = str(e)[:200]
print(f"❌ 失败!错误: {result['error']}")
return result
def main():
print("=" * 80)
print("LLM API 端点测试")
print("=" * 80)
print()
# 测试不同的端点
test_endpoints = [
None, # 默认端点
"https://integration.coze.cn/api", # 可能的集成端点
"https://api.coze.cn/v1", # v1 API
"https://api.coze.cn/v1/chat", # chat API
]
results = []
for endpoint in test_endpoints:
result = test_llm_endpoint(endpoint)
results.append(result)
print()
# 输出汇总
print("=" * 80)
print("测试汇总")
print("=" * 80)
print()
for result in results:
status = "✅ 成功" if result["success"] else "❌ 失败"
print(f"{status} - {result['base_url']}")
if result["error"]:
print(f" 错误: {result['error']}")
if result["response"]:
print(f" 响应: {result['response']}")
print()
# 查找成功的端点
successful_endpoints = [r for r in results if r["success"]]
if successful_endpoints:
print("=" * 80)
print("建议")
print("=" * 80)
print()
print("以下端点测试成功,建议使用:")
for r in successful_endpoints:
print(f" - {r['base_url']}")
print()
print("如果使用自定义端点,请在代码中配置:")
print("""
from coze_coding_dev_sdk import LLMClient, Config
config = Config(
base_url="正确的端点",
timeout=30
)
client = LLMClient(config=config, ctx=ctx)
""")
else:
print("=" * 80)
print("诊断")
print("=" * 80)
print()
print("所有端点测试都失败了。可能的原因:")
print("1. 认证信息缺失或无效")
print("2. 网络连接问题")
print("3. LLM 服务不可用")
print("4. 模型 ID 不正确")
print()
print("请检查:")
print("- 确保在 Coze 平台中正确配置了 LLM 服务")
print("- 检查网络连接")
print("- 联系 Coze 技术支持获取正确的 API 端点")
if __name__ == "__main__":
main()

147
scripts/test_urls.py Normal file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""
诊断脚本验证图片 URL Word 文档 URL 是否可访问
"""
import urllib.request
import requests
import sys
from typing import Dict, List, Tuple
def check_url(url: str, headers: Dict[str, str] = None, timeout: int = 10) -> Tuple[bool, str, int]:
"""
检查 URL 是否可访问
Args:
url: 要检查的 URL
headers: HTTP
timeout: 超时时间
Returns:
(is_valid, error_message, status_code)
"""
if headers is None:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': '*/*',
}
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as response:
status_code = response.getcode()
content_type = response.headers.get('Content-Type', '')
# 读取前 100 字节检查
preview = response.read(100)
# 检查是否为 HTML 错误页面
if b'<html' in preview.lower() or b'<!doctype' in preview.lower():
error_msg = f"返回 HTML 页面(状态码: {status_code}"
if b'404' in preview.lower():
error_msg = "返回 404 错误页面"
elif b'403' in preview.lower():
error_msg = "返回 403 禁止访问页面"
return (False, error_msg, status_code)
# 检查内容类型
if 'image' in content_type:
return (True, f"图片 - {content_type}", status_code)
elif 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' in content_type or 'application/msword' in content_type:
return (True, f"Word 文档 - {content_type}", status_code)
else:
return (True, f"其他类型 - {content_type}", status_code)
except urllib.error.HTTPError as e:
status_code = e.code
error_msg = f"HTTP 错误 {status_code}"
if status_code == 404:
error_msg = "404 Not Found - 资源不存在"
elif status_code == 403:
error_msg = "403 Forbidden - 禁止访问"
elif status_code == 401:
error_msg = "401 Unauthorized - 需要认证"
return (False, error_msg, status_code)
except urllib.error.URLError as e:
return (False, f"网络错误: {str(e)[:100]}", 0)
except Exception as e:
return (False, f"未知错误: {str(e)[:100]}", 0)
def main():
# 测试 URL 列表
image_urls = [
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/18/69baa4f5-4826-4901-00e1-c6e66f02947f.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6ff235a12b2.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c7055a396422.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fe7f9c07ef.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c70052d895e3.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fc6ca7c4bf.png?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c33f31-4826-4901-00e1-c6fb50697e06.png?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f614bc06d9.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/25/69c344ba-4826-4901-00e1-c6fd479e0a0e.jpg?x-oss-process=image/resize,w_1000",
"https://dpcclass.oss-cn-beijing.aliyuncs.com/umsupload/2026/03/23/69c1029b-4826-4901-00e1-c6f569b31a14.jpeg?x-oss-process=image/resize,w_1000",
]
doc_url = "https://dpc-oss.23544.com/umsupload/2026/03/25/69c353d0-4826-4901-00e1-c7081bcab988.docx"
print("=" * 80)
print("图片 URL 诊断")
print("=" * 80)
valid_count = 0
invalid_count = 0
for i, url in enumerate(image_urls, 1):
is_valid, msg, status_code = check_url(url)
status = "✅ 有效" if is_valid else "❌ 无效"
print(f"{i}. {status} - {msg}")
print(f" URL: {url}")
print(f" 状态码: {status_code}")
print()
if is_valid:
valid_count += 1
else:
invalid_count += 1
print("=" * 80)
print(f"汇总: {valid_count} 个有效,{invalid_count} 个无效")
print("=" * 80)
print()
print("=" * 80)
print("Word 文档 URL 诊断")
print("=" * 80)
is_valid, msg, status_code = check_url(doc_url)
status = "✅ 有效" if is_valid else "❌ 无效"
print(f"{status} - {msg}")
print(f"URL: {doc_url}")
print(f"状态码: {status_code}")
print()
if invalid_count > 0:
print("=" * 80)
print("⚠️ 警告:部分 URL 无法访问")
print("=" * 80)
print("可能的原因:")
print("1. URL 已过期(阿里云 OSS 签名 URL 有时效性)")
print("2. 访问权限不足(需要认证或 IP 白名单)")
print("3. 网络连接问题Docker 容器网络配置)")
print("4. 文件已被删除或移动")
print()
print("建议:")
print("- 检查这些 URL 是否在浏览器中可以访问")
print("- 如果使用签名 URL确保 URL 未过期")
print("- 检查 Docker 容器的网络配置和 DNS 设置")
print("- 考虑使用公开可访问的 URL 或配置 OSS 访问权限")
sys.exit(1)
else:
print("✅ 所有 URL 均可访问")
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -118,14 +118,44 @@ def download_and_extract_docx(url: str) -> str:
tmp_path = None tmp_path = None
try: try:
# 下载文件 # 验证 URL 格式
response = requests.get(url, timeout=60, allow_redirects=True) if not url or not isinstance(url, str):
response.raise_for_status() raise ValueError("Invalid URL: empty or not a string")
if not url.startswith(('http://', 'https://')):
raise ValueError(f"Invalid URL format: {url[:50]}...")
# 下载文件(增强版)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/msword,*/*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://www.aliyun.com/',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
response = requests.get(url, timeout=60, allow_redirects=True, headers=headers)
# 检查 HTTP 状态码
status_code = response.status_code
if status_code != 200:
error_msg = f"HTTP Error {status_code}"
if status_code == 404:
error_msg = "404 Not Found - Document does not exist"
elif status_code == 403:
error_msg = "403 Forbidden - Access denied"
elif status_code == 401:
error_msg = "401 Unauthorized - Authentication required"
raise ValueError(f"{error_msg}: {url[:50]}...")
# 检查内容类型 # 检查内容类型
content_type = response.headers.get('Content-Type', '') content_type = response.headers.get('Content-Type', '')
logger.debug(f"Response Content-Type: {content_type}") logger.debug(f"Response Content-Type: {content_type}")
if content_type and not any(ct in content_type.lower() for ct in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword', 'application/octet-stream', 'application/zip']):
logger.warning(f"Unexpected Content-Type for Word document: {content_type}")
# 检查文件大小 # 检查文件大小
if len(response.content) < 100: if len(response.content) < 100:
raise ValueError(f"Downloaded file too small: {len(response.content)} bytes") raise ValueError(f"Downloaded file too small: {len(response.content)} bytes")
@ -135,9 +165,19 @@ def download_and_extract_docx(url: str) -> str:
# 可能是 HTML 错误页面 # 可能是 HTML 错误页面
content_preview = response.content[:1000].lower() content_preview = response.content[:1000].lower()
if b'<html' in content_preview or b'<!doctype' in content_preview: if b'<html' in content_preview or b'<!doctype' in content_preview:
raise ValueError("URL returned HTML instead of docx (may need authentication or URL expired)") # 提取错误信息
error_msg = "Unknown error"
if b'404' in content_preview:
error_msg = "404 Not Found"
elif b'403' in content_preview:
error_msg = "403 Forbidden"
elif b'500' in content_preview:
error_msg = "500 Internal Server Error"
raise ValueError(f"URL returned HTML instead of docx ({error_msg}) - URL may be expired or inaccessible")
raise ValueError("Downloaded file is not a valid docx (not ZIP format)") raise ValueError("Downloaded file is not a valid docx (not ZIP format)")
logger.info(f"Successfully downloaded Word document: {len(response.content)} bytes")
# 保存到临时文件 # 保存到临时文件
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_file: with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_file:
tmp_file.write(response.content) tmp_file.write(response.content)

View File

@ -225,24 +225,79 @@ def recognize_and_correct_node(
correction_results=[] correction_results=[]
) )
# 验证 URL 是否可访问(尝试下载前 100 字节验证 # 验证 URL 是否可访问(增强版:检查 HTTP 状态码和内容类型
try: try:
import urllib.request import urllib.request
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://www.aliyun.com/',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
} }
req = urllib.request.Request(image_url, headers=headers) req = urllib.request.Request(image_url, headers=headers)
# 使用 urlopen 并捕获 HTTP 错误
try:
with urllib.request.urlopen(req, timeout=10) as response: with urllib.request.urlopen(req, timeout=10) as response:
# 只读取前 100 字节验证 # 检查 HTTP 状态码
preview = response.read(100) status_code = response.getcode()
if status_code not in (200, 206): # 200 OK, 206 Partial Content
raise ValueError(f"HTTP status code: {status_code}")
# 检查 Content-Type
content_type = response.headers.get('Content-Type', '')
if not content_type.startswith('image/'):
logger.warning(f"Unexpected Content-Type: {content_type} for URL: {image_url[:50]}")
# 只读取前 200 字节验证
preview = response.read(200)
if len(preview) < 10: if len(preview) < 10:
raise ValueError("Image too small or invalid") raise ValueError("Image too small or invalid")
# 检查是否为 HTML404 页面)
if b'<html' in preview.lower() or b'<!doctype' in preview.lower(): # 检查是否为 HTML404/500 错误页面)
raise ValueError("URL returned HTML page (404/500 error)") preview_lower = preview.lower()
logger.info(f"URL validation passed: {image_url[:50]}...") if b'<html' in preview_lower or b'<!doctype' in preview_lower:
# 提取错误信息
error_msg = "Unknown error"
if b'404' in preview_lower:
error_msg = "404 Not Found"
elif b'403' in preview_lower:
error_msg = "403 Forbidden"
elif b'500' in preview_lower:
error_msg = "500 Internal Server Error"
raise ValueError(f"URL returned HTML page ({error_msg})")
logger.info(f"URL validation passed: {image_url[:50]}... (status={status_code}, size={len(preview)} bytes, type={content_type})")
except urllib.error.HTTPError as e:
# 专门处理 HTTP 错误
status_code = e.code
error_msg = f"HTTP Error {status_code}"
if status_code == 404:
error_msg = "404 Not Found - Image does not exist"
elif status_code == 403:
error_msg = "403 Forbidden - Access denied"
elif status_code == 401:
error_msg = "401 Unauthorized - Authentication required"
# 详细记录错误信息
full_error = f"{error_msg}: {image_url[:50]}..."
logger.error(f"[URL VALIDATION FAILED] {full_error}")
logger.error(f"[URL DETAILS] Full URL: {image_url}")
logger.error(f"[URL DETAILS] Error: {str(e)[:200]}")
raise ValueError(full_error)
except urllib.error.URLError as e:
# 处理网络错误
raise ValueError(f"URL Error: {str(e)[:100]} - {image_url[:50]}...")
except ValueError as e:
logger.error(f"Image URL validation failed: {image_url[:50]}... Error: {str(e)[:150]}")
return RecognizeAndCorrectOutput(
question_items=[],
correction_results=[]
)
except Exception as e: except Exception as e:
logger.error(f"Image URL not accessible: {image_url[:50]}... Error: {str(e)[:100]}") logger.error(f"Unexpected error validating image URL: {image_url[:50]}... Error: {str(e)[:150]}")
return RecognizeAndCorrectOutput( return RecognizeAndCorrectOutput(
question_items=[], question_items=[],
correction_results=[] correction_results=[]