Skip to content

RagService 오프라인 B2B 배포 ​

목적과 구성 ​

RagService의 온프레미스 스택은 PostgreSQL 16, pgvector 0.7.0, pg_bigm, mecab-ko textsearch_ko, 로컬 BGE-M3 임베딩 API, Ollama 또는 vLLM, 그리고 독립 관리자 포탈 API를 하나의 Compose 계약으로 배포한다. 운영 환경은 외부 인터넷을 호출하지 않으며, 모델·extension source·base image는 연결된 staging 환경에서 검증·반입한다.

서비스역할외부 네트워크 필요 여부
postgres1024차원 vector, pg_bigm, mecab-ko FTS, FORCE RLS실행 시 불필요
embedding-apiread-only BGE-M3 HuggingFace cache를 통한 OpenAI 호환 /v1/embeddings실행 시 불필요
ollama로컬 Llama3/Solar 계열 chat API실행 시 불필요
vllm선택적 OpenAI 호환 GPU chat API실행 시 불필요
ragcenter-apiDirectory token 검증, SET LOCAL RLS, 검색/chat API, 관리자 UI실행 시 불필요

연결된 staging 환경에서의 반입 준비 ​

infra/postgres/vendor/는 PostgreSQL 확장 source bundle을 보관하는 staging 전용 경로이다. scripts/prepare_offline_postgres_bundle.sh를 연결된 승인 환경에서 실행해 pg_bigm, mecab-ko runtime/dictionary, textsearch_ko, 고정 pgvector/pgvector:0.7.0-pg16 image tarball을 확보한다. 출력된 SHA-256을 내부 artifact manifest에 기록한 다음, 승인된 반입 절차로 폐쇄망 서버에 전달한다.

bash
bash scripts/prepare_offline_postgres_bundle.sh
# isolated server
sudo docker load -i infra/postgres/vendor/pgvector-0.7.0-pg16.tar

BGE-M3 및 LLM은 staging host에서 각 runtime cache에 다운로드하고, .env.offline의 HF_MODEL_CACHE_DIR, OLLAMA_MODEL_CACHE_DIR로 지정한 디렉터리를 폐쇄망 서버에 read-only 또는 managed volume으로 반입한다. model cache 경로가 비어 있으면 embedding container가 시작에 실패하도록 설계되어 온라인 fallback을 만들지 않는다.

기동 ​

bash
cp .env.offline.example .env.offline
# deployment secret manager 또는 root-only permission으로 비밀값 입력
sudo docker compose --env-file .env.offline -f docker-compose.offline.yml build postgres ragcenter-api
sudo docker compose --env-file .env.offline -f docker-compose.offline.yml --profile embedding --profile ollama up -d
# vLLM을 사용할 때만
sudo docker compose --env-file .env.offline -f docker-compose.offline.yml --profile vllm up -d

GPU host는 NVIDIA driver와 NVIDIA Container Toolkit을 먼저 설치해야 한다. Compose의 GPU profile은 gpus: all을 사용하며, GPU가 없는 개발 host에서는 embedding-api, ollama, vllm profile을 실행하지 않고 기존 CPU inference endpoint를 PRIVATE_EMBEDDING_URL, PRIVATE_LLM_URL로 지정한다.

실측 검증(CPU host, 게이트웨이 조합) ​

오프라인 번들 없이도 위 조합을 손으로 재현해 전 구간을 확인할 수 있다(실제로 이 순서로 검증했다).

bash
# 1) 검색 스택 이미지 — vendor/ tarball 4종으로 한 번만 빌드한다.
docker build -f infra/postgres/Dockerfile -t ragcenter-postgres:16-pgvector0.7-pgbigm1.2 infra/postgres

# 2) 기동 — 확장·RLS·검색 함수는 init SQL 이 만든다(빈 data dir 최초 1회).
docker run -d --name ragcenter-pg -e POSTGRES_DB=ragcenter -e POSTGRES_USER=<admin> \
  -e POSTGRES_PASSWORD=<pw> -e RAG_APP_PASSWORD=<pw> \
  -v <pgdata>:/var/lib/postgresql/data -v $PWD/infra/postgres/init:/docker-entrypoint-initdb.d:ro \
  -p 55432:5432 ragcenter-postgres:16-pgvector0.7-pgbigm1.2
