对diagnose重构了一下

This commit is contained in:
pzc-x99 2025-03-01 00:23:43 +08:00
parent f56b66d8f1
commit fa00e1d19d
12 changed files with 342 additions and 186 deletions

View File

Before

Width:  |  Height:  |  Size: 928 KiB

After

Width:  |  Height:  |  Size: 928 KiB

View File

@ -1,9 +1,12 @@
from django.contrib import admin
# Register your models here.
from . models import Papers
from . models import Papers, OpenAIDiagnose
@admin.register(Papers)
class PaperAdmin(admin.ModelAdmin):
list_display = ('id', 'uuid', 'status', 'created_at', 'updated_at')
list_display = ('id', 'uuid', 'status', 'created_at', 'updated_at')
@admin.register(OpenAIDiagnose)
class OpenAIDiagnoseAdmin(admin.ModelAdmin):
list_display = ('id', 'uuid', 'status', 'diagnose_type', 'created_at', 'updated_at')

69
diagnose/db_views.py Normal file
View File

@ -0,0 +1,69 @@
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from authentication.authentication import CustomTokenAuthentication
import uuid
import os
from django.conf import settings
from django.http import JsonResponse
from rest_framework import status
from .models import OpenAIDiagnose
from .openai_dia import openai_diagnoser_asyna_wraper
from django.core.exceptions import ObjectDoesNotExist
import json
class MyProtectedUploadDiagnose(APIView):
authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证
permission_classes = [IsAuthenticated] # 需要用户认证才能访问
def post(self, request):
user = request.user
uuid_str = str(uuid.uuid4())
save_file_path = os.path.join(settings.BASE_DIR, 'upload_file', user.username, uuid_str)
os.makedirs(os.path.dirname(save_file_path), exist_ok=True)
# 获取上传的文件
uploaded_file = request.FILES.get('file')
if not uploaded_file:
return JsonResponse({"error": "No file uploaded."}, status=status.HTTP_400_BAD_REQUEST)
# 保存文件
try:
with open(save_file_path, 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
except:
return JsonResponse({"error": "Error saving file."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
obj = OpenAIDiagnose.objects.create(uuid=uuid_str, user=user, status='P')
obj.save()
user.subject_usage_count = user.subject_usage_count - 1
user.save()
openai_diagnoser_asyna_wraper.delay(uuid_str, user.username, "")
return JsonResponse({"message": "File uploaded successfully.", "uuid": uuid_str}, status=status.HTTP_200_OK)
class MyProtectedDiagnoseCheck(APIView):
authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证
permission_classes = [IsAuthenticated] # 需要用户认证才能访问
def post(self, request):
user = request.user
# 处理 POST 请求,更新用户的联系信息
try:
data = json.loads(request.body)
uuid_str = data.get("uuid")
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON format"}, status=400)
try:
# print(uuid_str)
obj = OpenAIDiagnose.objects.get(uuid=uuid_str, user=user) # 只获取一个对象
if obj.status == 'D':
return JsonResponse({"status": "done"})
else:
return JsonResponse({"status": "processing"})
except ObjectDoesNotExist:
return JsonResponse({"error_code": "2004"}, status=404)

View File

@ -1,14 +1,9 @@
# -*- coding: utf-8 -*-
import os
from openai import OpenAI
import markdown2
import pdfkit
from django.conf import settings
from django.http import JsonResponse, FileResponse
from celery import shared_task
import uuid
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from authentication.authentication import CustomTokenAuthentication
@ -16,6 +11,8 @@ from django.core.exceptions import ObjectDoesNotExist
from .models import Papers
from ec_user.models import SchoolInfo
import json
from .openai_gen import paper_gen_async_wraper, convert_to_pdf
# ============== PYdoc ===================
# from docx import Document
# import pypandoc
@ -57,101 +54,6 @@ class DpArguments():
def generate_html_paper(md_result, file_path_name):
# 写到md中
md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8')
md_file.write(md_result)
md_file.close()
# 从md中读出来再写到html中
md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8')
md_text = md_read_file.read()
md_read_file.close()
html = markdown2.markdown(md_text)
html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8')
html_write_file.write(
"""<head>
<meta charset="utf-8">
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
</head>"""+html)
html_write_file.close()
def convert_to_pdf(file_path_name):
# =============== pdfkit ================
# 从html中读出来再写到pdf中
f = open(f'{file_path_name}.html', 'r', encoding='utf-8')
content = f.read()
f.close()
config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_PATH)
pdfkit.from_string(content, f'{file_path_name}.pdf', configuration=config)
@shared_task
def async_wraper(file_path_name, uuid_str, subject, textbook_version, start_grade, \
start_semester, start_chapter, end_grade, end_semester, end_chapter, student):
# client = OpenAI(
# api_key = settings.OPAI_API_KEY,
# base_url = settings.OPAI_BASE_URL,
# )
# completion = client.chat.completions.create(
# model = "deepseek-r1-250120", # your model endpoint ID
# messages = [
# {"role": "system", "content": "你是教学助手"},
# # {"role": "user",
# # "content": f"请根据以下信息生成一份检测卷:学科为{subject},教材版本是{textbook_version},\
# # 检测范围从{start_grade}年级{start_semester}学期{start_chapter}章\
# # 到{end_grade}年级{end_semester}学期{end_chapter}章,学生年级为{student.get('grade')},\
# # 年龄为{student.get('age')},所在学校是{student.get('school')}。\
# # 根据答题所需时间一个半小时设置题量。题目有梯度。\
# # 试卷题目为{subject}{textbook_version}检测卷。只生成题目,不输出答案及说明等其他内容。"
# # },
# {"role": "user", "content": f"""请严格按照以下要求生成一份{subject}检测卷:
# 【试卷信息】
# 1. 标题:**{subject}{textbook_version}综合检测卷**
# 2. 检测范围:{start_grade}年级{start_semester}学期第{start_chapter}章 至
# {end_grade}年级{end_semester}学期第{end_chapter}章
# 3. 适用对象:{student.get('school')} {student.get('grade')}年级学生(年龄{student.get('age')}
# 【命题要求】
# 1. 题型结构:
# - 按难度梯度分为基础题60%、中档题30%、提高题10%
# - 根据学科特点设计合理的题型(如数学可设置选择题/填空题/计算题/应用题,语文可设置阅读理解/文言文/写作等)
# - 同一知识点不重复考查
# 2. 题量控制:
# - 总题数控制在18-22题以优秀学生完成时间约75分钟为标准
# - 选择题不超过5题填空题不超过6题
# - 需包含至少1道综合应用题理科或材料分析题文科
# 3. 内容要求:
# - 严格按照指定章节范围命题
# - 题目表述清晰无歧义,数字单位标注完整
# - 禁止出现需图片辅助的题目
# - 数学类题目需给出必要公式位置(如"提示可用公式____"
# 【输出格式】
# 仅输出以下内容:
# 1. 试卷标题(加粗居中)
# 2. 考试说明含总分值100分、考试时长、姓名班级填写处
# 3. 分模块的题目内容(标注题型、分值和题号)
# 4. "——以下为题目区域——" 分隔线
# 禁止包含:
# - 答案及解析
# - 评分标准
# - 额外说明文字
# - markdown格式及特殊符号"""}
# ],
# )
# generate_html_paper(completion.choices[0].message.content, file_path_name)
import time
time.sleep(30)
generate_html_paper(""" # Test md""", file_path_name)
try:
obj = Papers.objects.get(uuid=uuid_str) # 只获取一个对象
obj.status = 'D'
obj.updated_at = timezone.now()
obj.save()
except ObjectDoesNotExist:
print("warning: record not found")
class MyProtectedGeneratePaper(APIView):
authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证
permission_classes = [IsAuthenticated] # 需要用户认证才能访问
@ -197,7 +99,7 @@ class MyProtectedGeneratePaper(APIView):
obj.save()
user.subject_usage_count = user.subject_usage_count - 1
user.save()
async_wraper.delay(file_path_name, uuid_str, \
paper_gen_async_wraper.delay(file_path_name, uuid_str, \
dp_args.subject, dp_args.textbook_version, dp_args.start_grade, dp_args.start_semester, dp_args.start_chapter, \
dp_args.end_grade, dp_args.end_semester, dp_args.end_chapter, dp_args.student)
return JsonResponse({"status": "success", "uuid": uuid_str})

View File

@ -0,0 +1,28 @@
# Generated by Django 4.2.19 on 2025-02-28 15:39
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('diagnose', '0002_papers_created_at_papers_updated_at'),
]
operations = [
migrations.CreateModel(
name='OpenAIDiagnose',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.CharField(max_length=100)),
('status', models.CharField(choices=[('C', '任务创建'), ('P', '生成中'), ('D', '已完成')], default='C', max_length=1)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True, null=True)),
('diagnose_type', models.CharField(choices=[('M', '大模型试卷诊断'), ('S', '学校试卷诊断'), ('H', '作业诊断'), ('N', '课堂笔记诊断')], default='M', max_length=1)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -16,4 +16,27 @@ class Papers(models.Model):
updated_at = models.DateTimeField(auto_now=True,null=True, blank=True) # 更新时间
def __str__(self):
return self.user.username + '的试卷'
return self.user.name + '的试卷'
class OpenAIDiagnose(models.Model):
STATUS_CHOICES = (
('C', '任务创建'),
('P', '生成中'),
('D', '已完成')
)
DIAGNOSE_TYPE = (
('M', '大模型试卷诊断'), # big model
('S', '学校试卷诊断'), # school
('H', '作业诊断'), # homework
('N', '课堂笔记诊断') # notebook
)
uuid = models.CharField(max_length=100) # 一次分析唯一标识符
# 定义一个与EcUser一对多关联的外键
user = models.ForeignKey(EcUser, on_delete=models.CASCADE) # 当EcUser被删除时关联的Papers也会被删除
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='C') # 默认值设置为'C'
created_at = models.DateTimeField(auto_now_add=True,null=True, blank=True) # 创建时间
updated_at = models.DateTimeField(auto_now=True,null=True, blank=True) # 更新时间
diagnose_type = models.CharField(max_length=1, choices=DIAGNOSE_TYPE, default='M') # 默认值设置为'M'
def __str__(self):
return self.user.name + '的诊断'

90
diagnose/openai_dia.py Normal file
View File

@ -0,0 +1,90 @@
import os
from openai import OpenAI
import base64
from PIL import Image
from celery import shared_task
from django.conf import settings
import markdown2
from django.utils import timezone
from .models import OpenAIDiagnose
from django.core.exceptions import ObjectDoesNotExist
def get_image_type(image_path):
with Image.open(image_path) as img:
return img.format # 返回图片的格式,例如 'PNG' 或 'JPEG'
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 将图片转为Base64编码
def generate_html_paper(md_result, file_path_name):
# 写到md中
md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8')
md_file.write(md_result)
md_file.close()
# 从md中读出来再写到html中
md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8')
md_text = md_read_file.read()
md_read_file.close()
html = markdown2.markdown(md_text)
html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8')
html_write_file.write(
"""<head>
<meta charset="utf-8">
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
</head>"""+html)
html_write_file.close()
@shared_task
def openai_diagnoser_asyna_wraper(uuid_str: str, username: str, diagnose_type: str):
client = OpenAI(
api_key = settings.OPAI_IMG_API_KEY,
base_url = settings.OPAI_IMG_BASE_URL
)
# 需要传给大模型的图片
# image_path = "ch.jpg"
image_path = os.path.join(settings.BASE_DIR, 'upload_file', username, uuid_str)
base64_image = encode_image(image_path)
image_type = get_image_type(image_path)
image_type = image_type.lower()
# print("===> image type:" + image_type)
# Image input:
response = client.chat.completions.create(
model="doubao-1-5-vision-pro-32k-250115",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "图中是几个题目,帮我分析一下,我是否做对了",
},
{
"type": "image_url",
"image_url": {
# 需要注意传入Base64编码前需要增加前缀 data:image/{图片格式};base64,{Base64编码}
# PNG图片"url": f"data:image/png;base64,{base64_image}"
# JEPG图片"url": f"data:image/jpeg;base64,{base64_image}"
# WEBP图片"url": f"data:image/webp;base64,{base64_image}"
"url": f"data:image/{image_type};base64,{base64_image}"
},
},
],
}
],
)
# print(response.choices[0].message.content)
generate_html_paper(response.choices[0].message.content, image_path)
try:
obj = OpenAIDiagnose.objects.get(uuid=uuid_str) # 只获取一个对象
obj.status = 'D'
obj.updated_at = timezone.now()
obj.save()
except ObjectDoesNotExist:
print("warning: record not found")

