[AI] 12_Pydantic_스키마 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki
Pydantic 스키마 명세
AI Server의 모든 Request/Response Pydantic 스키마 정의
📚 목차
- 1. 개요
- 2. 공통 스키마
- 3. OCR 텍스트 추출
- 4. 임베딩 저장
- 5. 분석 및 매칭도
- 6. 모의 면접 - 질문 생성
- 7. 모의 면접 - Q&A 저장
- 8. 모의 면접 - 리포트
- 9. 대화 처리
- 10. 캘린더 파싱
- 11. 마스킹
- 12. 파일 구조
1. 개요
1.1. Pydantic 사용 목적
| 목적 | 설명 |
|---|---|
| 입력 검증 | Request 데이터의 타입, 필수 여부, 제약 조건 검증 |
| 출력 직렬화 | Response 데이터의 JSON 변환 및 스키마 보장 |
| API 문서화 | FastAPI 자동 OpenAPI 문서 생성 |
| 타입 안정성 | IDE 자동완성 및 타입 힌트 지원 |
1.2. 스키마 구조
app/schemas/
├── common.py # 공통 스키마
├── ocr.py # OCR 관련
├── embed.py # 임베딩 관련
├── analyze.py # 분석 관련
├── interview.py # 면접 관련
├── chat.py # 채팅 관련
├── calendar.py # 캘린더 관련
├── masking.py # 마스킹 관련
└── __init__.py # 전체 export
2. 공통 스키마
2.1. 기본 응답 스키마
# app/schemas/common.py
from pydantic import BaseModel, Field
from typing import Optional, Any, List
from datetime import datetime
from enum import Enum
class TaskStatus(str, Enum):
"""비동기 작업 상태"""
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
class ErrorDetail(BaseModel):
"""에러 상세 정보"""
code: str = Field(..., description="에러 코드", example="INVALID_REQUEST")
message: str = Field(..., description="에러 메시지", example="필수 필드가 누락되었습니다")
details: Optional[dict] = Field(None, description="추가 상세 정보")
class BaseResponse(BaseModel):
"""기본 응답 스키마"""
success: bool = Field(..., description="요청 성공 여부")
class ErrorResponse(BaseResponse):
"""에러 응답 스키마"""
success: bool = False
error: ErrorDetail
class AsyncTaskResponse(BaseModel):
"""비동기 작업 초기 응답"""
task_id: str = Field(..., description="비동기 작업 ID", example="task_abc123")
status: TaskStatus = Field(default=TaskStatus.PROCESSING, description="작업 상태")
class AsyncTaskStatusResponse(BaseModel):
"""비동기 작업 상태 조회 응답"""
task_id: str
status: TaskStatus
progress: Optional[int] = Field(None, ge=0, le=100, description="진행률 (%)")
message: Optional[str] = Field(None, description="상태 메시지")
result: Optional[Any] = Field(None, description="완료 시 결과 데이터")
error: Optional[ErrorDetail] = Field(None, description="실패 시 에러 정보")
2.2. 스트리밍 응답 스키마
# app/schemas/common.py (계속)
from typing import Literal
class StreamingChunk(BaseModel):
"""SSE 스트리밍 청크"""
type: Literal["chunk", "complete", "error"] = Field(..., description="청크 타입")
content: Optional[str] = Field(None, description="스트리밍 텍스트 (chunk 타입)")
data: Optional[dict] = Field(None, description="완료 시 전체 데이터 (complete 타입)")
error: Optional[ErrorDetail] = Field(None, description="에러 정보 (error 타입)")
# SSE 이벤트 포맷 예시
"""
data: {"type": "chunk", "content": "분석 결과..."}
data: {"type": "chunk", "content": "를 확인했습니다."}
data: {"type": "complete", "data": {"score": 85, "grade": "A"}}
"""
2.3. 페이지네이션 스키마
# app/schemas/common.py (계속)
from typing import Generic, TypeVar
T = TypeVar('T')
class PaginatedResponse(BaseModel, Generic[T]):
"""페이지네이션 응답"""
items: List[T]
total: int = Field(..., description="전체 항목 수")
page: int = Field(..., ge=1, description="현재 페이지")
per_page: int = Field(..., ge=1, le=100, description="페이지당 항목 수")
has_next: bool = Field(..., description="다음 페이지 존재 여부")
3. OCR 텍스트 추출
3.1. Request
# app/schemas/ocr.py
from pydantic import BaseModel, Field, HttpUrl, validator
from typing import Literal
class OCRExtractRequest(BaseModel):
"""OCR 텍스트 추출 요청"""
file_url: HttpUrl = Field(
...,
description="S3 파일 URL",
example="https://s3.amazonaws.com/bucket/resume.pdf"
)
file_type: Literal["pdf", "image"] = Field(
...,
description="파일 타입"
)
type: Literal["resume", "portfolio", "job_posting"] = Field(
...,
description="문서 타입"
)
user_id: str = Field(
...,
min_length=1,
description="사용자 ID",
example="user_456"
)
document_id: str = Field(
...,
min_length=1,
description="문서 ID",
example="resume_123"
)
@validator('file_url')
def validate_file_url(cls, v):
url_str = str(v)
if not any(ext in url_str.lower() for ext in ['.pdf', '.png', '.jpg', '.jpeg', '.webp']):
raise ValueError('지원하지 않는 파일 형식입니다')
return v
class Config:
json_schema_extra = {
"example": {
"file_url": "https://s3.amazonaws.com/bucket/resume.pdf",
"file_type": "pdf",
"type": "resume",
"user_id": "user_456",
"document_id": "resume_123"
}
}
3.2. Response
# app/schemas/ocr.py (계속)
from typing import List, Optional
from pydantic import BaseModel, Field
class PageText(BaseModel):
"""페이지별 텍스트"""
page: int = Field(..., ge=1, description="페이지 번호")
text: str = Field(..., description="페이지 텍스트")
class OCRResult(BaseModel):
"""OCR 처리 결과"""
success: bool
extracted_text: str = Field(..., description="추출된 전체 텍스트")
pages: List[PageText] = Field(default=[], description="페이지별 텍스트")
vector_id: str = Field(..., description="VectorDB 저장 ID")
collection: str = Field(..., description="VectorDB 컬렉션명")
class OCRExtractResponse(AsyncTaskResponse):
"""OCR 추출 초기 응답 (비동기)"""
pass # task_id, status 반환
class OCRStatusResponse(AsyncTaskStatusResponse):
"""OCR 상태 조회 응답"""
result: Optional[OCRResult] = None
4. 임베딩 저장
4.1. Request
# app/schemas/embed.py
from pydantic import BaseModel, Field, validator
from typing import Literal
class EmbedRequest(BaseModel):
"""임베딩 저장 요청"""
type: Literal["resume", "portfolio", "job_posting"] = Field(
...,
description="문서 타입"
)
id: str = Field(
...,
min_length=1,
description="문서 ID",
example="resume_123"
)
user_id: str = Field(
...,
min_length=1,
description="사용자 ID",
example="user_456"
)
text: str = Field(
...,
min_length=10,
description="임베딩할 텍스트"
)
@validator('text')
def validate_text_length(cls, v):
if len(v) > 100000: # 약 25,000 토큰
raise ValueError('텍스트가 너무 깁니다 (최대 100,000자)')
return v
class Config:
json_schema_extra = {
"example": {
"type": "resume",
"id": "resume_123",
"user_id": "user_456",
"text": "3년차 백엔드 개발자입니다. Python, FastAPI..."
}
}
4.2. Response
# app/schemas/embed.py (계속)
class EmbedResponse(BaseModel):
"""임베딩 저장 응답"""
success: bool = True
type: str = Field(..., description="문서 타입")
id: str = Field(..., description="문서 ID")
vector_id: str = Field(..., description="VectorDB 저장 ID")
collection: str = Field(..., description="VectorDB 컬렉션명")
class Config:
json_schema_extra = {
"example": {
"success": True,
"type": "resume",
"id": "resume_123",
"vector_id": "vec_abc123",
"collection": "resumes"
}
}
5. 분석 및 매칭도
5.1. Request
# app/schemas/analyze.py
from pydantic import BaseModel, Field, validator
from typing import Optional
class AnalyzeRequest(BaseModel):
"""분석 및 매칭도 요청"""
resume_id: str = Field(..., description="이력서 ID")
posting_id: str = Field(..., description="채용공고 ID")
resume_text: str = Field(
...,
min_length=50,
description="이력서 텍스트"
)
posting_text: str = Field(
...,
min_length=50,
description="채용공고 텍스트"
)
@validator('resume_text')
def validate_resume_text(cls, v):
if len(v) < 100:
raise ValueError('이력서 텍스트가 너무 짧습니다 (최소 100자)')
return v
@validator('posting_text')
def validate_posting_text(cls, v):
if len(v) < 50:
raise ValueError('채용공고 텍스트가 너무 짧습니다 (최소 50자)')
return v
5.2. Response
# app/schemas/analyze.py (계속)
from typing import List
from enum import Enum
class MatchGrade(str, Enum):
"""매칭 등급"""
S = "S"
A = "A"
B = "B"
C = "C"
D = "D"
F = "F"
class ResumeAnalysis(BaseModel):
"""이력서 분석 결과"""
strengths: List[str] = Field(..., description="강점 목록")
weaknesses: List[str] = Field(..., description="약점 목록")
suggestions: List[str] = Field(..., description="개선 제안")
class PostingAnalysis(BaseModel):
"""채용공고 분석 결과"""
company: str = Field(..., description="회사명")
position: str = Field(..., description="포지션")
required_skills: List[str] = Field(..., description="필수 스킬")
preferred_skills: List[str] = Field(default=[], description="우대 스킬")
class MatchingResult(BaseModel):
"""매칭도 분석 결과"""
score: int = Field(..., ge=0, le=100, description="매칭 점수")
grade: MatchGrade = Field(..., description="등급")
matched_skills: List[str] = Field(..., description="매칭된 스킬")
missing_skills: List[str] = Field(..., description="부족한 스킬")
class AnalyzeResponse(BaseModel):
"""분석 및 매칭도 응답"""
success: bool = True
resume_analysis: ResumeAnalysis
posting_analysis: PostingAnalysis
matching: MatchingResult
class Config:
json_schema_extra = {
"example": {
"success": True,
"resume_analysis": {
"strengths": ["Python 3년 경험", "FastAPI 숙련"],
"weaknesses": ["클라우드 경험 부족"],
"suggestions": ["AWS 자격증 취득 추천"]
},
"posting_analysis": {
"company": "카카오",
"position": "백엔드 개발자",
"required_skills": ["Python", "FastAPI"],
"preferred_skills": ["AWS", "Kubernetes"]
},
"matching": {
"score": 85,
"grade": "A",
"matched_skills": ["Python", "FastAPI"],
"missing_skills": ["AWS"]
}
}
}
6. 모의 면접 - 질문 생성
6.1. Request
# app/schemas/interview.py
from pydantic import BaseModel, Field
from typing import Literal, Optional
class InterviewType(str, Enum):
"""면접 유형"""
TECHNICAL = "technical"
PERSONALITY = "personality"
class InterviewQuestionRequest(BaseModel):
"""면접 질문 생성 요청"""
room_id: str = Field(..., description="채팅방 ID")
session_id: str = Field(..., description="면접 세션 ID")
interview_type: Literal["technical", "personality"] = Field(
...,
description="면접 유형"
)
resume_text: str = Field(..., min_length=50, description="이력서 텍스트")
posting_text: Optional[str] = Field(None, description="채용공고 텍스트 (선택)")
class Config:
json_schema_extra = {
"example": {
"room_id": "room_001",
"session_id": "session_abc123",
"interview_type": "technical",
"resume_text": "3년차 백엔드 개발자...",
"posting_text": "백엔드 개발자 채용..."
}
}
6.2. Response
# app/schemas/interview.py (계속)
class InterviewQuestionResponse(BaseModel):
"""면접 질문 생성 응답"""
success: bool = True
question_id: str = Field(..., description="질문 ID")
question: str = Field(..., description="생성된 질문")
is_followup: bool = Field(..., description="꼬리질문 여부")
question_number: int = Field(..., ge=1, le=5, description="질문 번호")
class Config:
json_schema_extra = {
"example": {
"success": True,
"question_id": "q_001",
"question": "Python의 GIL에 대해 설명해주세요.",
"is_followup": False,
"question_number": 1
}
}
7. 모의 면접 - Q&A 저장
7.1. Request
# app/schemas/interview.py (계속)
class InterviewSaveRequest(BaseModel):
"""면접 Q&A 저장 요청"""
room_id: str = Field(..., description="채팅방 ID")
session_id: str = Field(..., description="면접 세션 ID")
question_id: str = Field(..., description="질문 ID")
question: str = Field(..., description="질문 내용")
answer: str = Field(..., min_length=1, description="사용자 답변")
is_followup: bool = Field(..., description="꼬리질문 여부")
question_number: int = Field(..., ge=1, le=5, description="질문 번호")
class Config:
json_schema_extra = {
"example": {
"room_id": "room_001",
"session_id": "session_abc123",
"question_id": "q_001",
"question": "Python의 GIL에 대해 설명해주세요.",
"answer": "GIL은 Global Interpreter Lock으로...",
"is_followup": False,
"question_number": 1
}
}
7.2. Response
# app/schemas/interview.py (계속)
class InterviewSaveResponse(BaseModel):
"""면접 Q&A 저장 응답"""
success: bool = True
qa_id: str = Field(..., description="저장된 Q&A ID")
session_id: str = Field(..., description="면접 세션 ID")
saved_count: int = Field(..., ge=1, description="현재까지 저장된 Q&A 수")
max_questions: int = Field(default=5, description="최대 질문 수")
class Config:
json_schema_extra = {
"example": {
"success": True,
"qa_id": "qa_001",
"session_id": "session_abc123",
"saved_count": 1,
"max_questions": 5
}
}
8. 모의 면접 - 리포트
8.1. Request
# app/schemas/interview.py (계속)
class InterviewReportRequest(BaseModel):
"""면접 리포트 생성 요청"""
room_id: str = Field(..., description="채팅방 ID")
session_id: str = Field(..., description="면접 세션 ID")
interview_type: Literal["technical", "personality"] = Field(
...,
description="면접 유형"
)
resume_text: str = Field(..., description="이력서 텍스트")
posting_text: Optional[str] = Field(None, description="채용공고 텍스트")
ended_by: Literal["auto", "manual"] = Field(
...,
description="종료 방식 (auto: 5개 완료, manual: 직접 종료)"
)
class Config:
json_schema_extra = {
"example": {
"room_id": "room_001",
"session_id": "session_abc123",
"interview_type": "technical",
"resume_text": "이력서 텍스트...",
"posting_text": "채용공고 텍스트...",
"ended_by": "auto"
}
}
8.2. Response
# app/schemas/interview.py (계속)
from typing import List
class QAEvaluation(BaseModel):
"""개별 Q&A 평가"""
qa_id: str = Field(..., description="Q&A ID")
question: str = Field(..., description="질문")
answer: str = Field(..., description="답변")
score: int = Field(..., ge=0, le=100, description="점수")
good_points: List[str] = Field(..., description="잘한 점")
improvements: List[str] = Field(..., description="개선점")
class InterviewReport(BaseModel):
"""면접 종합 리포트"""
total_score: int = Field(..., ge=0, le=100, description="총점")
grade: str = Field(..., description="등급 (S/A/B/C/D/F)")
strength_patterns: List[str] = Field(..., description="강점 패턴")
weakness_patterns: List[str] = Field(..., description="약점 패턴")
learning_guide: List[str] = Field(..., description="학습 가이드")
class InterviewReportResponse(BaseModel):
"""면접 리포트 응답"""
success: bool = True
room_id: str
session_id: str
evaluations: List[QAEvaluation] = Field(..., description="개별 Q&A 평가")
report: InterviewReport = Field(..., description="종합 리포트")
class Config:
json_schema_extra = {
"example": {
"success": True,
"room_id": "room_001",
"session_id": "session_abc123",
"evaluations": [
{
"qa_id": "qa_001",
"question": "Python의 GIL이 뭔가요?",
"answer": "GIL은...",
"score": 80,
"good_points": ["기본 개념 이해"],
"improvements": ["심화 설명 추가"]
}
],
"report": {
"total_score": 78,
"grade": "B+",
"strength_patterns": ["기술 개념 이해도 높음"],
"weakness_patterns": ["심화 개념 설명 부족"],
"learning_guide": ["Python 심화 학습 권장"]
}
}
}
9. 대화 처리
9.1. Request
# app/schemas/chat.py
from pydantic import BaseModel, Field
from typing import Optional, List, Literal, Any
class ChatMessage(BaseModel):
"""채팅 메시지"""
role: Literal["user", "assistant"] = Field(..., description="메시지 발신자")
content: str = Field(..., description="메시지 내용")
class ToolResult(BaseModel):
"""Tool 실행 결과"""
tool: str = Field(..., description="실행된 Tool 이름")
success: bool = Field(..., description="실행 성공 여부")
data: Any = Field(..., description="Tool 실행 결과 데이터")
class ChatRequest(BaseModel):
"""채팅 요청"""
room_id: str = Field(..., description="채팅방 ID")
user_id: str = Field(..., description="사용자 ID")
message: Optional[str] = Field(None, description="사용자 메시지")
history: List[ChatMessage] = Field(
default=[],
max_length=20,
description="대화 히스토리 (최근 20개)"
)
tool_result: Optional[ToolResult] = Field(
None,
description="Tool 실행 결과 (Backend가 전달)"
)
class Config:
json_schema_extra = {
"example": {
"room_id": "room_001",
"user_id": "user_456",
"message": "이번 주 일정 알려줘",
"history": [
{"role": "user", "content": "안녕"},
{"role": "assistant", "content": "안녕하세요!"}
]
}
}
9.2. Response
# app/schemas/chat.py (계속)
class ToolCallParams(BaseModel):
"""Tool 호출 파라미터"""
# Tool에 따라 동적으로 변할 수 있음
class Config:
extra = 'allow'
class ToolCall(BaseModel):
"""Tool 호출 정보"""
tool: Literal[
"get_schedule",
"add_schedule",
"update_schedule",
"delete_schedule"
] = Field(..., description="호출할 Tool 이름")
params: dict = Field(..., description="Tool 파라미터")
class ChatResponse(BaseModel):
"""채팅 응답"""
success: bool = True
response: Optional[str] = Field(
None,
description="AI 응답 (tool_used가 있으면 null)"
)
tool_used: Optional[ToolCall] = Field(
None,
description="실행할 Tool 정보 (response가 있으면 null)"
)
class Config:
json_schema_extra = {
"examples": [
{
"name": "일반 응답",
"value": {
"success": True,
"response": "이력서 작성 팁을 알려드릴게요...",
"tool_used": None
}
},
{
"name": "Tool 호출",
"value": {
"success": True,
"response": None,
"tool_used": {
"tool": "get_schedule",
"params": {
"start_date": "2026-01-06",
"end_date": "2026-01-12"
}
}
}
}
]
}
10. 캘린더 파싱
10.1. Request
# app/schemas/calendar.py
from pydantic import BaseModel, Field, HttpUrl, validator
from typing import Optional
class CalendarParseRequest(BaseModel):
"""캘린더 일정 파싱 요청"""
file_url: Optional[HttpUrl] = Field(
None,
description="파일 URL (text와 둘 중 하나 필수)"
)
text: Optional[str] = Field(
None,
description="텍스트 (file_url과 둘 중 하나 필수)"
)
@validator('text', always=True)
def validate_input(cls, v, values):
if not v and not values.get('file_url'):
raise ValueError('file_url 또는 text 중 하나는 필수입니다')
return v
class Config:
json_schema_extra = {
"example": {
"file_url": None,
"text": "카카오 백엔드 개발자 채용\n서류 마감: 2026-01-15\n코딩테스트: 2026-01-20"
}
}
10.2. Response
# app/schemas/calendar.py (계속)
from typing import List, Optional
class ScheduleItem(BaseModel):
"""일정 항목"""
stage: str = Field(..., description="전형 단계")
date: str = Field(..., description="날짜 (YYYY-MM-DD)")
time: Optional[str] = Field(None, description="시간 (HH:MM)")
class CalendarParseResponse(BaseModel):
"""캘린더 파싱 응답"""
success: bool = True
company: str = Field(..., description="회사명")
position: str = Field(..., description="포지션")
schedules: List[ScheduleItem] = Field(..., description="일정 목록")
hashtags: List[str] = Field(default=[], description="자동 생성 해시태그")
class Config:
json_schema_extra = {
"example": {
"success": True,
"company": "카카오",
"position": "백엔드 개발자",
"schedules": [
{"stage": "서류 마감", "date": "2026-01-15", "time": None},
{"stage": "코딩테스트", "date": "2026-01-20", "time": "14:00"},
{"stage": "1차 면접", "date": "2026-01-25", "time": None}
],
"hashtags": ["#카카오", "#백엔드", "#신입"]
}
}
11. 마스킹
11.1. Request
# app/schemas/masking.py
from pydantic import BaseModel, Field, HttpUrl
from typing import Literal
class MaskingDraftRequest(BaseModel):
"""마스킹 요청"""
file_url: HttpUrl = Field(..., description="S3 파일 URL")
file_type: Literal["image", "pdf"] = Field(..., description="파일 타입")
class Config:
json_schema_extra = {
"example": {
"file_url": "https://s3.amazonaws.com/bucket/document.png",
"file_type": "image"
}
}
11.2. Response
# app/schemas/masking.py (계속)
from typing import List, Optional, Tuple
from enum import Enum
class PIIType(str, Enum):
"""개인정보 유형"""
NAME = "name"
PHONE = "phone"
EMAIL = "email"
ADDRESS = "address"
ID_NUMBER = "id_number"
FACE = "face"
class DetectedPII(BaseModel):
"""감지된 개인정보"""
type: PIIType = Field(..., description="개인정보 유형")
coordinates: List[int] = Field(
...,
min_length=4,
max_length=4,
description="좌표 [x1, y1, x2, y2]"
)
confidence: float = Field(..., ge=0, le=1, description="신뢰도")
class MaskingResult(BaseModel):
"""마스킹 처리 결과"""
success: bool = True
original_url: str = Field(..., description="원본 파일 URL")
masked_url: str = Field(..., description="마스킹된 파일 URL")
thumbnail_url: str = Field(..., description="썸네일 URL")
detected_pii: List[DetectedPII] = Field(..., description="감지된 개인정보 목록")
class MaskingDraftResponse(AsyncTaskResponse):
"""마스킹 초기 응답 (비동기)"""
pass # task_id, status 반환
class MaskingStatusResponse(AsyncTaskStatusResponse):
"""마스킹 상태 조회 응답"""
result: Optional[MaskingResult] = None
class Config:
json_schema_extra = {
"example": {
"task_id": "task_masking_001",
"status": "completed",
"result": {
"success": True,
"original_url": "https://s3.../document.png",
"masked_url": "https://s3.../document_masked.png",
"thumbnail_url": "https://s3.../document_masked_thumb.png",
"detected_pii": [
{"type": "name", "coordinates": [100, 50, 200, 80], "confidence": 0.95},
{"type": "phone", "coordinates": [100, 100, 250, 130], "confidence": 0.92}
]
}
}
}
12. 파일 구조
12.1. 전체 스키마 파일 구조
app/schemas/
├── __init__.py
├── common.py # 42번째 줄까지
├── ocr.py # 50줄
├── embed.py # 40줄
├── analyze.py # 80줄
├── interview.py # 150줄
├── chat.py # 80줄
├── calendar.py # 50줄
└── masking.py # 60줄
12.2. __init__.py 예시
# app/schemas/__init__.py
"""Pydantic 스키마 모듈"""
# Common
from .common import (
BaseResponse,
ErrorResponse,
ErrorDetail,
AsyncTaskResponse,
AsyncTaskStatusResponse,
TaskStatus,
StreamingChunk,
PaginatedResponse,
)
# OCR
from .ocr import (
OCRExtractRequest,
OCRExtractResponse,
OCRStatusResponse,
OCRResult,
)
# Embed
from .embed import (
EmbedRequest,
EmbedResponse,
)
# Analyze
from .analyze import (
AnalyzeRequest,
AnalyzeResponse,
ResumeAnalysis,
PostingAnalysis,
MatchingResult,
MatchGrade,
)
# Interview
from .interview import (
InterviewQuestionRequest,
InterviewQuestionResponse,
InterviewSaveRequest,
InterviewSaveResponse,
InterviewReportRequest,
InterviewReportResponse,
QAEvaluation,
InterviewReport,
)
# Chat
from .chat import (
ChatRequest,
ChatResponse,
ChatMessage,
ToolCall,
ToolResult,
)
# Calendar
from .calendar import (
CalendarParseRequest,
CalendarParseResponse,
ScheduleItem,
)
# Masking
from .masking import (
MaskingDraftRequest,
MaskingDraftResponse,
MaskingStatusResponse,
MaskingResult,
DetectedPII,
PIIType,
)
__all__ = [
# Common
"BaseResponse",
"ErrorResponse",
"ErrorDetail",
"AsyncTaskResponse",
"AsyncTaskStatusResponse",
"TaskStatus",
"StreamingChunk",
"PaginatedResponse",
# OCR
"OCRExtractRequest",
"OCRExtractResponse",
"OCRStatusResponse",
"OCRResult",
# Embed
"EmbedRequest",
"EmbedResponse",
# Analyze
"AnalyzeRequest",
"AnalyzeResponse",
"ResumeAnalysis",
"PostingAnalysis",
"MatchingResult",
"MatchGrade",
# Interview
"InterviewQuestionRequest",
"InterviewQuestionResponse",
"InterviewSaveRequest",
"InterviewSaveResponse",
"InterviewReportRequest",
"InterviewReportResponse",
"QAEvaluation",
"InterviewReport",
# Chat
"ChatRequest",
"ChatResponse",
"ChatMessage",
"ToolCall",
"ToolResult",
# Calendar
"CalendarParseRequest",
"CalendarParseResponse",
"ScheduleItem",
# Masking
"MaskingDraftRequest",
"MaskingDraftResponse",
"MaskingStatusResponse",
"MaskingResult",
"DetectedPII",
"PIIType",
]
12.3. 라우터에서 사용 예시
# app/modules/analyze/router.py
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from app.schemas import AnalyzeRequest, AnalyzeResponse
router = APIRouter(prefix="/ai", tags=["Analyze"])
@router.post(
"/analyze",
response_model=AnalyzeResponse,
summary="이력서 분석 및 매칭도 계산",
description="이력서와 채용공고를 분석하고 매칭도를 계산합니다. (SSE 스트리밍)"
)
async def analyze(request: AnalyzeRequest):
"""분석 API"""
...