페르소나 소스팩 동기화 정리

This commit is contained in:
Yun Chan 2026-06-28 20:12:35 +09:00
parent 6a81ec596c
commit e8e08935ed
10 changed files with 1126 additions and 283 deletions

View file

@ -22,7 +22,7 @@ from pydantic import BaseModel, Field
from ..db import acquire, get_pool
from ..deps import AIView, Principal, Role, require_role
from ..services import live_coach, rag
from ..services import rag, source_pack_sync
router = APIRouter(prefix="/kb", tags=["kb"])
@ -326,6 +326,8 @@ async def index_document(
# 관리자 인덱싱은 RLS 미적용(쓰기 — kb 스키마 직접). role 주입 없이 acquire.
async with acquire() as conn:
result = await rag.index_document(conn, req)
except rag.IndexPolicyViolation as e:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)) from e
except rag.NotConfigured as e:
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}")
except RuntimeError as e:
@ -354,64 +356,36 @@ async def sync_live_coach_source_packs(
evaluator RAG 검색에도 올린다. source row를 먼저 upsert한 content_hash 기반 증분 색인을
수행한다. 임베딩 모델 미가용 BM25-only degraded 색인으로 이어진다.
"""
source_rows = live_coach.build_rag_source_rows()
index_payloads = live_coach.build_rag_index_payloads()
source_rows, index_payloads = source_pack_sync.build_repo_source_pack_manifest()
if not source_rows or not index_payloads:
raise HTTPException(
status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="live coach source packs are empty",
)
items: list[LiveCoachSourcePackSyncItem] = []
source_row_by_id = {row["source_id"]: row for row in source_rows}
try:
async with acquire() as conn:
for row in source_rows:
await conn.execute(
"""
INSERT INTO kb.source
(source_id, title, kb_kind, license_class, origin_path, citation, external_llm_ok)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (source_id) DO UPDATE SET
title = EXCLUDED.title,
kb_kind = EXCLUDED.kb_kind,
license_class = EXCLUDED.license_class,
origin_path = EXCLUDED.origin_path,
citation = EXCLUDED.citation,
external_llm_ok = EXCLUDED.external_llm_ok
""",
row["source_id"],
row["title"],
row["kb_kind"],
row["license_class"],
row["origin_path"],
row["citation"],
row["external_llm_ok"],
)
for payload in index_payloads:
if payload["source_id"] not in source_row_by_id:
continue
result = await rag.index_document(conn, rag.IndexRequest(**payload))
items.append(
LiveCoachSourcePackSyncItem(
source_id=payload["source_id"],
doc_id=result.doc_id,
chunks_indexed=result.chunks_indexed,
skipped_unchanged=result.skipped_unchanged,
embedded=result.embedded,
degraded=result.degraded,
)
)
result = await source_pack_sync.sync_repo_source_packs(conn, apply=True)
except rag.NotConfigured as exc:
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {exc}") from exc
except RuntimeError as exc:
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {exc}") from exc
return LiveCoachSourcePackSyncResponse(
sources_upserted=len(source_rows),
chunks_indexed=sum(item.chunks_indexed for item in items),
skipped_unchanged=sum(1 for item in items if item.skipped_unchanged),
embedded=all(item.embedded for item in items) if items else False,
degraded=any(item.degraded for item in items),
items=items,
sources_upserted=result.sources_upserted,
chunks_indexed=result.chunks_indexed,
skipped_unchanged=result.skipped_unchanged,
embedded=result.embedded,
degraded=result.degraded,
items=[
LiveCoachSourcePackSyncItem(
source_id=item.source_id,
doc_id=item.doc_id,
chunks_indexed=item.chunks_indexed,
skipped_unchanged=item.skipped_unchanged,
embedded=item.embedded,
degraded=item.degraded,
)
for item in result.items
],
)