콘텐츠로 이동

벡터 스토어 패스스루: 설계 기록

이 문서는 /v1/vector_stores 패스스루의 설계를 기록합니다 (이슈 #2412). 경로는 src/http/handlers/vector_stores.rs와 src/services/vector_stores.rs에 구현되어 있으며, 파인튜닝 패스스루와 공유하는 버퍼링 전달 계층은 src/services/json_passthrough.rs에 있습니다.

구현된 표면

메서드 경로 동작
POST /v1/vector_stores 선택된 벡터 스토어 지원 백엔드 하나에 스토어를 생성하고 호출자에게 바인딩합니다
GET /v1/vector_stores 선택된 벡터 스토어 지원 백엔드 하나에서 호출자의 스토어 목록을 조회합니다
GET /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 조회합니다
POST /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 수정합니다
DELETE /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 삭제하고 바인딩을 삭제합니다
POST /v1/vector_stores/{store_id}/files 스토어에 프로바이더 파일을 첨부합니다
GET /v1/vector_stores/{store_id}/files 스토어에 첨부된 파일 목록을 조회합니다
GET /v1/vector_stores/{store_id}/files/{file_id} 첨부된 파일 하나를 조회합니다
DELETE /v1/vector_stores/{store_id}/files/{file_id} 첨부된 파일 하나를 제거합니다
POST /v1/vector_stores/{store_id}/search 스토어를 검색합니다

각 응답은 백엔드의 상태 코드, 콘텐츠 타입, 본문을 반환합니다. 목록 본문은 바이트 단위 그대로 전달하는 예외이며, data 배열에는 호출자 자신의 스토어만 남습니다(스토어 바인딩 참고).

정책 계층

벡터 스토어 호출은 채팅 디스패치가 아닙니다. 라우터는 모든 호출에 API 라우트 그룹의 인증, API 키 scope, 전역, 클라이언트별, 키별 속도 제한을 적용하고, 생성과 목록에는 백엔드별 디스패치 한도를 청구하며, completion 페이로드를 전제로 하는 계층은 의도적으로 실행하지 않습니다.

  • 요청 파라미터 정책: 적용하지 않습니다. 이 정책은 완성도 샘플링과 도구 필드를 재작성하는데, 스토어 페이로드에는 그런 필드가 없습니다.
  • 가드레일: 적용하지 않습니다. 입력 및 출력 게이트는 메시지와 완성 텍스트를 판정하며, 라우터는 인덱싱된 콘텐츠를 보지 않습니다.
  • 응답 캐싱: 적용하지 않습니다. 캐시된 목록, 생성, 검색 응답은 상태 변경을 숨깁니다.
  • 모델별 생성 예산: 청구하지 않습니다. 스토어 호출은 토큰을 생성하지 않습니다.
  • 측정: non-completion 요청으로 적용합니다. 응답에 usage 블록이 없으므로, 라우터는 요청 수, 지연 시간, 키 및 백엔드 귀속을 0 토큰(token_source: none)으로 기록합니다.

JSON 본문이 있는 경로에는 1MiB 본문 상한을 명시합니다. 생성 본문에는 이름, 메타데이터, 만료 정책이 담기고, 첨부 본문에는 프로바이더 파일 ID와 청킹 전략이 담기며, 검색 본문에는 질의와 필터가 담깁니다. 이 상한은 라우터가 전달 전에 임의 크기의 페이로드를 버퍼링하지 않게 합니다.

설계

  • 백엔드 범위: http/https의 OpenAI-wire 타입만 해당합니다(openai, generic). Azure OpenAI는 Vector Stores 엔드포인트 경로가 다르고 api-version이 필수이므로 제외합니다. 이 API를 서빙하지 않는 백엔드는 선택 전에 후보 집합에서 제외되며, 지원 백엔드가 하나도 없는 요청은 라우터 수준 404가 아니라 구조화된 400 vector_stores_not_supported를 반환합니다.
  • 백엔드 선택: 스토어 호출은 모델을 지정하지 않으므로 클라이언트의 x-backend 헤더를 사용하지 않습니다. 생성은 호출자가 접근할 수 있는 모든 후보에 설정된 선택 전략을 적용해 지원 백엔드 하나를 고르고, 목록도 같은 방식으로 고릅니다. 라이프사이클 호출에 요청별 핀을 적용하면 스토어를 소유하지 않은 백엔드로 전송될 수 있으므로, 스토어가 존재한 뒤에는 바인딩이 기준입니다.
  • 공유 전달 계층: 파인튜닝 패스스루는 이미 JSON 요청을 버퍼링하고 백엔드 응답을 전달합니다. 이제 두 표면은 자격 증명, 조직 ID, 요청 확장, 해석된 사용자 프로필, 라우터 홉 수, 읽기 상한, 서킷 정산에 json_passthrough::forward를 함께 사용합니다. 선택 규칙, 바인딩 테이블, 정책 결정은 표면별로 유지합니다.

스토어 바인딩

생성 응답은 스토어 ID를 읽을 수 있을 만큼만 버퍼링합니다. 그다음 라우터는 (주체, 스토어 ID) -> 백엔드를 AppState별 LRU 테이블에 기록합니다. 이는 파인튜닝 작업 테이블과 Responses 패스스루 테이블(services::responses::PassthroughRoutes, 이슈 #1941)과 같은 형태입니다. 모든 스토어 범위 호출은 이 바인딩을 찾은 뒤 실행 중인 설정과 호출자의 allowed_backends에 대해 다시 검사합니다. 알 수 없거나 다른 주체의 ID는 404 vector_store_not_found를 반환하며, 업스트림으로 아무것도 전송하지 않습니다.

GET /v1/vector_stores도 같은 소유권 규칙을 적용합니다. 프로바이더의 목록에는 같은 자격 증명으로 이 라우터를 통해 다른 주체가 만든 스토어를 포함해 백엔드 계정의 모든 스토어가 담기므로, 라우터는 페이지를 제공한 백엔드에서 이 호출자에게 바인딩한 ID의 항목만 남깁니다. 따라서 이 라우터가 바인딩하지 않은 스토어(재시작 전이나 다른 라우터 프로세스에서 생성한 스토어)는 목록에 나오지 않으며, 같은 스토어에 대한 조회, 수정, 삭제가 not-found를 반환하는 것과 일치합니다. has_more, first_id, last_id는 필터링된 목록이 아니라 백엔드 페이지를 설명하므로, 호출자는 has_more가 false가 될 때까지 페이지를 넘깁니다.

바인딩 테이블은 프로세스 메모리에 있습니다. 라우터를 재시작하기 전에 생성한 스토어는 백엔드에 남아 있지만, 이 라우터는 어느 백엔드를 호출해야 하는지 더 이상 알지 못하므로 not-found를 반환합니다. 이 테이블은 모든 주체가 공유하는 10,000개 항목 LRU 하나이므로, 테이블이 담을 수 있는 것보다 많은 스토어를 생성한 주체는 다른 주체의 바인딩을 밀어낼 수 있습니다. 그 스토어는 프로바이더에 남아 있지만 이 라우터는 not-found를 반환하고 목록에서도 숨깁니다. 영향은 노출이 아니라 가용성입니다. 주체별 할당량은 후속 작업입니다.

첨부 파일

첨부 본문은 프로바이더 파일 ID를 지정합니다. 라우터 자체 /v1/files는 업로드를 로컬 디스크(files.storage_path)에 저장하고 추론 요청에 해석해 넣을 뿐, 프로바이더에 업로드하지 않습니다. 따라서 라우터 파일 ID는 프로바이더 파일 ID가 아니며, 이 설계에는 프로바이더 업로드나 변환 단계가 없습니다. 파인튜닝 작업의 training_file에도 같은 경계가 적용됩니다.

스토어 바인딩이 첨부를 통제합니다. 호출자는 자신이 생성한 스토어에만 파일을 첨부하고, 그 스토어의 파일을 조회하거나 제거할 수 있습니다. 프로바이더 파일 ID 자체는 불투명하므로, 라우터는 어느 주체가 그 파일을 업로드했는지 추적하지 않습니다. 따라서 여러 테넌트가 하나의 프로바이더 자격 증명을 공유하면, 유출된 프로바이더 파일 ID를 다른 테넌트의 스토어에 첨부해 검색으로 읽을 수 있습니다. API 페이지는 서로의 파일을 볼 수 없어야 하는 테넌트끼리 하나의 자격 증명을 공유하지 말라고 이 점을 명시합니다.

남은 열린 질문

  • 바인딩 영속화: 재시작 후 바인딩 테이블을 어떻게 저장하고 복구할지. 메모리 테이블은 프로세스 수명 동안만 소유권을 검증합니다.
  • 다중 백엔드 목록: GET /v1/vector_stores가 백엔드 하나를 선택하는 대신 모든 지원 백엔드의 페이지를 병합해야 하는지. 병합은 정규화된 스토어 스키마를 만들어야 하며, 이슈는 라우터가 그렇게 하지 말라고 요청합니다.
  • 프로바이더 파일 소유권: 라우터가 자체 파일 ID를 프로바이더 업로드에 매핑해야 하는지, 아니면 각 프로바이더 파일 ID의 소유 주체를 추적해야 하는지. 둘 다 이번 패스스루에 포함되지 않습니다.