개선관리 요구사항과 Google 로그인을 완료

This commit is contained in:
Yun Chan 2026-08-28 16:07:09 +09:00
parent cc0a15b7c6
commit 2a39636163
112 changed files with 10166 additions and 527 deletions

View file

@ -137,6 +137,8 @@ class RetrievedChunk:
label_id: Optional[int] = None # 평가 정책에서만
meta: dict[str, Any] = field(default_factory=dict)
source_id: Optional[str] = None
license_class: Optional[str] = None
external_llm_ok: bool = False
dense_score: float = 0.0
sparse_score: float = 0.0
@ -297,8 +299,12 @@ WITH params AS (
dense AS (
SELECT c.chunk_id,
1 - (c.embedding <=> p.q_dense) AS s_dense
FROM kb.chunk c, params p
FROM kb.chunk c
JOIN kb.document d ON d.doc_id = c.doc_id AND d.is_active
LEFT JOIN kb.protocol_registration pr ON pr.source_id = c.source_id
CROSS JOIN params p
WHERE c.embedding IS NOT NULL
AND (pr.protocol_id IS NULL OR pr.status = 'active')
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)
@ -309,8 +315,12 @@ dense AS (
sparse AS (
SELECT c.chunk_id,
ts_rank_cd(to_tsvector('simple', c.chunk_text), p.q_ts) AS s_sparse
FROM kb.chunk c, params p
FROM kb.chunk c
JOIN kb.document d ON d.doc_id = c.doc_id AND d.is_active
LEFT JOIN kb.protocol_registration pr ON pr.source_id = c.source_id
CROSS JOIN params p
WHERE p.q_ts IS NOT NULL
AND (pr.protocol_id IS NULL OR pr.status = 'active')
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[]))
@ -329,9 +339,11 @@ fused AS (
)
SELECT c.chunk_id, c.kb_kind, c.heading_path, c.chunk_text, c.context_prefix,
c.label_id, c.meta, c.source_id,
src.license_class, src.external_llm_ok,
f.s_dense, f.s_sparse, f.fused_score
FROM fused f
JOIN kb.chunk c USING (chunk_id)
JOIN kb.source src ON src.source_id = c.source_id
ORDER BY f.fused_score DESC
LIMIT $9
"""
@ -537,6 +549,12 @@ async def search_kb(
# 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 {})
# 청크 JSON 메타는 관리자 입력/과거 색인값일 수 있다. 외부 전송 정책은
# 언제나 현재 kb.source 레코드가 권위 원본이며, 누락은 허용하지 않는다.
license_class = str(r["license_class"] or "").strip().upper() or None
external_llm_ok = r["external_llm_ok"] is True
meta["license_class"] = license_class
meta["external_llm_ok"] = external_llm_ok
body = r["chunk_text"] if policy.expose_body else None
cue = None
if not policy.expose_body:
@ -554,6 +572,8 @@ async def search_kb(
label_id=r["label_id"] if policy.include_label else None,
meta=meta if policy.include_label else {},
source_id=r["source_id"],
license_class=license_class,
external_llm_ok=external_llm_ok,
dense_score=float(r["s_dense"]),
sparse_score=float(r["s_sparse"]),
)
@ -845,6 +865,48 @@ def _validate_index_chunks(req: IndexRequest) -> None:
)
async def validate_index_source(conn: "asyncpg.Connection", source_id: str) -> None:
"""DB 등록 출처의 라이선스와 프로토콜 상태를 인덱싱 전에 강제한다.
호출자가 license/external_llm_ok 값을 임의로 보내는 우회는 허용하지 않는다.
``kb.source`` 없거나 등록 프로토콜이 active가 아니면 fail closed한다.
"""
try:
row = await conn.fetchrow(
"""
SELECT s.license_class, s.external_llm_ok, pr.status AS protocol_status
FROM kb.source s
LEFT JOIN kb.protocol_registration pr ON pr.source_id = s.source_id
WHERE s.source_id = $1
""",
source_id,
)
except Exception as exc:
raise NotConfigured(f"kb source policy lookup failed: {exc}") from exc
if row is None:
raise IndexPolicyViolation(
f"source must be registered in kb.source before indexing (source_id={source_id})"
)
license_class = str(row["license_class"] or "").upper()
external_llm_ok = bool(row["external_llm_ok"])
if license_class not in {"A", "B", "C", "D"}:
raise IndexPolicyViolation(f"source license is invalid (source_id={source_id})")
if license_class in {"C", "D"} and external_llm_ok:
raise IndexPolicyViolation(
"license C/D sources cannot allow external LLM use "
f"(source_id={source_id})"
)
protocol_status = row["protocol_status"]
if protocol_status is not None and str(protocol_status) != "active":
raise IndexPolicyViolation(
"registered protocols can be indexed only after activation "
f"(source_id={source_id}, status={protocol_status})"
)
async def index_document(
conn: "asyncpg.Connection",
req: IndexRequest,
@ -858,7 +920,7 @@ async def index_document(
4. 임베딩 모델 미가용 : embedding NULL 적재(텍스트만, BM25 동작) + degraded=True.
무거운 작업(임베딩) 본래는 백그라운드 워커/배치. 라우트는 BackgroundTasks 위임 권장.
DSM verbatim 저작권(license C/D): source.external_llm_ok=false 가드는 source 등록 시점 책임.
라이선스와 등록 상태 검증은 호출부가 ``validate_index_source`` DB 값을 확인해야 한다.
Raises: NotConfigured DB(kb 스키마/vector) 미가용.
"""
@ -997,5 +1059,6 @@ __all__ = [
"log_retrieval",
"IndexRequest",
"IndexResult",
"validate_index_source",
"index_document",
]