This commit is contained in:
parent
5cdea9140f
commit
b2ad58e30c
52
AGENTS.md
52
AGENTS.md
|
|
@ -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`)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
@ -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"]
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 3:Token 过期
|
||||||
|
|
||||||
|
**检查**:
|
||||||
|
```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 错误
|
||||||
|
- ✅ 工作流正常运行
|
||||||
|
|
@ -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"}
|
||||||
|
|
@ -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 服务网络配置
|
||||||
|
|
@ -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 状态码
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
# 检查是否为 HTML(404 页面)
|
|
||||||
if b'<html' in preview.lower() or b'<!doctype' in preview.lower():
|
# 检查是否为 HTML(404/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=[]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue