전 저장소 리팩터링과 SSOT 정비
This commit is contained in:
parent
14ecbd4e7d
commit
3dfddcac6f
173 changed files with 19679 additions and 6952 deletions
|
|
@ -33,7 +33,7 @@ RoleLiteral = Literal["client", "counselor", "evaluator"]
|
|||
|
||||
class KBSearchRequest(BaseModel):
|
||||
query: str = Field(..., min_length=1) # PII 마스킹된 질의(마스킹은 가드레일 책임)
|
||||
role: RoleLiteral = "evaluator" # 검색 주체(정보비대칭 정책 선택)
|
||||
role: RoleLiteral = "evaluator" # 검색 주체(정보비대칭 정책 선택)
|
||||
k: int = Field(default=5, ge=1, le=50)
|
||||
rerank: bool = True
|
||||
# 정책 화이트리스트를 *좁히는* 추가 필터만 허용(넓히지 못함 — 정보비대칭 보존)
|
||||
|
|
@ -51,9 +51,9 @@ class ChunkOut(BaseModel):
|
|||
kb_kind: str
|
||||
heading_path: Optional[str] = None
|
||||
context_prefix: Optional[str] = None
|
||||
body: Optional[str] = None # expose_body=True(상담사/평가) 정책에서만
|
||||
behavior_cue: Optional[str] = None # 내담자 정책: 본문 비노출, 행동단서만(M6)
|
||||
label_id: Optional[int] = None # 평가 정책에서만
|
||||
body: Optional[str] = None # expose_body=True(상담사/평가) 정책에서만
|
||||
behavior_cue: Optional[str] = None # 내담자 정책: 본문 비노출, 행동단서만(M6)
|
||||
label_id: Optional[int] = None # 평가 정책에서만
|
||||
meta: dict[str, Any] = Field(default_factory=dict)
|
||||
source_id: Optional[str] = None
|
||||
|
||||
|
|
@ -62,9 +62,9 @@ class KBSearchResponse(BaseModel):
|
|||
chunks: list[ChunkOut]
|
||||
policy: str
|
||||
top1_score: float
|
||||
crag_pass: bool # top1 >= 임계(F-06: 미달 시 관찰 프레이밍)
|
||||
crag_pass: bool # top1 >= 임계(F-06: 미달 시 관찰 프레이밍)
|
||||
latency_ms: int
|
||||
degraded: bool = False # reranker/embed 폴백 투명성
|
||||
degraded: bool = False # reranker/embed 폴백 투명성
|
||||
|
||||
|
||||
class MemoryRecallRequest(BaseModel):
|
||||
|
|
@ -79,11 +79,11 @@ class IndexChunkIn(BaseModel):
|
|||
seq: int
|
||||
chunk_text: str = Field(..., min_length=1)
|
||||
heading_path: Optional[str] = None
|
||||
context_prefix: Optional[str] = None # Contextual Retrieval 프리픽스(색인 대상)
|
||||
context_prefix: Optional[str] = None # Contextual Retrieval 프리픽스(색인 대상)
|
||||
kb_kind: Optional[str] = None
|
||||
visible_to: Optional[list[str]] = None # 미지정 시 {client,counselor,evaluator}
|
||||
visible_to: Optional[list[str]] = None # 미지정 시 {client,counselor,evaluator}
|
||||
sensitivity: Optional[int] = Field(default=None, ge=0, le=3)
|
||||
label_id: Optional[int] = None # taxonomy 정답 라벨 FK
|
||||
label_id: Optional[int] = None # taxonomy 정답 라벨 FK
|
||||
meta: Optional[dict[str, Any]] = None
|
||||
token_count: Optional[int] = None
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ class IndexRequestIn(BaseModel):
|
|||
class IndexResponse(BaseModel):
|
||||
doc_id: Optional[int]
|
||||
chunks_indexed: int
|
||||
skipped_unchanged: bool # content_hash 동일 → 증분 스킵
|
||||
embedded: bool # 임베딩 적재 여부(모델 미가용 시 False)
|
||||
skipped_unchanged: bool # content_hash 동일 → 증분 스킵
|
||||
embedded: bool # 임베딩 적재 여부(모델 미가용 시 False)
|
||||
degraded: bool = False
|
||||
|
||||
|
||||
|
|
@ -211,16 +211,11 @@ async def search(body: KBSearchRequest) -> KBSearchResponse:
|
|||
)
|
||||
except RuntimeError as e:
|
||||
# DB 풀 미초기화(lifespan 밖) — 시연/테스트 degraded
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}"
|
||||
)
|
||||
|
||||
return KBSearchResponse(
|
||||
chunks=[_to_chunk_out(c) for c in result.chunks],
|
||||
policy=result.policy_name,
|
||||
top1_score=round(result.top1_score, 6),
|
||||
crag_pass=result.top1_score >= rag.CRAG_TOP1_THRESHOLD,
|
||||
latency_ms=result.latency_ms,
|
||||
degraded=result.degraded,
|
||||
)
|
||||
return _search_response(result)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -254,18 +249,15 @@ async def eval_grounding(body: KBSearchRequest) -> KBSearchResponse:
|
|||
except Exception:
|
||||
pass
|
||||
except rag.NotConfigured as e:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}"
|
||||
)
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}"
|
||||
)
|
||||
|
||||
return KBSearchResponse(
|
||||
chunks=[_to_chunk_out(c) for c in result.chunks],
|
||||
policy=result.policy_name,
|
||||
top1_score=round(result.top1_score, 6),
|
||||
crag_pass=result.top1_score >= rag.CRAG_TOP1_THRESHOLD,
|
||||
latency_ms=result.latency_ms,
|
||||
degraded=result.degraded,
|
||||
)
|
||||
return _search_response(result)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -287,9 +279,19 @@ async def persona_memory(body: MemoryRecallRequest) -> KBSearchResponse:
|
|||
k=body.k,
|
||||
)
|
||||
except rag.NotConfigured as e:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}"
|
||||
)
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}"
|
||||
)
|
||||
|
||||
return _search_response(result)
|
||||
|
||||
|
||||
def _search_response(result: rag.SearchResult) -> KBSearchResponse:
|
||||
"""Project every RAG policy result through the same browser-facing contract."""
|
||||
|
||||
return KBSearchResponse(
|
||||
chunks=[_to_chunk_out(c) for c in result.chunks],
|
||||
|
|
@ -304,7 +306,9 @@ async def persona_memory(body: MemoryRecallRequest) -> KBSearchResponse:
|
|||
# ════════════════════════════════════════════════════════════════════════════
|
||||
# 인덱싱 트리거 — 관리자 전용(content_hash 증분, 오프라인 배치)
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
@router.post("/index", response_model=IndexResponse, status_code=status.HTTP_202_ACCEPTED)
|
||||
@router.post(
|
||||
"/index", response_model=IndexResponse, status_code=status.HTTP_202_ACCEPTED
|
||||
)
|
||||
async def index_document(
|
||||
body: IndexRequestIn,
|
||||
principal: Annotated[Principal, Depends(require_role(Role.ADMIN))],
|
||||
|
|
@ -329,9 +333,13 @@ async def index_document(
|
|||
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}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {e}"
|
||||
)
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}")
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {e}"
|
||||
)
|
||||
|
||||
return IndexResponse(
|
||||
doc_id=result.doc_id,
|
||||
|
|
@ -356,7 +364,9 @@ async def sync_live_coach_source_packs(
|
|||
evaluator RAG 검색에도 올린다. source row를 먼저 upsert한 뒤 content_hash 기반 증분 색인을
|
||||
수행한다. 임베딩 모델 미가용 시 BM25-only degraded 색인으로 이어진다.
|
||||
"""
|
||||
source_rows, index_payloads = source_pack_sync.build_repo_source_pack_manifest(refresh=True)
|
||||
source_rows, index_payloads = source_pack_sync.build_repo_source_pack_manifest(
|
||||
refresh=True
|
||||
)
|
||||
if not source_rows or not index_payloads:
|
||||
raise HTTPException(
|
||||
status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
|
|
@ -365,11 +375,17 @@ async def sync_live_coach_source_packs(
|
|||
|
||||
try:
|
||||
async with acquire() as conn:
|
||||
result = await source_pack_sync.sync_repo_source_packs(conn, apply=True, refresh=True)
|
||||
result = await source_pack_sync.sync_repo_source_packs(
|
||||
conn, apply=True, refresh=True
|
||||
)
|
||||
except rag.NotConfigured as exc:
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"RAG not configured: {exc}") from 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
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"DB not ready: {exc}"
|
||||
) from exc
|
||||
|
||||
return LiveCoachSourcePackSyncResponse(
|
||||
sources_upserted=result.sources_upserted,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue