개선관리 요구사항과 Google 로그인을 완료
This commit is contained in:
parent
cc0a15b7c6
commit
2a39636163
112 changed files with 10166 additions and 527 deletions
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue