diff --git a/agent/prompt.py b/agent/prompt.py index d1e27a9..4fa1547 100644 --- a/agent/prompt.py +++ b/agent/prompt.py @@ -81,4 +81,62 @@ knowledge_prompt = """ 5. 输出要求: - 仅输出 Markdown 正文,不要包含 ``、JSON 或其他格式 - 不要在开头重复本提示词内容 -""" \ No newline at end of file +""" + +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": [] + } +] +""" diff --git a/agent/study.py b/agent/study.py index cfbd2f2..2c1e312 100644 --- a/agent/study.py +++ b/agent/study.py @@ -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 +) diff --git a/routers/__init__.py b/routers/__init__.py index 0fed23e..90500ab 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -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, diff --git a/routers/agent.py b/routers/agent.py new file mode 100644 index 0000000..e5d9d2c --- /dev/null +++ b/routers/agent.py @@ -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", + ) diff --git a/routers/library.py b/routers/library.py index b1c8a27..aa7cc26 100644 --- a/routers/library.py +++ b/routers/library.py @@ -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) diff --git a/schemas/agent.py b/schemas/agent.py index c0b46ec..cff5558 100644 --- a/schemas/agent.py +++ b/schemas/agent.py @@ -9,3 +9,8 @@ class QueryKnowledgeRequest(BaseModel): subject: str module: str name: str + + +class QueryExamRequest(BaseModel): + subject: str + total: int diff --git a/service/agent_service.py b/service/agent_service.py index 82f1d4e..bdb3675 100644 --- a/service/agent_service.py +++ b/service/agent_service.py @@ -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 "信息检索失败,请重新输入问题提问"