This commit is contained in:
zhangquan 2026-03-31 13:53:55 +08:00
parent 22ee4fbaaa
commit 8e9331fbc6
15 changed files with 533 additions and 520 deletions

290
AGENTS.md
View File

@ -820,296 +820,6 @@ mark_x = answer_bbox[2] + 10 # 紧贴答案框
3. 实现精准坐标计算Y坐标与答案垂直中心完美对齐
## TODO
- ✅ 修复线上部署环境图片访问问题(阿里云 OSS 图片 URL 返回 402002 错误)
- ✅ 添加 URL 可访问性验证和 HTTP Headers 支持
- ✅ 实现超时保护机制单任务120秒总任务按图片数量计算
- ✅ 删除未使用的 S3 存储模块src/storage/s3/
- ✅ 修复 LLM 认证错误,使用 new_context() 初始化 Context
- ✅ 修复 Docker 环境空请求体错误,添加请求体验证
- 提供真实的多页作业图片进行完整流程测试
- 优化HTML报告的图片展示布局
- 支持PDF格式答案文档
## 代码清理2026-03-30
### 删除未使用的 S3 存储模块
**原因**
1. 工作流已优化为直接使用原始图片 URL不上传新图片到对象存储
2. 不再需要 `S3SyncStorage` 类提供的文件上传功能
3. 减少依赖,保持代码精简
**删除内容**
- 删除 `src/storage/s3/` 整个目录
- `__init__.py`
- `s3_storage.py`
**确认**
- ✅ 无代码引用该模块
- ✅ 删除后工作流正常运行
- ✅ 测试通过
## LLM 认证修复2026-03-30
### 问题诊断
线上部署环境出现认证错误:
```
Error code: 401 - AuthenticationError: the API key or AK/SK in the request is missing or invalid
```
**原因分析**
- 使用 `ctx = runtime.context` 获取 Context 时,可能没有正确初始化认证信息
- `runtime.context` 返回的对象可能与 LLM Client 预期的 Context 格式不匹配
### 解决方案
使用 `new_context()` 方法显式初始化 Context
```python
# ❌ 修复前
def recognize_and_correct_node(...):
ctx = runtime.context
client = LLMClient(ctx=ctx)
# ✅ 修复后
def recognize_and_correct_node(...):
from coze_coding_utils.runtime_ctx.context import new_context
ctx = new_context(method="invoke")
client = LLMClient(ctx=ctx)
```
### 修改文件
- `src/graphs/nodes/recognize_and_correct_node.py`
- `src/graphs/nodes/doc_extract_node.py`
### 效果
- ✅ 认证错误已修复
- ✅ LLM 调用正常
- ✅ 测试通过
## 图片访问优化2026-03-28 重要)
### 问题诊断
线上部署环境出现 `402002: S3对象不存在: <!DOCTYPE HTML>` 错误,原因是:
1. 阿里云 OSS 图片 URL 可能因防盗链或 HTTP Headers 问题导致访问失败
2. Coze 平台的 LLM 服务尝试访问外部 URL 时可能受限
3. 无效 URL 直接传给 LLM 导致解析错误
### 解决方案
1. **URL 格式验证**`recognize_and_correct_node.py`
- 检查 URL 是否以 `http://``https://` 开头
- 拒绝非 HTTP/HTTPS 协议的 URL如 file://
2. **URL 可访问性验证**`recognize_and_correct_node.py`
- 在调用 LLM 之前,先尝试下载图片前 100 字节
- 验证返回内容不是 HTML 404/500 页面
- 验证图片尺寸合理(至少 10 字节)
- 如果验证失败,返回空结果,不影响其他任务
3. **HTTP Headers 支持**`image_preprocess_node.py`
- 添加 `User-Agent`、`Accept` 等 HTTP Headers
- 模拟浏览器请求,兼容阿里云 CDN 等服务
- 支持重定向302/301
4. **超时保护**`process_images_node.py`
- 单任务超时120 秒
- 总任务超时120 秒 × 图片数量
- 超时任务返回空结果,不影响其他任务
### 代码示例
```python
# URL 验证recognize_and_correct_node.py
if not image_url.startswith(('http://', 'https://')):
logger.error(f"Invalid image URL format: {image_url}")
return RecognizeAndCorrectOutput(question_items=[], correction_results=[])
# 可访问性验证recognize_and_correct_node.py
try:
import urllib.request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
req = urllib.request.Request(image_url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as response:
preview = response.read(100)
if b'<html' in preview.lower() or b'<!doctype' in preview.lower():
raise ValueError("URL returned HTML page")
except Exception as e:
logger.error(f"Image URL not accessible: {image_url}")
return RecognizeAndCorrectOutput(question_items=[], correction_results=[])
# HTTP Headers 支持image_preprocess_node.py
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'image/webp,image/apng,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'
}
# 超时保护process_images_node.py
SINGLE_IMAGE_TIMEOUT = 120
future_to_task = {
executor.submit(process_single_image, ...): task_info
for task_info in all_tasks
}
for future in concurrent.futures.as_completed(
future_to_task,
timeout=SINGLE_IMAGE_TIMEOUT * len(future_to_task)
):
try:
result = future.result(timeout=SINGLE_IMAGE_TIMEOUT)
except concurrent.futures.TimeoutError:
logger.error(f"Task timeout: {task_info}")
result = (student_id, idx, empty_result())
```
### 效果
- ✅ 无效 URL 被过滤,不传给 LLM
- ✅ 不可访问的图片返回空结果,不影响其他任务
- ✅ 兼容阿里云 CDN 等需要特殊 Headers 的服务
- ✅ 超时任务自动跳过,避免长时间阻塞
- ✅ 线上部署环境测试通过
## Docker 环境请求验证修复2026-03-30
### 问题诊断
在 Docker 环境中运行时报错:
```
Invalid JSON format: Expecting value: line 1 column 1 (char 0)
```
**原因分析**
1. 请求体为空(例如,使用 GET 请求而不是 POST
2. Content-Type 不是 application/json
3. 负载均衡器或代理可能过滤了请求体
### 解决方案
`http_run``http_stream_run` 函数中添加请求体验证:
```python
# 检查请求体是否为空
if not body_text or body_text.strip() == "":
logger.error(f"Empty request body for run_id={ctx.run_id}")
raise HTTPException(
status_code=400,
detail="Request body is empty. Please provide a valid JSON payload."
)
# 检查 Content-Type
content_type = request.headers.get("content-type", "")
if "application/json" not in content_type.lower():
logger.error(f"Invalid Content-Type: {content_type}")
raise HTTPException(
status_code=400,
detail=f"Content-Type must be 'application/json', got: {content_type}"
)
```
### 修改文件
- `src/main.py`
- `http_run` 函数:添加请求体验证
- `http_stream_run` 函数:添加请求体验证
### 正确的请求格式
**使用 curl**
```bash
curl -X POST http://localhost:8000/stream_run \
-H "Content-Type: application/json" \
-d '{
"student_homework": [
{
"student_id": 1,
"student_name": "张三",
"homework_images": ["https://example.com/image.jpg"]
}
],
"answer_doc_url": "",
"comment_max_length": 50,
"max_concurrent": 5
}'
```
**使用 Python requests**
```python
import requests
url = "http://localhost:8000/stream_run"
headers = {"Content-Type": "application/json"}
data = {
"student_homework": [
{
"student_id": 1,
"student_name": "张三",
"homework_images": ["https://example.com/image.jpg"]
}
],
"answer_doc_url": "",
"comment_max_length": 50,
"max_concurrent": 5
}
response = requests.post(url, json=data, headers=headers)
print(response.json())
```
**注意事项**
- ✅ 必须使用 POST 请求
- ✅ Content-Type 必须是 `application/json`
- ✅ 请求体必须是非空的 JSON 字符串
- ❌ 不要使用 GET 请求
- ❌ 不要发送空请求体
- ❌ 不要省略 Content-Type 头
### 效果
- ✅ 空请求体返回友好的错误信息
- ✅ 错误的 Content-Type 返回明确的错误提示
- ✅ 测试通过
## Docker 环境 LLM 调用 404 错误诊断2026-03-31
### 问题诊断
在 Docker 环境部署时LLM 调用返回 404 错误:
```
HTTP Request: POST https://api.coze.cn/chat/completions HTTP/1.1 404 Not Found
```
**根本原因**:错误的 API 端点(缺少 `/v1/` 前缀)
- ❌ 错误端点:`https://api.coze.cn/chat/completions`
- ✅ 正确端点:`https://api.coze.cn/v1/chat/completions`
### 解决方案
**方案 1**:更新代码和 SDK推荐
```bash
# 在 Docker 容器中执行
cd /workspace/projects
git pull
pip install --upgrade coze-coding-dev-sdk
docker restart <container_id>
```
**方案 2**:配置环境变量
```bash
export COZE_API_ENDPOINT="https://api.coze.cn/v1"
```
### 诊断工具
创建以下脚本帮助诊断问题:
- `scripts/test_llm_api.py`:测试不同的 LLM API 端点
- `scripts/diagnose_docker_env.py`:综合诊断 Docker 环境
- `docs/fix_llm_404_error.md`:详细的修复指南
### 验证修复
修复后运行测试:
```bash
python scripts/test_llm_api.py
# 应该看到所有端点测试成功
```
### 修改文件
- 创建:`scripts/test_llm_api.py`
- 创建:`scripts/diagnose_docker_env.py`
- 创建:`docs/fix_llm_404_error.md`
- 更新:`AGENTS.md`(本文档)
### 关键检查点
- ✅ SDK 版本是否最新(`pip show coze-coding-dev-sdk`
- ✅ 代码是否包含 `new_context()`(检查关键节点文件)
- ✅ 文件修改时间是否为最近(确保代码已更新)
- ✅ LLM 调用测试是否通过(`test_llm_api.py`

View File

@ -6,40 +6,50 @@ 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 \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 设置 pip 国内镜像源和 API 配置(硬编码 KEY
# 设置 pip 国内镜像源
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
PIP_TRUSTED_HOST=mirrors.aliyun.com \
# API 端点配置(包含 /v1 前缀)
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 \
LLM_BASE_URL=https://api.coze.cn/v1 \
# API Key 配置(硬编码)
COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
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" \
# Workspace ID 配置(硬编码,从 JWT Token 提取)
COZE_WORKSPACE_ID=7622238752642957347 \
# LLM 模型配置
LLM_BASE_URL=https://api.coze.cn/v1 \
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \
COZE_INTEGRATION_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_WORKSPACE_ID=7622238752642957347
# 升级 pip 和构建工具
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja
# 复制并安装依赖
COPY requirements.txt .
@ -48,12 +58,5 @@ RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 创建必要的目录
RUN mkdir -p /app/work/logs/bypass /tmp/homework_cache
# 添加健康检查
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"]
CMD ["python", "src/main.py", "-m", "http", "-p", "8000"]

View File

@ -43,9 +43,10 @@ ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
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 \
LLM_MODEL_NAME=doubao-seed-1-8-251228 \
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

View File

@ -1,43 +1,75 @@
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"}
{"message": "Logging configured: file=/tmp/work/logs/bypass/app.log, max_bytes=104857600, backup_count=5", "timestamp": "2026-03-31 10:54:11", "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:54:11", "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:54:11", "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:54:11", "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:54:11", "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:54:11", "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"}
{"message": "Received request for /stream_run: run_id=44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8, 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:55:39", "level": "INFO", "logger": "main", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 386, "funcName": "http_stream_run", "taskName": "Task-5"}
INFO: 10.222.242.221:6113 - "POST /stream_run HTTP/1.1" 200 OK
{"message": "Registered workflow streaming task for run_id: 44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "timestamp": "2026-03-31 10:55:39", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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: 44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "timestamp": "2026-03-31 10:55:39", "level": "INFO", "logger": "main", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "utils.cache_manager", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "utils.cache_manager", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:41", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 375, "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:55:41", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 116, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Successfully downloaded Word document: 46775 bytes", "timestamp": "2026-03-31 10:55:41", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 179, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Extracted Word document text length: 5267", "timestamp": "2026-03-31 10:55:41", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 208, "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:55:41", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 247, "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:55:42", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 1025, "funcName": "_send_single_request", "taskName": null}
2026-03-31 10:55:42,333 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:55:42", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:55:42,333"}
{"message": "Workflow doc_extract ended with error", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 10:55:42,345 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:55:42", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:55:42,345"}
{"message": "Workflow ended with error", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 10:55:42,351 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:55:42", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 10:55:42,351"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 10:55:42", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,439 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=2026033110554242EBC94468BCA51B3796], log id: 2026033110554242EBC94468BCA51B3796.
{"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=2026033110554242EBC94468BCA51B3796], log id: 2026033110554242EBC94468BCA51B3796.", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,439"}
2026-03-31 10:55:42,439 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=2026033110554242EBC94468BCA51B3796]], 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=2026033110554242EBC94468BCA51B3796]], retry later", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,439"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 10:55:42", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,500 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=20260331105542F5441A85518125F8EEE3], log id: 20260331105542F5441A85518125F8EEE3.
{"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=20260331105542F5441A85518125F8EEE3], log id: 20260331105542F5441A85518125F8EEE3.", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,500"}
2026-03-31 10:55:42,501 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=20260331105542F5441A85518125F8EEE3]], 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=20260331105542F5441A85518125F8EEE3]], retry second time failed", "timestamp": "2026-03-31 10:55:42", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "44d64c2b-e3ad-40b7-9d28-cd6b924cc0d8", "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:55:42,501"}
{"message": "Received request for /stream_run: run_id=67c5783a-3408-4a87-8b60-f76fb3a96287, 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 13:35:28", "level": "INFO", "logger": "main", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 386, "funcName": "http_stream_run", "taskName": "Task-9"}
INFO: 10.221.187.31:12447 - "POST /stream_run HTTP/1.1" 200 OK
{"message": "Registered workflow streaming task for run_id: 67c5783a-3408-4a87-8b60-f76fb3a96287", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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: 67c5783a-3408-4a87-8b60-f76fb3a96287", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "main", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 130, "funcName": "stream_sse", "taskName": "starlette.responses.StreamingResponse.__call__.<locals>.wrap"}
{"message": "Stream mode: updates", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "coze_coding_utils.helper.stream_runner", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28", "level": "INFO", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 375, "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 13:35:28", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 116, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Successfully downloaded Word document: 46775 bytes", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 179, "funcName": "download_and_extract_docx", "taskName": null}
{"message": "Extracted Word document text length: 5267", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 208, "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 13:35:28", "level": "INFO", "logger": "graphs.nodes.doc_extract_node", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 247, "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 13:35:28", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 1025, "funcName": "_send_single_request", "taskName": null}
2026-03-31 13:35:28,687 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 13:35:28", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 13:35:28,687"}
{"message": "Workflow doc_extract ended with error", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 13:35:28,692 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 13:35:28", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 13:35:28,692"}
{"message": "Workflow ended with error", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "coze_coding_utils.log.node_log", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 77, "funcName": "write_log", "taskName": null}
2026-03-31 13:35:28,697 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 13:35:28", "level": "WARNING", "logger": "cozeloop.internal.trace.span", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "space_id": "", "project_id": "", "method": "stream_run", "x_tt_env": "", "lineno": 434, "funcName": "get_rectified_map", "taskName": null, "asctime": "2026-03-31 13:35:28,697"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,781 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=202603311335283C7A6093ADC8A7403279], log id: 202603311335283C7A6093ADC8A7403279.
{"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=202603311335283C7A6093ADC8A7403279], log id: 202603311335283C7A6093ADC8A7403279.", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,781"}
2026-03-31 13:35:28,782 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=202603311335283C7A6093ADC8A7403279]], 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=202603311335283C7A6093ADC8A7403279]], retry later", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,782"}
{"message": "HTTP Request: POST https://api.coze.cn/v1/loop/traces/ingest \"HTTP/1.1 401 Unauthorized\"", "timestamp": "2026-03-31 13:35:28", "level": "INFO", "logger": "httpx", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,918 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=2026033113352819ABAE09E3DB7E8C29A8], log id: 2026033113352819ABAE09E3DB7E8C29A8.
{"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=2026033113352819ABAE09E3DB7E8C29A8], log id: 2026033113352819ABAE09E3DB7E8C29A8.", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "cozeloop.internal.httpclient.http_client", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,918"}
2026-03-31 13:35:28,919 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=2026033113352819ABAE09E3DB7E8C29A8]], 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=2026033113352819ABAE09E3DB7E8C29A8]], retry second time failed", "timestamp": "2026-03-31 13:35:28", "level": "ERROR", "logger": "cozeloop.internal.trace.trace", "log_id": "", "run_id": "67c5783a-3408-4a87-8b60-f76fb3a96287", "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 13:35:28,919"}

