<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://s3wiki.yonsei.ac.kr/index.php?action=history&amp;feed=atom&amp;title=%EB%8F%84%EC%9B%80%EB%A7%90%3AS3_%EC%97%B0%EA%B5%AC_%EB%A9%94%EB%AA%A8%EB%A6%AC%2F%EB%B2%A1%ED%84%B0%C2%B7%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C_%EA%B2%80%EC%83%89_%EC%84%A4%EA%B3%84</id>
	<title>도움말:S3 연구 메모리/벡터·하이브리드 검색 설계 - 편집 역사</title>
	<link rel="self" type="application/atom+xml" href="https://s3wiki.yonsei.ac.kr/index.php?action=history&amp;feed=atom&amp;title=%EB%8F%84%EC%9B%80%EB%A7%90%3AS3_%EC%97%B0%EA%B5%AC_%EB%A9%94%EB%AA%A8%EB%A6%AC%2F%EB%B2%A1%ED%84%B0%C2%B7%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C_%EA%B2%80%EC%83%89_%EC%84%A4%EA%B3%84"/>
	<link rel="alternate" type="text/html" href="https://s3wiki.yonsei.ac.kr/index.php?title=%EB%8F%84%EC%9B%80%EB%A7%90:S3_%EC%97%B0%EA%B5%AC_%EB%A9%94%EB%AA%A8%EB%A6%AC/%EB%B2%A1%ED%84%B0%C2%B7%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C_%EA%B2%80%EC%83%89_%EC%84%A4%EA%B3%84&amp;action=history"/>
	<updated>2026-07-16T16:40:15Z</updated>
	<subtitle>이 문서의 편집 역사</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://s3wiki.yonsei.ac.kr/index.php?title=%EB%8F%84%EC%9B%80%EB%A7%90:S3_%EC%97%B0%EA%B5%AC_%EB%A9%94%EB%AA%A8%EB%A6%AC/%EB%B2%A1%ED%84%B0%C2%B7%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C_%EA%B2%80%EC%83%89_%EC%84%A4%EA%B3%84&amp;diff=202&amp;oldid=prev</id>
		<title>S3Admin: S3 연구 메모리 저장소 문서 게시</title>
		<link rel="alternate" type="text/html" href="https://s3wiki.yonsei.ac.kr/index.php?title=%EB%8F%84%EC%9B%80%EB%A7%90:S3_%EC%97%B0%EA%B5%AC_%EB%A9%94%EB%AA%A8%EB%A6%AC/%EB%B2%A1%ED%84%B0%C2%B7%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C_%EA%B2%80%EC%83%89_%EC%84%A4%EA%B3%84&amp;diff=202&amp;oldid=prev"/>
		<updated>2026-07-16T14:32:08Z</updated>

		<summary type="html">&lt;p&gt;S3 연구 메모리 저장소 문서 게시&lt;/p&gt;
