사실탐색기AGS권위 기반 대체에이전트 메모리지식 유지공적 기록이중 시간기권인증서한국어 NLPPostgres설계
약속은 사실이 아니다 — 코어닷투데이 사실탐색기 v2 설계
에이전트 기억 엔진 다섯 개를 해부하고 나서 남은 질문은 하나였다. 공적 기록에서 '지금 참인 것'은 무엇이고, 누가 그것을 바꿀 수 있는가. 최신 문장이 이기는 기억은 개인 비서에게는 맞지만, 국장의 약속·뉴스의 오보·공보의 확정이 섞인 세계에서는 틀린 답을 자신 있게 낸다. 코어닷투데이 사실탐색기 v2는 문장을 클레임과 명제로 가르고, 권위 격자(전언 ≺ 1인칭 ≺ 공식)와 발화행위로 '닫을 자격'을 따지는 AGS(권위 기반 대체)를 중심에 둔다. 닫는 조건 다섯 가지, 도착 순서와 무관한 키별 재생, 허용성 세 축과 인증서로 정하는 기권, 지우지 않고 가리는 봉인, 세상 사실과 사용자 기억의 분리, 그리고 한국어를 1급 시민으로 다루는 결정론 계층까지 — 설계의 뼈대를 위젯 4개로 직접 돌려 본다.
코어닷투데이2026-10-2541분
프롤로그 — 기억 엔진이 놓친 질문 하나
지난주 코어닷투데이는 에이전트 기억 엔진 다섯 개를 코드 수준에서 해부했다. Hindsight, Mem0, Graphiti, Letta, Cognee는 저마다 영리한 분업을 갖고 있었다. 엔티티는 글자 조각으로 합치고, 순위는 등수로 섞고, 쉬운 판단은 1초짜리 결정 모델에 맡긴다. 그런데 다섯 엔진이 공통으로 답하지 않는 질문이 하나 있었다.
"새 문장이 옛 문장과 다르면, 누가 이기는가?"
거의 모든 엔진의 답은 "나중에 들어온 쪽"이다. Graphiti는 새 엣지가 옛 엣지를 무효화하고, Mem0 v1은 UPDATE로 덮어썼으며 v2는 아예 추가만 한다. 개인 비서라면 이것이 맞다. 사용자가 "이제 커피 말고 차를 마셔"라고 하면 어제의 선호는 끝난 것이다. 사용자 자신이 자기 선호의 최고 권위이기 때문이다.
공적 기록의 세계는 다르다. 청년 월세 지원 사업 하나를 따라가 보자.
3월신문 기사: "시, 청년 월세 월 20만원 지원 추진." — 기자가 전한 의향이다.
5월시의회 회의록, 국장 답변: "20만원 수준으로 편성하겠습니다." — 공무원 본인의 약속이다.
9월시보에 조례 공포: "지원 금액은 월 30만원으로 한다." — 효력이 있는 공식 확정이다.
10월고시공고: "청년 월세 월 30만원, 10월 접수." — 공식 기록이 같은 값을 지지한다.
11월다른 신문 기사: "청년 월세 월 20만원 지원 중." — 옛 숫자를 다시 쓴 오보다.
"지금 얼마인가?"라고 물으면 최신 문장이 이기는 기억은 20만원이라고 답한다. 11월 기사가 가장 최근이기 때문이다. 3월이나 5월에 물었다면 아직 아무것도 정해지지 않았는데도 20만원이라고 답했을 것이다. 약속을 사실로 승격했기 때문이다.
G1과 G2가 핵심이다. 이 둘은 코어닷투데이가 준비 중인 논문의 연산자 AGS(Authority-Governed Supersession, 권위 기반 대체)가 정면으로 다룬다. 사실탐색기 v2는 이 연산자를 바꾸지 않고 중심에 놓는다. 나머지 넷은 논문 범위 밖이라 v2에서 새로 설계했다. 지난 3월의 서비스 제안서에는 저장소가 네 개(Postgres·Neo4j·RDF·OpenSearch)였고 규칙 엔진 레인도 따로 있었는데, v2는 이것을 Postgres 하나로 줄였다. 이유는 10절에서 다룬다.
2. 다섯 원칙
설계 전체를 다섯 문장으로 줄이면 이렇다. 뒤의 모든 결정이 이 다섯 줄에서 나온다.
1
LLM은 제안하고 코드가 결정한다
이름 해석, 시간 해석, 권위 부여, 닫기, 마스킹은 전부 코드다. 논문 준비 과정의 예비 실험에서 규칙을 프롬프트에 넣었더니 추출 재현율이 크게 떨어졌고, 같은 규칙을 코드로 빼자 재현율은 지키면서 정밀도가 올랐다.
2
저장소는 둘, 답변은 하나
세상 사실은 AGS로, 사용자 기억은 최신 우선으로 유지한다. 최신 우선이 틀린 것이 아니라 개인 비서에게만 맞는 기본값이다. 두 정책을 한 테이블에 섞지 않는다.
3
한국어는 결정론 계층의 1급 시민이다
해부 글의 결론은 "한국어에서는 LLM이 맡은 단계만 잘 되고 결정론 단계는 꺼지거나 헛돈다"였다. v2는 거꾸로다. 결정론 단계가 한국어를 먼저 처리해 LLM이 메워야 할 몫을 줄인다.
4
비파괴와 삭제권은 봉인으로 양립한다
사실은 지우지 않고 가린다. 사람의 개인정보는 법이 요구하면 지우되, "여기 무언가 있었고 언제 지웠다"는 봉인 기록은 남긴다.
5
상수는 설정이 아니라 측정값이다
RRF k, 유사도 문턱, 회차 간격 같은 숫자는 자기 데이터 회귀 세트로 고르고, 고른 값과 근거를 버전과 함께 남긴다. 문서는 코드에서 생성한다.
3. 클레임과 명제를 가른다
문장은 사실이 아니다
Hindsight는 세상 사실을 "누군가 그렇게 말한 것"이라고 정의한다. 옳은 관찰이다. 다만 Hindsight도 그 '누군가'를 구분해 저장하지는 않는다. 사실탐색기 v2는 이 관찰을 데이터 모델로 옮긴다. 기록에서 뽑은 모든 것은 먼저 클레임이 된다.
층
클레임 (claim)
명제 (proposition)
무엇인가
누가, 어떤 행위로, 어떤 권위로 무엇을 말했다
(주어, 술어, 값)이 언제부터 언제까지 참이다
예
5월 2일 국장이 회의록에서 "20만원으로 하겠다"고 약속했다
(청년 월세 지원, 지원금액, 30만원)이 9월 23일부터 유효하다
지워지나
지워지지 않는다. 근거로 영원히 남는다
지워지지 않고 대체(superseded)될 뿐이다
필드
화자, 발화행위, κ(권위), 효력일, 인용 스팬
유효 구간, 기록 구간, 대체한 명제, 상태, 등급, 인증서
명제는 클레임 여러 개의 지지를 받는다. 공보와 공고가 같은 30만원을 말하면 명제는 하나이고 근거가 둘이다. 반대로 클레임이 있다고 해서 명제가 생기는 것은 아니다. 명제가 되려면 두 관문을 넘어야 한다.
첫 번째 관문: 발화행위
"20만원으로 하겠다"는 약속이고, "20만원으로 했다"는 보고다. "20만원은 너무 적지 않습니까?"는 질의이고, "늘려야 한다"는 의견이다. 사실탐색기는 클레임마다 발화행위를 붙이고, 단정(assertive)인 클레임만 사실 후보로 받는다.
논문의 AGS는 이 조건(C5)을 '닫을 때'만 확인한다. v2는 입구에서도 확인한다. 비단정 클레임이 후보 집합에 들어오면 같은 열쇠의 후보 자체가 오염되기 때문이다. 약속은 버리지 않는다. 약속은 modality=promise로 따로 저장해 나중에 이행 여부를 추적한다.
한국어에서 까다로운 경우가 보도된 의향이다. "시가 지원금을 올린다고 밝혔다"는 현재형 인용이라 약속이고, "올렸다고 밝혔다"는 과거형이라 보고다. 직접 인용이 있으면 인용 안쪽을 먼저 분류한다. 이 규칙이 없으면 "추진한다고 밝혔다"는 기사가 그대로 사실이 된다. 해부 글의 엔진들이 빠진 함정이 바로 이것이다.
격자는 세 단계로 고정되어 있고, 코퍼스마다 바뀌는 것은 장르를 어느 단계에 둘지뿐이다. 이 매핑을 담은 것이 수단 표(instrument table)다.
코퍼스
공식
1인칭
전언
효력일 규칙
지방의회
공보·공고·의장 선포·의사일정
회의록 발언·직접 인용
뉴스 서술
조례=공포일, 예산=본회의 의결일, 공고=명시일
기업·금융
DART 공시·거래소 공시
보도자료·IR 발언
뉴스
공시 효력일, 이사회 결의일
법률
법령(관보)·판결문
입법예고·보도자료
뉴스·해설
시행일, 선고일
중앙정부
관보·고시
브리핑 직접 인용
뉴스
시행일
수단 표에는 효력일 규칙도 들어 있다. 같은 조례라도 효력일을 공포일로 볼지 시행일로 볼지에 따라 "9월 28일 기준"의 답이 달라진다(9월 25일 공포·10월 1일 시행이면 공포일 규칙은 '유효', 시행일 규칙은 '아직'). 이 선택은 코드가 아니라 표의 한 칸이다. 2편에서는 같은 표에 회차 규칙이라는 열이 하나 더 생긴다.
4. AGS — 닫는 조건 다섯 가지
이제 핵심 연산이다. 새 클레임이 같은 열쇠의 옛 명제와 다른 값을 말할 때, 옛 명제를 닫으려면(대체하려면) 다섯 조건을 모두 통과해야 한다.
C1 열쇠같은 (주어, 술어)인가. 2편에서 여기에 회차가 붙는다.
C2 시간유효 구간이 겹치는가. 안 겹치면 둘 다 각자 그때의 사실, 곧 이력이다.
C3 값값이 정말 다른가. "만 19세부터 34세까지"와 "만 19세~34세"는 같은 값이다. 수량은 정확히 비교하고, 텍스트는 정규화·포함 가드·판정 모델 순으로 본다.
하나라도 실패하면 닫지 않는다. 값이 다른데 C4가 실패하면 반박 큐(contradicted)로 가서 둘 다 남고 사람이 본다. 뉴스가 공보와 다른 숫자를 말한 경우가 전형적이다. 아래 점검기에서 사례를 바꿔 가며 어느 조건에서 걸리는지 확인해 보자.
왜 최신 우선보다 나쁠 수 없는가
다섯 조건은 최신 우선 규칙에 조건을 더한 것이다. 최신 우선은 "값이 다르면 닫는다"이고, AGS는 "값이 다르고, 권위가 충분하고, 단정이면 닫는다"이다. 조건이 늘어나면 닫는 경우는 줄어들 수밖에 없다. 그래서 AGS의 잘못된 닫기는 같은 입력에서 최신 우선의 잘못된 닫기보다 많을 수 없다(단조성). 대가는 반박 큐다. 닫지 못한 충돌은 사람에게 넘어간다. 그 큐가 얼마나 쌓이는지가 운영 지표가 되고, 2편에서 실제 숫자를 본다.
같은 권위 안의 정정
공식이 공식을 닫는 경우는 흔하다. DART의 [기재정정] 공시가 원 공시의 시가배당률 3.39%를 4.06%로 바꾸는 식이다. C4는 '이상'이므로 같은 권위끼리도 닫을 수 있다. 1인칭끼리는 닫지 못한다. 두 의원이 서로 다른 착공 연도를 말하면 둘 다 1인칭이라 θ_Close에 못 미치고, 반박 큐로 간다. 회의록에서 사람마다 기억이 다른 것은 흔한 일이고, 그것을 기계가 정리할 근거는 없다.
5. 순서가 바뀌어도 같은 답 — 키별 재생
현실의 수집기는 기록을 날짜순으로 가져오지 않는다. 오늘 수집한 뉴스가 지난주 공보보다 먼저 들어오고, 몇 달 전 공시의 정정이 뒤늦게 들어온다. 도착할 때마다 옛 명제를 닫는 방식은 결과가 도착 순서에 따라 바뀐다.
사실탐색기 v2는 키별 재생으로 이 문제를 없앤다. 클레임이 들어오면 그 열쇠의 클레임 집합에 넣고, 그 열쇠만 효력일 순으로 처음부터 다시 돈다. 결과는 도착 순서의 함수가 아니라 클레임 집합의 함수가 된다. 뒤늦게 도착한 공보가 앞선 뉴스를 반박하는 경우도, 역순으로 적재하는 경우도 따로 처리하지 않아도 맞는다. 테스트는 무작위 도착 순서 450회로 이 성질을 확인한다.
두 개의 시간
재생하면 명제의 판본이 바뀐다. 9월 23일에 적재된 30만원 명제는 다음 날 공보가 하나 더 들어와도 같은 명제지만, 뉴스 하나가 들어와 재생이 다시 돌면 그 순간의 판본과 다음 판본이 생긴다. v2는 옛 판본을 지우지 않고 기록 만료 시각(txn_exp)을 찍어 남긴다. 명제에는 두 개의 시간축이 있다.
유효 시간(valid): 세상에서 그 사실이 참이었던 구간. "청년 월세는 9월 23일부터 30만원"
기록 시간(transaction): 시스템이 그렇게 믿었던 구간. "우리는 10월 2일부터 그렇게 기록했다"
이중 시간(bitemporal) 모델 덕분에 "8월 기준으로 얼마였나"(유효 시간 질문)와 "지난주 우리 시스템은 뭐라고 답했을까"(기록 시간 질문)를 모두 답할 수 있다. 감사와 재현을 하려면 뒤의 질문이 꼭 필요하다.
유효 시작은 최고 권위의 날짜
작아 보이지만 중요한 결정이 하나 더 있다. 뉴스(3월)가 30만원을 먼저 말하고 공보(9월)가 확정하면, 명제는 언제부터 유효한가? 뉴스 날짜를 쓰면 공식 사실이 3월부터 참이었던 것처럼 되고, 그 사이의 다른 값을 3월에 닫아 버린다. v2는 최고 권위 단정 클레임의 가장 이른 효력일을 유효 시작으로 쓴다. 뉴스는 그 명제의 근거로 남되 날짜를 정하지 않는다.
6. 답하거나 기권하거나 — 허용성과 인증서
명제를 잘 유지해도 답변에서 무너질 수 있다. Graphiti는 무효화된 사실을 기본으로 함께 돌려주고, 대부분의 엔진은 순위가 가장 높은 후보를 그대로 답한다. v2의 답변 레인은 순서를 바꾼다. 허용성 먼저, 순위 나중.
0이 하나라도 있으면 후보가 아니다. 남은 후보 중 1이 가장 많은 것을 고르고, 그 세 축이 곧 인증서가 된다. 모두 1이면 complete, ?가 섞이면 partial(답하되 "1인칭 주장만 있다"고 적는다), 남은 후보가 없으면 missing이다. missing이면 기권하고 무엇이 빠졌는지를 이름 짓는다. "첫 공식 기록보다 앞선 시점이다", "뉴스만 있다", "술어 사전에 없다" 같은 식이다.
기권을 정하는 것은 라우터가 아니라 인증서라는 점이 중요하다. 라우터는 질문을 일곱 유형(단순 사실, as-of, 이력, 약속 이행, 교차 출처, 다중 홉, 근거 부족)으로 나누고 가장 싼 경로부터 간다. 슬롯 조회(주어·술어로 바로 찾기)가 답하면 벡터 검색도 순위 융합도 하지 않는다. 슬롯 조회가 실패할 때만 하이브리드 검색으로 넘어간다. 어느 경로로 왔든 마지막 관문은 허용성이다. 그래서 순위가 높아도 허용되지 않는 후보는 답이 될 수 없다.
7. 한국어 결정론 계층 — K 레인
해부 글에서 가장 아팠던 장면은 한국어였다. Hindsight의 이름 병합 공식은 후보 게이트로 trigram 유사도 0.3 이상을 요구하는데, '김민지'와 '민지'의 trigram 유사도는 0.17이다. 같은 사람이 영원히 두 사람으로 남는다. 시간 표현 해석기 dateparser는 한국어에서 '어제'만 잡았다. v2는 이 계층을 한국어 문법으로 다시 썼다.
후보 게이트: 이름 부분이 완전히 같거나('민지' = '김민지'의 이름), 음절 bigram Jaccard가 0.5 이상이거나(김민지 {김민, 민지} 대 민지 {민지} = 0.5), 승인된 별칭이면 후보가 된다.
맥락 점수: 같은 문서, 같은 이슈, 소속·직함, 시간 창. 동명이인은 (이름, 소속, 시기)로 가른다.
구현하면서 바로 부딪힌 함정이 있다. '같은 문서에 나오면 가점'이라는 규칙이 김민지 시의원과 김민지 교수를 자동으로 합쳐 버렸다. 그래서 직함을 10개 계열로 묶고, 계열이 다르면 큰 감점(−0.50)을 주도록 바꿨다. 병합은 네 상태(exact_same, alias_of, possible_same, related_but_distinct)로 표현하고, 자동 병합은 앞의 둘만 한다. possible_same은 사람이 결정한다.
시간
추출 쪽과 질의 쪽이 같은 모듈로 시간 표현을 푼다. 기사의 "지난달"과 질문의 "지난달"이 다른 규칙으로 풀리면 답이 어긋나기 때문이다.
부류
예
해석
지시어
어제, 지난주, 이번 달, 올해, 상반기, 1분기
기록 기준일에서 구간 + 단위
상대량
두 달 전, 3주 후, 열흘 전, 지난 3년간
구간
절대
2025년 3월, 3월 12일, 2025년 여름
구간 + 단위
회기
제320회 정례회, 제3차 본회의
회기 달력 표 조회
기한
9월 안에, 올해 연말까지, 10월 초, 32년에서 33년 사이
구간 끝 = 약속 기한
해석 불가
조만간, 최근, 곧
unknown (추측 금지)
'기한' 줄은 설계 문서에 없었다. 실제 회의록의 약속 값을 풀어 보니 16개 중 15개가 풀리지 않아 나중에 더했다. "9월 안에", "한 10월 정도", "내년도 10월" 같은 완곡한 표현이 대부분이었다. 2편에서 이 기한으로 약속 이행을 추적한다.
개인정보와 사인(私人)
주민등록번호(검증식 포함), 휴대전화, 사업자등록번호(체크섬), 계좌번호, 차량번호, 여권번호 등 한국형 비밀 패턴을 유니코드 경계로 쓰고 기본으로 켠다. Hindsight의 방어 정규식은 영어권 패턴과 ASCII 기준 경계로 쓰여 있고 기본값도 꺼져 있었다. 공고에 나오는 수상자·합격자·민원인은 '사인'으로 표시하고 누가(who) 슬롯을 채우지 않는다. 의원·공무원·기관은 공인이므로 기록에 적힌 대로 둔다.
8. 지우지 않고 가린다 — 봉인
AGS에는 비파괴 불변식이 있다. 사실은 대체될 뿐 지워지지 않는다. 그런데 개인정보보호법은 삭제를 요구할 수 있다. 사실에 대한 불변식과 사람에 대한 의무가 정면으로 부딪힌다. v2는 이 둘을 봉인(tombstone)으로 가른다.
추적그 스팬을 근거로 하는 모든 파생물을 따라간다. 이벤트 슬롯 → 클레임 → 명제 → 이슈 카드 → 답변 캐시. 근거 전체성 덕에 이 사슬은 외래키로 닫혀 있다.
재생그 스팬이 근거인 클레임을 후보에서 빼고 열쇠를 재생한다. 다른 근거가 남은 사실은 유지되고 유효 시작만 남은 근거의 날짜로 밀린다.
가림원문을 같은 길이의 ■로 바꾼다. 길이를 줄이면 다른 클레임의 인용 위치가 틀어진다.
봉인무엇을, 언제, 어떤 근거로, 누가 지웠는지를 봉인 행으로 남긴다. 검색·답변에는 노출되지 않고 감사에만 쓴다. 끝에 남은 참조가 0인지 검사한다.
Graphiti에는 한 가지 함정이 있다. 에피소드를 지워도 그 에피소드가 무효화했던 옛 엣지가 되살아나지 않는다. v2는 반대 방향을 조심한다. 봉인된 명제가 닫았던 옛 명제를 자동으로 되살리지 않고 반박 큐로 보낸다. 되살릴지는 사람이 정한다. 사람의 결정도 AGS의 언어로 적는다. "새 주장이 맞다"는 공식 권위의 리뷰 클레임을 하나 더 넣는 것이고, "틀리다"는 그 클레임을 후보에서 빼는 것이다. 명제 상태를 직접 바꾸는 API는 없다. 그래서 사람의 결정도 재생을 거쳐 살아남는다.
망각에 대한 답도 같은 방향이다. v2는 지우지 않고 보지 않는다. 명제에 hot(현행이고 최근 바뀜)·warm(현행이고 그대로)·cold(대체·철회·닫힌 이슈) 등급을 붙이고, '지금' 질의에서는 cold를 후보에서 뺀다. 이력 질의만 cold까지 연다. 등급은 접근 빈도가 아니라 상태와 마지막 변화 시각으로 정한다. 개인화가 아니라 사실의 생애주기이기 때문이다.
9. 두 저장소 — 세상 사실과 사용자 기억
사실탐색기도 사용자의 말을 기억해야 한다. "나는 부산진구 소식만 봐", "숫자는 억 단위로 보여 줘" 같은 선호와 지시다. 이것까지 AGS에 넣으면 안 된다. 사용자의 선호에는 권위 격자가 없다. 사용자 자신이 최고 권위이고, 나중 말이 이긴다.
그래서 v2는 사용자 기억을 U 레인으로 따로 둔다. 테넌트별 테이블에 최신 우선으로 저장하고, 수명(TTL)과 용량 상한(기본 200개, 오래된 것부터 삭제)을 둔다. U 레인은 답변 때만 합류한다.
같은 열쇠에 세상 사실이 있으면 메모는 "사용자 메모와 다르다/같다"는 주석으로만 붙고 값은 바뀌지 않는다. 사용자가 "그 위원회 정원 10명이야"라고 해도 공보가 22명이면 답은 22명이다.
세상 사실이 없을 때만 메모 값을 partial 인증서로 답하고, "사실이 아니라 사용자 주장"이라고 적는다.
이력·약속·교차 출처 질의에는 합류하지 않는다.
이 분리가 없으면 해부 글의 엔진들처럼 개인 비서용 규칙이 공적 사실을 덮는다. 반대로 개인 비서에 AGS를 쓰면 사용자가 "이제 차를 마셔"라고 해도 "1인칭 주장은 닫을 수 없다"며 커피를 고집하게 된다. 두 정책은 각자의 자리에서만 맞다.
10. Postgres 하나 — 배관
3월 제안서에는 Postgres, Neo4j, RDF 저장소, OpenSearch에 규칙 엔진 레인까지 있었다. v2는 이것을 Postgres 하나로 줄였다. 이유는 세 가지다. 해부 글의 여섯 엔진 가운데 배관이 가장 안정적이었던 것이 "Postgres 하나"를 고른 Hindsight였다. AGS 알고리즘이 전부 SQL과 코드로 쓰인다. 그리고 투영 레인이 늘수록 문서와 코드가 어긋난다(G6).
K 레인한국어 결정론이름 · 시간 · 조사 · 개인정보 · 사인 태깅
B 추출LLM 제안 → 코드 결정6슬롯 + 인용 스팬, 술어 사전, 권위 부여
C 표현이벤트 · 클레임 · 명제근거 스팬, 발화행위, κ, 이중 시간
D 유지AGS + 봉인 + 등급C1~C5, 키별 재생, 반박 큐
E 답변라우터 → 허용성 → 인증서가장 싼 경로부터, 답 또는 기권
U 레인사용자 기억최신 우선, 답변 때만 합류
Postgres 판에서 고른 구체적인 장치는 다음과 같다.
엔진은 하나. 순수 논리(재생·닫기·봉인·리뷰)는 메모리 저장소가 하고, Postgres 판은 생성할 때 DB를 되읽은 뒤 바뀐 것만 적는다. 같은 기록을 세 번 적재해도 판본이 늘지 않는다.
열쇠 단위 잠금. 열쇠를 재생할 때 pg_advisory_xact_lock(hashtext(key))를 잡는다. 같은 열쇠를 두 작업자가 동시에 재생하지 않는다.
명제의 키는 (id, version). 같은 순간에 같은 명제의 판본이 둘 생길 수 있어 기록 시각은 키가 될 수 없다.
결정론 id. 기록·단위·이벤트·클레임·명제 id가 파이프라인에서 결정되므로 모든 적재가 ON CONFLICT로 멱등이다.
작업 큐.FOR UPDATE SKIP LOCKED, 한 단위 = 한 작업, 멱등 키 (내용 해시, 단위 번호, 추출기 버전). 추출기 버전이 바뀌면 같은 단위를 다시 돌린다. AGS가 순서와 무관하므로 작업자 여럿의 결과가 한 프로세스의 결과와 같다.
벡터. pgvector로 엔티티 정규형과 술어 사전을 임베딩한다. 별도 벡터 DB는 없다.
상수 하나하나에도 원칙 5가 적용된다. RRF k, 유사도 문턱, 답변 문턱은 retrieval_config 표에 버전과 근거(어느 회귀 세트로 골랐는지)와 함께 묶여 있다. 수단 표·술어 사전·발화행위 목록은 코드에서 문서를 생성하고, CI가 문서와 코드가 다르면 실패시킨다. 해부 글에서 찾은 문서·코드 불일치 15건이 모두 "주석이 코드를 못 따라간" 경우였기 때문이다.
11. 합성 코퍼스에서 먼저 확인한 것
설계는 다섯 단계(P0~P4)로 구현했다. 실제 코퍼스를 넣기 전에 AGS의 시나리오를 덮는 합성 코퍼스로 먼저 확인했다. 약속, 보도, 공포, 공고, 정정, 오보가 모두 들어 있는 부산 시나리오 8건과 기업 공시 시나리오 5건이다.
부산 합성 · 최신 우선
5/9 틀림
부산 합성 · AGS
0/9
DART 합성 · 최신 우선
2/5 틀림
DART 합성 · AGS
0/5
바의 길이는 낡거나 틀린 사실을 답한 비율이다(as-of 질문 기준). 최신 우선이 틀린 다섯 건은 모두 3월의 약속·보도와 11월의 오보 때문이었다. AGS는 부산 18문항, DART 8문항을 모두 맞혔고, 기권해야 할 질문에서는 정확히 기권했다.
더 중요한 확인은 격자 재사용이었다. 두 번째 코퍼스(DART)를 붙일 때 AGS 코드는 한 줄도 바뀌지 않았다. 바뀐 것은 수단 표(공시=공식, 보도자료=1인칭, 뉴스=전언), 술어 사전(주당배당금·배당기준일 등 10개), 수량 단위('주당', '보통주')뿐이었다. 같은 권위 안의 오보도 확인했다. 뉴스가 "500원 결정"이라고 쓴 뒤 공시가 600원을 내면, 공시가 뉴스를 닫는다(공식 ⪰ 전언). 공시 전 시점에 물으면 사실탐색기는 500원이라고 답하지 않고, "공적 사실이 아직 없다"며 기권한다.
이 단계가 끝났을 때 회귀 테스트는 218건이었다. 깔끔했다. 너무 깔끔해서 오히려 불안했다. 합성 코퍼스는 우리가 상상한 실패만 담고 있기 때문이다.
12. 이 설계가 아직 모르는 것
설계 문서를 마무리하며 적어 둔 미지수는 세 가지였다.
같은 권위 안의 충돌. 운영에서 가장 자주 만날 경우는 권위가 같은 뉴스 둘이 다른 숫자를 말하는 것이다. 둘 다 전언이라 아무도 닫지 못하고 반박 큐에 쌓인다. 근거 수와 취재원의 독립성으로 2차 순서를 두는 타이브레이커를 θ_Close 아래에 넣으면 단조성을 해치지 않는다. 다만 큐가 실제로 쌓이는지부터 재야 한다.
실제 기록의 추출 품질. 합성 코퍼스는 AGS의 시나리오는 덮지만 추출 품질은 재지 않는다. 회의록의 긴 발언, 표로 된 공시, 글자가 이미지인 PDF는 어떤가.
상수. RRF k, 유사도 문턱, 판정 모델 확신도 문턱은 실제 코퍼스가 있어야 고를 수 있다.
실제 코퍼스는 이 세 가지보다 훨씬 기본적인 곳을 찔렀다. AGS의 다섯 조건 가운데 우리가 가장 당연하게 여긴 C1, 열쇠였다. 서로 다른 조례의 "이 조례"가 같은 열쇠가 되어 시행일이 서로를 덮었고, 분기배당 네 번이 한 열쇠가 되어 1분기 기준일이 3분기 기준일에 닫혔다. 그 이야기는 2편 「실패는 열쇠에서 났다」에서 이어진다.