This commit is contained in:
2026-05-28 19:13:14 -06:00
parent 6ab2886f77
commit fd7384980a
2 changed files with 29 additions and 12 deletions

View File

@@ -18,7 +18,7 @@ from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
from problem.models import Problem from problem.models import Problem
from submission.models import JudgeStatus, Submission from submission.models import JudgeStatus, Submission
from utils.api import APIView from utils.api import APIView
from utils.openai import get_ai_client from utils.openai import get_ai_client, get_async_ai_client
from utils.shortcuts import datetime2str from utils.shortcuts import datetime2str
CACHE_TIMEOUT = 300 CACHE_TIMEOUT = 300
@@ -163,10 +163,15 @@ def get_user_first_ac_submissions(
return user_first_ac, by_problem, problem_ids return user_first_ac, by_problem, problem_ids
def stream_ai_response(client, system_prompt, user_prompt, on_complete=None): async def stream_ai_response(client, system_prompt, user_prompt, on_complete=None):
"""SSE 流式响应生成器on_complete(full_text) 在流结束时调用""" """SSE 流式响应异步生成器on_complete(full_text) 在流结束时调用
必须是异步生成器:在 ASGI 下,同步生成器会被 Django 的
StreamingHttpResponse 通过 sync_to_async(list) 一次性消费完才发送,
导致整段内容生成完毕后才返回,失去流式效果。
"""
try: try:
stream = client.chat.completions.create( stream = await client.chat.completions.create(
model="deepseek-v4-flash", model="deepseek-v4-flash",
messages=[ messages=[
{"role": "system", "content": system_prompt}, {"role": "system", "content": system_prompt},
@@ -183,13 +188,13 @@ def stream_ai_response(client, system_prompt, user_prompt, on_complete=None):
yield "event: start\n\n" yield "event: start\n\n"
chunks = [] chunks = []
try: try:
for chunk in stream: async for chunk in stream:
if not chunk.choices: if not chunk.choices:
continue continue
choice = chunk.choices[0] choice = chunk.choices[0]
if choice.finish_reason: if choice.finish_reason:
if on_complete: if on_complete:
on_complete("".join(chunks).strip()) await on_complete("".join(chunks).strip())
yield f"data: {json.dumps({'type': 'done'})}\n\n" yield f"data: {json.dumps({'type': 'done'})}\n\n"
break break
content = choice.delta.content content = choice.delta.content
@@ -209,6 +214,8 @@ def make_sse_response(generator):
content_type="text/event-stream", content_type="text/event-stream",
) )
response["Cache-Control"] = "no-cache" response["Cache-Control"] = "no-cache"
# 关闭反向代理(如 nginx对流式响应的缓冲
response["X-Accel-Buffering"] = "no"
return response return response
@@ -667,7 +674,7 @@ class AIAnalysisAPI(APIView):
details = request.data.get("details") details = request.data.get("details")
duration = request.data.get("duration") duration = request.data.get("duration")
client = get_ai_client() client = get_async_ai_client()
system_prompt = ( system_prompt = (
"你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。" "你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。"
@@ -676,9 +683,11 @@ class AIAnalysisAPI(APIView):
) )
user_prompt = f"这段时间内的详细数据: {details}\n(其中部分字段含义是 flowcharts:流程图的提交,solved:代码的提交)\n每周或每月的数据: {duration}" user_prompt = f"这段时间内的详细数据: {details}\n(其中部分字段含义是 flowcharts:流程图的提交,solved:代码的提交)\n每周或每月的数据: {duration}"
def on_complete(full_text): user = request.user
AIAnalysis.objects.create(
user=request.user, async def on_complete(full_text):
await AIAnalysis.objects.acreate(
user=user,
provider="deepseek", provider="deepseek",
model="deepseek-v4-flash", model="deepseek-v4-flash",
data={"details": details, "duration": duration}, data={"details": details, "duration": duration},
@@ -705,7 +714,7 @@ class AIHintAPI(APIView):
return self.error("Submission not found") return self.error("Submission not found")
problem = submission.problem problem = submission.problem
client = get_ai_client() client = get_async_ai_client()
# 获取参考答案(同语言优先,否则取第一个) # 获取参考答案(同语言优先,否则取第一个)
answers = problem.answers or [] answers = problem.answers or []

View File

@@ -1,4 +1,4 @@
from openai import OpenAI from openai import AsyncOpenAI, OpenAI
from utils.shortcuts import get_env from utils.shortcuts import get_env
@@ -11,3 +11,11 @@ def get_ai_client() -> OpenAI:
raise Exception("缺少 AI_KEY") raise Exception("缺少 AI_KEY")
return OpenAI(api_key=key, base_url=BASE_URL) return OpenAI(api_key=key, base_url=BASE_URL)
def get_async_ai_client() -> AsyncOpenAI:
key = get_env("AI_KEY")
if not key:
raise Exception("缺少 AI_KEY")
return AsyncOpenAI(api_key=key, base_url=BASE_URL)