지난 글에서는 검색을 판정 경로에 넣기 전에 어디서 못 찾는지부터 쟀다. 이번 PR #24가 그 검색을 판정 경로에 넣는 M5-B다. 모델을 부르기 전에 공고의 요건 문장으로 프로필의 근거 문장 조각을 검색해 모델 입력에 붙이고, 판정의 주장마다 그 조각을 인용하게 했다. 모델 호출은 없었고 합성 입력으로만 확인했다.
이 글의 중심은 기능이 아니라 리뷰가 잡은 결함이다. 인용을 붙이자 인용 하나의 경계 오류가 판정 전체를 실패시킬 수 있게 됐고, 그 결함은 이틀 전 글의 교훈이 다른 자리에서 되풀이된 것이었다.
인용은 결과가 아니라 주장에 붙는다
처음 계획 초안은 결과 전체에 조각 참조 배열을 붙이는 것이었다. M5 계획 리뷰에서 이 설계로는 어떤 인용이 어떤 주장을 지지하는지 알 수 없다는 지적이 나왔고, 인용을 주장 단위로 옮겼다. 판정에는 주장이 세 종류 있다. 근거가 있는 요건 매칭, 부족한 요건, 결격 사유. 인용은 이 셋 중 하나의 id를 가리킨다.
id 규칙은 주장 종류에 따라 다르다. 매칭 주장은 요건 id와 정규화한 요건 문장과 근거 문장을 배열로 직렬화해 해시한다. 계획 리뷰에서 문자열을 이어 붙이는 수식이 틀렸다고 잡혔던 그 자리다. 부족과 결격은 요건 id가 있으면 req: 뒤에 id, 없으면 text: 뒤에 정규화한 요건 문장이다. M1 정책이 부족을 결격으로 승격시켜도 id가 바뀌지 않도록 한 규칙이고, 모델은 이 id를 만들지 않는다. 모델은 주장마다 참조를 붙일 뿐이고 서버가 id를 계산한다.
정책이 주장을 지우거나 합칠 때 인용이 따라가는 것이 이번 슬라이스의 핵심 규칙이다. 기존 M1 정책은 근거 없는 매칭을 지운다. 지워진 매칭의 인용은 함께 사라진다. 정책은 id 없는 결격 사유를 같은 요건의 승격된 부족과 하나로 합친다. 합쳐진 쪽의 인용은 살아남은 주장의 id로 옮겨 붙는다. 둘 다 고정 문구로 기록된다. 리뷰에서 대조군으로 요청했던 세 경우가 테스트에 있다. 같은 요건에 다른 근거, id 없는 다른 요건, 그리고 id 있는 부족과 id 없는 결격의 병합.
해시는 식별자이고, 소속은 흔적이 정한다
조각 참조는 chunk: 뒤에 조각 id다. 지난 글에서 정정한 대로 이 id는 내용 해시라 안정적인 식별자일 뿐이고, 인용된 조각이 이번 실행에 있었는지는 해시가 아니라 이번 실행의 검색 흔적이 정한다. 검증기는 흔적에 있는 id만 풀고, 흔적이 없으면 모든 조각 참조를 닫는다. 인용문은 조각 텍스트와 정규화 후 같아야 한다. 부분 문자열은 안 된다. B1과 같은 규칙이다.
실패하면 어떻게 되는가가 이 슬라이스의 계약이다. 무효한 인용은 버리고, 고정 문구를 남기고, 확신도를 낮춘다. 판정과 근거와 부족과 결격은 건드리지 않는다. 계획서의 문장은 “never fails the fit”이었다.
인용 하나의 경계 오류가 판정 전체를 실패시켰다
리뷰어는 정상 초안에 네 가지를 넣어 봤다. 인용문 2,001자, 참조 경로 161자, 인용 65개, 그리고 id 없는 196자 요건에 붙은 인용 하나. 넷 다 생성 계약은 통과했다. 그리고 넷 다 읽기 계약으로 바꾸는 변환 단계에서 예외를 냈다. 이 예외가 전파되면 도구는 새 판정 결과를 반환하지 못하고, 추천 배치는 해당 공고를 분석 실패로 처리한다. 리뷰어가 직접 재현한 것은 변환 함수의 예외이고, 도구와 배치의 영향은 코드 경로로 확인했다.
원인은 사용 확인 후속 글에서 정리한 읽기 계약과 생성 계약의 차이다. 이번 앱은 모델에게 보내는 구조화 출력 스키마를 최소로 두어 인용의 길이와 개수에 상한을 두지 않았고, 읽기 스키마에만 두었다. 그때는 점수의 정수 제약을 읽기 계약에 넣었다가 옛 기록을 못 읽게 된 문제였다. 이번에는 반대 방향이다. 읽기 계약의 경계를 생성 결과에 통째로 적용하니, 항목 하나가 경계를 넘으면 전체가 실패했다.
고치는 방법은 경계를 항목 단위로 옮기는 것이었다. 인용마다 읽기 계약으로 검사해서 벗어난 것만 버리고, 64개를 넘는 만큼만 잘라내고, 고정 문구를 남기고, 확신도를 낮춘다. 나머지 판정은 그대로 나간다. 196자 요건은 다른 문제였다. 요건 원문에는 길이 제한이 없는데 text: 뒤에 그 문장을 붙인 주장 id에는 제한이 있었다. 제한을 넘으면 같은 정규화 문장의 해시로 대체하도록 규칙을 하나 더 두었다. 리뷰어가 넣어 본 네 입력과 정상 대조군이 그대로 테스트가 됐다.
배열 항목의 계약을 항목 단위로 검사하지 않으면, 한 항목의 실패가 전체의 실패가 된다. 이틀 사이에 두 번 배운 셈이다.
새 인자는 replay까지 따라가야 한다
두 번째 결함은 하네스였다. 판정 후처리 함수에 검색 흔적이라는 새 인자가 생겼다. 도구 호출과 추천 배치는 넘기도록 고쳤는데, 라이브 측정 하네스의 replay는 옛 서명 그대로였다. replay는 저장된 결과가 같은 초안에 후처리를 다시 적용한 결과와 같은지 확인하는 검사다. 흔적 없이 다시 돌리면 유효한 조각 인용이 전부 닫혀서 버려지고 확신도가 낮아지니, 아무 문제 없는 실행이 불일치로 기록된다. 리뷰어가 유효 인용 하나를 넣어 재현했다.
측정 분석기가 호출이 본 검색 결과를 기록하고 replay가 그것을 쓰도록 고쳤다. 기록이 없으면 같은 입력으로 결정적 검색을 다시 만든다. 하네스의 가짜 분석기도 검색된 조각을 인용하도록 바꿔서, 하네스 통합 테스트가 이 경로를 지나게 했다. 함수에 인자를 더할 때는 호출하는 곳을 전부 찾아야 하는데, 나는 제품 경로만 찾고 검증 경로는 놓쳤다.
위젯이 주장을 읽게 하기
리뷰의 선택 의견은 위젯이 인용 옆에 어느 주장인지 보여 달라는 것이었다. 일반 요건은 id나 텍스트로 찾아 표시한다. 매칭 주장은 서버와 같은 튜플을, 길이 제한을 넘는 id 없는 요건은 정규화한 요건 문장을 Web Crypto로 SHA-256 해시해 연결한다. 그러려면 정규화 함수가 서버와 위젯에서 같아야 해서 공유 패키지로 옮겼다. 리뷰어가 Node 환경에서 위젯과 같은 Web Crypto 계산을 서버 값과 대조해 일치를 확인했다. 실제 호스트에서 그려지는지는 확인하지 않았다.
확인한 것과 남은 것
확인한 것은 합성 입력뿐이다. 테스트가 서른여덟 개 늘었고, 정책 평가는 인용 사례 일곱 개를 더해 서른다섯 건이 통과하며, M5-0 기준선과의 비교는 스키마 변경과 정책 변경을 보고하면서 28건을 그대로 비교했다. 위젯 주소는 v7이 됐고, 프롬프트 버전도 올랐다.
남은 것은 실제 모델이 해석 가능한 조각 id를 인용하는지다. 그것은 다음 슬라이스인 M5-D, 모델 모드 평가의 질문이다. 사전 검색이 더하는 지연은 다음 사용 확인 재실행에서 잰다. 인용의 두 지표는 참조가 풀리는지와 얼마나 덮이는지만 재고, 인용된 문장이 주장을 의미적으로 지지하는지는 사람이 검토할 항목으로 남겨 두었다.
검증기 전문은 저장소의 citations.ts에 있다.