feat:增加考试接口

This commit is contained in:
2026-08-02 22:57:36 +08:00
parent 5497b881c6
commit e84f4e7e18
7 changed files with 125 additions and 25 deletions

View File

@@ -82,3 +82,61 @@ knowledge_prompt = """
- 仅输出 Markdown 正文,不要包含 `<html>`、JSON 或其他格式
- 不要在开头重复本提示词内容
"""
exam_prompt = """
你是一位经验丰富的命题老师。请根据用户提供的学科和题目总数,生成一份仅包含选择题的试卷。
【题目分配规则】
1. 单选题占 80%,多选题占 20%
2. 题目总数向上取整,确保单选题数量优先。
3. 单选题必须排在前面,多选题排在后面。
4. id 从 1 开始连续编号,先单选后多选。
【字段要求】
每道题必须包含以下字段:
- id题号先单选后多选
- type
- single单选题answer 长度为 1
- multiple多选题answer 长度 ≥ 2
- question题干内容
- options选项数组固定 4 项,格式为 ["A. xxx", "B. xxx", "C. xxx", "D. xxx"]
- answer正确答案编号数组如 ["A"] 或 ["A", "C"]
- select用户选择永远为空数组 []
【约束】
1. 仅返回 JSON 数组,不要任何说明、注释或 Markdown。
2. 单选题的 answer 只能包含一个选项。
3. 多选题的 answer 至少包含两个选项。
4. 选项必须具有区分度,不能有明显错误或重复。
5. 题目难度适中,语言严谨,无歧义。
【示例】
[
{
"id": 1,
"type": "single",
"question": "下列关于光的传播说法正确的是?",
"options": [
"A. 光在同种均匀介质中沿直线传播",
"B. 光在真空中的传播速度为 3×10⁸ m/s",
"C. 光在不同介质中传播速度相同",
"D. 光不能在真空中传播"
],
"answer": ["A"],
"select": []
},
{
"id": 2,
"type": "multiple",
"question": "下列哪些属于可再生能源?",
"options": [
"A. 太阳能",
"B. 煤炭",
"C. 风能",
"D. 天然气"
],
"answer": ["A", "C"],
"select": []
}
]
"""

View File

@@ -1,7 +1,7 @@
from langchain.agents import create_agent
from agent.model import model
from agent.prompt import question_prompt, knowledge_prompt
from agent.prompt import question_prompt, knowledge_prompt, exam_prompt
question_agent = create_agent(
model=model,
@@ -12,3 +12,8 @@ knowledge_agent = create_agent(
model=model,
system_prompt=knowledge_prompt
)
exam_agent = create_agent(
model=model,
system_prompt=exam_prompt
)

View File

@@ -1,8 +1,10 @@
from .agent import router as agent_router
from .library import router as library_router
from .practice import router as practice_router
from .mistake import router as mistake_router
routers = [
agent_router,
library_router,
practice_router,
mistake_router,

31
routers/agent.py Normal file
View File

@@ -0,0 +1,31 @@
from fastapi import APIRouter
from starlette.responses import StreamingResponse
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest, QueryExamRequest
from service import agent_service
router = APIRouter(prefix="/agent", tags=["Agent"])
@router.post("/question")
def generate_question(query: QueryQuestionRequest):
return StreamingResponse(
agent_service.generate_question_agent(query),
media_type="text/event-stream",
)
@router.post("/knowledge")
def generate_knowledge(query: QueryKnowledgeRequest):
return StreamingResponse(
agent_service.generate_knowledge_agent(query),
media_type="text/event-stream",
)
@router.post("/exam")
def generate_exam(query: QueryExamRequest):
return StreamingResponse(
agent_service.generate_exam_agent(query),
media_type="text/event-stream",
)

View File

@@ -1,12 +1,10 @@
from typing import List
from fastapi import APIRouter, Depends, File, Form, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import StreamingResponse
from database import get_db
from schemas.library import LibraryFileRequest, LibraryFileResponse
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest
from service import library_service, agent_service
from service import library_service
from storage import upload_rustfs
from ocr import ocr_from_url
@@ -19,22 +17,6 @@ def upload(file: UploadFile = File(...), filename: str = Form(...)):
return ocr_from_url(url)
@router.post("/question")
def generate_question(query: QueryQuestionRequest):
return StreamingResponse(
agent_service.query_question_agent(query),
media_type="text/event-stream",
)
@router.post("/knowledge")
def generate_knowledge(query: QueryKnowledgeRequest):
return StreamingResponse(
agent_service.query_knowledge_agent(query),
media_type="text/event-stream",
)
@router.get("/files", response_model=List[LibraryFileResponse])
async def list_files(db: AsyncSession = Depends(get_db)):
return await library_service.list_files(db)

View File

@@ -9,3 +9,8 @@ class QueryKnowledgeRequest(BaseModel):
subject: str
module: str
name: str
class QueryExamRequest(BaseModel):
subject: str
total: int

View File

@@ -1,10 +1,10 @@
from langchain_core.messages import AIMessageChunk, HumanMessage
from agent.study import question_agent, knowledge_agent
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest
from agent.study import question_agent, knowledge_agent, exam_agent
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest, QueryExamRequest
def query_question_agent(query: QueryQuestionRequest):
def generate_question_agent(query: QueryQuestionRequest):
try:
user_msg = f"学习内容:\n{query.message}"
@@ -21,7 +21,7 @@ def query_question_agent(query: QueryQuestionRequest):
yield "信息检索失败,请重新输入问题提问"
def query_knowledge_agent(query: QueryKnowledgeRequest):
def generate_knowledge_agent(query: QueryKnowledgeRequest):
try:
user_msg = f"学科:{query.subject},模块:{query.module} 名称:{query.name}"
@@ -36,3 +36,20 @@ def query_knowledge_agent(query: QueryKnowledgeRequest):
except Exception as e:
print(f"\n[错误]: {str(e)}")
yield "信息检索失败,请重新输入问题提问"
def generate_exam_agent(query: QueryExamRequest):
try:
user_msg = f"学科:{query.subject},题目总数:{query.total}"
# 流式调用Agent
for chunk, metadata in exam_agent.stream(
{"messages": [HumanMessage(content=user_msg)]},
stream_mode="messages"
):
if isinstance(chunk, AIMessageChunk):
if isinstance(chunk, AIMessageChunk) and chunk.content:
yield chunk.content
except Exception as e:
print(f"\n[错误]: {str(e)}")
yield "信息检索失败,请重新输入问题提问"