docker run -d --name embedding-api -e EMBEDDING_DEVICE=cpu -e EMBEDDING_MODEL_PATH=/models/bge-m3 \
  -v <hf cache>/bge-m3:/models/bge-m3:ro -p 8081:8081 <embedding image>   # compose 의 embedding-api

# 3) 임베딩 endpoint 는 admin settings 가 소유한다(기본값은 compose DNS) — 기동 직후 한 번 PUT 한다.
curl -X PUT -H "Authorization: Bearer <admin JWT>" -H 'content-type: application/json' \
  -d '{"embedding":{"provider":"openai","endpoint":"http://embedding-api:8081","model":"bge-m3","dimensions":1024}}' \
  http://<ragcenter-host>:8080/api/v1/admin/settings

# 4) 수집 → 검색 → 게이트웨이 어댑터 순서로 확인한다.
curl -X POST -H "Authorization: Bearer <user JWT>" -H 'content-type: application/json' \
  -d '{"portalId":"pc","sourceDocumentId":"<uuid>","sourceVersion":"v1","tenantId":"<uuid>",
       "organizationIds":["<org uuid>"],"securityLevel":"INTERNAL","classification":"사내 규정",
       "title":"...","contentLocator":"http://<file-host>/doc.html","checksum":"<sha256 hex>",
       "contentType":"text/html","registrant":{"id":"u1","departmentCode":"HR"},"sizeBytes":1234}' \
  http://<ragcenter-host>:8080/api/v1/documents
curl -X POST -H "Authorization: Bearer <user JWT>" -H 'content-type: application/json' \
  -d '{"question":"...","limit":3}' http://<ragcenter-host>:8080/api/v1/search
curl -X POST -H "Authorization: Bearer $RAG_EXPLORE_GATEWAY_API_KEY" \
  -H "x-explore-user-token: <user token>" -H 'content-type: application/json' \
  -d '{"question":"..."}' http://<ragcenter-host>:8080/api/explore/v1/ask

점검 기준: 수집은 {"indexed":true}, 검색은 vector_score·hybrid_score 가 있는 contexts, /api/explore/v1/ask 는 게이트웨이 매핑이 없으면 근거 없음 폴백이 정상이고 매핑·토큰이 준비되면 실제 답변 + citations 를 돌려준다. contentLocator 는 HTTP(S) URL 이며 RAG_SERVICE_CONTENT_ALLOWED_HOSTS 로 호스트를 고정할 수 있고, checksum 은 본문 소문자 hex SHA-256 이어야 한다(불일치 시 checksum_mismatch). 조직 행(tb_organization)은 PortalConsole 디렉터리 동기화가 선행해야 한다(아래 RLS 절 참조).

모델 접근 경로 — ModelGateway 경유(권장) ​

모델·키·쿼터·비용·감사는 ModelGateway 단일 창구가 정본이다. MODEL_GATEWAY_URL과 MODEL_GATEWAY_SERVICE_TOKEN이 설정되면 답변 생성은 로컬 vLLM 대신 게이트웨이를 경유하고, 목적(x-purpose: rag.answer)만 보낸다(모델은 게이트웨이가 purposeModels로 고른다). 두 값이 없으면 기존처럼 local_llm(PRIVATE_LLM_URL)로 폴백하므로 설정 없이도 동작한다.

text
RagService --(x-purpose: rag.answer, Bearer 서비스 토큰)--> ModelGateway --> 모델 공급자
  • RAG_PORTAL_ID로 귀속 헤더 x-portal-id를 바꿀 수 있다(기본 rag-center).
  • PRIVATE_LLM_TIMEOUT_SECONDS는 두 경로 모두의 요청 타임아웃이다.

ExploreGateway 어댑터(검색·답변) ​

ExploreGateway(§14)는 상단 검색의 검색·답변 오케스트레이터다. 이 서비스는 두 어댑터를 노출하고, 둘 다 게이트웨이가 보낸 사용자 위임 토큰으로 RLS를 직접 강제한다.

경로카탈로그 능력용도
POST /api/explore/v1/searchsearch(kind=document)문서 결과를 상단 검색에 합류
POST /api/explore/v1/askanswer근거 기반 답변(provider)
  • RAG_EXPLORE_GATEWAY_API_KEY는 게이트웨이가 보내는 서비스 토큰과 같아야 한다(카탈로그 rag-center 등록값).
  • PORTAL_CONSOLE_JWT_AUDIENCE는 게이트웨이가 재위임한 토큰도 받도록 rag-center,explore-gateway로 둔다.

