음성 재생과 운영 배포 정리

This commit is contained in:
Yun Chan 2026-06-28 12:18:20 +09:00
parent 8ed185ce6c
commit ac7db95542
1020 changed files with 46863 additions and 2175 deletions

View file

@ -252,6 +252,7 @@ def _vector_literal(vec: Sequence[float]) -> str:
# $7 = w_sparse(real)
# $8 = pre_k (int — dense/sparse 각 후보 수, 보통 50)
# $9 = k (int — 융합 후 반환 수)
# $10 = source_ids(text[] — 빈 배열이면 전체 허용)
#
# 정보비대칭 강제: 두 CTE 모두 `$4 = ANY(visible_to) AND sensitivity <= $5` 사전필터.
# kinds 빈 배열 처리: cardinality($3)=0 이면 kb_kind 조건을 통과(전체).
@ -267,6 +268,7 @@ dense AS (
FROM kb.chunk c, params p
WHERE c.embedding IS NOT NULL
AND (cardinality($3::text[]) = 0 OR c.kb_kind = ANY($3::text[]))
AND (cardinality($10::text[]) = 0 OR c.source_id = ANY($10::text[]))
AND $4 = ANY(c.visible_to)
AND c.sensitivity <= $5
ORDER BY c.embedding <=> p.q_dense
@ -279,6 +281,7 @@ sparse AS (
WHERE p.q_ts IS NOT NULL
AND to_tsvector('simple', c.chunk_text) @@ p.q_ts
AND (cardinality($3::text[]) = 0 OR c.kb_kind = ANY($3::text[]))
AND (cardinality($10::text[]) = 0 OR c.source_id = ANY($10::text[]))
AND $4 = ANY(c.visible_to)
AND c.sensitivity <= $5
ORDER BY s_sparse DESC
@ -473,6 +476,7 @@ async def search_kb(
fs = filters.get("sensitivity_max")
if isinstance(fs, int):
sens_max = min(sens_max, fs) # 더 엄격하게만
source_filter = [str(item) for item in (filters.get("source_id", []) if filters else [])]
# (2) 질의 임베딩(dense+sparse). 모델 미가용 → NotConfigured 전파.
eq = await asyncio.to_thread(embed_query, query) # CPU 인코딩 → 스레드풀(이벤트루프 비차단)
@ -491,17 +495,13 @@ async def search_kb(
policy.w_sparse, # $7
pre_k, # $8 pre_k
max(k * 4, k), # $9 융합 후 1차 컷(리랭킹 입력 여유분)
source_filter, # $10 source_id 좁힘
)
except Exception as e: # UndefinedFunction(vector 미설치) / UndefinedColumn 등
raise NotConfigured(f"KB hybrid query failed (DB/pgvector not ready): {e}") from e
# source_id 추가 좁힘(SQL 후처리 — 화이트리스트 보존, 코드 단순화)
src_filter = set(filters.get("source_id", [])) if filters else set()
chunks: list[RetrievedChunk] = []
for r in rows:
if src_filter and r["source_id"] not in src_filter:
continue
# asyncpg는 jsonb를 str(JSON text)로 반환 → 파싱. 코덱 등록 시 dict 그대로도 수용.
_meta_raw = r["meta"]
meta = json.loads(_meta_raw) if isinstance(_meta_raw, str) else dict(_meta_raw or {})
@ -635,6 +635,7 @@ async def retrieve_eval_grounding(
query: str,
k: int = 5,
kinds: Optional[Sequence[str]] = None,
source_ids: Optional[Sequence[str]] = None,
rerank: bool = True,
) -> RetrievalResult:
"""평가 AI 채점 근거 회수 — DSM/이론/taxonomy 정답라벨 + 논평.
@ -644,13 +645,17 @@ async def retrieve_eval_grounding(
kinds: 평가 차원에 따라 좁히기(: 기법 채점 ['technique','supervisor_pattern']).
"""
filters = {"kb_kind": list(kinds)} if kinds else None
filters: dict[str, Any] = {}
if kinds:
filters["kb_kind"] = list(kinds)
if source_ids:
filters["source_id"] = list(source_ids)
return await search_kb(
conn,
query=query,
role=AIRole.EVALUATOR,
k=k,
filters=filters,
filters=filters or None,
rerank=rerank,
)