Files
exAichatbot_agent/exAiChatBot-chatbot2.0-agent/VECTORDB_ADMIN_API.md
T
Macbook 4b86b2a660 Agent 2.0 exdev 서버 배포 스택
- server-dev start/stop/deploy 및 Gitea push 자동 배포
- local-dev 로컬 개발 환경

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 22:57:30 +09:00

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. 동작/주의 사항 (프론트 개발 시)

  1. 추가/삭제는 즉시 반영됩니다. 같은 Qdrant를 실시간 /ask가 공유하므로, 추가/삭제 후 챗봇 검색에 바로 적용됩니다(재시작 불필요).
  2. 삭제/수정 키는 id(결정적 UUID)입니다. 목록/검색 응답의 id를 그대로 쓰세요.
  3. 수정(update): PUT /api/points/{id} 사용. 전달한 필드만 갱신되고 질문은 자동 재임베딩됩니다. 응답의 updated(새 id)와 moved 플래그를 확인해 화면을 갱신하세요(대개 moved:false로 id 유지).
  4. 페이지네이션은 커서 방식입니다. next_offset을 다음 요청 offset으로 넘기세요(페이지 번호 점프는 미지원).
  5. 검색 score는 코사인 유사도(0~1)이며 검색 응답에만 있습니다. 목록 응답에는 없습니다.
  6. 인증 없음 — 프론트에서 접근제어가 필요하면 별도 협의.

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 /docs Swagger 주소 (실시간 스키마 확인)
  • 데이터 필드 의미 (q/a/category/source/source_id/url/score)
  • 삭제/수정 키 = id(결정적 UUID) 규칙
  • CORS 허용 정책 / 인증 유무(현재 없음)
  • 수정은 PUT /api/points/{id} (응답 updated/moved 처리 안내)
  • 필터(category/source)는 정확일치만 지원 (부분검색 아님)
  • (향후) 실패질문 목록·파일 업로드 기능 추가 예정 여부