[AI] 04_모델_API_설계 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki
[AI] 04_모델_API_설계
AI Server 내부 API 명세서
프로젝트: Devths AI 취업 도우미 작성일: 2026-01-13 API 버전: v1.6 마지막 업데이트: 2026-01-26
개요
이 문서는 Backend(Spring Boot) → AI Server(FastAPI) 간의 내부 통신 API를 정의합니다.
[Frontend] → [Backend] → [AI Server] → [VectorDB/LLM]
↑
이 구간의 API
Table of Contents
API 엔드포인트 목록
| # | 기능 | Method | Endpoint | 처리방식 |
|---|---|---|---|---|
| 1 | 텍스트 추출 + 임베딩 | POST | /ai/text/extract |
비동기 |
| 2 | 채팅 (대화/면접 질문/면접 리포트) | POST | /ai/chat |
스트리밍 (SSE) |
| 3 | 캘린더 일정 파싱 | POST | /ai/calendar/parse |
동기 |
| 4 | 게시판 첨부파일 마스킹 | POST | /ai/masking/draft |
비동기 |
| 5 | 비동기 작업 상태 조회 | GET | /ai/task/{task_id} |
동기 |
공통 사항
인증
X-API-Key: your-api-key-here
Base URL
http://ai-server:8000
1. 텍스트 추출 + 임베딩
개요
이력서와 채용공고를 함께 받아 OCR 처리 후 임베딩 저장 및 분석 리포트를 생성합니다.
처리 흐름: resume OCR → job_posting OCR → VectorDB 저장 → 분석 리포트 생성 → 응답 응답 활용: Backend는 OCR 텍스트를 RDB에 저장하고, 분석 리포트를 채팅 메시지로 표시
Endpoint
POST /ai/text/extract
처리 방식
비동기 - task_id 반환 → 폴링 필요
Request Headers
| Header | 값 | 필수 |
|---|---|---|
| X-API-Key | your-api-key-here | ✅ |
| Content-Type | application/json | ✅ |
Request Body
{
"model": "gemini",
"user_id": 12,
"resume": {
"file_id": 23,
"s3_key": "https://bucket.s3.amazonaws.com/users/12/resume/abc123.pdf",
"file_type": "pdf",
"text": null
},
"job_posting": {
"file_id": null,
"s3_key": null,
"file_type": null,
"text": "카카오 백엔드 개발자 채용\n자격요건: Java, Spring..."
}
}
사용 예시:
- 파일 업로드:
s3_key+file_type제공,text는null - 텍스트 입력:
text제공,s3_key와file_type은null - 혼합:
resume은 파일,job_posting은 텍스트 (또는 그 반대)
Request Fields
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| model | string | ❌ | gemini(기본값), openai, vllm |
| user_id | int | ✅ | 사용자 ID |
| resume | object | ✅ | 이력서/포트폴리오 정보 |
| job_posting | object | ✅ | 채용공고 정보 |
⚠️
resume과job_posting은 필수 입력해야 합니다
Document Object (resume / job_posting)
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| file_id | int | null | ❌ | 파일 ID (파일 업로드 시 사용, 선택사항) |
| s3_key | string | null | ❌ | S3 파일 URL 또는 키 (파일 업로드 시 사용) |
| file_type | string | null | ❌ | pdf, image (파일 업로드 시 사용) |
| text | string | null | ❌ | 직접 입력 텍스트 (텍스트 입력 시 사용) |
입력 규칙:
- ⚠️ 각 문서에서
s3_key+file_type또는text중 하나는 필수입니다 - ⚠️
s3_key와text를 동시에 사용할 수 없습니다 - ⚠️
s3_key사용 시file_type은 필수입니다
Response (202 Accepted)
{
"task_id": "task_abc123def456",
"status": "processing"
}
Error Responses
400 Bad Request
resume과 job_posting 필수 입력 오류:
{
"detail": {
"code": "INVALID_REQUEST",
"message": "resume과 job_posting 는 필수 입력해야합니다"
}
}
잘못된 file_type:
{
"detail": {
"code": "INVALID_FILE_TYPE",
"message": "file_type은 pdf 또는 image만 가능합니다",
"field": "resume.file_type"
}
}
s3_key 또는 text 필수 오류:
{
"detail": {
"code": "INVALID_DOCUMENT",
"message": "s3_key 또는 text 중 하나는 필수입니다",
"field": "resume"
}
}
401 Unauthorized
{
"detail": {
"code": "UNAUTHORIZED",
"message": "유효하지 않은 API Key입니다"
}
}
404 Not Found
{
"detail": {
"code": "FILE_NOT_FOUND",
"message": "파일을 찾을 수 없습니다: users/12/resume/abc123.pdf"
}
}
422 Unprocessable Entity
{
"detail": {
"code": "OCR_FAILED",
"message": "이미지에서 텍스트를 추출할 수 없습니다"
}
}
429 Too Many Requests
{
"detail": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "요청 한도 초과. 1분 후 재시도하세요"
}
}
500 Internal Server Error
{
"detail": {
"code": "INTERNAL_ERROR",
"message": "내부 서버 오류가 발생했습니다"
}
}
503 Service Unavailable
LLM 서비스 불가:
{
"detail": {
"code": "LLM_UNAVAILABLE",
"message": "AI 서비스에 연결할 수 없습니다"
}
}
S3 스토리지 불가:
{
"detail": {
"code": "S3_UNAVAILABLE",
"message": "파일 스토리지에 연결할 수 없습니다"
}
}
Polling 완료 시 (GET /ai/task/{task_id})
분석 완료 응답:
{
"task_id": "task_abc123def456",
"room_id": 32,
"status": "completed",
"result": {
"success": true,
"resume_ocr": "이력서 OCR 텍스트...",
"job_posting_ocr": "채용공고 OCR 텍스트...",
"resume_analysis": {
"strengths": ["React 숙련도", "프로젝트 경험"],
"weaknesses": ["백엔드 경험 부족"],
"suggestions": ["Spring 학습 권장"]
},
"posting_analysis": {
"company": "카카오",
"position": "백엔드 개발자",
"required_skills": ["Java", "Spring", "MySQL"],
"preferred_skills": ["Docker", "Kubernetes"]
}
}
}
Response Fields
| 필드 | 타입 | 설명 |
|---|---|---|
| task_id | string | 작업 ID |
| room_id | int | 채팅방 ID |
| status | string | processing, completed, failed |
| result.success | boolean | 성공 여부 |
| result.resume_ocr | string | 이력서 OCR 텍스트 |
| result.job_posting_ocr | string | 채용공고 OCR 텍스트 |
| result.resume_analysis | object | 이력서 분석 결과 |
| result.posting_analysis | object | 채용공고 분석 결과 |
2. 채팅 (대화/면접 질문/면접 리포트)
개요
모든 LLM 응답을 통합 처리합니다. context.mode로 기능을 구분합니다.
Endpoint
POST /ai/chat
처리 방식
스트리밍 (SSE) - Server-Sent Events로 실시간 응답
Request Headers
| Header | 값 | 필수 |
|---|---|---|
| X-API-Key | your-api-key-here | ✅ |
| Content-Type | application/json | ✅ |
Request Body - 일반 대화 (mode: general)
{
"model": "gemini",
"room_id": "room_001",
"user_id": 12,
"message": "이력서 작성 팁 알려줘",
"context": {
"mode": "general",
"resume": null,
"job_posting": null,
"interview_type": null,
"session_id": null,
"question_count": null
}
}
Request Body - 면접 질문 생성 (mode: interview_question)
{
"model": "gemini",
"room_id": "room_001",
"user_id": 12,
"message": "기술 면접 질문 생성해줘",
"context": {
"mode": "interview_question",
"resume_ocr": "이력서 OCR 내용...",
"job_posting": "채용공고 OCR 내용...",
"interview_type": "technical",
"session_id": "interview_001",
"question_count": 0
}
}
Request Body - 면접 리포트 생성 (면접 종료)
{
"model": "gemini",
"room_id": "room_001",
"user_id": 12,
"session_id": "interview_001",
"context": [
{
"question": "React의 Virtual DOM이 무엇인가요?",
"answer": "실제 DOM과 비교해서 변경된 부분만 업데이트하는 거예요"
},
{
"question": "Reconciliation 알고리즘에 대해 설명해주세요.",
"answer": "변경사항을 비교하는 알고리즘입니다"
}
]
}
Request Fields
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| model | string | null | ❌ | gemini(기본값), vllm |
| room_id | string | ✅ | 채팅방 ID |
| user_id | int | ✅ | 사용자 ID |
| message | string | null | ❌ | 사용자 메시지 (일반 대화 시 필수) |
| session_id | string | null | ❌ | 면접 세션 ID (면접 리포트 시 필수) |
| context | object | array | ❌ | 채팅 컨텍스트 또는 Q&A 배열 |
Context Object (일반 대화/면접 질문)
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| mode | string | ❌ | general(기본값), interview_question, interview_report |
| resume_ocr | string | null | ❌ | 이력서 OCR 텍스트 (면접 질문 생성 시) |
| job_posting | string | null | ❌ | 채용공고 OCR 텍스트 (면접 질문 생성 시) |
| interview_type | string | null | ❌ | technical, personality (면접 모드 시) |
| session_id | string | null | ❌ | 면접 세션 ID |
| question_count | int | null | ❌ | 현재까지 생성된 질문 수 |
Context Array (면접 리포트)
면접 리포트 생성 시 context는 Q&A 배열입니다:
[
{ "question": "질문1", "answer": "답변1" },
{ "question": "질문2", "answer": "답변2" }
]
ChatMode 설명
| Mode | 설명 | 사용 시점 |
|---|---|---|
general |
일반 대화 (기본값) | 취업 관련 질문, RAG 검색 |
interview_question |
면접 질문 생성 | 면접 모드 시작 시, 꼬리질문 생성 시 |
interview_report |
면접 리포트 생성 | 면접 종료 시 평가 및 피드백 |
SSE Response 형식
일반 대화:
{
"success": true,
"mode": "general",
"response": "이력서 작성 팁을 알려드릴게요..."
}
면접 질문:
{
"success": true,
"mode": "interview_question",
"response": "React와 Vue의 차이점에 대해 설명해주세요.",
"interview_type": "technical"
}
면접 리포트:
{
"success": true,
"mode": "interview_report",
"report": {
"evaluations": [
{
"question": "React의 Virtual DOM이 무엇인가요?",
"answer": "실제 DOM과 비교해서 변경된 부분만 업데이트하는 거예요",
"good_points": ["Virtual DOM의 기본 개념을 잘 이해하고 있음"],
"improvements": ["Reconciliation 알고리즘 설명 추가하면 좋음"]
}
],
"strength_patterns": ["기술 개념에 대한 이해도가 높음"],
"weakness_patterns": ["심화 개념 설명이 부족함"],
"learning_guide": ["React 심화 개념 학습 (Fiber, Concurrent Mode)"]
}
}
Error Responses
400 Bad Request
필수 필드 누락:
{
"detail": {
"code": "INVALID_REQUEST",
"message": "room_id는 필수입니다",
"field": "room_id"
}
}
잘못된 mode:
{
"detail": {
"code": "INVALID_MODE",
"message": "mode는 general, interview_question, interview_report 중 하나여야 합니다",
"field": "context.mode"
}
}
잘못된 면접 타입:
{
"detail": {
"code": "INVALID_INTERVIEW_TYPE",
"message": "interview_type은 technical 또는 personality만 가능합니다",
"field": "context.interview_type"
}
}
필수 context 누락:
{
"detail": {
"code": "MISSING_CONTEXT",
"message": "interview_question 모드에서 resume은 필수입니다",
"field": "context.resume"
}
}
빈 메시지:
{
"detail": {
"code": "EMPTY_MESSAGE",
"message": "message는 비어있을 수 없습니다",
"field": "message"
}
}
history 초과:
{
"detail": {
"code": "HISTORY_TOO_LONG",
"message": "history는 최대 20개까지 가능합니다",
"field": "history"
}
}
401 Unauthorized
{
"detail": {
"code": "UNAUTHORIZED",
"message": "유효하지 않은 API Key입니다"
}
}
404 Not Found
{
"detail": {
"code": "FILE_NOT_FOUND",
"message": "파일을 찾을 수 없습니다"
}
}
422 Unprocessable Entity
{
"detail": {
"code": "SESSION_NOT_FOUND",
"message": "면접 세션을 찾을 수 없습니다: interview_001"
}
}
429 Too Many Requests
{
"detail": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "동시 연결 한도 초과"
}
}
500 Internal Server Error
{
"detail": {
"code": "STREAM_ERROR",
"message": "스트리밍 연결이 중단되었습니다"
}
}
503 Service Unavailable
LLM 서비스 불가:
{
"detail": {
"code": "LLM_UNAVAILABLE",
"message": "AI 서비스에 연결할 수 없습니다"
}
}
VectorDB 서비스 불가:
{
"detail": {
"code": "VECTORDB_UNAVAILABLE",
"message": "검색 서비스에 연결할 수 없습니다"
}
}
3. 캘린더 일정 파싱
개요
채용공고 파일/텍스트를 분석하여 일정 정보를 추출합니다.
Endpoint
POST /ai/calendar/parse
처리 방식
동기 - 즉시 응답 반환 (Gemini Flash API 사용)
Request Headers
| Header | 값 | 필수 |
|---|---|---|
| X-API-Key | your-api-key-here | ✅ |
Request Body
{
"s3_key": "https://s3.../job_posting.png",
"text": null
}
또는
{
"s3_key": null,
"text": "카카오 백엔드 개발자 채용\n서류마감: 2026-01-15\n코딩테스트: 2026-01-20..."
}
Request Fields
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| s3_key | string | null | ⚠️ | 채용공고 파일 S3 URL |
| text | string | null | ⚠️ | 채용공고 텍스트 |
⚠️
s3_key또는text중 하나는 필수
Response (200 OK)
{
"success": true,
"company": "카카오",
"position": "백엔드 개발자",
"schedules": [
{ "stage": "서류 마감", "date": "2026-01-15", "time": null },
{ "stage": "코딩테스트", "date": "2026-01-20", "time": "14:00" },
{ "stage": "1차 면접", "date": "2026-01-25", "time": null }
],
"hashtags": ["#카카오", "#백엔드", "#신입"]
}
Response Fields
| 필드 | 타입 | 설명 |
|---|---|---|
| success | boolean | 성공 여부 |
| company | string | 회사명 |
| position | string | 포지션 |
| schedules | array | 전형 일정 목록 |
| schedules[].stage | string | 전형 단계 |
| schedules[].date | string | 날짜 (YYYY-MM-DD) |
| schedules[].time | string | null | 시간 (HH:MM) |
| hashtags | array | 추출된 해시태그 |
Error Responses
400 Bad Request
필수 필드 누락:
{
"detail": {
"code": "INVALID_REQUEST",
"message": "s3_key 또는 text 중 하나는 필수입니다"
}
}
잘못된 URL:
{
"detail": {
"code": "INVALID_URL",
"message": "유효하지 않은 URL 형식입니다",
"field": "s3_key"
}
}
401 Unauthorized
{
"detail": {
"code": "UNAUTHORIZED",
"message": "유효하지 않은 API Key입니다"
}
}
404 Not Found
{
"detail": {
"code": "FILE_NOT_FOUND",
"message": "파일을 찾을 수 없습니다"
}
}
422 Unprocessable Entity
파싱 실패:
{
"detail": {
"code": "PARSE_FAILED",
"message": "일정 정보를 추출할 수 없습니다"
}
}
일정 없음:
{
"detail": {
"code": "NO_SCHEDULE_FOUND",
"message": "채용공고에서 일정을 찾을 수 없습니다"
}
}
503 Service Unavailable
{
"detail": {
"code": "LLM_UNAVAILABLE",
"message": "AI 서비스에 연결할 수 없습니다"
}
}
4. 게시판 첨부파일 마스킹
개요
게시판 첨부파일에서 개인정보(이름, 전화번호, 이메일, 얼굴)를 감지하고 마스킹합니다.
Endpoint
POST /ai/masking/draft
처리 방식
비동기 - task_id 반환 → 폴링 필요
Request Headers
| Header | 값 | 필수 |
|---|---|---|
| X-API-Key | your-api-key-here | ✅ |
Request Body
{
"s3_key": "https://s3.../document.png",
"file_type": "image",
"model": "gemini"
}
Request Fields
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| s3_key | string | ✅ | 원본 파일 S3 URL |
| file_type | string | ✅ | image, pdf |
| model | string | ❌ | gemini(기본값), openai, vllm |
Response (202 Accepted)
{
"task_id": "task_masking_001",
"status": "processing"
}
Error Responses
400 Bad Request
필수 필드 누락:
{
"detail": {
"code": "INVALID_REQUEST",
"message": "s3_key은 필수입니다",
"field": "s3_key"
}
}
잘못된 파일 타입:
{
"detail": {
"code": "INVALID_FILE_TYPE",
"message": "file_type은 image 또는 pdf만 가능합니다",
"field": "file_type"
}
}
잘못된 URL:
{
"detail": {
"code": "INVALID_URL",
"message": "유효하지 않은 URL 형식입니다",
"field": "s3_key"
}
}
401 Unauthorized
{
"detail": {
"code": "UNAUTHORIZED",
"message": "유효하지 않은 API Key입니다"
}
}
404 Not Found
{
"detail": {
"code": "FILE_NOT_FOUND",
"message": "파일을 찾을 수 없습니다"
}
}
422 Unprocessable Entity
{
"detail": {
"code": "MASKING_FAILED",
"message": "이미지 마스킹에 실패했습니다"
}
}
503 Service Unavailable
{
"detail": {
"code": "S3_UNAVAILABLE",
"message": "파일 저장에 실패했습니다"
}
}
Polling 완료 시 (GET /ai/task/{task_id})
{
"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 },
{ "type": "email", "coordinates": [100, 150, 300, 180], "confidence": 0.98 },
{ "type": "face", "coordinates": [400, 50, 500, 150], "confidence": 0.99 }
]
}
}
PII Type
| Type | 설명 |
|---|---|
| name | 이름 |
| phone | 전화번호 |
| 이메일 | |
| face | 얼굴 |
5. 비동기 작업 상태 조회
개요
비동기 처리 작업의 상태를 조회하고 결과를 확인합니다.
Endpoint
GET /ai/task/{task_id}
Path Parameters
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| task_id | string | ✅ | 작업 ID |
Response - 처리 중
{
"task_id": "task_abc123",
"status": "processing",
"progress": 65,
"message": "OCR 처리 중..."
}
Response - 완료
{
"task_id": "task_abc123",
"status": "completed",
"result": { ... }
}
참고:
result내용은 작업 유형에 따라 다릅니다:
- 텍스트 추출 + 분석:
resume_ocr,job_posting_ocr,resume_analysis,posting_analysis포함- 마스킹:
original_url,masked_url,thumbnail_url,detected_pii포함
Response - 실패
{
"task_id": "task_abc123",
"status": "failed",
"error": {
"code": "OCR_ERROR",
"message": "파일 형식을 인식할 수 없습니다."
}
}
Status 값
| Status | 설명 |
|---|---|
| processing | 처리 중 |
| completed | 완료 |
| failed | 실패 |
Error Responses
400 Bad Request
{
"detail": {
"code": "INVALID_TASK_ID",
"message": "유효하지 않은 task_id 형식입니다"
}
}
401 Unauthorized
{
"detail": {
"code": "UNAUTHORIZED",
"message": "유효하지 않은 API Key입니다"
}
}
404 Not Found
{
"detail": {
"code": "TASK_NOT_FOUND",
"message": "작업을 찾을 수 없습니다: task_abc123"
}
}
410 Gone
{
"detail": {
"code": "TASK_EXPIRED",
"message": "작업이 만료되었습니다"
}
}
폴링 권장 사항
- 초기 폴링 간격: 1초
- 최대 대기 시간: 300초 (5분)
- 지수 백오프: 실패 시 간격을 점진적으로 증가 (1초 → 2초 → 4초 → 8초)
공통 에러 응답
HTTP Status Codes
| Status | 설명 | 사용 시점 |
|---|---|---|
| 400 | Bad Request | 필수 파라미터 누락, 잘못된 형식 |
| 401 | Unauthorized | X-API-Key 누락/불일치 |
| 404 | Not Found | task_id 없음, 파일 없음 |
| 410 | Gone | 작업 만료 |
| 422 | Unprocessable Entity | 파라미터 형식은 맞지만 처리 불가 |
| 429 | Too Many Requests | Rate Limit 초과 |
| 500 | Internal Server Error | 서버 내부 오류 |
| 503 | Service Unavailable | 외부 서비스(Gemini, S3) 연결 실패 |
에러 응답 형식
{
"detail": {
"code": "ERROR_CODE",
"message": "사람이 읽을 수 있는 에러 메시지",
"field": "에러 발생 필드 (optional)"
}
}
에러 코드 전체 목록
| Code | 설명 | 관련 API |
|---|---|---|
| INVALID_REQUEST | 필수 파라미터 누락 | 전체 |
| INVALID_FILE_TYPE | 지원하지 않는 파일 타입 | 1, 4 |
| INVALID_DOCUMENT | 문서 정보 불완전 | 1 |
| INVALID_MODE | 잘못된 chat mode | 2 |
| INVALID_INTERVIEW_TYPE | 잘못된 면접 타입 | 2 |
| INVALID_TASK_ID | 잘못된 task_id 형식 | 5 |
| INVALID_URL | 잘못된 URL 형식 | 3, 4 |
| MISSING_CONTEXT | 필수 context 누락 | 2 |
| EMPTY_MESSAGE | 빈 메시지 | 2 |
| HISTORY_TOO_LONG | history 초과 | 2 |
| UNAUTHORIZED | 인증 실패 | 전체 |
| FILE_NOT_FOUND | 파일 없음 | 1, 2, 3, 4 |
| TASK_NOT_FOUND | 작업 없음 | 5 |
| TASK_EXPIRED | 작업 만료 | 5 |
| SESSION_NOT_FOUND | 면접 세션 없음 | 2 |
| OCR_FAILED | OCR 실패 | 1 |
| PARSE_FAILED | 파싱 실패 | 3 |
| NO_SCHEDULE_FOUND | 일정 없음 | 3 |
| MASKING_FAILED | 마스킹 실패 | 4 |
| STREAM_ERROR | 스트리밍 오류 | 2 |
| RATE_LIMIT_EXCEEDED | 요청 한도 초과 | 전체 |
| INTERNAL_ERROR | 내부 서버 오류 | 전체 |
| LLM_UNAVAILABLE | LLM 서비스 불가 | 1, 2, 3 |
| S3_UNAVAILABLE | S3 서비스 불가 | 1, 4 |
| VECTORDB_UNAVAILABLE | VectorDB 서비스 불가 | 2 |
Rate Limiting
| API 유형 | 제한 |
|---|---|
| 동기 API | 100 requests/min |
| 비동기 API | 50 requests/min |
| 스트리밍 API | 20 connections/min |
헤더 응답
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
백엔드 ↔ AI 서버 매핑 가이드
백엔드 외부 API와 AI 서버 내부 API의 매핑:
| 백엔드 외부 API | AI 서버 내부 API | 설명 |
|---|---|---|
POST /api/ai-chatrooms/{roomId}/messages |
POST /ai/chat (mode: general) |
일반 채팅 |
POST /api/ai/chatrooms/{roomId}/analysis |
POST /ai/text/extract |
분석 요청 |
POST /api/ai/chatrooms/{roomId}/interview |
POST /ai/chat (mode: interview_question) |
면접 시작 |
POST /api/ai/chatrooms/{roomId}/evaluation |
POST /ai/chat (mode: interview_report) |
면접 평가 |
POST /api/ai/events/extraction |
POST /ai/calendar/parse |
일정 추출 |
POST /api/board/upload |
POST /ai/masking/draft |
마스킹 요청 |
에러 처리 권장 사항
Backend 에러 핸들링
// Spring Boot 예시
@ExceptionHandler
public ResponseEntity<ErrorResponse> handleAIServerError(AIServerException e) {
switch (e.getCode()) {
case "TASK_NOT_FOUND":
return ResponseEntity.status(404).body(e.toResponse());
case "LLM_UNAVAILABLE":
// 재시도 로직 또는 사용자에게 안내
return ResponseEntity.status(503).body(e.toResponse());
default:
return ResponseEntity.status(500).body(e.toResponse());
}
}
재시도 정책
| 에러 코드 | 재시도 | 설명 |
|---|---|---|
| LLM_UNAVAILABLE | ✅ | 최대 3회, 지수 백오프 (1초 → 2초 → 4초) |
| S3_UNAVAILABLE | ✅ | 최대 3회, 지수 백오프 |
| VECTORDB_UNAVAILABLE | ✅ | 최대 3회, 지수 백오프 |
| RATE_LIMIT_EXCEEDED | ✅ | X-RateLimit-Reset 헤더 확인 후 재시도 |
| TASK_EXPIRED | ❌ | 새 요청 필요 |
| OCR_FAILED | ❌ | 파일 확인 필요 |
| UNAUTHORIZED | ❌ | API Key 확인 필요 |
문서 작성: 2026-01-13 작성자: AI Team 버전: v1.6 마지막 업데이트: 2026-01-26
변경 이력
| 버전 | 날짜 | 변경 내용 |
|---|---|---|
| v1.6 | 2026-01-26 | 백엔드 회의 후 API 명세 업데이트: s3_key 필드 통일, 면접 리포트 context 배열 구조, 에러 응답 상세화 |
| v1.5 | 2026-01-25 | API 명세 최신화: file_url 필드 추가, 에러 응답 상세화, Request/Response 예시 보완 |
| v1.4 | 2026-01-23 | API별 상세 에러 케이스 추가 |
| v1.3 | 2026-01-23 | /ai/text/extract를 batch 구조로 변경 (resume + job_posting 동시 처리) |
| v1.2 | 2026-01-23 | Request Body 필드명 RDB 컬럼명과 동기화 |
| v1.1 | 2026-01-23 | 초기 API 명세 작성 |