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 或其他格式 - 仅输出 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 langchain.agents import create_agent
from agent.model import model 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( question_agent = create_agent(
model=model, model=model,
@@ -12,3 +12,8 @@ knowledge_agent = create_agent(
model=model, model=model,
system_prompt=knowledge_prompt 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 .library import router as library_router
from .practice import router as practice_router from .practice import router as practice_router
from .mistake import router as mistake_router from .mistake import router as mistake_router
routers = [ routers = [
agent_router,
library_router, library_router,
practice_router, practice_router,
mistake_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 typing import List
from fastapi import APIRouter, Depends, File, Form, UploadFile from fastapi import APIRouter, Depends, File, Form, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import StreamingResponse
from database import get_db from database import get_db
from schemas.library import LibraryFileRequest, LibraryFileResponse from schemas.library import LibraryFileRequest, LibraryFileResponse
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest from service import library_service
from service import library_service, agent_service
from storage import upload_rustfs from storage import upload_rustfs
from ocr import ocr_from_url from ocr import ocr_from_url
@@ -19,22 +17,6 @@ def upload(file: UploadFile = File(...), filename: str = Form(...)):
return ocr_from_url(url) 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]) @router.get("/files", response_model=List[LibraryFileResponse])
async def list_files(db: AsyncSession = Depends(get_db)): async def list_files(db: AsyncSession = Depends(get_db)):
return await library_service.list_files(db) return await library_service.list_files(db)

View File

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

View File

@@ -1,10 +1,10 @@
from langchain_core.messages import AIMessageChunk, HumanMessage from langchain_core.messages import AIMessageChunk, HumanMessage
from agent.study import question_agent, knowledge_agent from agent.study import question_agent, knowledge_agent, exam_agent
from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest from schemas.agent import QueryQuestionRequest, QueryKnowledgeRequest, QueryExamRequest
def query_question_agent(query: QueryQuestionRequest): def generate_question_agent(query: QueryQuestionRequest):
try: try:
user_msg = f"学习内容:\n{query.message}" user_msg = f"学习内容:\n{query.message}"
@@ -21,7 +21,7 @@ def query_question_agent(query: QueryQuestionRequest):
yield "信息检索失败,请重新输入问题提问" yield "信息检索失败,请重新输入问题提问"
def query_knowledge_agent(query: QueryKnowledgeRequest): def generate_knowledge_agent(query: QueryKnowledgeRequest):
try: try:
user_msg = f"学科:{query.subject},模块:{query.module} 名称:{query.name}" user_msg = f"学科:{query.subject},模块:{query.module} 名称:{query.name}"
@@ -36,3 +36,20 @@ def query_knowledge_agent(query: QueryKnowledgeRequest):
except Exception as e: except Exception as e:
print(f"\n[错误]: {str(e)}") print(f"\n[错误]: {str(e)}")
yield "信息检索失败,请重新输入问题提问" 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 "信息检索失败,请重新输入问题提问"