File size: 6,406 Bytes
bae14fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76ef840
bae14fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76ef840
 
 
bae14fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76ef840
 
bae14fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import os
import shutil
import uuid
from dotenv import load_dotenv

load_dotenv()

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional

from agents.sessions import session_manager
from agents.summarizer import summarize_pdf
from agents.examiner import generate_quiz
from agents.supervisor import provide_feedback, chat_with_supervisor

app = FastAPI(
    title="AI Tutor API",
    description="Multi-agent tutoring system with summarization, quiz generation, and Socratic feedback"
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

UPLOAD_DIR = "documents"
os.makedirs(UPLOAD_DIR, exist_ok=True)


class ExaminerRequest(BaseModel):
    session_id: str
    num_questions: int = 5
    comment: Optional[str] = None


class SupervisorRequest(BaseModel):
    session_id: str
    message: str
    user_answers: Optional[List[str]] = None


class SummaryResponse(BaseModel):
    session_id: str
    summary: str


class QuizResponse(BaseModel):
    quiz: List[dict]


class SupervisorResponse(BaseModel):
    response: str
    messages: List[dict]


class SessionResponse(BaseModel):
    session_id: str
    has_summary: bool
    has_quiz: bool
    message_count: int


@app.post("/summarizer", response_model=SummaryResponse)
async def summarize_document(file: UploadFile = File(...)):
    """
    Upload a PDF and get a summary.
    Creates a new session and returns session_id with the summary.
    """
    if not file.filename or not file.filename.lower().endswith(".pdf"):
        raise HTTPException(status_code=400, detail="Only PDF files are supported")
    
    safe_filename = f"{uuid.uuid4()}.pdf"
    file_path = os.path.join(UPLOAD_DIR, safe_filename)
    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    
    try:
        session = session_manager.create_session(file_path)
        summary = summarize_pdf(file_path)
        session_manager.update_summary(session.session_id, summary)
        
        return SummaryResponse(
            session_id=session.session_id,
            summary=summary
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/examiner", response_model=QuizResponse)
async def generate_quiz_endpoint(request: ExaminerRequest):
    """
    Generate a quiz based on a previously summarized document.
    Requires a valid session_id from /summarizer.
    
    Optional comment parameter can be used to focus the quiz on specific areas,
    such as supervisor feedback from previous quizzes.
    """
    session = session_manager.get_session(request.session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")
    
    if not session.summary:
        raise HTTPException(status_code=400, detail="No summary found. Call /summarizer first.")
    
    if not session.docs:
        raise HTTPException(status_code=400, detail="Document not loaded")
    
    try:
        quiz = generate_quiz(
            docs=session.docs,
            summary=session.summary,
            num_questions=request.num_questions,
            comment=request.comment
        )
        session_manager.update_quiz(request.session_id, quiz)
        
        quiz_data = [task.model_dump() for task in quiz.tasks]
        return QuizResponse(quiz=quiz_data)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/supervisor", response_model=SupervisorResponse)
async def supervisor_chat(request: SupervisorRequest):
    """
    Chat with the Socratic tutor supervisor.
    
    First call should include user_answers to get initial feedback.
    Subsequent calls can just include message for follow-up questions.
    """
    session = session_manager.get_session(request.session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")
    
    if not session.docs:
        raise HTTPException(status_code=400, detail="Document not loaded")
    
    if not session.summary:
        raise HTTPException(status_code=400, detail="No summary found")
    
    try:
        if request.user_answers:
            if not session.quiz:
                raise HTTPException(status_code=400, detail="No quiz found. Call /examiner first.")
            
            session_manager.update_user_answers(request.session_id, request.user_answers)
            
            response = provide_feedback(
                docs=session.docs,
                summary=session.summary,
                quiz=session.quiz,
                user_answers=request.user_answers
            )
        else:
            response = chat_with_supervisor(
                docs=session.docs,
                summary=session.summary,
                user_message=request.message,
                conversation_history=session.messages
            )
        
        session_manager.add_message(request.session_id, "user", request.message)
        session_manager.add_message(request.session_id, "assistant", response)
        
        return SupervisorResponse(
            response=response,
            messages=session_manager.get_messages(request.session_id)
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/session/{session_id}", response_model=SessionResponse)
async def get_session_info(session_id: str):
    """Get information about a session."""
    session = session_manager.get_session(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")
    
    return SessionResponse(
        session_id=session.session_id,
        has_summary=bool(session.summary),
        has_quiz=session.quiz is not None,
        message_count=len(session.messages)
    )


@app.delete("/session/{session_id}")
async def delete_session(session_id: str):
    """Delete a session and clean up resources."""
    if session_manager.delete_session(session_id):
        return {"message": "Session deleted successfully"}
    raise HTTPException(status_code=404, detail="Session not found")


@app.get("/health")
async def health_check():
    """Health check endpoint."""
    return {"status": "healthy"}