116
diagnose/openai_gen.py Normal file
View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
from openai import OpenAI
import markdown2
import pdfkit
from django.conf import settings
from celery import shared_task
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from .models import Papers
# ============== PYdoc ===================
# from docx import Document
# import pypandoc
# pypandoc.download_pandoc()
# ================ weasyprint ===========
# 库用不了
# from weasyprint import HTML
# HTML(string=html).write_pdf("output.pdf")
def generate_html_paper(md_result, file_path_name):
# 写到md中
md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8')
md_file.write(md_result)
md_file.close()
# 从md中读出来再写到html中
md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8')
md_text = md_read_file.read()
md_read_file.close()
html = markdown2.markdown(md_text)
html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8')
html_write_file.write(
"""<head>
<meta charset="utf-8">
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
</head>"""+html)
html_write_file.close()
def convert_to_pdf(file_path_name):
# =============== pdfkit ================
# 从html中读出来再写到pdf中
f = open(f'{file_path_name}.html', 'r', encoding='utf-8')
content = f.read()
f.close()
config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_PATH)
pdfkit.from_string(content, f'{file_path_name}.pdf', configuration=config)
@shared_task
def paper_gen_async_wraper(file_path_name, uuid_str, subject, textbook_version, start_grade, \
start_semester, start_chapter, end_grade, end_semester, end_chapter, student):
# client = OpenAI(
# api_key = settings.OPAI_API_KEY,
# base_url = settings.OPAI_BASE_URL,
# )
# completion = client.chat.completions.create(
# model = "deepseek-r1-250120", # your model endpoint ID
# messages = [
# {"role": "system", "content": "你是教学助手"},
# # {"role": "user",
# # "content": f"请根据以下信息生成一份检测卷:学科为{subject},教材版本是{textbook_version},\
# # 检测范围从{start_grade}年级{start_semester}学期{start_chapter}章\
# # 到{end_grade}年级{end_semester}学期{end_chapter}章,学生年级为{student.get('grade')},\
# # 年龄为{student.get('age')},所在学校是{student.get('school')}。\
# # 根据答题所需时间一个半小时设置题量。题目有梯度。\
# # 试卷题目为{subject}{textbook_version}检测卷。只生成题目,不输出答案及说明等其他内容。"
# # },
# {"role": "user", "content": f"""请严格按照以下要求生成一份{subject}检测卷:
# 【试卷信息】
# 1. 标题:**{subject}{textbook_version}综合检测卷**
# 2. 检测范围:{start_grade}年级{start_semester}学期第{start_chapter}章 至
# {end_grade}年级{end_semester}学期第{end_chapter}章
# 3. 适用对象:{student.get('school')} {student.get('grade')}年级学生(年龄{student.get('age')}
# 【命题要求】
# 1. 题型结构:
# - 按难度梯度分为基础题60%、中档题30%、提高题10%
# - 根据学科特点设计合理的题型(如数学可设置选择题/填空题/计算题/应用题,语文可设置阅读理解/文言文/写作等)
# - 同一知识点不重复考查
# 2. 题量控制:
# - 总题数控制在18-22题以优秀学生完成时间约75分钟为标准
# - 选择题不超过5题填空题不超过6题
# - 需包含至少1道综合应用题理科或材料分析题文科
# 3. 内容要求:
# - 严格按照指定章节范围命题
# - 题目表述清晰无歧义,数字单位标注完整
# - 禁止出现需图片辅助的题目
# - 数学类题目需给出必要公式位置(如"提示可用公式____"
# 【输出格式】
# 仅输出以下内容:
# 1. 试卷标题(加粗居中)
# 2. 考试说明含总分值100分、考试时长、姓名班级填写处
# 3. 分模块的题目内容(标注题型、分值和题号)
# 4. "——以下为题目区域——" 分隔线
# 禁止包含:
# - 答案及解析
# - 评分标准
# - 额外说明文字
# - markdown格式及特殊符号"""}
# ],
# )
# generate_html_paper(completion.choices[0].message.content, file_path_name)
import time
time.sleep(30)
generate_html_paper(""" # Test md""", file_path_name)
try:
obj = Papers.objects.get(uuid=uuid_str) # 只获取一个对象
obj.status = 'D'
obj.updated_at = timezone.now()
obj.save()
except ObjectDoesNotExist:
print("warning: record not found")

