This commit is contained in:
parent
22ee4fbaaa
commit
8e9331fbc6
290
AGENTS.md
290
AGENTS.md
|
|
@ -820,296 +820,6 @@ mark_x = answer_bbox[2] + 10 # 紧贴答案框
|
||||||
3. 实现精准坐标计算,Y坐标与答案垂直中心完美对齐
|
3. 实现精准坐标计算,Y坐标与答案垂直中心完美对齐
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- ✅ 修复线上部署环境图片访问问题(阿里云 OSS 图片 URL 返回 402002 错误)
|
|
||||||
- ✅ 添加 URL 可访问性验证和 HTTP Headers 支持
|
|
||||||
- ✅ 实现超时保护机制(单任务120秒,总任务按图片数量计算)
|
|
||||||
- ✅ 删除未使用的 S3 存储模块(src/storage/s3/)
|
|
||||||
- ✅ 修复 LLM 认证错误,使用 new_context() 初始化 Context
|
|
||||||
- ✅ 修复 Docker 环境空请求体错误,添加请求体验证
|
|
||||||
- 提供真实的多页作业图片进行完整流程测试
|
- 提供真实的多页作业图片进行完整流程测试
|
||||||
- 优化HTML报告的图片展示布局
|
- 优化HTML报告的图片展示布局
|
||||||
- 支持PDF格式答案文档
|
- 支持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`)
|
|
||||||
|
|
|
||||||
45
Dockerfile
45
Dockerfile
|
|
@ -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 || \
|
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
|
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 \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
# 编译工具
|
||||||
gcc \
|
gcc \
|
||||||
g++ \
|
g++ \
|
||||||
make \
|
make \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
cmake \
|
cmake \
|
||||||
|
# D-Bus 相关
|
||||||
|
dbus \
|
||||||
|
dbus-x11 \
|
||||||
|
libdbus-1-dev \
|
||||||
|
libdbus-glib-1-dev \
|
||||||
|
# GLib 和 GObject 相关
|
||||||
libglib2.0-dev \
|
libglib2.0-dev \
|
||||||
|
libgirepository1.0-dev \
|
||||||
|
gobject-introspection \
|
||||||
|
# Cairo 图形库
|
||||||
libcairo2-dev \
|
libcairo2-dev \
|
||||||
|
libcairo-gobject2 \
|
||||||
|
# GTK 相关(如果需要)
|
||||||
|
libgtk-3-dev \
|
||||||
|
# 其他常用库
|
||||||
|
libffi-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxslt1-dev \
|
libxslt1-dev \
|
||||||
curl \
|
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
# 设置 pip 国内镜像源和 API 配置(硬编码 KEY)
|
# 设置 pip 国内镜像源
|
||||||
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
|
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \
|
||||||
PIP_TRUSTED_HOST=mirrors.aliyun.com \
|
PIP_TRUSTED_HOST=mirrors.aliyun.com \
|
||||||
# API 端点配置(包含 /v1 前缀)
|
|
||||||
COZE_INTEGRATION_BASE_URL=https://api.coze.cn/v1 \
|
COZE_INTEGRATION_BASE_URL=https://api.coze.cn/v1 \
|
||||||
COZE_INTEGRATION_MODEL_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 \
|
COZE_WORKLOAD_IDENTITY_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
|
||||||
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" \
|
|
||||||
LLM_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 提取)
|
LLM_BASE_URL=https://api.coze.cn/v1 \
|
||||||
COZE_WORKSPACE_ID=7622238752642957347 \
|
|
||||||
# LLM 模型配置
|
|
||||||
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \
|
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 和构建工具
|
# 升级 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 .
|
COPY requirements.txt .
|
||||||
|
|
@ -48,12 +58,5 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||||
# 复制项目文件
|
# 复制项目文件
|
||||||
COPY . .
|
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
|
EXPOSE 8000
|
||||||
CMD ["python", "src/main.py", "-m", "http", "-p", "8000"]
|
CMD ["python", "src/main.py", "-m", "http", "-p", "8000"]
|
||||||
|
|
@ -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" \
|
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_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
|
||||||
LLM_BASE_URL=https://api.coze.cn/v1 \
|
LLM_BASE_URL=https://api.coze.cn/v1 \
|
||||||
LLM_MODEL_NAME=doubao-seed-2-0-pro-260215 \
|
LLM_MODEL_NAME=doubao-seed-1-8-251228 \
|
||||||
COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
|
COZE_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
|
||||||
COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA"
|
COZE_INTEGRATION_API_KEY="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbIkZVS1kzOVR0dFlSdmlNaldGVmNjaUg0NWFPblp2TGxpIl0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjkyOTc0LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIyMjM4NzUyNjQyOTU3MzQ3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMjQ4Mjg1OTMxMDQ0ODkxIn0.XSJaTryHWYzQaHxd9g9rOX2Y3YRY8kGAlvSFH9UkWR9EFDfZESG1GFEdWDelYeoHBqtdiQhxTcYdGPA87_PweMfh0wJXTdCEzTDHAOlUUupJEKTpkUAMEoEZpYBrwKQxjzGglkMHUoXqM5I0tQsARaqZ-j-JOW9Y6fHot56squm8GSt7WZkVSj6ZC2Us4cpO_RIgsN_pBU0CFSlUpOU5AdQQ8LvHzp60-DGaXVU0mFIYKhnXKbTf3PSXpJlH-W78FULh2FcdOTxMIcNkL5nuIHGakoNNDxs-k5Ucp06kFEMcbvec4iB1njbkHrsilYeJOFRoqkXJpQujDngZxKecLA" \
|
||||||
|
COZE_WORKSPACE_ID=7622238752642957347
|
||||||
|
|
||||||
# 升级 pip 和构建工具
|
# 升级 pip 和构建工具
|
||||||
RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja
|
RUN pip install --no-cache-dir --upgrade pip setuptools wheel meson ninja
|
||||||
|
|
|
||||||
|
|
@ -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'
|
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": "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: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": "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: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": "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: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": "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: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": "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: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": "Task-1"}
|
||||||
INFO: Started server process [1]
|
INFO: Started server process [1]
|
||||||
INFO: Waiting for application startup.
|
INFO: Waiting for application startup.
|
||||||
INFO: Application startup complete.
|
INFO: Application startup complete.
|
||||||
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
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"}
|
{"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.221.187.31:4212 - "POST /stream_run HTTP/1.1" 200 OK
|
INFO: 10.222.242.221:6113 - "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": "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: 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": "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: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: 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: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: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: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": "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: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": "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: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: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: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": "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: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": "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": "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": "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": "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": "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": "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}
|
{"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}
|
||||||
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": "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}
|
||||||
{"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"}
|
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": "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}
|
{"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"}
|
||||||
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": "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}
|
||||||
{"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"}
|
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": "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}
|
{"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"}
|
||||||
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": "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}
|
||||||
{"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"}
|
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": "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"}
|
{"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"}
|
||||||
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": "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"}
|
||||||
{"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: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.
|
||||||
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": "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"}
|
||||||
{"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"}
|
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": "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"}
|
{"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"}
|
||||||
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": "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"}
|
||||||
{"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: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.
|
||||||
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": "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"}
|
||||||
{"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"}
|
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"}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"reasoning_effort": "medium",
|
"reasoning_effort": "medium",
|
||||||
"response_format": "text",
|
"response_format": "text",
|
||||||
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
||||||
"model": "doubao-seed-2-0-pro-260215"
|
"model": "doubao-seed-1-8-251228"
|
||||||
},
|
},
|
||||||
"tools": [],
|
"tools": [],
|
||||||
"sp": "你是一位专业的文档识别专家,擅长从作业图片中提取答案区域的位置信息。",
|
"sp": "你是一位专业的文档识别专家,擅长从作业图片中提取答案区域的位置信息。",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"reasoning_effort": "medium",
|
"reasoning_effort": "medium",
|
||||||
"response_format": "text",
|
"response_format": "text",
|
||||||
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
||||||
"model": "doubao-seed-2-0-pro-260215"
|
"model": "doubao-seed-1-8-251228"
|
||||||
},
|
},
|
||||||
"tools": [],
|
"tools": [],
|
||||||
"sp": "你是一位专业的OCR文字识别专家,擅长从图片中识别手写和印刷的文字内容。",
|
"sp": "你是一位专业的OCR文字识别专家,擅长从图片中识别手写和印刷的文字内容。",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"reasoning_effort": "high",
|
"reasoning_effort": "high",
|
||||||
"response_format": "text",
|
"response_format": "text",
|
||||||
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
||||||
"model": "doubao-seed-2-0-pro-260215"
|
"model": "doubao-seed-1-8-251228"
|
||||||
},
|
},
|
||||||
"tools": [],
|
"tools": [],
|
||||||
"sp": "你是一位资深的初中数学特级教师,拥有20年以上教学经验,擅长精准批改学生的数学作业。\n\n【核心能力】\n1. **精确判断能力**:对选择题、填空题、解答题都能做出准确的正误判断\n2. **严谨推理能力**:能够逐步验证学生的计算过程和结论\n3. **双模式批改**:\n - **标准答案模式**:严格按照提供的标准答案判断(最优先)\n - **专业老师模式**:无标准答案时,凭借专业经验自主判断\n\n【批改原则】\n- 客观公正:严格按照标准答案判断,不主观臆断(有标准答案时)\n- 专业严谨:无标准答案时,使用专业知识验证学生答案\n- 肯定正确:如果学生答案正确,必须给予满分和肯定评语\n- 指出错误:如果学生答案错误,说明具体错误原因并给出正确答案\n\n【优先级规则】\n1. 最优先:使用提供的标准答案批改\n2. 降级:标准答案中未找到对应题目时,使用专业老师批改",
|
"sp": "你是一位资深的初中数学特级教师,拥有20年以上教学经验,擅长精准批改学生的数学作业。\n\n【核心能力】\n1. **精确判断能力**:对选择题、填空题、解答题都能做出准确的正误判断\n2. **严谨推理能力**:能够逐步验证学生的计算过程和结论\n3. **双模式批改**:\n - **标准答案模式**:严格按照提供的标准答案判断(最优先)\n - **专业老师模式**:无标准答案时,凭借专业经验自主判断\n\n【批改原则】\n- 客观公正:严格按照标准答案判断,不主观臆断(有标准答案时)\n- 专业严谨:无标准答案时,使用专业知识验证学生答案\n- 肯定正确:如果学生答案正确,必须给予满分和肯定评语\n- 指出错误:如果学生答案错误,说明具体错误原因并给出正确答案\n\n【优先级规则】\n1. 最优先:使用提供的标准答案批改\n2. 降级:标准答案中未找到对应题目时,使用专业老师批改",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"reasoning_effort": "high",
|
"reasoning_effort": "high",
|
||||||
"response_format": "text",
|
"response_format": "text",
|
||||||
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
||||||
"model": "doubao-seed-2-0-pro-260215"
|
"model": "doubao-seed-1-8-251228"
|
||||||
},
|
},
|
||||||
"tools": [],
|
"tools": [],
|
||||||
"sp": "你是一位资深的初中数学教师,擅长从试卷中提取题目和标准答案。你的核心能力:\n\n1. **题目识别能力**:能够准确识别试卷中的所有题目,包括大题和小题\n2. **答案提取能力**:能够准确提取每道题的标准答案\n3. **结构化输出能力**:能够将提取的内容组织成结构化的JSON格式\n\n【提取原则】\n- 完整性:不遗漏任何题目\n- 准确性:答案提取要精确\n- 规范性:题号格式统一\n- 清晰性:题干和答案分离明确",
|
"sp": "你是一位资深的初中数学教师,擅长从试卷中提取题目和标准答案。你的核心能力:\n\n1. **题目识别能力**:能够准确识别试卷中的所有题目,包括大题和小题\n2. **答案提取能力**:能够准确提取每道题的标准答案\n3. **结构化输出能力**:能够将提取的内容组织成结构化的JSON格式\n\n【提取原则】\n- 完整性:不遗漏任何题目\n- 准确性:答案提取要精确\n- 规范性:题号格式统一\n- 清晰性:题干和答案分离明确",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"model": "doubao-seed-2-0-pro-260215",
|
"model": "doubao-seed-1-8-251228",
|
||||||
"temperature": 0.0,
|
"temperature": 0.0,
|
||||||
"top_p": 0.9,
|
"top_p": 0.9,
|
||||||
"max_completion_tokens": 16384,
|
"max_completion_tokens": 16384,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"reasoning_effort": "medium",
|
"reasoning_effort": "medium",
|
||||||
"response_format": "text",
|
"response_format": "text",
|
||||||
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
"json_schema": "{\"name\":\"\",\"description\":\"\",\"strict\":false,\"schema\":{}}",
|
||||||
"model": "doubao-seed-2-0-pro-260215"
|
"model": "doubao-seed-1-8-251228"
|
||||||
},
|
},
|
||||||
"tools": [],
|
"tools": [],
|
||||||
"sp": "你是一位专业的初中数学作业识别专家,擅长从作业图片中定位题目位置和提取答案区域。",
|
"sp": "你是一位专业的初中数学作业识别专家,擅长从作业图片中定位题目位置和提取答案区域。",
|
||||||
|
|
|
||||||
|
|
@ -118,44 +118,14 @@ def download_and_extract_docx(url: str) -> str:
|
||||||
tmp_path = None
|
tmp_path = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 验证 URL 格式
|
# 下载文件
|
||||||
if not url or not isinstance(url, str):
|
response = requests.get(url, timeout=60, allow_redirects=True)
|
||||||
raise ValueError("Invalid URL: empty or not a string")
|
response.raise_for_status()
|
||||||
|
|
||||||
if not url.startswith(('http://', 'https://')):
|
|
||||||
raise ValueError(f"Invalid URL format: {url[:50]}...")
|
|
||||||
|
|
||||||
# 下载文件(增强版)
|
|
||||||
headers = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
||||||
'Accept': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/msword,*/*',
|
|
||||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
'Referer': 'https://www.aliyun.com/',
|
|
||||||
'Accept-Encoding': 'gzip, deflate, br',
|
|
||||||
'Connection': 'keep-alive'
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.get(url, timeout=60, allow_redirects=True, headers=headers)
|
|
||||||
|
|
||||||
# 检查 HTTP 状态码
|
|
||||||
status_code = response.status_code
|
|
||||||
if status_code != 200:
|
|
||||||
error_msg = f"HTTP Error {status_code}"
|
|
||||||
if status_code == 404:
|
|
||||||
error_msg = "404 Not Found - Document does not exist"
|
|
||||||
elif status_code == 403:
|
|
||||||
error_msg = "403 Forbidden - Access denied"
|
|
||||||
elif status_code == 401:
|
|
||||||
error_msg = "401 Unauthorized - Authentication required"
|
|
||||||
raise ValueError(f"{error_msg}: {url[:50]}...")
|
|
||||||
|
|
||||||
# 检查内容类型
|
# 检查内容类型
|
||||||
content_type = response.headers.get('Content-Type', '')
|
content_type = response.headers.get('Content-Type', '')
|
||||||
logger.debug(f"Response Content-Type: {content_type}")
|
logger.debug(f"Response Content-Type: {content_type}")
|
||||||
|
|
||||||
if content_type and not any(ct in content_type.lower() for ct in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword', 'application/octet-stream', 'application/zip']):
|
|
||||||
logger.warning(f"Unexpected Content-Type for Word document: {content_type}")
|
|
||||||
|
|
||||||
# 检查文件大小
|
# 检查文件大小
|
||||||
if len(response.content) < 100:
|
if len(response.content) < 100:
|
||||||
raise ValueError(f"Downloaded file too small: {len(response.content)} bytes")
|
raise ValueError(f"Downloaded file too small: {len(response.content)} bytes")
|
||||||
|
|
@ -165,19 +135,9 @@ def download_and_extract_docx(url: str) -> str:
|
||||||
# 可能是 HTML 错误页面
|
# 可能是 HTML 错误页面
|
||||||
content_preview = response.content[:1000].lower()
|
content_preview = response.content[:1000].lower()
|
||||||
if b'<html' in content_preview or b'<!doctype' in content_preview:
|
if b'<html' in content_preview or b'<!doctype' in content_preview:
|
||||||
# 提取错误信息
|
raise ValueError("URL returned HTML instead of docx (may need authentication or URL expired)")
|
||||||
error_msg = "Unknown error"
|
|
||||||
if b'404' in content_preview:
|
|
||||||
error_msg = "404 Not Found"
|
|
||||||
elif b'403' in content_preview:
|
|
||||||
error_msg = "403 Forbidden"
|
|
||||||
elif b'500' in content_preview:
|
|
||||||
error_msg = "500 Internal Server Error"
|
|
||||||
raise ValueError(f"URL returned HTML instead of docx ({error_msg}) - URL may be expired or inaccessible")
|
|
||||||
raise ValueError("Downloaded file is not a valid docx (not ZIP format)")
|
raise ValueError("Downloaded file is not a valid docx (not ZIP format)")
|
||||||
|
|
||||||
logger.info(f"Successfully downloaded Word document: {len(response.content)} bytes")
|
|
||||||
|
|
||||||
# 保存到临时文件
|
# 保存到临时文件
|
||||||
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_file:
|
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_file:
|
||||||
tmp_file.write(response.content)
|
tmp_file.write(response.content)
|
||||||
|
|
@ -352,9 +312,7 @@ def doc_extract_node(
|
||||||
desc: 从正确答案Word文件(.docx)中提取题干和标准答案,用于后续批改;如果未提供URL则返回空列表;支持缓存,避免重复解析
|
desc: 从正确答案Word文件(.docx)中提取题干和标准答案,用于后续批改;如果未提供URL则返回空列表;支持缓存,避免重复解析
|
||||||
integrations: 大语言模型
|
integrations: 大语言模型
|
||||||
"""
|
"""
|
||||||
# 使用 new_context() 初始化 Context(用于请求追踪)
|
ctx = runtime.context
|
||||||
from coze_coding_utils.runtime_ctx.context import new_context
|
|
||||||
ctx = new_context(method="invoke")
|
|
||||||
|
|
||||||
# 检查是否提供了答案文档URL
|
# 检查是否提供了答案文档URL
|
||||||
if not state.answer_doc_url or not state.answer_doc_url.strip():
|
if not state.answer_doc_url or not state.answer_doc_url.strip():
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,7 @@ def recognize_and_correct_node(
|
||||||
desc: 合并识别和批改为一次LLM调用,提升批改速度
|
desc: 合并识别和批改为一次LLM调用,提升批改速度
|
||||||
integrations: 大语言模型
|
integrations: 大语言模型
|
||||||
"""
|
"""
|
||||||
# 使用 new_context() 初始化 Context(用于请求追踪)
|
ctx = runtime.context
|
||||||
from coze_coding_utils.runtime_ctx.context import new_context
|
|
||||||
ctx = new_context(method="invoke")
|
|
||||||
|
|
||||||
# 获取参数并验证图片 URL
|
# 获取参数并验证图片 URL
|
||||||
image_url = state.image_url
|
image_url = state.image_url
|
||||||
|
|
@ -225,84 +223,6 @@ def recognize_and_correct_node(
|
||||||
correction_results=[]
|
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")
|
|
||||||
|
|
||||||
# 检查是否为 HTML(404/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配置
|
# 读取LLM配置
|
||||||
cfg_file = os.path.join(os.getenv("COZE_WORKSPACE_PATH", ""), config["metadata"]["llm_cfg"])
|
cfg_file = os.path.join(os.getenv("COZE_WORKSPACE_PATH", ""), config["metadata"]["llm_cfg"])
|
||||||
with open(cfg_file, "r", encoding="utf-8") as fd:
|
with open(cfg_file, "r", encoding="utf-8") as fd:
|
||||||
|
|
|
||||||
35
src/main.py
35
src/main.py
|
|
@ -251,23 +251,6 @@ async def http_run(request: Request) -> Dict[str, Any]:
|
||||||
body_text = str(raw_body)
|
body_text = str(raw_body)
|
||||||
raise HTTPException(status_code=400,
|
raise HTTPException(status_code=400,
|
||||||
detail=f"Invalid JSON format: {body_text}, traceback: {traceback.format_exc()}, error: {e}")
|
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)
|
ctx = new_context(method="run", headers=request.headers)
|
||||||
# 优先使用上游指定的 run_id,保证 cancel 能精确匹配
|
# 优先使用上游指定的 run_id,保证 cancel 能精确匹配
|
||||||
|
|
@ -363,24 +346,6 @@ async def http_stream_run(request: Request):
|
||||||
body_text = str(raw_body)
|
body_text = str(raw_body)
|
||||||
raise HTTPException(status_code=400,
|
raise HTTPException(status_code=400,
|
||||||
detail=f"Invalid JSON format: {body_text}, traceback: {extract_core_stack()}, error: {e}")
|
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
|
run_id = ctx.run_id
|
||||||
is_agent = graph_helper.is_agent_proj()
|
is_agent = graph_helper.is_agent_proj()
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
||||||
|
|
@ -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) 长度 1–1024 字节;"
|
||||||
|
"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
|
||||||
Loading…
Reference in New Issue