From b2ad58e30cd3bda620da49a601378ce10b75a62a Mon Sep 17 00:00:00 2001 From: zhangquan Date: Tue, 31 Mar 2026 10:47:52 +0800 Subject: [PATCH] 1 --- AGENTS.md | 52 ++++ Dockerfile | 3 +- assets/Dockerfile | 61 ++++ assets/Dockerfile.fixed | 51 ++++ assets/check_env.py | 139 +++++++++ assets/docker-compose.yml | 24 ++ assets/fix_docker_404.md | 267 ++++++++++++++++++ assets/physics-correction.log | 43 +++ docs/docker_deployment_guide.md | 210 ++++++++++++++ docs/fix_llm_404_error.md | 216 ++++++++++++++ scripts/check_env_vars.py | 149 ++++++++++ scripts/diagnose_docker_env.py | 196 +++++++++++++ scripts/diagnose_full_error.py | 221 +++++++++++++++ scripts/test_llm_api.py | 147 ++++++++++ scripts/test_urls.py | 147 ++++++++++ src/graphs/nodes/doc_extract_node.py | 48 +++- .../nodes/recognize_and_correct_node.py | 79 +++++- 17 files changed, 2036 insertions(+), 17 deletions(-) create mode 100644 assets/Dockerfile create mode 100644 assets/Dockerfile.fixed create mode 100644 assets/check_env.py create mode 100644 assets/docker-compose.yml create mode 100644 assets/fix_docker_404.md create mode 100644 assets/physics-correction.log create mode 100644 docs/docker_deployment_guide.md create mode 100644 docs/fix_llm_404_error.md create mode 100644 scripts/check_env_vars.py create mode 100644 scripts/diagnose_docker_env.py create mode 100644 scripts/diagnose_full_error.py create mode 100644 scripts/test_llm_api.py create mode 100644 scripts/test_urls.py diff --git a/AGENTS.md b/AGENTS.md index ffc1fcc..486a35b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1061,3 +1061,55 @@ print(response.json()) - ✅ 空请求体返回友好的错误信息 - ✅ 错误的 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 +``` + +**方案 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`) diff --git a/Dockerfile b/Dockerfile index e70fe21..7865026 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,7 +45,8 @@ ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \ 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" + COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \ + COZE_WORKSPACE_ID=7622238752642957347 # 升级 pip 和构建工具 RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja diff --git a/assets/Dockerfile b/assets/Dockerfile new file mode 100644 index 0000000..e70fe21 --- /dev/null +++ b/assets/Dockerfile @@ -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"] \ No newline at end of file diff --git a/assets/Dockerfile.fixed b/assets/Dockerfile.fixed new file mode 100644 index 0000000..7923f72 --- /dev/null +++ b/assets/Dockerfile.fixed @@ -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"] diff --git a/assets/check_env.py b/assets/check_env.py new file mode 100644 index 0000000..8468dfe --- /dev/null +++ b/assets/check_env.py @@ -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()) diff --git a/assets/docker-compose.yml b/assets/docker-compose.yml new file mode 100644 index 0000000..bea5ca8 --- /dev/null +++ b/assets/docker-compose.yml @@ -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 diff --git a/assets/fix_docker_404.md b/assets/fix_docker_404.md new file mode 100644 index 0000000..e08136d --- /dev/null +++ b/assets/fix_docker_404.md @@ -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 错误 +- ✅ 工作流正常运行 diff --git a/assets/physics-correction.log b/assets/physics-correction.log new file mode 100644 index 0000000..fdccbf6 --- /dev/null +++ b/assets/physics-correction.log @@ -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__..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__..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__..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__..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__..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__..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__..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__..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__..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__..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__..wrap", "asctime": "2026-03-31 10:11:58,866"} diff --git a/docs/docker_deployment_guide.md b/docs/docker_deployment_guide.md new file mode 100644 index 0000000..e1c97c5 --- /dev/null +++ b/docs/docker_deployment_guide.md @@ -0,0 +1,210 @@ +# Docker 部署问题排查指南 + +## 问题描述 +在 Docker 环境中部署工作流时,出现以下错误: +``` +S3对象不存在: ...404 Not Found... +``` + +## 诊断步骤 + +### 1. 运行 URL 诊断脚本 + +在 Docker 容器内运行: +```bash +python scripts/test_urls.py +``` + +**预期结果**:所有 URL 应该显示 `✅ 有效` + +**如果显示 `❌ 无效`**: +- 检查 Docker 容器网络配置 +- 检查 DNS 解析 +- 检查防火墙规则 + +### 2. 检查 Docker 网络配置 + +```bash +# 检查容器是否可以访问外网 +docker exec ping -c 3 8.8.8.8 + +# 检查 DNS 解析 +docker exec nslookup dpcclass.oss-cn-beijing.aliyuncs.com + +# 检查是否能访问 OSS +docker exec 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' /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 +``` + +### 方案 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 +``` + +### 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 状态码 diff --git a/scripts/check_env_vars.py b/scripts/check_env_vars.py new file mode 100644 index 0000000..5a390ce --- /dev/null +++ b/scripts/check_env_vars.py @@ -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() diff --git a/scripts/diagnose_docker_env.py b/scripts/diagnose_docker_env.py new file mode 100644 index 0000000..da9e770 --- /dev/null +++ b/scripts/diagnose_docker_env.py @@ -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 ") + 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() diff --git a/scripts/diagnose_full_error.py b/scripts/diagnose_full_error.py new file mode 100644 index 0000000..40263b0 --- /dev/null +++ b/scripts/diagnose_full_error.py @@ -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) diff --git a/scripts/test_llm_api.py b/scripts/test_llm_api.py new file mode 100644 index 0000000..d042986 --- /dev/null +++ b/scripts/test_llm_api.py @@ -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() diff --git a/scripts/test_urls.py b/scripts/test_urls.py new file mode 100644 index 0000000..39f1a1c --- /dev/null +++ b/scripts/test_urls.py @@ -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' 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() diff --git a/src/graphs/nodes/doc_extract_node.py b/src/graphs/nodes/doc_extract_node.py index 1dd9e8e..694cb35 100644 --- a/src/graphs/nodes/doc_extract_node.py +++ b/src/graphs/nodes/doc_extract_node.py @@ -118,14 +118,44 @@ def download_and_extract_docx(url: str) -> str: tmp_path = None try: - # 下载文件 - response = requests.get(url, timeout=60, allow_redirects=True) - response.raise_for_status() + # 验证 URL 格式 + if not url or not isinstance(url, str): + 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', '') 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: raise ValueError(f"Downloaded file too small: {len(response.content)} bytes") @@ -135,9 +165,19 @@ def download_and_extract_docx(url: str) -> str: # 可能是 HTML 错误页面 content_preview = response.content[:1000].lower() if b'