View File

@ -1,43 +0,0 @@
import os
from openai import OpenAI
import base64
client = OpenAI(
api_key = 'd04d386a-7c67-4927-8251-171a236583a6',
base_url = "https://ark.cn-beijing.volces.com/api/v3",
)
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 将图片转为Base64编码
# 需要传给大模型的图片
image_path = "ch.jpg"
base64_image = encode_image(image_path)
# Image input:
response = client.chat.completions.create(
model="doubao-1-5-vision-pro-32k-250115",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "图中是几个语文题目,帮我分析一下,我是否做对了",
},
{
"type": "image_url",
"image_url": {
# 需要注意传入Base64编码前需要增加前缀 data:image/{图片格式};base64,{Base64编码}
# PNG图片"url": f"data:image/png;base64,{base64_image}"
# JEPG图片"url": f"data:image/jpeg;base64,{base64_image}"
# WEBP图片"url": f"data:image/webp;base64,{base64_image}"
"url": f"data:image/jpg;base64,{base64_image}"
},
},
],
}
],
)
print(response.choices[0].message.content)

View File

@ -1,36 +0,0 @@
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from authentication.authentication import CustomTokenAuthentication
import uuid
import os
from django.conf import settings
from django.http import JsonResponse
from rest_framework import status
class MyProtectedUploadfile(APIView):
authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证
permission_classes = [IsAuthenticated] # 需要用户认证才能访问
def post(self, request):
user = request.user
uuid_str = str(uuid.uuid4())
save_file_path = os.path.join(settings.BASE_DIR, 'upload_file', user.username, uuid_str)
os.makedirs(os.path.dirname(save_file_path), exist_ok=True)
# 获取上传的文件
uploaded_file = request.FILES.get('file')
if not uploaded_file:
return JsonResponse({"error": "No file uploaded."}, status=status.HTTP_400_BAD_REQUEST)
# 保存文件
try:
with open(save_file_path, 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
except:
return JsonResponse({"error": "Error saving file."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return JsonResponse({"message": "File uploaded successfully."}, status=status.HTTP_200_OK)

View File

@ -1,11 +1,12 @@
from django.urls import path
from .dp_views import MyProtectedGeneratePaper, MyProtectedGenerateCheck, MyProtectedDownloadPaper
from .upload_views import MyProtectedUploadfile
from .db_views import MyProtectedUploadDiagnose, MyProtectedDiagnoseCheck
urlpatterns = [
path('', MyProtectedGeneratePaper.as_view(), name='api_generate_paper'),
path('check/', MyProtectedGenerateCheck.as_view(), name='api_generate_check'),
path('download/', MyProtectedDownloadPaper.as_view(), name='api_download_paper'),
path('upload/', MyProtectedUploadfile.as_view(), name='api_upload_file')
path('upload/', MyProtectedUploadDiagnose.as_view(), name='api_upload_file'),
path('upload_diagnose_check/', MyProtectedDiagnoseCheck.as_view(), name='api_diagnose_file')
]

View File

@ -172,6 +172,9 @@ ERROR_CODE_MAP = {
OPAI_API_KEY = 'e119bd6c-a22a-404e-a002-6d72a2cea65d'
OPAI_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3'
OPAI_IMG_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3'
OPAI_IMG_API_KEY = 'd04d386a-7c67-4927-8251-171a236583a6'
WKHTMLTOPDF_PATH = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe'
# settings.py