Initial commit
This commit is contained in:
commit
785d74c653
53
.gitignore
vendored
Normal file
53
.gitignore
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.Python
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Custom
|
||||
ai作业/
|
||||
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# AI作业批改系统
|
||||
|
||||
本项目通过调用Dify Workflow API,实现自动化AI作业批改功能。系统读取本地Excel作业提交记录和随作业提交的YML配置文件,自动完成作业批改流程。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 自动读取学生作业提交记录(Excel格式)
|
||||
- 解析学生提交的YML配置文件
|
||||
- 调用Dify API进行作业批改
|
||||
- 支持文件上传和API结果返回
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
1. 在`source/grade_assignments.py`中配置Dify API参数:
|
||||
```python
|
||||
# API配置
|
||||
DIFY_API_KEY = "your_api_key_here" # 替换为实际API Key
|
||||
DIFY_API_URL = "http://192.168.100.143/v1" # Dify API地址
|
||||
FILE_UPLOAD_URL = f"{DIFY_API_URL}/files/upload" # 文件上传地址
|
||||
```
|
||||
|
||||
2. 准备学生作业提交Excel文件,格式如下:
|
||||
| 学生姓名 | 学号 | 提交时间 | 作业状态 |
|
||||
|----------|------|----------|----------|
|
||||
|
||||
3. 每个学生作业需包含一个YML配置文件,描述作业内容
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 将学生作业Excel文件放在`data/`目录下
|
||||
2. 将学生作业YML文件放在`assignments/`目录下
|
||||
3. 运行主程序:
|
||||
|
||||
```bash
|
||||
python source/grade_assignments.py
|
||||
```
|
||||
|
||||
4. 查看批改结果,结果将保存在`results/`目录下
|
||||
|
||||
## 测试
|
||||
|
||||
运行测试用例验证系统功能:
|
||||
|
||||
```bash
|
||||
python source/test_grade.py
|
||||
```
|
||||
|
||||
## API文档
|
||||
|
||||
详细API调用规范请参考[docs/workflow_api.md](docs/workflow_api.md)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保网络可以访问Dify API服务器
|
||||
- 上传文件大小不超过API限制
|
||||
- 作业YML文件需符合规范格式
|
||||
632
docs/workflow_api.md
Normal file
632
docs/workflow_api.md
Normal file
@ -0,0 +1,632 @@
|
||||
Workflow 应用 API
|
||||
Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等等。
|
||||
|
||||
Base URL
|
||||
Code
|
||||
http://192.168.100.143/v1
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Authentication
|
||||
Dify Service API 使用 API-Key 进行鉴权。 强烈建议开发者把 API-Key 放在后端存储,而非分享或者放在客户端存储,以免 API-Key 泄露,导致财产损失。 所有 API 请求都应在 Authorization HTTP Header 中包含您的 API-Key,如下所示:
|
||||
|
||||
Code
|
||||
Authorization: Bearer {API_KEY}
|
||||
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
POST
|
||||
/workflows/run
|
||||
执行 workflow
|
||||
执行 workflow,没有已发布的 workflow,不可执行。
|
||||
|
||||
Request Body
|
||||
inputs (object) Required 允许传入 App 定义的各变量值。 inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。变量可以是文件列表类型。 文件列表类型变量适用于传入文件结合文本理解并回答问题,仅当模型支持该类型文件解析能力时可用。如果该变量是文件列表类型,该变量对应的值应是列表格式,其中每个元素应包含以下内容:
|
||||
type (string) 支持类型:
|
||||
document 具体类型包含:'TXT', 'MD', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB'
|
||||
image 具体类型包含:'JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'
|
||||
audio 具体类型包含:'MP3', 'M4A', 'WAV', 'WEBM', 'AMR'
|
||||
video 具体类型包含:'MP4', 'MOV', 'MPEG', 'MPGA'
|
||||
custom 具体类型包含:其他文件类型
|
||||
transfer_method (string) 传递方式,remote_url 图片地址 / local_file 上传文件
|
||||
url (string) 图片地址(仅当传递方式为 remote_url 时)
|
||||
upload_file_id (string) (string) 上传文件 ID(仅当传递方式为 local_file 时)
|
||||
response_mode (string) Required 返回响应模式,支持:
|
||||
streaming 流式模式(推荐)。基于 SSE(Server-Sent Events)实现类似打字机输出方式的流式返回。
|
||||
blocking 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。
|
||||
user (string) Required 用户标识,用于定义终端用户的身份,方便检索、统计。 由开发者定义规则,需保证用户标识在应用内唯一。
|
||||
Response
|
||||
当 response_mode 为 blocking 时,返回 CompletionResponse object。 当 response_mode 为 streaming时,返回 ChunkCompletionResponse object 流式序列。
|
||||
|
||||
CompletionResponse
|
||||
返回完整的 App 结果,Content-Type 为 application/json 。
|
||||
|
||||
workflow_run_id (string) workflow 执行 ID
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
data (object) 详细内容
|
||||
id (string) workflow 执行 ID
|
||||
workflow_id (string) 关联 Workflow ID
|
||||
status (string) 执行状态, running / succeeded / failed / stopped
|
||||
outputs (json) Optional 输出内容
|
||||
error (string) Optional 错误原因
|
||||
elapsed_time (float) Optional 耗时(s)
|
||||
total_tokens (int) Optional 总使用 tokens
|
||||
total_steps (int) 总步数(冗余),默认 0
|
||||
created_at (timestamp) 开始时间
|
||||
finished_at (timestamp) 结束时间
|
||||
ChunkCompletionResponse
|
||||
返回 App 输出的流式块,Content-Type 为 text/event-stream。 每个流式块均为 data: 开头,块之间以 \n\n 即两个换行符分隔,如下所示:
|
||||
|
||||
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
流式块中根据 event 不同,结构也不同,包含以下类型:
|
||||
|
||||
event: workflow_started workflow 开始执行
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
workflow_run_id (string) workflow 执行 ID
|
||||
event (string) 固定为 workflow_started
|
||||
data (object) 详细内容
|
||||
id (string) workflow 执行 ID
|
||||
workflow_id (string) 关联 Workflow ID
|
||||
sequence_number (int) 自增序号,App 内自增,从 1 开始
|
||||
created_at (timestamp) 开始时间
|
||||
event: node_started node 开始执行
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
workflow_run_id (string) workflow 执行 ID
|
||||
event (string) 固定为 node_started
|
||||
data (object) 详细内容
|
||||
id (string) workflow 执行 ID
|
||||
node_id (string) 节点 ID
|
||||
node_type (string) 节点类型
|
||||
title (string) 节点名称
|
||||
index (int) 执行序号,用于展示 Tracing Node 顺序
|
||||
predecessor_node_id (string) 前置节点 ID,用于画布展示执行路径
|
||||
inputs (object) 节点中所有使用到的前置节点变量内容
|
||||
created_at (timestamp) 开始时间
|
||||
event: node_finished node 执行结束,成功失败同一事件中不同状态
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
workflow_run_id (string) workflow 执行 ID
|
||||
event (string) 固定为 node_finished
|
||||
data (object) 详细内容
|
||||
id (string) node 执行 ID
|
||||
node_id (string) 节点 ID
|
||||
index (int) 执行序号,用于展示 Tracing Node 顺序
|
||||
predecessor_node_id (string) optional 前置节点 ID,用于画布展示执行路径
|
||||
inputs (object) 节点中所有使用到的前置节点变量内容
|
||||
process_data (json) Optional 节点过程数据
|
||||
outputs (json) Optional 输出内容
|
||||
status (string) 执行状态 running / succeeded / failed / stopped
|
||||
error (string) Optional 错误原因
|
||||
elapsed_time (float) Optional 耗时(s)
|
||||
execution_metadata (json) 元数据
|
||||
total_tokens (int) optional 总使用 tokens
|
||||
total_price (decimal) optional 总费用
|
||||
currency (string) optional 货币,如 USD / RMB
|
||||
created_at (timestamp) 开始时间
|
||||
event: workflow_finished workflow 执行结束,成功失败同一事件中不同状态
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
workflow_run_id (string) workflow 执行 ID
|
||||
event (string) 固定为 workflow_finished
|
||||
data (object) 详细内容
|
||||
id (string) workflow 执行 ID
|
||||
workflow_id (string) 关联 Workflow ID
|
||||
status (string) 执行状态 running / succeeded / failed / stopped
|
||||
outputs (json) Optional 输出内容
|
||||
error (string) Optional 错误原因
|
||||
elapsed_time (float) Optional 耗时(s)
|
||||
total_tokens (int) Optional 总使用 tokens
|
||||
total_steps (int) 总步数(冗余),默认 0
|
||||
created_at (timestamp) 开始时间
|
||||
finished_at (timestamp) 结束时间
|
||||
event: tts_message TTS 音频流事件,即:语音合成输出。内容是Mp3格式的音频块,使用 base64 编码后的字符串,播放的时候直接解码即可。(开启自动播放才有此消息)
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
message_id (string) 消息唯一 ID
|
||||
audio (string) 语音合成之后的音频块使用 Base64 编码之后的文本内容,播放的时候直接 base64 解码送入播放器即可
|
||||
created_at (int) 创建时间戳,如:1705395332
|
||||
event: tts_message_end TTS 音频流结束事件,收到这个事件表示音频流返回结束。
|
||||
task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口
|
||||
message_id (string) 消息唯一 ID
|
||||
audio (string) 结束事件是没有音频的,所以这里是空字符串
|
||||
created_at (int) 创建时间戳,如:1705395332
|
||||
event: ping 每 10s 一次的 ping 事件,保持连接存活。
|
||||
Errors
|
||||
400,invalid_param,传入参数异常
|
||||
400,app_unavailable,App 配置不可用
|
||||
400,provider_not_initialize,无可用模型凭据配置
|
||||
400,provider_quota_exceeded,模型调用额度不足
|
||||
400,model_currently_not_support,当前模型不可用
|
||||
400,workflow_request_error,workflow 执行失败
|
||||
500,服务内部异常
|
||||
Request
|
||||
POST
|
||||
/workflows/run
|
||||
curl -X POST 'http://192.168.100.143/v1/workflows/run' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"inputs": {},
|
||||
"response_mode": "streaming",
|
||||
"user": "abc-123"
|
||||
}'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Example: file array as an input variable
|
||||
{
|
||||
"inputs": {
|
||||
"{variable_name}":
|
||||
[
|
||||
{
|
||||
"transfer_method": "local_file",
|
||||
"upload_file_id": "{upload_file_id}",
|
||||
"type": "{document_type}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Blocking Mode
|
||||
Response
|
||||
{
|
||||
"workflow_run_id": "djflajgkldjgd",
|
||||
"task_id": "9da23599-e713-473b-982c-4328d4f5c78a",
|
||||
"data": {
|
||||
"id": "fdlsjfjejkghjda",
|
||||
"workflow_id": "fldjaslkfjlsda",
|
||||
"status": "succeeded",
|
||||
"outputs": {
|
||||
"text": "Nice to meet you."
|
||||
},
|
||||
"error": null,
|
||||
"elapsed_time": 0.875,
|
||||
"total_tokens": 3562,
|
||||
"total_steps": 8,
|
||||
"created_at": 1705407629,
|
||||
"finished_at": 1727807631
|
||||
}
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Streaming Mode
|
||||
Response
|
||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}}
|
||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
|
||||
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
File upload sample code
|
||||
import requests
|
||||
import json
|
||||
|
||||
def upload_file(file_path, user):
|
||||
upload_url = "https://api.dify.ai/v1/files/upload"
|
||||
headers = {
|
||||
"Authorization": "Bearer app-xxxxxxxx",
|
||||
}
|
||||
|
||||
try:
|
||||
print("上传文件中...")
|
||||
with open(file_path, 'rb') as file:
|
||||
files = {
|
||||
'file': (file_path, file, 'text/plain') # 确保文件以适当的MIME类型上传
|
||||
}
|
||||
data = {
|
||||
"user": user,
|
||||
"type": "TXT" # 设置文件类型为TXT
|
||||
}
|
||||
|
||||
response = requests.post(upload_url, headers=headers, files=files, data=data)
|
||||
if response.status_code == 201: # 201 表示创建成功
|
||||
print("文件上传成功")
|
||||
return response.json().get("id") # 获取上传的文件 ID
|
||||
else:
|
||||
print(f"文件上传失败,状态码: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def run_workflow(file_id, user, response_mode="blocking"):
|
||||
workflow_url = "https://api.dify.ai/v1/workflows/run"
|
||||
headers = {
|
||||
"Authorization": "Bearer app-xxxxxxxxx",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"inputs": {
|
||||
"orig_mail": [{
|
||||
"transfer_method": "local_file",
|
||||
"upload_file_id": file_id,
|
||||
"type": "document"
|
||||
}]
|
||||
},
|
||||
"response_mode": response_mode,
|
||||
"user": user
|
||||
}
|
||||
|
||||
try:
|
||||
print("运行工作流...")
|
||||
response = requests.post(workflow_url, headers=headers, json=data)
|
||||
if response.status_code == 200:
|
||||
print("工作流执行成功")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"工作流执行失败,状态码: {response.status_code}")
|
||||
return {"status": "error", "message": f"Failed to execute workflow, status code: {response.status_code}"}
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
# 使用示例
|
||||
file_path = "{your_file_path}"
|
||||
user = "difyuser"
|
||||
|
||||
# 上传文件
|
||||
file_id = upload_file(file_path, user)
|
||||
if file_id:
|
||||
# 文件上传成功,继续运行工作流
|
||||
result = run_workflow(file_id, user)
|
||||
print(result)
|
||||
else:
|
||||
print("文件上传失败,无法执行工作流")
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
GET
|
||||
/workflows/run/:workflow_id
|
||||
获取workflow执行情况
|
||||
根据 workflow 执行 ID 获取 workflow 任务当前执行结果
|
||||
|
||||
Path
|
||||
workflow_id (string) workflow 执行 ID,可在流式返回 Chunk 中获取
|
||||
Response
|
||||
id (string) workflow 执行 ID
|
||||
workflow_id (string) 关联的 Workflow ID
|
||||
status (string) 执行状态 running / succeeded / failed / stopped
|
||||
inputs (json) 任务输入内容
|
||||
outputs (json) 任务输出内容
|
||||
error (string) 错误原因
|
||||
total_steps (int) 任务执行总步数
|
||||
total_tokens (int) 任务执行总 tokens
|
||||
created_at (timestamp) 任务开始时间
|
||||
finished_at (timestamp) 任务结束时间
|
||||
elapsed_time (float) 耗时(s)
|
||||
Request Example
|
||||
Request
|
||||
GET
|
||||
/workflows/run/:workflow_id
|
||||
curl -X GET 'http://192.168.100.143/v1/workflows/run/:workflow_id' \
|
||||
-H 'Authorization: Bearer {api_key}' \
|
||||
-H 'Content-Type: application/json'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Response Example
|
||||
Response
|
||||
{
|
||||
"id": "b1ad3277-089e-42c6-9dff-6820d94fbc76",
|
||||
"workflow_id": "19eff89f-ec03-4f75-b0fc-897e7effea02",
|
||||
"status": "succeeded",
|
||||
"inputs": "{\"sys.files\": [], \"sys.user_id\": \"abc-123\"}",
|
||||
"outputs": null,
|
||||
"error": null,
|
||||
"total_steps": 3,
|
||||
"total_tokens": 0,
|
||||
"created_at": "Thu, 18 Jul 2024 03:17:40 -0000",
|
||||
"finished_at": "Thu, 18 Jul 2024 03:18:10 -0000",
|
||||
"elapsed_time": 30.098514399956912
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
POST
|
||||
/workflows/tasks/:task_id/stop
|
||||
停止响应
|
||||
仅支持流式模式。
|
||||
|
||||
Path
|
||||
task_id (string) 任务 ID,可在流式返回 Chunk 中获取
|
||||
Request Body
|
||||
user (string) Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。
|
||||
Response
|
||||
result (string) 固定返回 "success"
|
||||
Request Example
|
||||
Request
|
||||
POST
|
||||
/workflows/tasks/:task_id/stop
|
||||
curl -X POST 'http://192.168.100.143/v1/workflows/tasks/:task_id/stop' \
|
||||
-H 'Authorization: Bearer {api_key}' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{"user": "abc-123"}'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Response Example
|
||||
Response
|
||||
{
|
||||
"result": "success"
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
POST
|
||||
/files/upload
|
||||
上传文件
|
||||
上传文件并在发送消息时使用,可实现图文多模态理解。 支持您的工作流程所支持的任何格式。 上传的文件仅供当前终端用户使用。
|
||||
|
||||
Request Body
|
||||
该接口需使用 multipart/form-data 进行请求。
|
||||
|
||||
Name
|
||||
file
|
||||
Type
|
||||
file
|
||||
Description
|
||||
要上传的文件。
|
||||
|
||||
Name
|
||||
user
|
||||
Type
|
||||
string
|
||||
Description
|
||||
用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。
|
||||
|
||||
Response
|
||||
成功上传后,服务器会返回文件的 ID 和相关信息。
|
||||
|
||||
id (uuid) ID
|
||||
name (string) 文件名
|
||||
size (int) 文件大小(byte)
|
||||
extension (string) 文件后缀
|
||||
mime_type (string) 文件 mime-type
|
||||
created_by (uuid) 上传人 ID
|
||||
created_at (timestamp) 上传时间
|
||||
Errors
|
||||
400,no_file_uploaded,必须提供文件
|
||||
400,too_many_files,目前只接受一个文件
|
||||
400,unsupported_preview,该文件不支持预览
|
||||
400,unsupported_estimate,该文件不支持估算
|
||||
413,file_too_large,文件太大
|
||||
415,unsupported_file_type,不支持的扩展名,当前只接受文档类文件
|
||||
503,s3_connection_failed,无法连接到 S3 服务
|
||||
503,s3_permission_denied,无权限上传文件到 S3
|
||||
503,s3_file_too_large,文件超出 S3 大小限制
|
||||
Request
|
||||
POST
|
||||
/files/upload
|
||||
curl -X POST 'http://192.168.100.143/v1/files/upload' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \
|
||||
--form 'user=abc-123'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Response
|
||||
{
|
||||
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
|
||||
"name": "example.png",
|
||||
"size": 1024,
|
||||
"extension": "png",
|
||||
"mime_type": "image/png",
|
||||
"created_by": 123,
|
||||
"created_at": 1577836800,
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
GET
|
||||
/workflows/logs
|
||||
获取 workflow 日志
|
||||
倒序返回workflow日志
|
||||
|
||||
Query
|
||||
Name
|
||||
keyword
|
||||
Type
|
||||
string
|
||||
Description
|
||||
关键字
|
||||
|
||||
Name
|
||||
status
|
||||
Type
|
||||
string
|
||||
Description
|
||||
执行状态 succeeded/failed/stopped
|
||||
|
||||
Name
|
||||
page
|
||||
Type
|
||||
int
|
||||
Description
|
||||
当前页码, 默认1.
|
||||
|
||||
Name
|
||||
limit
|
||||
Type
|
||||
int
|
||||
Description
|
||||
每页条数, 默认20.
|
||||
|
||||
Response
|
||||
page (int) 当前页码
|
||||
limit (int) 每页条数
|
||||
total (int) 总条数
|
||||
has_more (bool) 是否还有更多数据
|
||||
data (array[object]) 当前页码的数据
|
||||
id (string) 标识
|
||||
workflow_run (object) Workflow 执行日志
|
||||
id (string) 标识
|
||||
version (string) 版本
|
||||
status (string) 执行状态, running / succeeded / failed / stopped
|
||||
error (string) (可选) 错误
|
||||
elapsed_time (float) 耗时,单位秒
|
||||
total_tokens (int) 消耗的token数量
|
||||
total_steps (int) 执行步骤长度
|
||||
created_at (timestamp) 开始时间
|
||||
finished_at (timestamp) 结束时间
|
||||
created_from (string) 来源
|
||||
created_by_role (string) 角色
|
||||
created_by_account (string) (可选) 帐号
|
||||
created_by_end_user (object) 用户
|
||||
id (string) 标识
|
||||
type (string) 类型
|
||||
is_anonymous (bool) 是否匿名
|
||||
session_id (string) 会话标识
|
||||
created_at (timestamp) 创建时间
|
||||
Request
|
||||
GET
|
||||
/workflows/logs
|
||||
curl -X GET 'http://192.168.100.143/v1/workflows/logs'\
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Response Example
|
||||
Response
|
||||
{
|
||||
"page": 1,
|
||||
"limit": 1,
|
||||
"total": 7,
|
||||
"has_more": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "e41b93f1-7ca2-40fd-b3a8-999aeb499cc0",
|
||||
"workflow_run": {
|
||||
"id": "c0640fc8-03ef-4481-a96c-8a13b732a36e",
|
||||
"version": "2024-08-01 12:17:09.771832",
|
||||
"status": "succeeded",
|
||||
"error": null,
|
||||
"elapsed_time": 1.3588523610014818,
|
||||
"total_tokens": 0,
|
||||
"total_steps": 3,
|
||||
"created_at": 1726139643,
|
||||
"finished_at": 1726139644
|
||||
},
|
||||
"created_from": "service-api",
|
||||
"created_by_role": "end_user",
|
||||
"created_by_account": null,
|
||||
"created_by_end_user": {
|
||||
"id": "7f7d9117-dd9d-441d-8970-87e5e7e687a3",
|
||||
"type": "service_api",
|
||||
"is_anonymous": false,
|
||||
"session_id": "abc-123"
|
||||
},
|
||||
"created_at": 1726139644
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
GET
|
||||
/info
|
||||
获取应用基本信息
|
||||
用于获取应用的基本信息
|
||||
|
||||
Response
|
||||
name (string) 应用名称
|
||||
description (string) 应用描述
|
||||
tags (array[string]) 应用标签
|
||||
Request
|
||||
GET
|
||||
/info
|
||||
curl -X GET 'http://192.168.100.143/v1/info' \
|
||||
-H 'Authorization: Bearer {api_key}'
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
Response
|
||||
{
|
||||
"name": "My App",
|
||||
"description": "This is my app.",
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2"
|
||||
]
|
||||
}
|
||||
|
||||
Copy
|
||||
Copied!
|
||||
GET
|
||||
/parameters
|
||||
获取应用参数
|
||||
用于进入页面一开始,获取功能开关、输入参数名称、类型及默认值等使用。
|
||||
|
||||
Response
|
||||
user_input_form (array[object]) 用户输入表单配置
|
||||
text-input (object) 文本输入控件
|
||||
label (string) 控件展示标签名
|
||||
variable (string) 控件 ID
|
||||
required (bool) 是否必填
|
||||
default (string) 默认值
|
||||
paragraph (object) 段落文本输入控件
|
||||
label (string) 控件展示标签名
|
||||
variable (string) 控件 ID
|
||||
required (bool) 是否必填
|
||||
default (string) 默认值
|
||||
select (object) 下拉控件
|
||||
label (string) 控件展示标签名
|
||||
variable (string) 控件 ID
|
||||
required (bool) 是否必填
|
||||
default (string) 默认值
|
||||
options (array[string]) 选项值
|
||||
file_upload (object) 文件上传配置
|
||||
image (object) 图片设置 当前仅支持图片类型:png, jpg, jpeg, webp, gif
|
||||
enabled (bool) 是否开启
|
||||
number_limits (int) 图片数量限制,默认 3
|
||||
transfer_methods (array[string]) 传递方式列表,remote_url , local_file,必选一个
|
||||
system_parameters (object) 系统参数
|
||||
file_size_limit (int) 文档上传大小限制 (MB)
|
||||
image_file_size_limit (int) 图片文件上传大小限制(MB)
|
||||
audio_file_size_limit (int) 音频文件上传大小限制 (MB)
|
||||
video_file_size_limit (int) 视频文件上传大小限制 (MB)
|
||||
|
||||
|
||||
Request
|
||||
GET
|
||||
/parameters
|
||||
curl -X GET 'http://192.168.100.143/v1/parameters'
|
||||
|
||||
|
||||
Response
|
||||
{
|
||||
"user_input_form": [
|
||||
{
|
||||
"paragraph": {
|
||||
"label": "Query",
|
||||
"variable": "query",
|
||||
"required": true,
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"file_upload": {
|
||||
"image": {
|
||||
"enabled": false,
|
||||
"number_limits": 3,
|
||||
"detail": "high",
|
||||
"transfer_methods": [
|
||||
"remote_url",
|
||||
"local_file"
|
||||
]
|
||||
}
|
||||
},
|
||||
"system_parameters": {
|
||||
"file_size_limit": 15,
|
||||
"image_file_size_limit": 10,
|
||||
"audio_file_size_limit": 50,
|
||||
"video_file_size_limit": 100
|
||||
}
|
||||
}
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
openpyxl>=3.1.2
|
||||
pyyaml>=6.0.1
|
||||
requests>=2.31.0
|
||||
200
source/grade_assignments.py
Normal file
200
source/grade_assignments.py
Normal file
@ -0,0 +1,200 @@
|
||||
import os
|
||||
import yaml
|
||||
import openpyxl
|
||||
import requests
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
|
||||
# API配置
|
||||
API_KEY = "app-m7XGgbTe3BVHmA1TAYg9Ec4v" # Dify API Key
|
||||
WORKFLOW_ID = os.getenv("DIFY_WORKFLOW_ID", "your-workflow-id-here") # 工作流ID
|
||||
API_BASE_URL = "http://192.168.100.143/v1" # API基础地址
|
||||
FILE_UPLOAD_URL = f"{API_BASE_URL}/files/upload" # 文件上传地址
|
||||
EXCEL_PATH = "ai作业/AI考试作业.xlsx"
|
||||
ASSIGNMENT_DIR = "ai作业/作业"
|
||||
OUTPUT_DIR = "results"
|
||||
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "批改结果.xlsx")
|
||||
|
||||
# 确保输出目录存在
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
def read_excel_submissions():
|
||||
"""读取Excel中的作业提交记录"""
|
||||
wb = openpyxl.load_workbook(EXCEL_PATH)
|
||||
ws = wb.active
|
||||
submissions = []
|
||||
|
||||
# 获取标题行确定列索引
|
||||
headers = [cell.value for cell in ws[1]]
|
||||
name_col = headers.index('填写人')
|
||||
workflow_col = headers.index('工作流程描述')
|
||||
solution_col = headers.index('Dify工作流解决方案设计')
|
||||
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
if len(row) > max(name_col, workflow_col, solution_col): # 确保有足够列
|
||||
submissions.append({
|
||||
'name': row[name_col],
|
||||
'work_description': row[workflow_col],
|
||||
'solution': row[solution_col]
|
||||
})
|
||||
return submissions
|
||||
|
||||
def find_assignment_yml(name):
|
||||
"""根据姓名查找对应的YML作业文件"""
|
||||
for filename in os.listdir(ASSIGNMENT_DIR):
|
||||
if filename.endswith('.yml'): # 仅支持.yml格式
|
||||
# 从文件名中提取姓名部分(第二个下划线分隔的部分)
|
||||
parts = filename.split('_')
|
||||
if len(parts) >= 2 and name == parts[1]:
|
||||
return os.path.join(ASSIGNMENT_DIR, filename)
|
||||
return None
|
||||
|
||||
def parse_yml_file(yml_path):
|
||||
"""解析YML文件内容"""
|
||||
with open(yml_path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def call_dify_api(yml_path, assignment_data):
|
||||
"""调用Dify API进行作业批改,上传YML文件"""
|
||||
# 先上传文件
|
||||
upload_headers = {
|
||||
"Authorization": f"Bearer {API_KEY}"
|
||||
}
|
||||
upload_data = {
|
||||
"user": "ai-grading-system",
|
||||
"type": "yml" # 自定义文件类型
|
||||
}
|
||||
|
||||
try:
|
||||
# 上传文件
|
||||
with open(yml_path, 'rb') as f:
|
||||
files = {'file': (os.path.basename(yml_path), f, 'text/plain')}
|
||||
upload_response = requests.post(
|
||||
FILE_UPLOAD_URL,
|
||||
headers=upload_headers,
|
||||
files=files,
|
||||
data=upload_data
|
||||
)
|
||||
upload_response.raise_for_status()
|
||||
file_id = upload_response.json().get('id')
|
||||
|
||||
if not file_id:
|
||||
print("文件上传失败: 未获取到文件ID")
|
||||
return None
|
||||
|
||||
# 执行工作流
|
||||
run_url = f"{API_BASE_URL}/workflows/run"
|
||||
run_headers = {
|
||||
"Authorization": f"Bearer {API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
run_data = {
|
||||
"inputs": {
|
||||
"yml_file": {
|
||||
"transfer_method": "local_file",
|
||||
"upload_file_id": file_id,
|
||||
"type": "custom"
|
||||
},
|
||||
"work_description": assignment_data.get('work_description', ''),
|
||||
"solution": assignment_data.get('solution', '')
|
||||
},
|
||||
"response_mode": "blocking",
|
||||
"user": "ai-grading-system"
|
||||
}
|
||||
|
||||
response = requests.post(run_url, headers=run_headers, json=run_data)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"API调用失败: {e}")
|
||||
return None
|
||||
|
||||
def save_results(results):
|
||||
"""保存批改结果到Excel"""
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "批改结果"
|
||||
|
||||
# 添加表头
|
||||
headers = ["姓名", "评分", "评分详情"]
|
||||
ws.append(headers)
|
||||
|
||||
# 设置表头样式
|
||||
for cell in ws[1]:
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
# 添加数据
|
||||
for result in results:
|
||||
ws.append([
|
||||
result['name'],
|
||||
result.get('score', 'N/A'),
|
||||
str(result.get('details', '无详情'))
|
||||
])
|
||||
|
||||
# 自动调整列宽
|
||||
for column in ws.columns:
|
||||
max_length = 0
|
||||
column = [cell for cell in column]
|
||||
for cell in column:
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(cell.value)
|
||||
except:
|
||||
pass
|
||||
adjusted_width = (max_length + 2) * 1.2
|
||||
ws.column_dimensions[column[0].column_letter].width = adjusted_width
|
||||
|
||||
wb.save(OUTPUT_FILE)
|
||||
print(f"批改结果已保存到: {OUTPUT_FILE}")
|
||||
|
||||
def main():
|
||||
# 读取Excel中的作业提交
|
||||
submissions = read_excel_submissions()
|
||||
results = []
|
||||
|
||||
for sub in submissions:
|
||||
print(f"正在处理: {sub['name']}")
|
||||
|
||||
# 查找对应的YML文件
|
||||
yml_path = find_assignment_yml(sub['name'])
|
||||
if not yml_path:
|
||||
print(f"未找到 {sub['name']} 的作业文件")
|
||||
continue
|
||||
|
||||
# 解析YML文件
|
||||
try:
|
||||
yml_content = parse_yml_file(yml_path)
|
||||
except Exception as e:
|
||||
print(f"解析YML文件失败: {e}")
|
||||
continue
|
||||
|
||||
# 准备API调用数据
|
||||
assignment_data = {
|
||||
**sub,
|
||||
**yml_content
|
||||
}
|
||||
|
||||
# 调用API进行批改(上传YML文件)
|
||||
api_response = call_dify_api(yml_path, assignment_data)
|
||||
if not api_response:
|
||||
print(f"{sub['name']} 批改失败")
|
||||
continue
|
||||
|
||||
# 解析API响应(直接从文件上传返回的结果)
|
||||
try:
|
||||
outputs = api_response.get('data', {}).get('outputs', {})
|
||||
results.append({
|
||||
'name': sub['name'],
|
||||
'score': outputs.get('score'),
|
||||
'details': outputs.get('details')
|
||||
})
|
||||
print(f"{sub['name']} 批改完成")
|
||||
except Exception as e:
|
||||
print(f"解析API响应失败: {e}")
|
||||
|
||||
# 保存结果
|
||||
save_results(results)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
123
source/test_script.py
Normal file
123
source/test_script.py
Normal file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
测试脚本 - 测试grade_assignments.py功能
|
||||
使用CSV格式保存测试结果
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import yaml
|
||||
from grade_assignments import (
|
||||
read_excel_submissions,
|
||||
find_assignment_yml,
|
||||
parse_yml_file,
|
||||
call_dify_api,
|
||||
save_results
|
||||
)
|
||||
|
||||
def test_grade_assignments():
|
||||
"""测试作业批改全流程,包含Excel结果写入"""
|
||||
print("=== 开始测试 ===")
|
||||
|
||||
# 1. 创建测试YML文件
|
||||
test_yml = "ai作业/作业/第3题_测试_20250331_测试作业_1.yml"
|
||||
test_data = {
|
||||
"work_description": "测试工作流程描述",
|
||||
"solution": "测试解决方案设计",
|
||||
"additional_field": "测试额外字段"
|
||||
}
|
||||
|
||||
with open(test_yml, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(test_data, f, allow_unicode=True)
|
||||
print(f"已创建测试YML文件: {test_yml}")
|
||||
|
||||
# 2. 测试API调用
|
||||
print("\n[测试1] 调用Dify API...")
|
||||
api_response = call_dify_api(test_yml, test_data)
|
||||
|
||||
if not api_response:
|
||||
print("API调用失败")
|
||||
return
|
||||
|
||||
print("API调用成功")
|
||||
print(f"响应结果: {api_response}")
|
||||
|
||||
# 3. 测试结果写入Excel
|
||||
print("\n[测试2] 测试结果写入Excel...")
|
||||
# 从API响应中提取评分结果
|
||||
api_output = api_response['data']['outputs']['text']
|
||||
score_match = re.search(r'总分:(\d+)分', api_output)
|
||||
feedback_match = re.search(r'优点和不足之处(.*?)改进建议', api_output, re.DOTALL)
|
||||
|
||||
test_result = {
|
||||
"name": "测试学生",
|
||||
"score": int(score_match.group(1)) if score_match else 0,
|
||||
"feedback": feedback_match.group(1).strip() if feedback_match else "无反馈",
|
||||
"status": "已完成",
|
||||
"api_response": api_response
|
||||
}
|
||||
|
||||
# 临时结果文件路径 - 使用Excel格式
|
||||
from openpyxl import Workbook
|
||||
import uuid
|
||||
test_xlsx = f"results/测试结果_{uuid.uuid4().hex[:8]}.xlsx"
|
||||
os.makedirs("results", exist_ok=True)
|
||||
|
||||
try:
|
||||
# 创建Excel工作簿
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "测试结果"
|
||||
|
||||
# 写入表头
|
||||
ws.append(['姓名', '分数', '反馈'])
|
||||
|
||||
# 写入数据
|
||||
ws.append([
|
||||
test_result['name'],
|
||||
test_result['score'],
|
||||
test_result['feedback']
|
||||
])
|
||||
|
||||
# 保存Excel文件
|
||||
wb.save(test_xlsx)
|
||||
print(f"测试结果已写入Excel: {test_xlsx}")
|
||||
|
||||
# 验证文件是否存在
|
||||
if os.path.exists(test_xlsx):
|
||||
print("Excel文件写入验证成功")
|
||||
else:
|
||||
print("Excel文件写入失败")
|
||||
except Exception as e:
|
||||
print(f"写入Excel文件时出错: {str(e)}")
|
||||
|
||||
# 清理测试文件 - 添加重试机制处理文件锁定
|
||||
import time
|
||||
max_retries = 3
|
||||
retry_delay = 1 # 秒
|
||||
|
||||
def safe_remove(filepath):
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
print(f"已清理文件: {filepath}")
|
||||
return True
|
||||
except Exception as e:
|
||||
if i == max_retries - 1:
|
||||
print(f"无法清理文件 {filepath}: {str(e)}")
|
||||
return False
|
||||
time.sleep(retry_delay)
|
||||
return False
|
||||
|
||||
print("\n[清理] 删除测试文件...")
|
||||
safe_remove(test_yml)
|
||||
safe_remove(test_xlsx)
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_grade_assignments()
|
||||
Loading…
Reference in New Issue
Block a user