&lt;p&gt;&lt;b&gt;새 문서&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;lt;!-- S3RM managed repository document; edit the Markdown source, not this page. --&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;이 페이지는 저장소 문서에서 자동으로 동기화됩니다. 위키에서 직접 편집하지 마세요.&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=== 문서 상태 ===&lt;br /&gt;
&lt;br /&gt;
이 문서는 추후 도입할 검색 계층의 설계안입니다. 현재 서비스는 MediaWiki 전체 텍스트 검색과 제한된 단어 겹침으로 동작합니다. 이 문서에 나온 벡터 인덱스, 설정값, 응답 필드는 아직 구현된 기능이 아닙니다.&lt;br /&gt;
&lt;br /&gt;
=== 결정 요약 ===&lt;br /&gt;
&lt;br /&gt;
* Semantic MediaWiki를 유일한 기준 저장소로 유지합니다.&lt;br /&gt;
* 벡터 인덱스는 후보 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;Lesson:&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; ID만 반환하는 파생 데이터로 둡니다.&lt;br /&gt;
* 최종 결과는 항상 MediaWiki 최신 리비전을 다시 읽어 만듭니다.&lt;br /&gt;
* 외부 MCP 도구 일곱 개와 중앙 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;/mcp&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 진입점은 그대로 유지합니다.&lt;br /&gt;
* 전체 텍스트, 의미 유사도, 명시적 relation을 각각 찾은 뒤 결정적인 규칙으로 합칩니다.&lt;br /&gt;
* 처음에는 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mcp-http&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 프로세스 안에서 exact vector search를 사용합니다.&lt;br /&gt;
* 재구축 시간, 메모리 또는 지연 시간이 목표를 넘을 때만 내부 검색 서비스로 옮깁니다.&lt;br /&gt;
* 임베딩 모델, 입력 구성 또는 거리 계산이 바뀌면 새 인덱스 세대를 만듭니다.&lt;br /&gt;
* 의미 검색이 실패해도 전체 텍스트와 relation 검색은 계속 제공합니다.&lt;br /&gt;
벡터 저장 제품은 이 경계를 구현하는 선택 사항입니다. 검색 품질과 운영 규모를 확인하기 전에 제품별 API가 도메인 계층으로 퍼지지 않게 합니다.&lt;br /&gt;
&lt;br /&gt;
=== 목표 ===&lt;br /&gt;
&lt;br /&gt;
다음 문제를 해결합니다.&lt;br /&gt;
&lt;br /&gt;
# 표현이 달라도 증상과 제약이 비슷한 Lesson을 찾습니다.&lt;br /&gt;
# 비슷한 문제에 쓰인 서로 다른 해결 원리를 함께 보여 줍니다.&lt;br /&gt;
# 한국어, 영어와 두 언어가 섞인 메모를 같은 검색 흐름에서 다룹니다.&lt;br /&gt;
# 새 Lesson은 기존 전체 텍스트 검색으로 바로 찾을 수 있게 합니다.&lt;br /&gt;
# 임베딩 제공자와 인덱스 구현을 중단 없이 바꿀 수 있게 합니다.&lt;br /&gt;
# 여러 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mcp-http&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 인스턴스로 확장해도 같은 검색 세대를 사용하게 합니다.&lt;br /&gt;
다음 기능은 이 설계의 범위에 넣지 않습니다.&lt;br /&gt;
&lt;br /&gt;
* 벡터 유사도를 사실, 인과관계 또는 relation으로 자동 저장&lt;br /&gt;
* 별도 Lesson 데이터베이스 또는 사용자별 지식 사본&lt;br /&gt;
* 벡터 저장 제품의 범용 API를 MCP 도구로 공개&lt;br /&gt;
* 검색 결과를 근거 없이 요약하거나 해결책으로 확정&lt;br /&gt;
* 다중 테넌트 저장소와 사용자별 비공개 인덱스&lt;br /&gt;
=== 전체 구조 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;lt;nowiki&amp;gt;&lt;br /&gt;
flowchart LR&lt;br /&gt;
    C[&amp;quot;MCP 클라이언트&amp;quot;] --&amp;gt; M[&amp;quot;중앙 mcp-http&amp;quot;]&lt;br /&gt;
    M --&amp;gt; R[&amp;quot;검색 조정 계층&amp;quot;]&lt;br /&gt;
    R --&amp;gt; L[&amp;quot;MediaWiki 전체 텍스트 후보&amp;quot;]&lt;br /&gt;
    R --&amp;gt; G[&amp;quot;MediaWiki relation 후보&amp;quot;]&lt;br /&gt;
    R --&amp;gt; V[&amp;quot;의미 검색 후보&amp;quot;]&lt;br /&gt;
    L --&amp;gt; F[&amp;quot;후보 결합과 다양성 정렬&amp;quot;]&lt;br /&gt;
    G --&amp;gt; F&lt;br /&gt;
    V --&amp;gt; F&lt;br /&gt;
    F --&amp;gt; H[&amp;quot;MediaWiki 최신 리비전 일괄 조회&amp;quot;]&lt;br /&gt;
    H --&amp;gt; O[&amp;quot;필터·페이지 나누기·출력 제한&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    W[&amp;quot;Semantic MediaWiki 리비전&amp;quot;] --&amp;gt; I[&amp;quot;비공개 색인기&amp;quot;]&lt;br /&gt;
    I --&amp;gt; P[&amp;quot;버전이 있는 입력 구성&amp;quot;]&lt;br /&gt;
    P --&amp;gt; E[&amp;quot;교체 가능한 임베딩 제공자&amp;quot;]&lt;br /&gt;
    E --&amp;gt; X[&amp;quot;세대별 파생 벡터 인덱스&amp;quot;]&lt;br /&gt;
    X --&amp;gt; V&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
읽기 경로와 색인 경로를 분리합니다. 검색 요청이 벡터 인덱스를 직접 읽더라도 최종 Lesson 본문과 필터 판정은 MediaWiki에서 가져옵니다. 색인 실패는 위키 쓰기를 되돌리지 않습니다.&lt;br /&gt;
&lt;br /&gt;
=== 내부 경계 ===&lt;br /&gt;
&lt;br /&gt;
현재 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;WikiAdapter&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;에는 기준 저장소 작업과 후보 검색이 함께 있습니다. 동작을 바꾸기 전에 다음 경계로 나눕니다.&lt;br /&gt;
&lt;br /&gt;
==== Lesson 저장소 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;LessonRepository&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 MediaWiki 읽기와 허용된 쓰기만 담당합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;lt;nowiki&amp;gt;&lt;br /&gt;
@dataclass(frozen=True, slots=True)&lt;br /&gt;
class LessonSnapshot:&lt;br /&gt;
    lesson: Lesson&lt;br /&gt;
    revision_id: int&lt;br /&gt;
    retrieval_hash: str&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class LessonRepository(Protocol):&lt;br /&gt;
    async def get_snapshot(&lt;br /&gt;
        self, lesson_id: str&lt;br /&gt;
    ) -&amp;gt; LessonSnapshot | None: ...&lt;br /&gt;
&lt;br /&gt;
    async def get_snapshots(&lt;br /&gt;
        self, lesson_ids: Sequence[str]&lt;br /&gt;
    ) -&amp;gt; dict[str, LessonSnapshot]: ...&lt;br /&gt;
&lt;br /&gt;
    async def create_lesson(self, lesson: Lesson) -&amp;gt; Lesson: ...&lt;br /&gt;
    async def add_relation(&lt;br /&gt;
        self, relation: LessonRelation&lt;br /&gt;
    ) -&amp;gt; LessonRelation: ...&lt;br /&gt;
    async def add_evidence(&lt;br /&gt;
        self, lesson_id: str, evidence: Evidence&lt;br /&gt;
    ) -&amp;gt; Evidence: ...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;get_snapshots&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 후보 ID를 최대 50개씩 읽습니다. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;retrieval_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 임베딩 대상 필드만 정규화해 만든 해시입니다. 근거나 relation만 바뀌어 리비전 ID가 달라져도 이 해시가 같으면 기존 벡터를 계속 쓸 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
==== 후보 검색 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;RetrievalBackend&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 Lesson 본문이 아니라 작은 후보 레코드만 반환합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;lt;nowiki&amp;gt;&lt;br /&gt;
@dataclass(frozen=True, slots=True)&lt;br /&gt;
class RetrievalCandidate:&lt;br /&gt;
    lesson_id: str&lt;br /&gt;
    basis: str&lt;br /&gt;
    rank: int&lt;br /&gt;
    source_score: float | None&lt;br /&gt;
    indexed_revision_id: int | None&lt;br /&gt;
    indexed_retrieval_hash: str | None&lt;br /&gt;
    matched_facets: tuple[str, ...]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@dataclass(frozen=True, slots=True)&lt;br /&gt;
class RetrievalBatch:&lt;br /&gt;
    candidates: tuple[RetrievalCandidate, ...]&lt;br /&gt;
    generation: str&lt;br /&gt;
    has_more: bool&lt;br /&gt;
    partial: bool&lt;br /&gt;
    scanned: int&lt;br /&gt;
    scan_limit: int&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class RetrievalBackend(Protocol):&lt;br /&gt;
    async def retrieve(&lt;br /&gt;
        self, request: RetrievalRequest&lt;br /&gt;
    ) -&amp;gt; RetrievalBatch: ...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
제품별 client, collection 이름과 거리 계산 방식은 이 구현 안에 둡니다. 서비스 계층은 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;rank&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 기준으로 후보 경로를 합치며 raw cosine 값을 서로 직접 비교하지 않습니다. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;source_score&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 한 후보 경로 안의 진단과 기준값 적용에만 씁니다. 결합이 끝난 뒤 검색 조정 계층이 별도의 서비스 점수를 만듭니다.&lt;br /&gt;
&lt;br /&gt;
==== 입력 구성과 임베딩 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ProjectionBuilder&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 같은 Lesson에서 검색 목적별 텍스트를 결정적으로 만듭니다. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;EmbeddingProvider&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 텍스트 묶음을 벡터로 바꿉니다. 두 경계에는 각각 독립된 버전을 둡니다.&lt;br /&gt;
&lt;br /&gt;
임베딩 제공자는 다음 정보를 고정해 보고해야 합니다.&lt;br /&gt;
&lt;br /&gt;
* 모델 식별자와 artifact digest&lt;br /&gt;
* tokenizer 식별자와 digest&lt;br /&gt;
* 벡터 차원&lt;br /&gt;
* 정규화 여부&lt;br /&gt;
* 거리 계산 방식&lt;br /&gt;
* 한 입력의 최대 token 수&lt;br /&gt;
외부 API, 서버 안의 모델 또는 별도 내부 추론 서비스는 같은 인터페이스를 구현할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
==== 색인 동기화 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;IndexSynchronizer&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 MediaWiki 변경을 읽어 파생 인덱스를 갱신합니다. MCP에는 색인 관리 도구를 추가하지 않습니다.&lt;br /&gt;
&lt;br /&gt;
* MCP 쓰기 성공 뒤 해당 Lesson을 갱신하라는 힌트를 보냅니다.&lt;br /&gt;
* 브라우저 편집을 찾기 위해 MediaWiki recent changes를 주기적으로 읽습니다.&lt;br /&gt;
* 누락된 이벤트를 복구하기 위해 전체 Lesson을 주기적으로 대조합니다.&lt;br /&gt;
* 체크포인트, 재시도 큐와 인덱스 세대는 파생 운영 상태로 취급합니다.&lt;br /&gt;
* 모든 upsert는 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;lesson_id + revision_id&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 기준으로 반복 실행해도 같은 결과가 나와야 합니다.&lt;br /&gt;
==== 호환 이행 ====&lt;br /&gt;
&lt;br /&gt;
첫 구조 변경은 검색 결과를 바꾸지 않습니다.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ResearchMemoryService&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;가 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;LessonRepository&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 선택적인 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;RetrievalBackend&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 받게 합니다.&lt;br /&gt;
* 검색 backend가 없으면 현재 동작을 감싼 legacy 구현을 사용합니다.&lt;br /&gt;
* 기존 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;WikiAdapter&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;의 검색 메서드는 in-memory adapter와 기존 호출자를 옮기는 동안 유지합니다.&lt;br /&gt;
* 일괄 snapshot 조회에는 단일 조회를 반복하는 기본 구현을 둘 수 있습니다.&lt;br /&gt;
* 외부 MCP 도구 이름, 입력과 응답은 이 단계에서 바꾸지 않습니다.&lt;br /&gt;
새 내부 경계와 legacy 구현의 결과가 같은지 domain-level 테스트로 확인한 뒤 의미 검색을 추가합니다.&lt;br /&gt;
&lt;br /&gt;
=== 검색용 표현 ===&lt;br /&gt;
&lt;br /&gt;
Lesson 전체를 한 벡터로 합치지 않습니다. 첫 입력 구성 버전은 세 가지 facet을 사용합니다.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! facet !! 포함하는 필드 !! 용도&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;situation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;question&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;context&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;observation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 비슷한 문제와 관찰 찾기&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;approach&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;attempt&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 이미 시도한 방법과 실패 형태 찾기&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mechanism&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;interpretation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;reusable_lesson&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;applicability&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 해결 원리와 적용 조건 찾기&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;title&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;은 전체 텍스트 검색에서 높은 가중치로 다룹니다. evidence 개요, 구조화된 인용, URL, author, reviewer, timestamp와 relation은 임베딩하지 않습니다. 이 값은 검색 근거, 필터 또는 relation 후보 경로에서 따로 사용합니다.&lt;br /&gt;
&lt;br /&gt;
입력은 다음 규칙으로 만듭니다.&lt;br /&gt;
&lt;br /&gt;
# Unicode NFKC로 정규화하고 연속 공백을 하나로 줄입니다.&lt;br /&gt;
# 필드 이름과 필드 값을 고정된 순서로 붙입니다.&lt;br /&gt;
# 빈 필드는 허용하지 않는 현재 Lesson 계약을 그대로 사용합니다.&lt;br /&gt;
# 모델 입력 한도를 넘으면 facet별 token 예산에 따라 앞부분과 뒷부분을 보존합니다.&lt;br /&gt;
# 잘림 여부를 색인 상태에 기록합니다.&lt;br /&gt;
# 요약용 LLM은 색인 경로에 넣지 않습니다.&lt;br /&gt;
긴 입력이 자주 잘리기 시작하면 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ProjectionBuilder&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;만 바꿔 필드별 chunk embedding과 결정적인 pooling을 도입합니다. 이 변경도 새 입력 구성 버전과 새 인덱스 세대를 사용합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;retrieval_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;에는 입력 구성 버전과 정규화된 facet 텍스트를 넣습니다. 다음 값은 별도 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;metadata_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;로 관리합니다.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;confidence&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;record_origin&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;author&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;review_state&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;created_at&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;updated_at&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* 구조화된 evidence 존재 여부&lt;br /&gt;
이렇게 하면 evidence 추가처럼 의미 본문이 바뀌지 않은 수정은 재임베딩하지 않고 필터 metadata만 갱신할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
=== 인덱스 레코드 ===&lt;br /&gt;
&lt;br /&gt;
파생 인덱스에는 다음 값만 저장합니다.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 값 !! 설명&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;lesson_id&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || MediaWiki 페이지의 고정 외부 ID&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;indexed_revision_id&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 색인할 때 읽은 리비전 ID&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;retrieval_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 임베딩 대상 내용의 해시&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;metadata_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 필터 metadata의 해시&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;updated_at&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 지연과 최신성 확인용 시각&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;generation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || 모델과 입력 구성의 세대&lt;br /&gt;
|-&lt;br /&gt;
| filter scalar || 기존 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;SearchFilters&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 미리 적용하기 위한 값&lt;br /&gt;
|-&lt;br /&gt;
| facet vectors || &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;situation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;approach&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mechanism&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 벡터&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
정규화된 본문, evidence 전문과 relation 목록은 저장하지 않습니다. 필터 scalar는 성능 최적화용 사본입니다. 최종 결과에는 MediaWiki에서 다시 읽은 필터를 적용합니다.&lt;br /&gt;
&lt;br /&gt;
=== 후보 생성과 순위 ===&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;search_lessons&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
다음 후보 경로를 독립적으로 실행합니다.&lt;br /&gt;
&lt;br /&gt;
# MediaWiki 전체 텍스트 검색&lt;br /&gt;
# query와 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;situation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; facet의 의미 유사도&lt;br /&gt;
# query와 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;approach&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; facet의 의미 유사도&lt;br /&gt;
# query와 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mechanism&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; facet의 의미 유사도&lt;br /&gt;
정확한 식별자, 제목과 오류 문자열은 전체 텍스트 결과가 놓치지 않게 합니다. 의미 검색은 같은 뜻을 다른 표현으로 쓴 후보를 보완합니다.&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;find_analogies&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; ====&lt;br /&gt;
&lt;br /&gt;
다음 순서로 후보를 만듭니다.&lt;br /&gt;
&lt;br /&gt;
# 저장된 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;analogous_to&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; relation 후보&lt;br /&gt;
# source의 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;situation&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;과 비슷한 후보&lt;br /&gt;
# source의 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mechanism&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;과 비슷한 교차 문제 후보&lt;br /&gt;
# 현재 Unicode 단어와 한국어 n-gram 후보&lt;br /&gt;
relation 후보는 명시적으로 저장된 연결이라는 점을 계속 구분합니다. 벡터 후보는 relation이 아니며 자동으로 저장하지 않습니다.&lt;br /&gt;
&lt;br /&gt;
==== 결합과 다양성 ====&lt;br /&gt;
&lt;br /&gt;
백엔드마다 점수 분포가 다르므로 raw 점수를 더하지 않습니다. 각 후보 경로의 순위를 versioned reciprocal-rank fusion으로 합칩니다. 같은 해결 방식이 상위 결과를 차지하지 않도록 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;approach&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mechanism&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 벡터에 작은 다양성 가중치를 적용합니다.&lt;br /&gt;
&lt;br /&gt;
다양성은 관련성을 대체하지 않습니다. 먼저 적용 조건과 문제 유사도가 기준을 넘은 후보만 남기고, 그 안에서 서로 다른 해결 원리를 섞습니다. 정렬 규칙과 가중치는 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ranking_version&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;으로 고정합니다.&lt;br /&gt;
&lt;br /&gt;
외부 응답의 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;score&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 raw cosine이 아니라 0~1 범위의 서비스 점수입니다. 현재 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;find_analogies&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;의 lexical score 의미는 shadow 평가와 문서·테스트 변경 없이 조용히 바꾸지 않습니다. 의미 검색을 활성화할 때는 다음을 함께 적용합니다.&lt;br /&gt;
&lt;br /&gt;
* 고정 평가셋으로 서비스 점수를 보정&lt;br /&gt;
* 응답에 작은 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ranking_version&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 또는 동등한 진단 값 제공&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;basis&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;에 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;semantic_similarity&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;hybrid&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 추가&lt;br /&gt;
* lexical 후보의 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;shared_terms&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 유지&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;min_score&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 서비스 점수에 적용한다고 명시&lt;br /&gt;
=== 기준 저장소 재검증 ===&lt;br /&gt;
&lt;br /&gt;
후보를 받은 뒤 다음 순서로 확인합니다.&lt;br /&gt;
&lt;br /&gt;
# 후보 ID를 중복 제거하고 내부 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;scan_limit&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;까지만 유지합니다.&lt;br /&gt;
# MediaWiki에서 최신 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;LessonSnapshot&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;을 일괄 조회합니다.&lt;br /&gt;
# 없어진 페이지는 결과에서 빼고 인덱스 정리 대상으로 표시합니다.&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;retrieval_hash&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;가 달라진 semantic 후보는 결과에서 빼고 재색인합니다.&lt;br /&gt;
# 해시는 같고 리비전만 다르면 최신 Lesson을 사용합니다.&lt;br /&gt;
# 기존 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;SearchFilters&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 최신 Lesson에 다시 적용합니다.&lt;br /&gt;
# 안정적인 순서로 정렬한 뒤 페이지와 UTF-8 출력 제한을 적용합니다.&lt;br /&gt;
인덱스 지연으로 오래된 Lesson 내용이 반환되지는 않게 합니다. 대신 일부 semantic 후보가 잠시 빠질 수 있습니다. 이 경우 응답의 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;partial&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;을 참으로 표시합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;scanned&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;는 인덱스 내부 ANN node 수가 아니라 기준 저장소 재검증 전에 서비스가 검토한 후보 ID 수로 정의합니다. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;scan_limit&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;은 모든 후보 경로를 합친 엄격한 상한입니다.&lt;br /&gt;
&lt;br /&gt;
=== 인덱스 세대 ===&lt;br /&gt;
&lt;br /&gt;
다음 중 하나가 바뀌면 새 generation을 만듭니다.&lt;br /&gt;
&lt;br /&gt;
* 임베딩 모델 또는 artifact&lt;br /&gt;
* tokenizer&lt;br /&gt;
* 입력 정규화와 facet 구성&lt;br /&gt;
* token 예산 또는 chunk pooling&lt;br /&gt;
* 벡터 차원, 정규화 또는 거리 계산&lt;br /&gt;
* 순위 결합에 필요한 인덱스 구조&lt;br /&gt;
generation ID는 이 설정을 직렬화한 값의 불투명 해시로 만듭니다. 기존 인덱스를 제자리에서 덮어쓰지 않습니다.&lt;br /&gt;
&lt;br /&gt;
새 generation은 다음 순서로 활성화합니다.&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;BUILDING&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 상태로 빈 generation을 만듭니다.&lt;br /&gt;
# MediaWiki recent changes의 high-water mark를 기록합니다.&lt;br /&gt;
# 전체 Lesson을 최대 50개씩 읽어 색인합니다.&lt;br /&gt;
# build 중 생긴 변경을 high-water mark 이후부터 다시 적용합니다.&lt;br /&gt;
# 누락, orphan, 리비전과 모델 구성을 검사합니다.&lt;br /&gt;
# shadow query로 현재 generation과 품질·지연 시간을 비교합니다.&lt;br /&gt;
# 검사를 통과하면 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;READY&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;로 바꾸고 active alias를 원자적으로 전환합니다.&lt;br /&gt;
# 이전 generation은 cursor 유효 기간과 rollback 기간 동안 최신 상태로 유지합니다.&lt;br /&gt;
# rollback 기간이 끝나면 이전 generation을 삭제합니다.&lt;br /&gt;
새 generation에 문제가 있으면 active alias만 이전 값으로 되돌립니다. 파생 볼륨이 손상되면 MediaWiki 리비전에서 다시 만듭니다.&lt;br /&gt;
&lt;br /&gt;
=== 페이지 나누기와 cursor ===&lt;br /&gt;
&lt;br /&gt;
의미 검색을 활성화하면 cursor에는 요청 fingerprint와 함께 generation을 넣습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;lt;nowiki&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;v&amp;quot;: 2,&lt;br /&gt;
  &amp;quot;tool&amp;quot;: &amp;quot;find_analogies&amp;quot;,&lt;br /&gt;
  &amp;quot;fp&amp;quot;: &amp;quot;&amp;lt;request fingerprint&amp;gt;&amp;quot;,&lt;br /&gt;
  &amp;quot;gen&amp;quot;: &amp;quot;&amp;lt;retrieval generation&amp;gt;&amp;quot;,&lt;br /&gt;
  &amp;quot;pos&amp;quot;: 37,&lt;br /&gt;
  &amp;quot;exp&amp;quot;: 1780000000&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
fingerprint에는 query, filters, fields, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;min_score&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 검색 전략을 포함합니다. generation이 cursor 유효 기간 안에 남아 있으면 같은 결과 순서를 이어서 반환합니다. 해당 generation을 더 이상 제공할 수 없으면 첫 페이지부터 다시 조회하라는 cursor 오류를 반환합니다.&lt;br /&gt;
&lt;br /&gt;
출력 byte 제한 때문에 페이지 끝의 결과를 빼면 cursor도 실제로 반환한 마지막 후보 까지만 이동합니다. 현재 v1 cursor는 기존 검색 경로에서 한시적으로 읽고, v2 전환은 MCP 문서와 테스트를 함께 바꿉니다.&lt;br /&gt;
&lt;br /&gt;
=== 동기화와 일관성 ===&lt;br /&gt;
&lt;br /&gt;
일관성 보장은 다음처럼 나눕니다.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 항목 !! 보장&lt;br /&gt;
|-&lt;br /&gt;
| 반환한 Lesson 내용 || MediaWiki 최신 리비전으로 확인&lt;br /&gt;
|-&lt;br /&gt;
| 전체 텍스트 검색 || 저장 성공 뒤 바로 검색 가능&lt;br /&gt;
|-&lt;br /&gt;
| 의미 검색 완전성 || 짧은 지연을 허용하는 eventual consistency&lt;br /&gt;
|-&lt;br /&gt;
| relation 의미 || MediaWiki에 저장된 typed relation만 기준&lt;br /&gt;
|-&lt;br /&gt;
| 벡터 유사도 || 후보 추천이며 사실 또는 인과관계가 아님&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
MCP 쓰기는 항상 MediaWiki에 먼저 저장합니다. 성공 뒤 색인 갱신 힌트를 보내지만, 이 힌트가 실패해도 쓰기는 성공으로 처리합니다. recent changes poll과 전체 대조가 누락을 복구합니다.&lt;br /&gt;
&lt;br /&gt;
한 페이지의 임베딩이 반복해서 실패하면 제한된 횟수만 재시도합니다. 이후 해당 ID를 파생 오류 목록에 남기고 다음 변경 처리를 계속합니다. 한 페이지 때문에 전체 체크포인트가 멈추지 않게 합니다.&lt;br /&gt;
&lt;br /&gt;
=== 장애 동작 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 장애 !! 동작&lt;br /&gt;
|-&lt;br /&gt;
| query 임베딩 실패 || 전체 텍스트와 relation 후보로 fallback&lt;br /&gt;
|-&lt;br /&gt;
| 벡터 인덱스 연결 실패 || 전체 텍스트와 relation 후보로 fallback&lt;br /&gt;
|-&lt;br /&gt;
| 인덱스 지연 || hash가 다른 후보를 빼고 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;partial=true&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 표시&lt;br /&gt;
|-&lt;br /&gt;
| 일부 Lesson 임베딩 실패 || 성공한 항목을 활성화하고 실패 ID만 재시도&lt;br /&gt;
|-&lt;br /&gt;
| 파생 볼륨 손상 || MediaWiki에서 새 generation 재구축&lt;br /&gt;
|-&lt;br /&gt;
| recent changes 누락 || 주기적 전체 대조로 복구&lt;br /&gt;
|-&lt;br /&gt;
| 모델과 인덱스 구성 불일치 || readiness 실패, generation 활성화 금지&lt;br /&gt;
|-&lt;br /&gt;
| 새 모델 품질 저하 || active alias를 이전 generation으로 rollback&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
semantic 검색 장애만으로 공개 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;/healthz&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 실패시키지 않습니다. 전체 텍스트 fallback이 가능하면 MCP는 읽기 서비스를 계속 제공합니다. 상세 semantic readiness는 내부 상태와 제한된 로그·지표로 확인합니다.&lt;br /&gt;
&lt;br /&gt;
=== 단계별 확장 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! 단계 !! 구성 !! 다음 단계로 옮기는 기준&lt;br /&gt;
|-&lt;br /&gt;
| 0 || 현재 전체 텍스트와 lexical analogy || 평가 기준선 확보&lt;br /&gt;
|-&lt;br /&gt;
| 1 || &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mcp-http&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 안의 immutable exact vector generation || 품질과 비용 검증&lt;br /&gt;
|-&lt;br /&gt;
| 2 || backend network 전용 검색 sidecar와 파생 volume || 재구축 시간, 메모리 또는 p95 지연 목표 초과&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 공유 내부 검색 서비스와 여러 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;mcp-http&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 인스턴스 || 가용성 또는 동시 요청 때문에 수평 확장 필요&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
단계 1에서는 서버가 먼저 전체 텍스트 검색을 제공하고, 백그라운드에서 새 immutable generation을 만든 뒤 포인터를 교체합니다. Lesson 수, 벡터 차원, 실제 메모리, 재구축 시간과 exact cosine p95를 측정합니다. 단순한 Lesson 수만으로 제품을 바꾸지 않습니다.&lt;br /&gt;
&lt;br /&gt;
단계 2의 검색 sidecar도 도메인 전용 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;search_candidates&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 내부 상태 확인만 제공합니다. 벡터 저장 제품의 포트를 호스트나 외부 네트워크에 열지 않습니다.&lt;br /&gt;
&lt;br /&gt;
단계 3에서는 색인기를 한 인스턴스만 실행하거나 lease로 조정합니다. 모든 MCP 인스턴스는 같은 active generation을 읽습니다. 외부에는 계속 중앙 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;/mcp&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; 주소 하나만 제공합니다. 현재 프로세스 메모리에 있는 인증 상태도 수평 확장 전에 공유 상태 또는 안전한 routing으로 바꿔야 합니다.&lt;br /&gt;
&lt;br /&gt;
=== 보안과 개인정보 ===&lt;br /&gt;
&lt;br /&gt;
임베딩은 익명화된 값이 아닙니다. 원문과 같은 민감도로 다룹니다.&lt;br /&gt;
&lt;br /&gt;
* 기본 선택은 서버 안이나 내부 네트워크의 다국어 임베딩 모델입니다.&lt;br /&gt;
* 외부 임베딩 API는 서버 담당자가 전송, 보존, 학습 사용, region과 비용 조건을 확인한 뒤 명시적으로 켭니다.&lt;br /&gt;
* 검색 query는 아직 위키에 저장하지 않은 민감한 문제를 포함할 수 있습니다.&lt;br /&gt;
* 원문 query와 Lesson 본문을 로그에 남기지 않습니다.&lt;br /&gt;
* 지표에는 generation, revision, 지연 시간, 결과 수와 오류 코드만 남깁니다.&lt;br /&gt;
* 벡터 인덱스는 backend network에서만 접근합니다.&lt;br /&gt;
* 별도 호스트를 쓰면 서비스 인증과 전송 암호화를 적용합니다.&lt;br /&gt;
* 색인기 계정은 Lesson 읽기에 필요한 최소 권한만 가집니다.&lt;br /&gt;
* 모델과 tokenizer artifact는 digest로 고정합니다.&lt;br /&gt;
* 페이지가 삭제되거나 접근 정책이 바뀌면 active와 rollback generation에서 모두 제거합니다.&lt;br /&gt;
공개 위키에 있는 Lesson도 외부 임베딩 제공자에게 전송해도 된다고 자동으로 간주하지 않습니다. 검색 query는 위키보다 더 민감할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
=== 관측 항목 ===&lt;br /&gt;
&lt;br /&gt;
다음 값을 query 원문 없이 측정합니다.&lt;br /&gt;
&lt;br /&gt;
* recent changes 지연 시간과 처리하지 않은 revision 수&lt;br /&gt;
* 마지막 poll과 전체 대조 시각&lt;br /&gt;
* 기준 Lesson 수와 generation별 색인 수&lt;br /&gt;
* missing, orphan, stale hash 수&lt;br /&gt;
* embedding, 후보 검색, MediaWiki 재조회와 재정렬 지연 시간&lt;br /&gt;
* fallback 비율과 원인&lt;br /&gt;
* hash 불일치로 제거한 후보 수&lt;br /&gt;
* query당 후보 수와 canonical filter로 제거한 수&lt;br /&gt;
* generation build 진행률, 실패, 재시도와 파생 오류 수&lt;br /&gt;
* active model, 입력 구성과 ranking version&lt;br /&gt;
* cursor 무효화와 출력 byte 제한 발생 비율&lt;br /&gt;
=== 품질 평가 ===&lt;br /&gt;
&lt;br /&gt;
벡터 검색은 기술적으로 동작하는 것만으로 활성화하지 않습니다. 한국어, 영어와 혼합 query를 포함한 작은 고정 평가셋을 먼저 만듭니다.&lt;br /&gt;
&lt;br /&gt;
다음 항목을 현재 검색과 비교합니다.&lt;br /&gt;
&lt;br /&gt;
* top 5 안에서 실제로 적용 가능한 후보 비율&lt;br /&gt;
* 다른 분야이지만 도움이 되는 후보 비율&lt;br /&gt;
* 저장된 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;analogous_to&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; relation의 회수율&lt;br /&gt;
* 정확한 오류 문자열과 고유 명칭 검색의 회귀 여부&lt;br /&gt;
* 같은 해결책이 반복되는 비율&lt;br /&gt;
* 근거와 적용 조건이 부족한 후보 비율&lt;br /&gt;
* p50, p95 지연 시간과 fallback 비율&lt;br /&gt;
평가용 query 원문에 비밀이나 개인 정보를 넣지 않습니다. 실제 사용 기록을 저장소에 그대로 커밋하지 않습니다. 새 generation은 품질 기준, 출력 제한과 장애 fallback 테스트를 모두 통과한 뒤 활성화합니다.&lt;br /&gt;
&lt;br /&gt;
=== 구현 순서 ===&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;LessonSnapshot&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;과 MediaWiki 일괄 조회를 추가합니다.&lt;br /&gt;
# 현재 동작을 감싼 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;RetrievalBackend&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;를 만들고 결과가 같은지 확인합니다.&lt;br /&gt;
# 고정 벡터를 쓰는 in-memory fake와 domain-level 테스트를 추가합니다.&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;ProjectionBuilder&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, 교체 가능한 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;EmbeddingProvider&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 exact index를 추가합니다.&lt;br /&gt;
# 의미 결과를 반환하지 않는 shadow mode에서 품질과 비용을 측정합니다.&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;find_analogies&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;에 relation, lexical, semantic 결합을 적용합니다.&lt;br /&gt;
# 평가가 끝나면 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;search_lessons&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;에 hybrid 검색을 적용합니다.&lt;br /&gt;
# 실제 지표가 전환 기준을 넘을 때만 sidecar 또는 공유 검색 서비스로 옮깁니다.&lt;br /&gt;
각 단계에서 기존 최대 20건, cursor, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;partial&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;scanned&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;scan_limit&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;와 UTF-8 직렬화 상한을 유지합니다. 모델, 응답 또는 검색 정책이 바뀌면 MCP 모델, 예시와 문서를 함께 수정합니다.&lt;/div&gt;</summary>
		<author><name>S3Admin</name></author>
	</entry>
</feed>