4b86b2a660
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
10 KiB
벡터DB 큐레이션 API 명세 (웹 개발자 핸드오프)
벡터DB(Qdrant)를 웹에서 검색/조회/삭제/추가하기 위한 백엔드 API입니다.
프론트엔드는 이 명세에 맞춰 개발하면 됩니다. (백엔드: scripts/admin_service.py)
1. 접속 정보
| 항목 | 값 |
|---|---|
| Base URL | http://<WAS서버>:28013 (포트는 .env의 ADMIN_PORT, 기본 28013) |
| 형식 | JSON (요청/응답 모두 application/json) |
| 인증 | 현재 없음 (내부망 전용). 외부 노출 시 별도 인증 필요 |
| CORS | 기본 전체 허용(*). 제한하려면 ADMIN_CORS_ORIGINS="http://host:port,..." |
| 문서 | GET /docs (FastAPI 자동 Swagger UI) |
같은 서버에 레퍼런스용 단일 페이지 UI(
GET /)가 이미 떠 있습니다. 동작 확인/참고용이며, 실제 프론트는 아래 API로 자유롭게 구현하면 됩니다.
2. 데이터 모델 (point = FAQ 1건)
검색/조회 응답의 항목(item) 공통 형태:
{
"id": "3d6f...e9", // Qdrant point ID (결정적 UUID). 삭제/조회의 키
"q": "하이패스 단말기는 어디서 구입하나요?", // 질문
"a": "하이패스 단말기는 ...", // 답변
"category": "하이패스 이용", // 분류 (nullable)
"source": "hipass_faq", // 출처 (nullable)
"source_id": "ser_no_1", // 원본 식별자 (nullable)
"url": "https://...", // 공식 FAQ 링크 (nullable)
"score": 0.87 // 검색 결과에서만 포함 (유사도, 0~1)
}
ID 규칙 (중요)
id는source:source_id(없으면 질문 텍스트) 기반 결정적 UUID입니다.- 같은 FAQ는 항상 같은
id→ 삭제/수정 시 이id를 키로 사용하면 됩니다. - 같은 항목을 다시 추가하면 덮어쓰기(upsert) 되어 중복이 생기지 않습니다.
3. 엔드포인트
3.1 통계
GET /api/stats
응답:
{ "vector_store": "qdrant", "count": 3421 }
3.2 의미 검색
POST /api/search
Content-Type: application/json
요청:
{ "query": "단말기 어디서 사요?", "top_k": 20, "threshold": null, "category": null, "source": null }
| 필드 | 필수 | 설명 |
|---|---|---|
query |
✅ | 검색어 (1~500자) |
top_k |
✕ | 결과 수 (기본 20, 1~100) |
threshold |
✕ | 최소 유사도 (미지정 시 전체) |
category |
✕ | 분류 정확일치 필터 |
source |
✕ | 출처 정확일치 필터 |
응답:
{
"count": 2,
"items": [
{ "id": "3d6f...", "q": "...", "a": "...", "category": "...", "source": "...", "source_id": "...", "url": "...", "score": 0.87 }
]
}
3.2-b 키워드(문자열 포함) 검색
POST /api/keyword-search
Content-Type: application/json
의미 검색과 달리 q/a 텍스트에 입력 문자열이 실제 포함된 항목만 반환합니다(부분일치, 대소문자 무시). DB 전체를 스캔합니다.
요청:
{ "keyword": "1588-2504", "field": "both", "category": null, "source": null, "limit": 50 }
| 필드 | 필수 | 설명 |
|---|---|---|
keyword |
✅ | 포함 검색할 문자열 (1~200자) |
field |
✕ | both(기본) / q / a — 검색 대상 |
category,source |
✕ | 정확일치 필터 |
limit |
✕ | 최대 결과 수 (기본 50, 1~500) |
응답:
{
"count": 12,
"scanned": 3421,
"exhausted": true,
"items": [ { "id": "...", "q": "...", "a": "...", "category": "...", "source": "...", "source_id": "...", "url": "..." } ]
}
scanned: 스캔한 항목 수,exhausted: 전체를 끝까지 스캔했는지(false면limit에서 끊겨 더 있을 수 있음 →limit을 올리거나 필터로 좁히기).- score는 없습니다(유사도가 아니라 포함 여부).
3.2-c 카테고리 목록
GET /api/categories
등록된 category 값과 건수를 반환합니다(분류 필터/트리용, 전체 스캔).
{ "count": 3, "items": [ { "category": "하이패스 이용", "count": 120 }, { "category": "미납통행료", "count": 80 } ] }
3.3 목록 (페이지네이션 + 필터)
GET /api/points?limit=20&offset=<next_offset>&category=하이패스 이용&source=hipass_faq
| 파라미터 | 설명 |
|---|---|
limit |
페이지 크기 (기본 50) |
offset |
다음 페이지 커서. 첫 페이지는 생략, 이후 응답의 next_offset 사용 |
category |
분류 정확일치 필터 (선택) |
source |
출처 정확일치 필터 (선택) |
응답:
{
"count": 20,
"items": [ { "id": "...", "q": "...", "a": "...", "category": "...", "source": "...", "source_id": "...", "url": "..." } ],
"next_offset": "오리피셜커서값 또는 null"
}
next_offset이null이면 마지막 페이지입니다. (커서 기반 페이지네이션 — 페이지 번호가 아니라 커서를 넘김)
3.4 단건 조회
GET /api/points/{id}
응답: item 1건. 없으면 404.
3.5 단건 수정
PUT /api/points/{id}
Content-Type: application/json
요청 (전달한 필드만 갱신):
{ "q": "수정된 질문", "a": "수정된 답변", "category": "분류", "url": "링크" }
| 필드 | 필수 | 설명 |
|---|---|---|
q |
✕ | 질문 (바뀌면 자동 재임베딩) |
a |
✕ | 답변 |
category |
✕ | 분류 |
url |
✕ | 링크 |
응답:
{ "updated": "point id", "moved": false }
source/source_id는 유지되어 보통 같은 id로 덮어쓰기됩니다.- 단,
source_id가 없어 질문(q) 기반으로 id가 만들어진 항목은 q 수정 시 id가 바뀝니다. 이 경우 새 id로 추가 후 옛 id를 삭제하며"moved": true로 응답합니다. 프론트는 응답의updated(새 id)로 화면 상태를 갱신하세요.
3.6 단건 삭제
DELETE /api/points/{id}
응답:
{ "deleted": "3d6f..." }
없으면 404.
3.7 일괄 삭제
POST /api/points/delete-batch
Content-Type: application/json
요청:
{ "ids": ["3d6f...", "a17c..."] }
응답:
{ "deleted": 2 }
3.8 추가 (텍스트 → 임베딩 → upsert)
POST /api/points
Content-Type: application/json
요청:
{ "q": "질문", "a": "답변", "category": "분류(선택)", "url": "링크(선택)" }
| 필드 | 필수 | 설명 |
|---|---|---|
q |
✅ | 질문 |
a |
✅ | 답변 |
category |
✕ | 분류 |
url |
✕ | 링크 |
source |
✕ | 기본 admin_manual |
source_id |
✕ | 미지정 시 자동 생성 |
응답:
{ "created": "생성된 point id", "source_id": "manual_ab12cd34" }
추가 시 질문이 임베딩 서버(TEI)로 벡터화되어 검색 대상이 됩니다. 임베딩 실패 시
502.
4. 에러 형식
FastAPI 표준 형식입니다.
{ "detail": "에러 메시지" }
| 코드 | 의미 |
|---|---|
| 400 | 잘못된 요청 / Qdrant 아닌 스토어에서 호출 |
| 404 | 해당 id 없음 |
| 422 | 요청 본문 검증 실패(필수 누락 등) |
| 502 | 임베딩 서버 호출 실패 |
4-b. 두 가지 검색의 차이 (프론트에서 분리 제공 권장)
| 검색 | 엔드포인트 | 동작 | 용도 |
|---|---|---|---|
| 의미 검색 | POST /api/search |
임베딩 유사도(뜻이 비슷) — 챗봇이 실제 찾는 것과 동일 | "이 질문에 챗봇이 뭘 답할까" 확인, 유사/중복 후보 탐색 |
| 키워드 검색 | POST /api/keyword-search |
문자열 실제 포함(부분일치) | "이 단어/번호가 든 FAQ 전부 찾기"(일괄 수정 등) |
의미 검색은 글자가 달라도 뜻이 가까우면 나오고, 키워드 검색은 그 문자열이 들어간 것만 정확히 나옵니다.
5. 동작/주의 사항 (프론트 개발 시)
- 추가/삭제는 즉시 반영됩니다. 같은 Qdrant를 실시간
/ask가 공유하므로, 추가/삭제 후 챗봇 검색에 바로 적용됩니다(재시작 불필요). - 삭제/수정 키는
id(결정적 UUID)입니다. 목록/검색 응답의id를 그대로 쓰세요. - 수정(update):
PUT /api/points/{id}사용. 전달한 필드만 갱신되고 질문은 자동 재임베딩됩니다. 응답의updated(새 id)와moved플래그를 확인해 화면을 갱신하세요(대개moved:false로 id 유지). - 페이지네이션은 커서 방식입니다.
next_offset을 다음 요청offset으로 넘기세요(페이지 번호 점프는 미지원). - 검색 score는 코사인 유사도(0~1)이며 검색 응답에만 있습니다. 목록 응답에는 없습니다.
- 인증 없음 — 프론트에서 접근제어가 필요하면 별도 협의.
6. 빠른 테스트 (curl)
# 통계
curl http://<WAS서버>:28013/api/stats
# 검색
curl -X POST http://<WAS서버>:28013/api/search \
-H "Content-Type: application/json" \
-d '{"query":"단말기 구입","top_k":5}'
# 목록
curl "http://<WAS서버>:28013/api/points?limit=10"
# 추가
curl -X POST http://<WAS서버>:28013/api/points \
-H "Content-Type: application/json" \
-d '{"q":"테스트 질문","a":"테스트 답변","category":"기타"}'
# 수정
curl -X PUT http://<WAS서버>:28013/api/points/<id> \
-H "Content-Type: application/json" \
-d '{"a":"수정된 답변"}'
# 키워드(문자열 포함) 검색
curl -X POST http://<WAS서버>:28013/api/keyword-search \
-H "Content-Type: application/json" \
-d '{"keyword":"1588-2504","field":"a","limit":100}'
# 필터 검색/목록
curl -X POST http://<WAS서버>:28013/api/search \
-H "Content-Type: application/json" \
-d '{"query":"단말기","source":"hipass_faq"}'
curl "http://<WAS서버>:28013/api/points?limit=10&category=하이패스 이용"
# 삭제
curl -X DELETE http://<WAS서버>:28013/api/points/<id>
7. 웹 개발자에게 함께 전달할 정보 체크리스트
- Base URL / 포트 (
http://<WAS서버>:28013) - 이 문서(엔드포인트·스키마·에러·페이지네이션 방식)
GET /docsSwagger 주소 (실시간 스키마 확인)- 데이터 필드 의미 (
q/a/category/source/source_id/url/score) - 삭제/수정 키 =
id(결정적 UUID) 규칙 - CORS 허용 정책 / 인증 유무(현재 없음)
- 수정은
PUT /api/points/{id}(응답updated/moved처리 안내) - 필터(
category/source)는 정확일치만 지원 (부분검색 아님) - (향후) 실패질문 목록·파일 업로드 기능 추가 예정 여부