feat:增加考试接口
This commit is contained in:
@@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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
31
routers/agent.py
Normal 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",
|
||||||
|
)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 "信息检索失败,请重新输入问题提问"
|
||||||
|
|||||||
Reference in New Issue
Block a user