-
Notifications
You must be signed in to change notification settings - Fork 0
API 요청 응답, DTO
API 요청/응답 스키마(DTO) 와 엔드포인트·입출력 예시만 정리한 문서입니다.
파이프라인 동작·흐름·Config는 PIPELINE/PIPELINE_OVERVIEW.md 를 참고하세요.
-
정의 위치:
src/models.py -
라우터:
src/api/routers/llm.py,sentiment.py,vector.py
| 섹션 | 내용 |
|---|---|
| 1. API 구조 | prefix, 엔드포인트 목록, 문서 링크 |
| 2. 엔드포인트·DTO 요약 | API별 Request/Response DTO 링크 |
| 3. 업로드·검색 데이터 요구 형태 | Vector upload / Sentiment 리뷰 필수 필드 |
| 4. API 입출력 JSON | 요청/응답 예시 (Comparison, Summary, Sentiment, Vector) |
| 5. API 요청/응답 DTO | DTO별 필드 정의, 공통 에러, 선택 필드 설명 |
| Method | path | Request DTO | Response DTO |
|---|---|---|---|
| POST | /api/v1/llm/comparison |
ComparisonRequest | ComparisonResponse |
| POST | /api/v1/llm/comparison/batch |
ComparisonBatchRequest | ComparisonBatchResponse |
| POST | /api/v1/llm/summarize |
SummaryRequest | SummaryDisplayResponse |
| POST | /api/v1/llm/summarize/batch |
SummaryBatchRequest | SummaryBatchResponse |
| POST | /api/v1/sentiment/analyze |
SentimentAnalysisRequest | SentimentAnalysisResponse (debug 시 상세) |
| POST | /api/v1/sentiment/analyze/batch |
SentimentAnalysisBatchRequest | SentimentAnalysisBatchResponse |
| POST | /api/v1/vector/search/similar |
VectorSearchRequest | VectorSearchResponse |
| POST | /api/v1/vector/upload |
VectorUploadRequest | VectorUploadResponse |
| Method | path | 설명 |
|---|---|---|
| GET | / |
앱 정보·버전·docs·health 링크 |
| GET | /health |
헬스 체크 |
| GET | /ready |
Readiness (warm-up 완료 시 200, 미완료 시 503) |
| GET | /metrics |
Prometheus 메트릭 (설치 시) |
| 문서 | 경로 |
|---|---|
| Swagger UI | {BASE_URL}/docs |
| ReDoc | {BASE_URL}/redoc |
| OpenAPI JSON | {BASE_URL}/openapi.json |
Vector upload / Sentiment 요청에 넣는 리뷰는 아래 필드가 필요합니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| id | int | O | 리뷰 식별자 |
| restaurant_id | int | O | 레스토랑 식별자 |
| content | str | O | 리뷰 본문 |
| created_at | datetime (ISO 8601) | O | 작성 시각 |
Vector upload 시 레스토랑은 선택: restaurants: [{ id?, name }]. 리뷰만으로 업로드 가능.
POST /api/v1/llm/comparison
// 요청 (restaurant_id만 사용)
{
"restaurant_id": 1
}
// 응답 (debug=false)
{
"restaurant_id": 1,
"comparisons": [
{"category": "service", "lift_percentage": 18.5},
{"category": "price", "lift_percentage": 12.2}
],
"total_candidates": 120,
"validated_count": 2,
"category_lift": {"service": 18.5, "price": 12.2},
"comparison_display": ["서비스 만족도는 평균보다 약 19% 높아요. 전반적으로 서비스 평가가 좋은 편입니다.", "가격 만족도는 평균보다 약 12% 높아요. 전반적으로 가격 평가가 좋은 편입니다."]
}배치 POST /api/v1/llm/comparison/batch
- 요청: restaurants 배열(각 항목:
restaurant_id필수). all_average_data_path: 선택, 전체 평균 데이터 파일 경로(미지정 시 Config 사용). - 응답:
results배열. 각 요소는 단일 Comparison 응답과 동일(ComparisonResponse). -
배치 (Config):
COMPARISON_BATCH_ASYNC=true면 레스토랑별 compare를asyncio.gather로 병렬, false면 순차.
// 요청 (restaurants + all_average_data_path 선택)
{
"restaurants": [
{"restaurant_id": 1},
{"restaurant_id": 2}
],
"all_average_data_path": null
}
// 응답 — { results: [ ComparisonResponse, ... ] }
{
"results": [
{
"restaurant_id": 1,
"comparisons": [{"category": "service", "lift_percentage": 18.5}, {"category": "price", "lift_percentage": 12.2}],
"total_candidates": 120,
"validated_count": 2,
"category_lift": {"service": 18.5, "price": 12.2},
"comparison_display": ["서비스 만족도는 평균보다 약 19% 높아요.", "가격 만족도는 평균보다 약 12% 높아요."]
},
{
"restaurant_id": 2,
"comparisons": [{"category": "service", "lift_percentage": 5.1}],
"total_candidates": 80,
"validated_count": 2,
"category_lift": {"service": 5.1, "price": 0},
"comparison_display": ["서비스 만족도는 평균보다 약 5% 높아요."]
}
]
}단일과 배치는 요청·응답 구조가 다름.
단일 POST /summarize
|
배치 POST /summarize/batch
|
|
|---|---|---|
| 요청 |
평탄 객체 { restaurant_id, limit }
|
래핑 객체 { restaurants: [ { restaurant_id, limit? } ] }
|
| 응답 |
루트가 요약 1개 (results 없음). X-Debug: true면 SummaryResponse, 아니면 SummaryDisplayResponse(필드 적음) |
{ results: [ ... ] } 배열. 각 요소는 SummaryDisplayResponse 형식(단일 debug=false와 동일, positive_reviews 등 없음) |
단일 POST /api/v1/llm/summarize
- 요청: 평탄.
restaurant_id, limit: 카테고리(service/price/food)당 검색·요약에 쓸 최대 리뷰 수 (1~100, 기본 10).
// 요청 (평탄 객체, 레스토랑 1개. limit: 1~100, 기본 10)
{
"restaurant_id": 1,
"limit": 10
}
// 응답 (debug=false) — 루트가 요약 1개. SummaryDisplayResponse (positive_reviews, positive_count 등 없음)
{
"restaurant_id": 1,
"overall_summary": "이 음식점은 서비스·가격·음식 전반에서 만족도가 높으며, 분위기와 맛을 꼽는 리뷰가 많습니다.",
"categories": {
"service": {
"summary": "직원과 사장님이 친절하고 응대가 빠르다.",
"bullets": ["친절한 서비스", "빠른 응대", "사장님 인사"],
"evidence": [{"review_id": "r1", "snippet": "직원분이 친절해요", "rank": 0},...]
},
"price": {
"summary": "가격 대비 만족도가 높은 편이다.",
"bullets": ["합리적인 가격", "무한 리필", "가성비 좋음"],
"evidence": [{"review_id": "r2", "snippet": "가격 대비 괜찮아요", "rank": 1},...]
},
"food": {
"summary": "음식 맛과 분위기를 칭찬하는 리뷰가 많다.",
"bullets": ["맛있음", "분위기 좋음", "타르트 인기"],
"evidence": [{"review_id": "r3", "snippet": "타르트가 맛있어요", "rank": 0},...]
}
}
}
// 응답 (debug=true) — SummaryDisplayResponse에 debug 필드 추가. (positive_reviews, negative_reviews 등은 미사용·미노출)배치 POST /api/v1/llm/summarize/batch
- 요청:
restaurants배열(각 항목:restaurant_id), limit: 카테고리당 검색·요약에 쓸 최대 리뷰 수 (1~100, 기본 10). 선택, 전체 공통. - 응답:
results배열. 각 요소는 SummaryDisplayResponse 형식(단일 debug=false와 동일). -
배치 (Config):
SUMMARY_SEARCH_ASYNC=aspect(service/price/food) 서치 병렬,SUMMARY_RESTAURANT_ASYNC=음식점 간 병렬. 둘 다 false면 완전 순차.SUMMARY_LLM_ASYNC=LLM 호출 비동기(to_thread vs AsyncOpenAI). 동시성:BATCH_SEARCH_CONCURRENCY,BATCH_LLM_CONCURRENCY. 자세한 내용은 BATCH_SUMMARY_MODE.md 참고.
// 요청 (restaurants + limit 전체 공통. limit: 1~100, 기본 10)
{
"restaurants": [
{"restaurant_id": 1},
{"restaurant_id": 2}
],
"limit": 10
}
// 응답 — { results: [ SummaryDisplayResponse, ... ] }. 단일(debug=false)과 동일한 필드; results로만 감쌈
{
"results": [
{
"restaurant_id": 1,
"overall_summary": "이 음식점은...",
"categories": { "service": {...}, "price": {...}, "food": {...} }
},
{
"restaurant_id": 2,
"overall_summary": "...",
"categories": { "service": {...}, "price": {...}, "food": {...} }
}
]
}POST /api/v1/sentiment/analyze
- 리뷰는 벡터 DB에서 조회하여 사용. 요청에는
restaurant_id만 전달.
// 요청 (restaurant_id만 사용)
{
"restaurant_id": 1
}
// 응답 (debug=false, SentimentAnalysisDisplayResponse)
{
"restaurant_id": 1,
"positive_ratio": 75,
"negative_ratio": 25
}
// 응답 (debug=true, SentimentAnalysisResponse)
{
"restaurant_id": 1,
"positive_count": 75,
"negative_count": 25,
"neutral_count": 0,
"total_count": 100,
"positive_ratio": 75,
"negative_ratio": 25,
"neutral_ratio": 0,
"debug": {"request_id": "...", "processing_time_ms": 120.5, "tokens_used": null, "model_version": null, "warnings": null}
}POST /api/v1/sentiment/analyze/batch
- 각 레스토랑 리뷰는 벡터 DB에서 조회. 요청에는
restaurant_id(및 선택restaurant_name)만 전달.
// 요청 (restaurant_id만 사용)
{
"restaurants": [
{"restaurant_id": 1},
{"restaurant_id": 2},
{"restaurant_id": 3}
]
}
// 응답
{
"results": [
{"restaurant_id": 1, "positive_count": 2, "negative_count": 0, "neutral_count": 0, "total_count": 2, "positive_ratio": 100, "negative_ratio": 0, "neutral_ratio": 0},
{"restaurant_id": 2, "positive_count": 1, "negative_count": 0, "neutral_count": 0, "total_count": 1, "positive_ratio": 100, "negative_ratio": 0, "neutral_ratio": 0},
{"restaurant_id": 3, "positive_count": 1, "negative_count": 0, "neutral_count": 0, "total_count": 1, "positive_ratio": 100, "negative_ratio": 0, "neutral_ratio": 0}
]
}POST /api/v1/vector/search/similar
- 검색: 하이브리드 통일 (Dense prefetch dense_prefetch_limit(기본 200), Sparse prefetch sparse_prefetch_limit(기본 300) → RRF → 최종 limit개).
- 폴백(단일 벡터/Sparse 실패 등): Dense만 사용. fallback_min_score: 폴백 경로 최소 유사도 (0.0~1.0, 기본 0.2).
- 요청: limit: 반환할 최대 개수 (1
100, 기본 3). dense_prefetch_limit: Dense prefetch 개수 (12000, 기본 200). sparse_prefetch_limit: Sparse prefetch 개수 (1~2000, 기본 300). fallback_min_score: 폴백 경로에서만 적용.
// 요청 (limit: 1~100 기본 3, dense/sparse_prefetch_limit: 1~2000 기본 200/300, fallback_min_score: 폴백만)
{
"query_text": "분위기 좋고 데이트하기 좋은",
"restaurant_id": 1,
"limit": 5,
"dense_prefetch_limit": 200,
"sparse_prefetch_limit": 300,
"fallback_min_score": 0.2
}
// 응답
{
"results": [
{"review": {"id": 1, "restaurant_id": 1, "content": "분위기 좋고 데이트하기 딱이에요", ...}, "score": 0.89},
{"review": {"id": 2, "restaurant_id": 1, "content": "연인과 오기 좋은 분위기", ...}, "score": 0.85}
],
"total": 2
}POST /api/v1/vector/upload
// 요청 (reviews: id, restaurant_id, content, created_at 필수 / restaurants: id 선택, name, reviews)
{
"reviews": [
{"id": 1, "restaurant_id": 1, "content": "맛있어요", "created_at": "2025-02-17T17:02:45.366789"}
],
"restaurants": [
{"id": 1, "name": "테스트 음식점"}
]
}
// 응답
{
"message": "데이터 업로드 완료",
"points_count": 100,
"collection_name": "reviews_collection"
}API별 요청/응답에 사용되는 Pydantic 모델(DTO)과 필드를 정리합니다. 정의 위치: src/models.py.
(라우터: src/api/routers/llm.py, sentiment.py, vector.py 기준으로 검증됨.)
| API | Request DTO | Response DTO |
|---|---|---|
POST /api/v1/llm/comparison |
ComparisonRequest |
ComparisonResponse (또는 Dict, 에러/스킵 시) |
POST /api/v1/llm/comparison/batch |
ComparisonBatchRequest |
ComparisonBatchResponse |
POST /api/v1/llm/summarize |
SummaryRequest |
SummaryDisplayResponse (debug 필드 선택) |
POST /api/v1/llm/summarize/batch |
SummaryBatchRequest |
SummaryBatchResponse |
POST /api/v1/sentiment/analyze |
SentimentAnalysisRequest |
SentimentAnalysisDisplayResponse (debug=false) / SentimentAnalysisResponse (debug=true) |
POST /api/v1/sentiment/analyze/batch |
SentimentAnalysisBatchRequest |
SentimentAnalysisBatchResponse |
POST /api/v1/vector/search/similar |
VectorSearchRequest |
VectorSearchResponse |
POST /api/v1/vector/upload |
VectorUploadRequest |
VectorUploadResponse |
| DTO | 필드 | 타입 | 설명 |
|---|---|---|---|
| ComparisonRequest | restaurant_id | int | 타겟 레스토랑 ID |
| restaurant_name | str? | 레스토랑 이름 (응답에 그대로 반환, 선택) | |
| ComparisonDetail | category | str | "service" | "price" |
| lift_percentage | float | (단일−전체)/전체×100 | |
| ComparisonResponse | restaurant_id | int | 레스토랑 ID |
| restaurant_name | str? | 레스토랑 이름 (payload 또는 요청에서, 선택) | |
| comparisons | List[ComparisonDetail] | 비교 항목 리스트 | |
| total_candidates | int | 근거 후보 총 개수 | |
| validated_count | int | 검증 통과 개수 | |
| category_lift | Dict[str, float]? | service/price lift | |
| comparison_display | List[str]? | 표시 문장 리스트 | |
| debug | DebugInfo? | 디버그 정보 | |
| ComparisonBatchRequest | restaurants | List[Dict] | [{ restaurant_id }, ...]. 각 항목에 restaurant_id 필수 |
| all_average_data_path | str? | 전체 평균·표본용 데이터 파일 경로. 미지정 시 Config 사용 | |
| ComparisonBatchResponse | results | List[ComparisonResponse] | 레스토랑별 비교 결과 |
| DTO | 필드 | 타입 | 설명 |
|---|---|---|---|
| SummaryRequest | restaurant_id | int | 레스토랑 ID |
| restaurant_name | str? | 레스토랑 이름 (응답에 그대로 반환, 선택) | |
| limit | int | 카테고리(service/price/food)당 검색·요약 최대 리뷰 수 (1~100, 기본 10) | |
| SummaryBatchRequest | restaurants | List[Dict] | [{ restaurant_id }, ...] |
| limit | int | 카테고리당 검색·요약 최대 리뷰 수, 전체 공통 (1~100, 기본 10) | |
| CategorySummary | summary | str | 카테고리 요약 |
| bullets | List[str] | 핵심 포인트 | |
| evidence | List[Dict] | [{ review_id, snippet, rank }] | |
| SummaryDisplayResponse | restaurant_id | int | 레스토랑 ID |
| restaurant_name | str? | 레스토랑 이름 (payload 또는 요청에서, 선택) | |
| overall_summary | str | 전체 요약 | |
| categories | Dict[str, CategorySummary]? | service/price/food 요약 | |
| debug | DebugInfo? | X-Debug: true 시에만 | |
| SummaryBatchResponse | results | List[SummaryDisplayResponse] | 레스토랑별 요약 |
| DTO | 필드 | 타입 | 설명 |
|---|---|---|---|
| SentimentAnalysisRequest | restaurant_id | int | 레스토랑 ID (벡터 DB에서 리뷰 조회) |
| restaurant_name | str? | 레스토랑 이름 (응답에 그대로 반환) | |
| SentimentAnalysisDisplayResponse | restaurant_id | int | 레스토랑 ID |
| restaurant_name | str? | 레스토랑 이름 | |
| positive_ratio | int | 긍정 비율 (%) | |
| negative_ratio | int | 부정 비율 (%) | |
| SentimentAnalysisResponse | (Display 필드) + | ||
| restaurant_name | str? | 레스토랑 이름 | |
| positive_count, negative_count, neutral_count | int | 개수 | |
| total_count | int | 전체 리뷰 수 | |
| neutral_ratio | int | 중립 비율 (%) | |
| debug | DebugInfo? | 디버그 정보 | |
| SentimentRestaurantBatchInput | restaurant_id | int | 레스토랑 ID (벡터 DB에서 리뷰 조회) |
| restaurant_name | str? | 레스토랑 이름 (응답에 그대로 반환) | |
| SentimentAnalysisBatchRequest | restaurants | List[SentimentRestaurantBatchInput] | 레스토랑 ID 리스트 (각 항목 리뷰는 벡터 DB에서 조회) |
| SentimentAnalysisBatchResponse | results | List[SentimentAnalysisResponse] | 레스토랑별 결과 |
| DTO | 필드 | 타입 | 설명 |
|---|---|---|---|
| VectorSearchRequest | query_text | str | 검색 쿼리 |
| restaurant_id | int? | 레스토랑 필터 | |
| limit | int | 반환할 최대 개수 (1~100, 기본 3). 하이브리드: RRF 후 이 개수만 반환. | |
| fallback_min_score | float | 폴백(Dense만) 경로에서만 적용 (0.0~1.0, 기본 0.2). 하이브리드 경로에는 미적용. | |
| dense_prefetch_limit | int | 하이브리드 Dense prefetch 개수 (1~2000, 기본 200). | |
| sparse_prefetch_limit | int | 하이브리드 Sparse prefetch 개수 (1~2000, 기본 300). | |
| VectorSearchResult | review | ReviewModel | 리뷰 payload |
| score | float | 유사도 점수 | |
| VectorSearchResponse | results | List[VectorSearchResult] | 검색 결과 |
| total | int | 총 개수 | |
| VectorUploadReviewInput | id | int | 리뷰 ID (필수) |
| restaurant_id | int | 레스토랑 ID | |
| content | str | 리뷰 내용 | |
| created_at | datetime | 리뷰 작성 시각 (ISO 8601, 필수) | |
| VectorUploadRestaurantInput | id | int? | 레스토랑 ID (선택) |
| name | str | 레스토랑 이름 | |
| reviews | List[VectorUploadReviewInput] | 중첩 리뷰 (선택) | |
| VectorUploadRequest | reviews | List[VectorUploadReviewInput] | 리뷰 리스트 |
| restaurants | List[VectorUploadRestaurantInput]? | 레스토랑 리스트 | |
| VectorUploadResponse | message | str | 메시지 |
| points_count | int | 업로드된 포인트 수 | |
| collection_name | str | 컬렉션 이름 |
| DTO | 필드 | 타입 | 설명 |
|---|---|---|---|
| ErrorResponse | code | int | HTTP status code |
| message | str | 에러 메시지(요약) | |
| details | Any? | 에러 상세(검증 오류 배열/추가 정보 등) | |
| request_id | str | 요청 추적용 ID (X-Request-Id와 동일) |
|
| DebugInfo | request_id | str? | 요청 ID |
| processing_time_ms | float? | 처리 시간 (ms) | |
| tokens_used | int? | 사용 토큰 수 | |
| model_version | str? | 모델 버전 | |
| warnings | List[str]? | 경고 메시지 | |
| ReviewModel | id | int | 리뷰 ID (필수) |
| restaurant_id | int | 레스토랑 ID | |
| content | str | 리뷰 내용 |
모든 API의 4xx/5xx 및 validation 오류는 아래 포맷으로 반환됩니다.
{
"code": 422,
"message": "Validation error",
"details": [
{"loc": ["body", "field"], "msg": "...", "type": "..."}
],
"request_id": "..."
}-
request_id
- 요청 헤더에
X-Request-Id(또는X-Request-ID)를 주면 해당 값 사용 - 없으면 서버가 UUID를 생성
- 응답 헤더
X-Request-Id에도 동일 값이 포함됨
- 요청 헤더에
-
Validation 오류(422):
message="Validation error",details=errors[] -
HTTP 오류(4xx/5xx):
message는detail문자열,details는 원본detail -
Unhandled(500):
message="Internal server error",details=null
선택(optional) 필드는 없어도 요청/응답이 성립하지만, 있으면 그 값이 동작에 반영됩니다.
요청(Request) 쪽
| DTO | 필드 | 역할 |
|---|---|---|
| VectorSearchRequest | restaurant_id | 검색 범위 제한. 값을 넣으면 해당 레스토랑 리뷰만 검색, None이면 전체 검색. |
| VectorUploadReviewInput | id | 필수. 업로드 시 리뷰 식별·upsert 시 "어느 포인트를 갱신할지" 구분. |
| VectorUploadRestaurantInput | id | 레스토랑 식별·대표 벡터 매핑용. 없으면 이름만으로 처리. |
| VectorUploadRequest | restaurants | 레스토랑 메타 전달. 리뷰만 올릴 수도 있고, 넣으면 레스토랑 대표 벡터·이름 매핑에 사용. |
| ErrorResponse | details | 422 등 검증/에러 시 상세(필드별 오류 등). 없으면 null. |
응답(Response) 쪽
| DTO | 필드 | 역할 |
|---|---|---|
| DebugInfo (전부 선택) | request_id, processing_time_ms, tokens_used, model_version, warnings |
디버그/운영용. X-Debug: true 또는 debug=true일 때만 응답에 포함. 요청 추적·지연·토큰·경고 확인용. |
| SentimentAnalysisResponse | debug | 감성 분석 응답에 디버그 블록 추가. |
| SummaryDisplayResponse | categories | 카테고리별 요약(service/price/food). 파이프라인 결과가 없으면 None. |
| SummaryDisplayResponse | debug | 요약 응답에 디버그 블록 추가. |
| ComparisonResponse | category_lift | service/price별 lift 퍼센트. 통계·LLM 설명용. 없으면 None. |
| ComparisonResponse | comparison_display | 표시용 문장(퍼센트+해석, 표본 수별 톤). 없으면 None. |
| ComparisonResponse | debug | 비교 응답에 디버그 블록 추가. |
파이프라인 동작·흐름·Config는 PIPELINE/PIPELINE_OVERVIEW.md 를 참고하세요.