동적 RLS 보안 모델 ​

tb_ai_document_registry는 원본 문서 lifecycle을 보유하고, tb_doc_chunk_vector.document_id는 ON DELETE CASCADE로 registry를 참조한다. 원본 삭제 이벤트가 registry purge로 변환되면 모든 child vector가 단일 DB transaction에서 제거된다. 소스 수정·재색인 시에는 staging COPY에 없는 이전 chunk_index를 먼저 삭제해 stale vector가 남지 않게 한다.

애플리케이션은 PortalConsole가 서명한 bearer token의 tenant_id, organization_id, clearance_level, directory_admin/rag:admin claim으로만 RlsPrincipal을 만들고, 다음 값을 요청 transaction 안에서 set_config(..., true)로 설정한다.

text
my.app_tenant_id
my.app_user_org_id
my.app_user_clearance_level
my.app_is_admin

true는 PostgreSQL transaction-local 값임을 의미한다. commit/rollback 뒤 pool connection에 값이 남지 않으므로 다른 사용자의 principal이 재사용되지 않는다. 브라우저가 이 값을 header, body, form field로 보내는 API는 제공하지 않는다.

public_organization_id(공용 버킷)까지 포함해 5개 값이 설정되며, directory_admin(또는 rag:admin) principal은 app_current_is_admin()으로 조직 조건이 우회된다. 따라서 RLS 격리 검증·회귀 테스트는 반드시 비관리자 principal로 수행한다(관리자 토큰만 쓰는 검증은 격리를 증명하지 못한다).

조직 행 선조건 — PortalConsole 디렉터리 동기화 ​

tb_ai_document_registry.organization_id와 tb_rag_audit_logs.organization_id는 tb_organization을 참조한다. 즉 수집·검색 전에 사용자·문서가 속한 조직 행이 이미 있어야 한다(소유자는 PortalConsole 디렉터리 동기화). 행이 없으면 수집은 tb_ai_document_registry_organization_id_fkey 위반으로 거절되고, 검색은 감사 insert의 FK 위반이 RAG_SERVICE_AUDIT_REQUIRED에 걸려 503 rag_audit_unavailable로 fail closed 된다. 전사 공용 버킷 행은 ragcenter/identity.py의 public_organization_id() 값으로 함께 만들어 둔다.

한국어 하이브리드 검색 ​

textsearch_ko가 제공하는 korean configuration으로 search_tsv generated column과 GIN index를 만들고, pg_bigm GIN index로 CJK substring matching을 보조한다. fn_hybrid_search_rrf는 RLS가 먼저 허용한 tb_doc_chunk_vector와 tb_ai_document_registry 행만 후보로 삼고, vector cosine·한국어 FTS·bigram 후보 순위를 Reciprocal Rank Fusion(RRF)으로 결합한다. 관리자가 조정하는 rrf_k는 순위 차이에 대한 민감도를 제어하며, LLM에는 최종 상위 3~5개 안전 컨텍스트만 전달한다.

관리자 포탈 ​

http://<ragcenter-host>:8080/은 RagService 독립 관리자 화면이다. PortalConsole 중앙 관리자 또는 위임된 rag:admin token이 있어야 overview·설정 API를 호출할 수 있다. 화면은 다음 값을 관리한다.

범주설정
Ingestionchunk size/overlap, worker 수, batch size, retry/backoff
Embeddingprovider, endpoint, model, 1024 dimension 정책
RetrievalRRF k, korean FTS config, 안전 컨텍스트 수
Local LLMOllama/vLLM provider, endpoint, model, temperature, context 상한
LifecycleRAG 발행 승인, 보존 기간, 원본 삭제 cascade, FileStore 승인 필수 여부

화면은 secret 원문, database password, Directory signing key, 사용자 RLS principal을 저장하거나 표시하지 않는다. 운영에서는 PortalConsole OIDC/session gateway가 Authorization header를 주입하며, UI의 bearer token 입력은 로컬 개발 검증용으로만 사용한다.

References ​

[1] pg_bigm releases — PostgreSQL 16 support

[2] textsearch_ko — mecab-ko based Korean PostgreSQL FTS

[3] pgvector 0.7.0 release