๋ชจ๋ Core Server REST API๋ ๋ณธ ๋ฌธ์์ ๊ท์ฝ์ ๋ฐ๋ฅธ๋ค. OpenAPI ์คํ์ springdoc-openapi๊ฐ ์๋ ์์ฑํ๊ณ , ํ๋ก ํธ๋
openapi-typescript๋ก ํ์ ์ ์์ฑํ๋ค.
/api/{resource} # ์ปฌ๋ ์
/api/{resource}/{id} # ๋จ์ผ ๋ฆฌ์์ค
/api/{resource}/{id}/{sub} # ์๋ธ ๋ฆฌ์์ค
/api/{resource}/{id}:{action} # ์ก์
(PATCH ๋์ ๋ช
์์ ๋์ฌ)
- ๋ฆฌ์์ค๋ช
์ ๋ณต์ํ, kebab-case ๊ธ์ง, snake_case ๊ธ์ง โ
repositories,interview-sessions๋์sessions(์งง๊ณ ๋ช ํํ๊ฒ) - ์ก์
์ด RESTful ๋์ฌ๋ก ํํ ์ ๋ ๋๋ง
:start,:endํ์ ์ฌ์ฉ
| ๋์ | ๋ฉ์๋ | ๊ฒฝ๋ก ์์ |
|---|---|---|
| ์ปฌ๋ ์ ์กฐํ | GET | /api/sessions |
| ๋จ๊ฑด ์กฐํ | GET | /api/sessions/{id} |
| ์์ฑ | POST | /api/sessions |
| ๋ถ๋ถ ์์ | PATCH | /api/sessions/{id} |
| ์ ์ฒด ๊ต์ฒด | PUT | (๊ฑฐ์ ์ฌ์ฉํ์ง ์์) |
| ์ญ์ (soft) | DELETE | /api/sessions/{id} |
| ์ก์ ํธ๋ฆฌ๊ฑฐ | PATCH | /api/sessions/{id}/start, /api/sessions/{id}/end |
POST /api/auth/github GitHub OAuth ์์ (URL ๋ฐ๊ธ)
GET /api/auth/github/callback OAuth ์ฝ๋ฐฑ ์ฒ๋ฆฌ
POST /api/auth/refresh ํ ํฐ ๊ฐฑ์
DELETE /api/auth/logout ๋ก๊ทธ์์ (refresh token revoke)
GET /api/users/me ๋ด ์ ๋ณด
PATCH /api/users/me ๋ด ์ ๋ณด ์์ (์ ํ์ )
DELETE /api/users/me ํ์ ํํด (soft)
GET /api/users/me/stats ๋ด ํต๊ณ (์ ์ ์ถ์ด)
GET /api/users/me/consents ๋์ ์ด๋ ฅ
POST /api/users/me/consents ๋์ ์ ์ถ
GET /api/repositories ๋ด๊ฐ ๋ฑ๋กํ ๋ ํฌ ๋ชฉ๋ก
GET /api/repositories/github GitHub์ ๋ด ์ ์ฒด ๋ ํฌ (๋ฑ๋ก ๊ฐ๋ฅ ํ๋ณด)
POST /api/repositories ๋ฑ๋ก
GET /api/repositories/{id} ์์ธ (๋ถ์ ์ํ ํฌํจ)
DELETE /api/repositories/{id} ์ํํธ ์ญ์
POST /api/repositories/{id}/reanalyze ์ฌ๋ถ์ ์์ฒญ
POST /api/resumes ์
๋ก๋
GET /api/resumes ๋ชฉ๋ก
GET /api/resumes/{id} ์์ธ
DELETE /api/resumes/{id} ์ํํธ ์ญ์
POST /api/resumes/{id}/reanalyze ์ฌ๋ถ์
GET /api/documents ๋ถ์ ๋ฌธ์ ๋ชฉ๋ก
GET /api/documents/{id} ๋ถ์ ๋ฌธ์ ์์ธ (S3 URL ํฌํจ)
GET /api/documents/{id}/content ๋ถ์ ์๋ฌธ(๋งํฌ๋ค์ด) ํ๋ก์ โ presigned ๋ ๋ด๋ถ ํธ์คํธ๋ผ Core ์ค๊ณ
POST /api/sessions ์์ฑ
GET /api/sessions ํ์คํ ๋ฆฌ ๋ชฉ๋ก
GET /api/sessions/{id} ์์ธ (๋ฉ์์ง/ํผ๋๋ฐฑ ํฌํจ)
PATCH /api/sessions/{id} ๋ฉ๋ชจ/์ ๋ชฉ ์์
PATCH /api/sessions/{id}/start ์์ (READY โ IN_PROGRESS)
PATCH /api/sessions/{id}/end ์ข
๋ฃ
DELETE /api/sessions/{id} ์ํํธ ์ญ์
GET /api/sessions/{id}/messages ๋ฉ์์ง ๋ชฉ๋ก
POST /api/sessions/{id}/messages ๋ฉ์์ง ์ถ๊ฐ (RealTime โ Core ์ ์ฉ)
GET /api/sessions/{id}/feedback ํผ๋๋ฐฑ ์กฐํ
GET /api/sessions/{id}/feedback/report AI ํ์ต ๋ฆฌํฌํธ(๋งํฌ๋ค์ด) ํ๋ก์ โ ์์ ์ ์ ์ฉ, /documents/{id}/content ์ ๋์ผ ํจํด
GET /api/system/health ํฌ์ค์ฒดํฌ (PG/MQ/S3/AI ์ํ)
GET /api/system/version ๋ฒ์ ์ ๋ณด
/api/internal/* prefix. ์ธ๋ถ์์ ํธ์ถ ๊ธ์ง โ Spring Security์์ ์ธ๋ถ ํจํท์ ์ฐจ๋จํ๊ณ X-Internal-API-Key ํค๋๋ก ๊ฒ์ฆ. AI Server์ RealTime Server๋ง ํธ์ถ์.
GET /api/internal/users/{userId}/github-token AI โ Core
PUT /api/internal/documents/{documentId}/embeddings AI โ Core
POST /api/internal/embeddings/search AI โ Core
POST /api/internal/ai-logs AI โ Core
์์ธํ ์์ฒญยท์๋ต ์คํค๋ง์ ์ธ์ฆ ๊ท์ฝ์ ยง10 ๋ด๋ถ API ๋ถ๋ก ์ฐธ์กฐ.
| ํค๋ | ํ์ | ์ค๋ช |
|---|---|---|
Authorization: Bearer {token} |
์ธ์ฆ ํ์ ์ | JWT access token |
X-Trace-Id |
๊ถ์ฅ | ํด๋ผ์ด์ธํธ๊ฐ ๋ถ์ฌํ์ง ์์ผ๋ฉด Gateway๊ฐ ๋ถ์ฌ |
Content-Type: application/json |
POST/PATCH | multipart/form-data๋ ํ์ผ ์ ๋ก๋ ์๋ง |
GET /api/sessions?page=0&size=20&sort=createdAt,desc
- 0-based page
size์ต๋ 100 (์ด๊ณผ ์ 100์ผ๋ก clamp)- ์ ๋ ฌ:
{field},{asc|desc}ํ์, ๋ค์ค ์ ๋ ฌ์&sort=๋ฐ๋ณต
GET /api/sessions?status=COMPLETED&jobCategory=BACKEND&from=2026-01-01&to=2026-04-30
- ๋ ์ง๋ ISO 8601 (
YYYY-MM-DD๋๋ RFC 3339) - ๋ค์ค ๊ฐ์ ์ฝค๋ง:
?status=COMPLETED,CANCELLED
๋จ๊ฑด:
{
"id": 42,
"title": "๋ฐฑ์๋ ๋ชจ์๋ฉด์ #3",
"status": "COMPLETED",
"createdAt": "2026-04-25T14:30:00Z"
}์ปฌ๋ ์ (ํ์ด์ง):
{
"content": [ {...}, {...} ],
"page": 0,
"size": 20,
"totalElements": 137,
"totalPages": 7,
"first": true,
"last": false
}ํ์ค ํ์:
{
"code": "RESUME_INVALID_FILE_TYPE",
"message": "PDF ํ์ผ๋ง ์
๋ก๋ ๊ฐ๋ฅํฉ๋๋ค.",
"traceId": "9f4e5b...",
"timestamp": "2026-04-27T15:20:00Z",
"details": {
"uploadedType": "image/jpeg"
}
}| ํ๋ | ์ค๋ช |
|---|---|
code |
๋๋ฉ์ธ ์๋ฌ ์ฝ๋ (๋๋ฌธ์ SNAKE_CASE) โ ํด๋ผ์ด์ธํธ ๋ถ๊ธฐ ๊ธฐ์ค |
message |
์ฌ์ฉ์์๊ฒ ๋ ธ์ถ ๊ฐ๋ฅํ ํ๊ตญ์ด ๋ฉ์์ง |
traceId |
๋ก๊ทธ ์ถ์ ์ฉ, ์ฌ์ฉ์ ๋ฌธ์ ์ ํ์ฉ |
details |
์ถ๊ฐ ์ปจํ ์คํธ (validation ์คํจ ํ๋ ๋ฑ) |
| ์ํฉ | ์ฝ๋ |
|---|---|
| ์ฑ๊ณต (์กฐํ/์์ ) | 200 |
| ์์ฑ ์ฑ๊ณต | 201 (Location ํค๋ ํฌํจ) |
| ๋น๋๊ธฐ ์ฒ๋ฆฌ ์์ | 202 |
| ๋ณธ๋ฌธ ์์ (DELETE ๋ฑ) | 204 |
| ์๋ชป๋ ์์ฒญ (validation ์คํจ) | 400 |
| ์ธ์ฆ ๋๋ฝ/๋ง๋ฃ | 401 |
| ๊ถํ ๋ถ์กฑ | 403 |
| ๋ฆฌ์์ค ์์ | 404 |
| ์ถฉ๋ (์ค๋ณต ๋ฑ๋ก ๋ฑ) | 409 |
| ์ฒ๋ฆฌ ๊ฐ๋ฅํ์ง๋ง ์๋ฏธ์ ์ค๋ฅ | 422 |
| ์๋ฒ ์ค๋ฅ | 500 |
| ์ธ๋ถ ์์กด์ฑ ์ฅ์ (LLM API ๋ฑ) | 502 / 503 |
ํด๋ผ์ด์ธํธ ์๋ชป์ ๋ฐ๋์ 4xx ๋ก ๋๋ธ๋ค. 5xx ๋ก ์์ด ๋๊ฐ๋ฉด ๋ ๊ฐ์ง๊ฐ ๋์์ ๋ง๊ฐ์ง๋ค โ
ํด๋ผ์ด์ธํธ ์ฌ์๋ ๋ก์ง์ด ์์ํ ์ฑ๊ณตํ์ง ๋ชปํ ์์ฒญ์ ๋ฐ๋ณตํ๊ณ , ์ด์ ๋ก๊ทธ์์ ์ง์ง ์ฅ์ ๊ฐ
์คํ URLยท๋ด ์ค์บ์ ๋ฌปํ๋ค. ์ค์ ๋ก 2026-09-27 ์ ๊ฒ ์์ ์ด์ ๋ก๊ทธ์ SYS_INTERNAL_ERROR
11๊ฑด์ด ์ ๋ถ ์๋ ๋ค ์ข
๋ฅ์๊ณ ์๋ฒ ๋ฒ๊ทธ๋ 0๊ฑด์ด์๋ค.
| ์ํฉ | ์์ธ | ์๋ต |
|---|---|---|
| ๋ณธ๋ฌธ ๋๋ฝยท๊นจ์ง JSONยทํ๋ ํ์ ๋ถ์ผ์น | HttpMessageNotReadableException |
400 MALFORMED_REQUEST |
๊ฒฝ๋ก๋ณ์ ๋ณํ ์คํจ (/api/sessions/abc) |
MethodArgumentTypeMismatchException |
400 MALFORMED_REQUEST |
@Valid ์๋ฐ |
MethodArgumentNotValidException |
400 VALIDATION_ERROR |
| ์กด์ฌํ์ง ์๋ ๊ฒฝ๋ก | NoResourceFoundException |
404 ENDPOINT_NOT_FOUND |
| ์ง์ํ์ง ์๋ ๋ฉ์๋ | HttpRequestMethodNotSupportedException |
405 METHOD_NOT_ALLOWED |
ํ์ฑ ์คํจ ์๋ต์ ์๋ฌธ ์์ธ ๋ฉ์์ง๋ฅผ ์ฃ์ง ์๋๋ค โ ๊นจ์ง ๋ณธ๋ฌธ ์กฐ๊ฐ(= ์ฌ์ฉ์ ์ ๋ ฅ)์ด ๊ทธ๋๋ก ๋ฐ์ฌ๋๋ค. ๊ณ ์ ๋ฌธ๊ตฌ๋ง ๋ด๋ณด๋ด๊ณ ์์ธ์ ์๋ฒ ๋ก๊ทธ์ ์์ธ ํ์ ์ผ๋ก๋ง ๋จ๊ธด๋ค.
์ @RequestBody ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ ๋ ์ด ํ๋ฅผ ๋ค์ ๋ณผ ํ์๋ ์๋ค โ
GlobalExceptionHandler ๊ฐ ์ ์ญ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ํ๊ท๋
GlobalExceptionHandlerClientErrorTest ๊ฐ ๊ณ ์ ํ๋ค.
์ ๊ท ์ถ๊ฐ ์ ๋ณธ ์นดํ๋ก๊ทธ์ ๋ฐ๋์ ๋ฑ๋ก. ๊ฐ์ ์ฝ๋๋ฅผ ๋ ๋๋ฉ์ธ์์ ์ฌ์ฉ ๊ธ์ง.
# ์ธ์ฆ (AUTH_*)
AUTH_INVALID_TOKEN 401 ํ ํฐ ํ์ ์ค๋ฅ
AUTH_EXPIRED_TOKEN 401 ๋ง๋ฃ
AUTH_REVOKED_TOKEN 401 revoke๋จ
AUTH_GITHUB_OAUTH_FAILED 401 GitHub OAuth ์คํจ
AUTH_CONSENT_REQUIRED 403 ํ์ ๋์ ๋ฏธ์๋ฃ
# ์ฌ์ฉ์ (USER_*)
USER_NOT_FOUND 404
USER_ALREADY_DELETED 410
# ์ด๋ ฅ์ (RESUME_*)
RESUME_INVALID_FILE_TYPE 400 PDF ์ธ ์
๋ก๋
RESUME_FILE_TOO_LARGE 400 ํฌ๊ธฐ ์ด๊ณผ
RESUME_EMPTY_FILE 400
RESUME_NOT_FOUND 404
RESUME_IN_USE 409 ํ์ฑ ์ธ์
์์ ์ฌ์ฉ ์ค
# ๋ ํฌ (REPO_*)
REPO_NOT_FOUND 404
REPO_ALREADY_REGISTERED 409
REPO_GITHUB_API_FAILED 502
REPO_PRIVATE_NO_ACCESS 403
# ๋ถ์ (DOC_*)
DOC_NOT_ANALYZED 422 ANALYZED ์ํ ์๋
DOC_ANALYSIS_FAILED 500
DOC_NOT_FOUND 404
# ์ธ์
(SESSION_*)
SESSION_INVALID_STATE 422 ์ํ ์ ์ด ๋ถ๊ฐ
SESSION_MAX_REACHED 422 ์ต๋ ์ง๋ฌธ/์๊ฐ ๋๋ฌ
SESSION_NOT_FOUND 404
SESSION_FORBIDDEN 403 ํ์ธ ์ธ์
์ ๊ทผ
# ํผ๋๋ฐฑ (FEEDBACK_*)
FEEDBACK_NOT_READY 404 ์์ง ์์ฑ ์ค (ํด๋ง/SSE ๋๊ธฐ ์ง์)
FEEDBACK_GENERATION_FAILED 404 ์์ฑ ์คํจ ๋ง์ปค ์กด์ฌ โ ๋๊ธฐ ์ค๋จ, ์ฌ์์ฑ ์ ๋. details.retriable(boolean) ๋๋ด
FEEDBACK_NOT_FOUND 404 ๊ณต์ ํ ํฐ ๋ฌดํจ
FEEDBACK_ALREADY_EXISTS 409 ์ฌ์์ฑ ์์ฒญ ์ ์ด๋ฏธ ์กด์ฌ (์ฌ์กฐํ ์ ํธ)
FEEDBACK_REPORT_NOT_AVAILABLE 422 ๋ฆฌํฌํธ ํ์ผ ์์ โ AI ์ ์ฅ ์คํจ ํด๋ฐฑ(reportS3Key=null)ยท๊ตฌ๋ฒ์ ํผ๋๋ฐฑ
# ์์คํ
(SYS_*)
SYS_RATE_LIMITED 429
SYS_DEPENDENCY_DOWN 503 RabbitMQ/AI/LLM ๋ค์ด
SYS_INTERNAL_ERROR 500
POST ์์ฒญ ์ค ๊ฒฐ์ ยท๋ฉ์์ง ๋ฐํ ๋ฑ ์ค๋ณต ์ฒ๋ฆฌ ์ํ์ด ์๋ ๊ฒฝ์ฐ:
POST /api/sessions
Idempotency-Key: 8b3c5e2a-...
์๋ฒ๋ 24์๊ฐ ๋์ ๊ฐ์ ํค๋ก ๋ค์ด์จ ์์ฒญ์ ๋ํด ๋์ผ ์๋ต์ ๋ฐํ.
Phase 1์์๋ ๊ฒฐ์ ์์ผ๋ฏ๋ก ํ์๋ ์๋๋, AI ์์ ๋ฐํ API(
reanalyze๋ฑ)์ ์ ์ฉ ๊ถ์ฅ.
- ๋ณธ ๋ฌธ์ ๊ธฐ์ค v1. ๋ชจ๋ ๊ฒฝ๋ก์
/api/v1/...์ ๋๋ ์ฌ์ฉํ์ง ์์ (Breaking Change ๋ฐ์ ์์ ์ ๋์ ). - Breaking Change ์ ์ฑ : ์ ๊ท ๊ฒฝ๋ก ์ถ๊ฐ + ๊ตฌ ๊ฒฝ๋ก๋ ์ต์ 1๊ฐ phase ์ ์ง ํ ์ ๊ฑฐ.
- springdoc-openapi
application.properties:springdoc.api-docs.path=/api/v3/api-docs springdoc.swagger-ui.path=/api/swagger-ui.html @Operation,@Schema์ ๋ ธํ ์ด์ ์ผ๋ก ์๋ต ์์ ์์ฑ
openapi-typescript์ผ๋กfrontend/src/shared/api/generated.ts์์ฑ- ๋น๋ ์คํฌ๋ฆฝํธ:
npx openapi-typescript http://localhost:8080/api/v3/api-docs -o src/shared/api/generated.ts
- ๋ณ๊ฒฝ ์ PR์ ํฌํจ
POST/PATCH๊ฐ ์ฆ์ ์๋ฃ๋์ง ์๋ ๊ฒฝ์ฐ (AI ๋ถ์ ํธ๋ฆฌ๊ฑฐ ๋ฑ):
HTTP/1.1 202 Accepted
{
"taskId": "doc-analyze-42",
"status": "QUEUED",
"statusUrl": "/api/documents/42",
"streamUrl": "/api/stream/documents/42"
}ํด๋ผ์ด์ธํธ๋:
- SSE (
streamUrl) ์ฐ์ ๊ตฌ๋ โ ์ํ ํธ์ ์์ - SSE ๋๊ธฐ๋ฉด
statusUrlํด๋ง fallback (5์ด ๊ฐ๊ฒฉ)
์์ธ: event-stream.md
/api/internal/* endpoint๋ ๋ค์ ๊ท์ฝ์ ๋ฐ๋ฅธ๋ค.
- ๋ชจ๋ ์์ฒญ์
X-Internal-API-Key: {key}ํค๋ ํ์ - ํค๋ ํ๊ฒฝ๋ณ์
CORE_INTERNAL_API_KEY(Core/AI/RealTime ๋์ผ ๊ฐ) - JWT ์ฌ์ฉ์ ์ธ์ฆ ๋ถํ์. ๋จ ํธ์ถ์๊ฐ ์ ๋ขฐ ์์ญ ์์ ์์ด์ผ ํจ (Docker ๋ด๋ถ๋ง, K8s ClusterIP ๋ฑ)
- ํค ๋๋ฝ/๋ถ์ผ์น โ
401 Unauthorized, body:{ "code": "INTERNAL_AUTH_FAILED" }
๋ถ์ ์์ ์ ์ฌ์ฉ์ GitHub access token์ ์งง๊ฒ ์์. AES-256์ผ๋ก ์ ์ฅ๋ ํ๋ฌธ token์ ๋ณตํธํํด ๋ฐํ (๋ฉ๋ชจ๋ฆฌ/๋ก๊ทธ/์๋ต body์๋ง ๋ ธ์ถ, DBยท์ด๋ฒคํธ์๋ฒจ๋กํ์๋ ์ ๋ ๋๋ดํ์ง ์์).
Path parameter
| ์ด๋ฆ | ํ์ | ์ค๋ช |
|---|---|---|
userId |
long | users.id |
Response 200
{ "accessToken": "ghp_xxx..." }Response 404 โ ์ฌ์ฉ์ ์์ ๋๋ token ๋๋ฝ (USER_NOT_FOUND)
Response 401/403 โ ์ธ์ฆ ์คํจ (INTERNAL_AUTH_FAILED)
Response 5xx โ Core ์ผ์ ์ฅ์ (ํธ์ถ์๋ retriable๋ก ๊ฐ์ฃผ)
analyzed_documents ํ ๊ฑด์ ๋ํ chunk + embedding์ document_embeddings ํ
์ด๋ธ์ idempotent upsert. AI๊ฐ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ callback ๋ฐํ ์ง์ ์ ํธ์ถํ๋ค.
Path parameter
| ์ด๋ฆ | ํ์ | ์ค๋ช |
|---|---|---|
documentId |
long | analyzed_documents.id (Core๊ฐ publish ์ง์ ๋ฏธ๋ฆฌ ์์ฑ) |
Request body
{
"model": "gemini-embedding-001",
"dim": 1536,
"chunks": [
{ "chunkIndex": 0, "chunkText": "...", "embedding": [0.012, -0.003, ...] },
{ "chunkIndex": 1, "chunkText": "...", "embedding": [...] }
]
}| ํ๋ | ํ์ | ๋น๊ณ |
|---|---|---|
model |
string | ์๋ฒ ๋ฉ ๋ชจ๋ธ ID (๊ฐ์ฌ์ฉ) |
dim |
int | ์๋ฒ ๋ฉ ์ฐจ์. DB ์ปฌ๋ผ ์ฐจ์๊ณผ ์ผ์นํด์ผ ํจ (๋ถ์ผ์น โ 400) |
chunks[].chunkIndex |
int | 0-base, ๊ฐ์ document ๋ด unique |
chunks[].chunkText |
string | ์๋ฌธ ์ฒญํฌ (โค N KB, ์ปฌ๋ผ ๊ธธ์ด ์ ํ) |
chunks[].embedding |
float[] | ๊ธธ์ด == dim |
Response 200
{ "upserted": 18 }Response 400 โ dim ๋ถ์ผ์น, payload ๊ฒ์ฆ ์คํจ (EMBEDDING_BAD_REQUEST)
Response 404 โ analyzed_documents.id ์์ (DOCUMENT_NOT_FOUND)
Response 401/403 โ INTERNAL_AUTH_FAILED
Response 5xx โ Core ์ผ์ ์ฅ์ (retriable)
Idempotency
- ๊ฐ์
(documentId, chunkIndex)์กฐํฉ ์ฌํธ์ถ ์ row๋ฅผ ๋ฎ์ด์ (INSERT ... ON CONFLICT UPDATE) - ๋ถ๋ถ ์ฌ์๋ ์ ๋๋ฝ ์ฒญํฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ก AI๋ ์ ์ฒด ์ฒญํฌ๋ฅผ ํ ๋ฒ์ ๋ณด๋ด๋ ๊ฒ์ ๊ถ์ฅ
RAG ๊ฒ์. AI๊ฐ ์ง๋ฌธ ํยท๊ผฌ๋ฆฌ์ง๋ฌธยทํผ๋๋ฐฑ ์์ฑ ์ ์ปจํ
์คํธ ์ฒญํฌ๋ฅผ ๊ฐ์ ธ์จ๋ค. document_embeddings์์ pgvector cosine topK โ queryText๊ฐ ์ฃผ์ด์ง๋ฉด ๋ฒกํฐ + full-text(BM25) RRF ํ์ด๋ธ๋ฆฌ๋๋ก ๊ฒ์ํ๋ค.
full-text ์ชฝ tsquery ๋ OR ๊ฒฐํฉ์ด๋ค(2026-09-29).
plainto_tsquery๊ธฐ๋ณธ๊ฐ์ธ AND ๋ก๋ ์ด API ์ ์ค์ ์ง์(๊ผฌ๋ฆฌ์ง๋ฌธ = ์ง์ ์ง๋ฌธ+๋ต๋ณ, ์ง๋ฌธ ํ = ๋ชจ๋+์ง๊ตฐ+์๊ธฐ์๊ฐ 600์, ํ ํฐ ์์ญ ๊ฐ)๋ฅผ ์ ๋ถ ๋ด์ ์ฒญํฌ๊ฐ ์์ด full-text ๋ธ๋์น๊ฐ ํญ์ 0๊ฑด์ด์๊ณ RRF ๊ฐ ๋ฒกํฐ ๋จ๋ ์ผ๋ก ํดํํ๋ค โ ์ด์ ์ค์ธก 47/47ยท30/30 ์ด 0๊ฑด. OR ๋ก ๋ฐ๊พผ ๋ค ์ด์๊ณผ ๊ฐ์ ํค๋ฉ ์ฒญํน ์ธ๋ฑ์ค์์ MRR 0.791โ0.885, Top-1 0.681โ0.830, Recall 0.936โ0.979.
Request body
{
"queryEmbedding": [0.012, -0.003, ...],
"queryText": "๋๊ด์ ๋ฝ ๋์์ฑ ์ ์ด",
"documentIds": [12, 13],
"topK": 5
}| ํ๋ | ํ์ | ๋น๊ณ |
|---|---|---|
queryEmbedding |
float[] | ํ์. ์ฟผ๋ฆฌ ์๋ฒ ๋ฉ ๋ฒกํฐ |
queryText |
string | ์ ํ. ์ฃผ์ด์ง๋ฉด ํ์ด๋ธ๋ฆฌ๋(RRF) ๊ฒ์ |
documentIds |
long[] | ๊ฒ์ ๋ฒ์ ์ ํ. ๋น์ด ์์ผ๋ฉด ์ ์ฒด |
topK |
int | ์์. ๋ฏธ์ง์ ์ 5 |
Response 200
{
"hits": [
{ "documentId": 12, "chunkIndex": 3, "chunkText": "...", "distance": 0.18 }
]
}Response 400 โ queryEmbedding ๋๋ฝ
Response 401/403 โ INTERNAL_AUTH_FAILED
RAG ๋ณด๊ฐ์ฉ์ด๋ผ fatal ์ด ์๋๋ค โ AI(
core/client.py: search_embeddings)๋ ํธ์ถ ์คํจยทnon-2xx ๋ฅผ ๋น ๊ฒฐ๊ณผ๋ก ํด๋ฐฑํ๊ณ ๊ฒ์ ์์ด ์์ฑ์ ๊ณ์ํ๋ค.
AI์ LLM ํธ์ถ๋ณ ํ ํฐยท์ง์ฐ์๊ฐ์ ai_request_logs์ ๊ธฐ๋ก (US-30 ๊ด์ธก์ฑ). LangChain ์ฝ๋ฐฑ(observability/llm_logging_callback.py)์ด ํธ์ถ ์๋ฃยท์คํจ ์๋ง๋ค ๋ฐ์ฌํ๋ค.
Request body
{
"userId": 123,
"sessionId": 99,
"requestType": "generate.followup",
"modelName": "gemini-3.5-flash-lite",
"inputTokens": 1820,
"outputTokens": 210,
"latencyMs": 1830,
"status": "SUCCEEDED",
"errorMessage": null
}| ํ๋ | ํ์ | ๋น๊ณ |
|---|---|---|
requestType |
string | ํ์ (NotBlank) |
status |
string | ํ์ |
| ๋๋จธ์ง | โ | ์ ๋ถ nullable |
Response 202 โ ๊ธฐ๋ก ํ์ (๋ณธ๋ฌธ ์์)
Response 401/403 โ INTERNAL_AUTH_FAILED
Fire-and-forget โ AI๋ ์คํจํด๋ raise ํ์ง ์๊ณ ๊ฒฝ๊ณ ๋ก๊ทธ๋ง ๋จ๊ธด๋ค(๊ด์ธก ์คํจ๊ฐ ๋ณธ ์์ ์ ๋ง์ง ์๊ฒ).