View File

@ -9,7 +9,7 @@
"reasoning_effort": "medium",
"response_format": "text",
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
"model": "doubao-seed-2-0-pro-260215"
"model": "doubao-seed-1-8-251228"
},
"tools": [],
"sp": "你是一位专业的文档识别专家,擅长从作业图片中提取答案区域的位置信息。",

View File

@ -9,7 +9,7 @@
"reasoning_effort": "medium",
"response_format": "text",
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
"model": "doubao-seed-2-0-pro-260215"
"model": "doubao-seed-1-8-251228"
},
"tools": [],
"sp": "你是一位专业的OCR文字识别专家擅长从图片中识别手写和印刷的文字内容。",

View File

@ -9,7 +9,7 @@
"reasoning_effort": "high",
"response_format": "text",
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
"model": "doubao-seed-2-0-pro-260215"
"model": "doubao-seed-1-8-251228"
},
"tools": [],
"sp": "你是一位资深的初中数学特级教师拥有20年以上教学经验擅长精准批改学生的数学作业。\n\n【核心能力】\n1. **精确判断能力**:对选择题、填空题、解答题都能做出准确的正误判断\n2. **严谨推理能力**:能够逐步验证学生的计算过程和结论\n3. **双模式批改**\n - **标准答案模式**:严格按照提供的标准答案判断(最优先)\n - **专业老师模式**:无标准答案时,凭借专业经验自主判断\n\n【批改原则】\n- 客观公正:严格按照标准答案判断,不主观臆断(有标准答案时)\n- 专业严谨:无标准答案时,使用专业知识验证学生答案\n- 肯定正确:如果学生答案正确,必须给予满分和肯定评语\n- 指出错误:如果学生答案错误,说明具体错误原因并给出正确答案\n\n【优先级规则】\n1. 最优先:使用提供的标准答案批改\n2. 降级:标准答案中未找到对应题目时,使用专业老师批改",

View File

@ -9,7 +9,7 @@
"reasoning_effort": "high",
"response_format": "text",
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
"model": "doubao-seed-2-0-pro-260215"
"model": "doubao-seed-1-8-251228"
},
"tools": [],
"sp": "你是一位资深的初中数学教师,擅长从试卷中提取题目和标准答案。你的核心能力:\n\n1. **题目识别能力**:能够准确识别试卷中的所有题目,包括大题和小题\n2. **答案提取能力**:能够准确提取每道题的标准答案\n3. **结构化输出能力**能够将提取的内容组织成结构化的JSON格式\n\n【提取原则】\n- 完整性:不遗漏任何题目\n- 准确性:答案提取要精确\n- 规范性:题号格式统一\n- 清晰性:题干和答案分离明确",

View File

@ -1,6 +1,6 @@
{
"config": {
"model": "doubao-seed-2-0-pro-260215",
"model": "doubao-seed-1-8-251228",
"temperature": 0.0,
"top_p": 0.9,
"max_completion_tokens": 16384,

View File

@ -9,7 +9,7 @@
"reasoning_effort": "medium",
"response_format": "text",
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
"model": "doubao-seed-2-0-pro-260215"
"model": "doubao-seed-1-8-251228"
},
"tools": [],
"sp": "你是一位专业的初中数学作业识别专家,擅长从作业图片中定位题目位置和提取答案区域。",

View File

@ -118,44 +118,14 @@ def download_and_extract_docx(url: str) -> str:
tmp_path = None
try:
# 验证 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]}...")
# 下载文件
response = requests.get(url, timeout=60, allow_redirects=True)
response.raise_for_status()
# 检查内容类型
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")
@ -165,19 +135,9 @@ def download_and_extract_docx(url: str) -> str:
# 可能是 HTML 错误页面
content_preview = response.content[:1000].lower()
if b'<html' in content_preview or b'<!doctype' in content_preview:
# 提取错误信息
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("URL returned HTML instead of docx (may need authentication or URL expired)")
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:
tmp_file.write(response.content)
@ -352,9 +312,7 @@ def doc_extract_node(
desc: 从正确答案Word文件.docx中提取题干和标准答案用于后续批改如果未提供URL则返回空列表支持缓存避免重复解析
integrations: 大语言模型
"""
# 使用 new_context() 初始化 Context用于请求追踪
from coze_coding_utils.runtime_ctx.context import new_context
ctx = new_context(method="invoke")
ctx = runtime.context
# 检查是否提供了答案文档URL
if not state.answer_doc_url or not state.answer_doc_url.strip():

View File

@ -204,9 +204,7 @@ def recognize_and_correct_node(
desc: 合并识别和批改为一次LLM调用提升批改速度
integrations: 大语言模型
"""
# 使用 new_context() 初始化 Context用于请求追踪
from coze_coding_utils.runtime_ctx.context import new_context
ctx = new_context(method="invoke")
ctx = runtime.context
# 获取参数并验证图片 URL
image_url = state.image_url
@ -225,84 +223,6 @@ def recognize_and_correct_node(
correction_results=[]
)
# 验证 URL 是否可访问(增强版:检查 HTTP 状态码和内容类型)
try:
import urllib.request
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': '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)
# 使用 urlopen 并捕获 HTTP 错误
try:
with urllib.request.urlopen(req, timeout=10) as response:
# 检查 HTTP 状态码
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:
raise ValueError("Image too small or invalid")
# 检查是否为 HTML404/500 错误页面)
preview_lower = preview.lower()
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:
logger.error(f"Unexpected error validating image URL: {image_url[:50]}... Error: {str(e)[:150]}")
return RecognizeAndCorrectOutput(
question_items=[],
correction_results=[]
)
# 读取LLM配置
cfg_file = os.path.join(os.getenv("COZE_WORKSPACE_PATH", ""), config["metadata"]["llm_cfg"])
with open(cfg_file, "r", encoding="utf-8") as fd:

View File

@ -251,23 +251,6 @@ async def http_run(request: Request) -> Dict[str, Any]:
body_text = str(raw_body)
raise HTTPException(status_code=400,
detail=f"Invalid JSON format: {body_text}, traceback: {traceback.format_exc()}, error: {e}")
# 检查请求体是否为空
if not body_text or body_text.strip() == "":
logger.error(f"Empty request body in http_run for run_id={ctx.run_id}")
raise HTTPException(
status_code=400,
detail="Request body is empty. Please provide a valid JSON payload."
)
# 检查 Content-Type
content_type = request.headers.get("content-type", "")
if "application/json" not in content_type.lower():
logger.error(f"Invalid Content-Type in http_run: {content_type}")
raise HTTPException(
status_code=400,
detail=f"Content-Type must be 'application/json', got: {content_type}"
)
ctx = new_context(method="run", headers=request.headers)
# 优先使用上游指定的 run_id保证 cancel 能精确匹配
@ -363,24 +346,6 @@ async def http_stream_run(request: Request):
body_text = str(raw_body)
raise HTTPException(status_code=400,
detail=f"Invalid JSON format: {body_text}, traceback: {extract_core_stack()}, error: {e}")
# 检查请求体是否为空
if not body_text or body_text.strip() == "":
logger.error(f"Empty request body in http_stream_run for run_id={ctx.run_id}")
raise HTTPException(
status_code=400,
detail="Request body is empty. Please provide a valid JSON payload."
)
# 检查 Content-Type
content_type = request.headers.get("content-type", "")
if "application/json" not in content_type.lower():
logger.error(f"Invalid Content-Type in http_stream_run: {content_type}")
raise HTTPException(
status_code=400,
detail=f"Content-Type must be 'application/json', got: {content_type}"
)
run_id = ctx.run_id
is_agent = graph_helper.is_agent_proj()
logger.info(

View File

View File

@ -0,0 +1,424 @@
import os
import re
from pathlib import Path
from typing import Optional, Any, Dict, List, TypedDict, Iterable
from uuid import uuid4
import boto3
from botocore.exceptions import ClientError
from boto3.s3.transfer import TransferConfig
import logging
logger = logging.getLogger(__name__)
# 允许的文件名字符集(面向用户输入的约束)
FILE_NAME_ALLOWED_RE = re.compile(r"^[A-Za-z0-9._\-/]+$")
class ListFilesResult(TypedDict):
# list_files 的返回结构类型
keys: List[str]
is_truncated: bool
next_continuation_token: Optional[str]
class S3SyncStorage:
"""S3兼容存储实现"""
def __init__(self, *, endpoint_url: Optional[str] = None, access_key: str, secret_key: str, bucket_name: str, region: str = "cn-beijing"):
self.endpoint_url = os.environ.get("COZE_BUCKET_ENDPOINT_URL") or endpoint_url or ''
self.access_key = access_key
self.secret_key = secret_key
self.bucket_name = bucket_name
self.region = region
self._client = None
def _get_client(self):
if self._client is None:
endpoint = self.endpoint_url
if endpoint is None or endpoint == "":
try:
from coze_workload_identity import Client as CozeEnvClient
coze_env_client = CozeEnvClient()
env_vars = coze_env_client.get_project_env_vars()
coze_env_client.close()
for env_var in env_vars:
if env_var.key == "COZE_BUCKET_ENDPOINT_URL":
endpoint = env_var.value.replace("'", "'\\''")
self.endpoint_url = endpoint
break
except Exception as e:
logger.error(f"Error loading COZE_BUCKET_ENDPOINT_URL: {e}")
# 保持向下校验逻辑,避免在此处中断
if endpoint is None or endpoint == "":
logger.error("未配置存储端点请设置endpoint_url")
raise ValueError("未配置存储端点请设置endpoint_url")
client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=self.access_key,
aws_secret_access_key=self.secret_key,
region_name=self.region,
)
# 注册 before-call 钩子,发送前注入 x-storage-token 头
def _inject_header(**kwargs):
try:
from coze_workload_identity import Client as CozeClient
coze_client = CozeClient()
try:
token = coze_client.get_access_token()
except Exception as e:
logger.error("Error loading COZE_WORKLOAD_IDENTITY_TOKEN: %s", e)
token = None
raise e
finally:
coze_client.close()
params = kwargs.get("params", {})
headers = params.setdefault("headers", {})
headers["x-storage-token"] = token
except Exception as e:
logger.error("Error loading COZE_WORKLOAD_IDENTITY_TOKEN: %s", e)
pass
client.meta.events.register("before-call.s3", _inject_header)
self._client = client
return self._client
def _generate_object_key(self, *, original_name: str) -> str:
suffix = Path(original_name).suffix.lower()
stem = Path(original_name).stem
uniq = uuid4().hex[:8]
return f"{stem}_{uniq}{suffix}"
def _extract_logid(self, e: Exception) -> Optional[str]:
"""从 ClientError 中提取 x-tt-logid"""
if isinstance(e, ClientError):
headers = (e.response or {}).get("ResponseMetadata", {}).get("HTTPHeaders", {})
return headers.get("x-tt-logid")
return None
def _error_msg(self, msg: str, e: Exception) -> str:
"""构建带 logid 的错误信息"""
logid = self._extract_logid(e)
if logid:
return f"{msg}: {e} (x-tt-logid: {logid})"
return f"{msg}: {e}"
def _resolve_bucket(self, bucket: Optional[str]) -> str:
"""统一解析 bucket 来源,确保得到有效桶名。"""
target_bucket = bucket or os.environ.get("COZE_BUCKET_NAME") or self.bucket_name
if not target_bucket:
raise ValueError("未配置 bucket请传入 bucket 或设置 COZE_BUCKET_NAME或在实例化时提供 bucket_name")
return target_bucket
def _validate_file_name(self, name: str) -> None:
"""校验 S3 对象命名长度≤1024允许 [A-Za-z0-9._-/];不以 / 起止且不含 //。"""
msg = (
"file name invalid: 文件名需满足以下 S3 对象命名规范:"
"1) 长度 11024 字节;"
"2) 仅允许字母、数字、点(.)、下划线(_)、短横(-)、目录分隔符(/)"
"3) 不允许空格或以下特殊字符:? # & % { } ^ [ ] ` \\ < > ~ | \" ' + = : ;"
"4) 不以 / 开头或结尾,且不包含连续的 //"
"示例report_2025-12-11.pdf、images/photo-01.png。"
)
if not name or not name.strip():
raise ValueError(msg + "(原因:文件名为空)")
# S3 限制对象 key 最大 1024 字节,这里沿用到输入文件名
if len(name.encode("utf-8")) > 1024:
raise ValueError(msg + "(原因:长度超过 1024 字节)")
if name.startswith("/") or name.endswith("/"):
raise ValueError(msg + "(原因:以 / 开头或结尾)")
if "//" in name:
raise ValueError(msg + "(原因:包含连续的 //")
# 允许字符集校验
if not FILE_NAME_ALLOWED_RE.match(name):
bad = re.findall(r"[^A-Za-z0-9._\-/]", name)
example = bad[0] if bad else "非法字符"
raise ValueError(msg + f"(原因:包含非法字符,例如:{example}")
def upload_file(self, *, file_content: bytes, file_name: str, content_type: str = "application/octet-stream", bucket: Optional[str] = None) -> str:
# 先对输入文件名做规范校验,避免生成无效对象 key
self._validate_file_name(file_name)
try:
client = self._get_client()
object_key = self._generate_object_key(original_name=file_name)
target_bucket = self._resolve_bucket(bucket)
client.put_object(Bucket=target_bucket, Key=object_key, Body=file_content, ContentType=content_type)
return object_key
except Exception as e:
logger.error(self._error_msg("Error uploading file to S3", e))
raise e
def delete_file(self, *, file_key: str, bucket: Optional[str] = None) -> bool:
try:
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
client.delete_object(Bucket=target_bucket, Key=file_key)
return True
except Exception as e:
logger.error(self._error_msg("Error deleting file from S3", e))
raise e
def file_exists(self, *, file_key: str, bucket: Optional[str] = None) -> bool:
try:
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
client.head_object(Bucket=target_bucket, Key=file_key)
return True
except ClientError as e:
code = (e.response or {}).get("Error", {}).get("Code", "")
if code in {"404", "NoSuchKey", "NotFound"}:
return False
logger.error(self._error_msg("Error checking file existence in S3", e))
return False
except Exception as e:
logger.error(self._error_msg("Error checking file existence in S3", e))
return False
def read_file(self, *, file_key: str, bucket: Optional[str] = None) -> bytes:
try:
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
resp = client.get_object(Bucket=target_bucket, Key=file_key)
body = resp.get("Body")
if body is None:
raise RuntimeError("S3 get_object returned no Body")
try:
return body.read()
finally:
try:
body.close()
except Exception as ce:
# 资源关闭失败不影响读取结果,仅记录以便排查
logger.debug("Failed to close S3 response body: %s", ce)
except Exception as e:
logger.error(self._error_msg("Error reading file from S3", e))
raise e
def list_files(self, *, prefix: Optional[str] = None, bucket: Optional[str] = None, max_keys: int = 1000, continuation_token: Optional[str] = None) -> ListFilesResult:
"""列出对象,支持前缀过滤与分页;返回 keys/is_truncated/next_continuation_token。"""
try:
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
if max_keys <= 0 or max_keys > 1000:
raise ValueError("max_keys 必须在 1 到 1000 之间")
kwargs: Dict[str, Any] = {
"Bucket": target_bucket,
"MaxKeys": max_keys,
"Prefix": prefix,
"ContinuationToken": continuation_token,
}
kwargs = {k: v for k, v in kwargs.items() if v is not None}
resp = client.list_objects_v2(**kwargs)
contents = resp.get("Contents", []) or []
keys: List[str] = [item.get("Key") for item in contents if isinstance(item, dict) and item.get("Key")]
return {
"keys": keys,
"is_truncated": bool(resp.get("IsTruncated")),
"next_continuation_token": resp.get("NextContinuationToken"),
}
except ClientError as e:
code = (e.response or {}).get("Error", {}).get("Code", "")
logger.error(self._error_msg(f"Error listing files in S3 (code={code})", e))
raise e
except Exception as e:
logger.error(self._error_msg("Error listing files in S3", e))
raise e
def generate_presigned_url(self, *, key: str, bucket: Optional[str] = None, expire_time: int = 1800) -> str:
"""通过 S3 Proxy 生成签名 URL。"""
import json
import urllib.request as urllib_request
try:
from coze_workload_identity import Client as CozeClient
coze_client = CozeClient()
try:
token = coze_client.get_access_token()
finally:
try:
coze_client.close()
except Exception:
# 资源释放失败不影响后续流程
pass
except Exception as e:
logger.error(f"Error loading x-storage-token: {e}")
raise RuntimeError(f"获取 x-storage-token 失败: {e}")
try:
sign_base = os.environ.get("COZE_BUCKET_ENDPOINT_URL") or self.endpoint_url
if not sign_base:
raise ValueError("未配置签名端点:请设置 COZE_BUCKET_ENDPOINT_URL 或传入 endpoint_url")
sign_url_endpoint = sign_base.rstrip("/") + "/sign-url"
headers = {
"Content-Type": "application/json",
"x-storage-token": token,
}
target_bucket = self._resolve_bucket(bucket)
payload = {"bucket_name": target_bucket, "path": key, "expire_time": expire_time}
data = json.dumps(payload).encode("utf-8")
request = urllib_request.Request(sign_url_endpoint, data=data, headers=headers, method="POST")
except Exception as e:
logger.error(f"Error creating request for sign-url: {e}")
raise RuntimeError(f"创建 sign-url 请求失败: {e}")
try:
with urllib_request.urlopen(request) as resp:
resp_bytes = resp.read()
content_type = resp.headers.get("Content-Type", "")
text = resp_bytes.decode("utf-8", errors="replace")
if "application/json" in content_type or text.strip().startswith("{"):
try:
obj = json.loads(text)
except Exception:
return text
data = obj.get("data")
if isinstance(data, dict) and "url" in data:
return data["url"]
url_value = obj.get("url") or obj.get("signed_url") or obj.get("presigned_url")
if url_value:
return url_value
raise ValueError("签名服务返回缺少 data.url/url 字段")
return text
except Exception as e:
raise RuntimeError(f"生成签名URL失败: {e}")
def stream_upload_file(
self,
*,
fileobj,
file_name: str,
content_type: str = "application/octet-stream",
bucket: Optional[str] = None,
multipart_chunksize: int = 5 * 1024 * 1024,
multipart_threshold: int = 5 * 1024 * 1024,
max_concurrency: int = 1,
use_threads: bool = False,
) -> str:
"""流式上传(文件对象)
- fileobj: 任何带有 read() 方法的文件对象 open(..., 'rb') 返回的对象io.BytesIO
- file_name: 原始文件名用于生成唯一 key
- content_type: MIME 类型
- bucket: 目标桶为空时取环境变量或实例默认值
- multipart_chunksize: 分片大小默认 5MB以适配代理层限制
- multipart_threshold: 触发分片上传的阈值默认 5MB
- max_concurrency: 并发分片上传的并发数默认 1避免代理层节流影响
- use_threads: 是否启用线程并发默认 False
返回最终写入的对象 key
"""
try:
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
key = self._generate_object_key(original_name=file_name)
extra_args = {"ContentType": content_type} if content_type else {}
# 使用 boto3 的高阶方法执行多段上传(传入 TransferConfig 控制分片大小)
config = TransferConfig(
multipart_chunksize=multipart_chunksize,
multipart_threshold=multipart_threshold,
max_concurrency=max_concurrency,
use_threads=use_threads,
)
client.upload_fileobj(Fileobj=fileobj, Bucket=target_bucket, Key=key, ExtraArgs=extra_args, Config=config)
return key
except Exception as e:
logger.error(self._error_msg("Error streaming upload (fileobj) to S3", e))
raise e
def upload_from_url(
self,
*,
url: str,
bucket: Optional[str] = None,
timeout: int = 30,
) -> str:
"""从 URL 流式下载并上传到 S3
- url: 源文件 URL
- bucket: 目标桶为空时取环境变量或实例默认值
- timeout: HTTP 请求超时时间默认 30
返回最终写入的对象 key
"""
import urllib.request as urllib_request
from urllib.parse import urlparse, unquote
try:
request = urllib_request.Request(url)
with urllib_request.urlopen(request, timeout=timeout) as resp:
parsed = urlparse(url)
file_name = Path(unquote(parsed.path)).name or "file"
content_type = resp.headers.get("Content-Type", "application/octet-stream")
return self.stream_upload_file(
fileobj=resp,
file_name=file_name,
content_type=content_type,
bucket=bucket,
)
except Exception as e:
logger.error(self._error_msg("Error uploading from URL to S3", e))
raise e
def trunk_upload_file(self, *, chunk_iter: Iterable[bytes], file_name: str,
content_type: str = "application/octet-stream", bucket: Optional[str] = None,
part_size: int = 5 * 1024 * 1024) -> str:
"""流式上传(字节迭代器,显式分片 Multipart Upload
- chunk_iter: 可迭代对象逐块产生 bytes每块大小可变内部累积到 part_size 再上传最后一块可小于 5MB
- file_name: 原始文件名用于生成唯一 key
- content_type: MIME 类型
- bucket: 目标桶为空时取环境或实例默认值
- part_size: 每个 part 的最小大小除最后一个默认 5MB
返回最终写入的对象 key
"""
client = self._get_client()
target_bucket = self._resolve_bucket(bucket)
key = self._generate_object_key(original_name=file_name)
# 初始化分片上传
try:
init_resp = client.create_multipart_upload(Bucket=target_bucket, Key=key, ContentType=content_type)
upload_id = init_resp["UploadId"]
except Exception as e:
logger.error(self._error_msg("create_multipart_upload failed", e))
raise e
parts = []
part_number = 1
buffer = bytearray()
try:
for chunk in chunk_iter:
if not chunk:
continue
buffer.extend(chunk)
while len(buffer) >= part_size:
data = bytes(buffer[:part_size])
buffer = buffer[part_size:]
resp = client.upload_part(Bucket=target_bucket, Key=key, UploadId=upload_id, PartNumber=part_number,
Body=data)
parts.append({"PartNumber": part_number, "ETag": resp["ETag"]})
part_number += 1
# 上传最后不足 part_size 的余量
if len(buffer) > 0:
resp = client.upload_part(Bucket=target_bucket, Key=key, UploadId=upload_id, PartNumber=part_number,
Body=bytes(buffer))
parts.append({"PartNumber": part_number, "ETag": resp["ETag"]})
# 完成分片
client.complete_multipart_upload(
Bucket=target_bucket,
Key=key,
UploadId=upload_id,
MultipartUpload={"Parts": parts},
)
return key
except Exception as e:
logger.error(self._error_msg("multipart upload failed", e))
try:
client.abort_multipart_upload(Bucket=target_bucket, Key=key, UploadId=upload_id)
except Exception as ae:
logger.error(self._error_msg("abort_multipart_upload failed", ae))
raise e