한 줄 요약
지방계약법령과 행정안전부 예규, CSR 유권해석, 네이버 블로그 해설 글을 그냥 모아두는 데서 멈추지 않고, 의미 단위로 쪼개고, 검색 가능하게 만들고, 답변마다 출처와 신뢰도를 붙이는 LLM Wiki로 바꿨다.
중요한 것은 개발자가 멋진 검색기를 만들었다는 이야기가 아니다. 사용자가 실제로 지방계약 실무 질문을 던졌을 때, "이 답변은 어디를 보고 나온 것인가", "공식 근거인가 참고 사례인가", "다음에도 믿고 써도 되는가"를 확인할 수 있게 만든 과정이다.
이런 분들께 도움된다
지방계약법, 예규, 유권해석, 블로그 해설을 자주 찾아보는 실무자
자료는 많은데 막상 질문하면 어디를 봐야 할지 매번 다시 찾는 사람
AI 답변을 쓰고 싶지만, 출처 없는 답변은 불안한 사람
Obsidian이나 Markdown으로 법령 자료를 모아두었지만 검색 품질이 아쉬운 사람
"RAG가 좋다는데, 법령 업무에서 어떻게 믿을 수 있게 만들지?"가 궁금한 사람
시작점: 자료는 많았지만, 믿고 묻기는 어려웠다
지방계약 업무를 하다 보면 자료가 한 곳에만 있지 않다.
행정안전부 예규가 있고, 지방계약법령이 있고, CSR 유권해석처럼 오래 축적된 실무 사례가 있고, 네이버 블로그에는 사람이 읽기 좋게 풀어쓴 해설 글이 있다. 각각 가치가 있지만 성격은 다르다.
공식 예규는 직접 근거가 될 수 있다. CSR 유권해석은 실무 감각을 잡는 데 좋지만 국가계약법 기반 과거 사례가 섞여 있을 수 있다. 블로그 글은 이해하기 쉽지만, 공식 근거와 같은 층위로 쓰면 위험하다.
처음에는 이 자료들을 Markdown으로 모아두면 충분할 줄 알았다. 하지만 실제 질문은 이렇게 들어온다.
재입찰과 재공고입찰이 구체적으로 어떤 상황에서 차이가 있어?임대차계약의 경우 수의계약 사유에 해당하려면 어떤 요건을 갖추어야 하나?시공실적으로 입찰참가자격을 제한한 공사입찰에서 입찰참가자격이 없는 자가 추첨하여 결정된 예정가격이 효력이 없어 해당 입찰을 취소하고 새로운 입찰에 부쳐야 하는지?이런 질문에 답하려면 단순 키워드 검색만으로는 부족하다. "비슷한 문서"를 찾는 것보다 더 중요한 것은, 답변의 근거가 될 수 있는 문장과 그 문장이 속한 문맥을 찾는 일이다.
그래서 목표가 바뀌었다.
자료를 많이 모으는 것이 아니라, 자료를 믿고 물어볼 수 있게 만드는 것.
모은 자료: 공식 근거, 사례, 해설을 한데 모으되 섞지는 않았다
가장 먼저 한 일은 자료를 모으고 성격을 나누는 일이었다.
네이버 블로그는 평소 참고했던 지방계약법 해설 글 1,028개를 개인 학술 백업 용 Markdown으로 정리했다. 처음에는 권리 확인 전이라 제목, 주소, 날짜 같은 metadata만 저장했고, 사용 권한을 확인한 뒤에야 본문 전체 백업으로 확장했다. 구형 네이버 글은 본문 구조가 달라 일부 글이 짧게 추출되는 문제가 있었지만, 본문 컨테이너를 보강해 1,028개 모두 full text 상태로 맞췄다.
행정안전부 예규는 HWPX에서 Markdown으로 변환했다. 핵심 원문은 두 가지였다.
SRC-001: 지방자치단체 입찰시 낙찰자 결정기준SRC-002: 지방자치단체 입찰 및 계약집행기준
CSR 자료는 건설계약연구원 웹 corpus로 들어왔다. 정규화된 RAG chunk는 11,948개였고, raw source 문서도 source bridge를 통해 위키와 연결했다. 여기서 중요한 기준을 세웠다. CSR 자료는 실무 사례로 유용하지만, 지방계약 직접 근거와 같은 자리에 놓지 않는다.
자료는 한데 모았지만, 같은 층위로 섞지는 않았다. 이 구분이 나중에 신뢰도 계산의 출발점이 됐다.
첫 번째 문제: 긴 문서를 그냥 넣으면 AI도 길을 잃는다
HWPX에서 변환한 예규 문서는 길고 복잡했다. 특히 표가 한 줄로 붙어 있거나, 조문과 설명이 길게 이어지는 부분은 사람이 읽기도 어렵고 AI가 검색하기에도 좋지 않았다.
그래서 문서를 의미 단위로 쪼갰다. 이것이 chunking이다.
처음에는 "문서를 적당한 길이로 자르면 되겠지"라고 생각하기 쉽다. 하지만 법령과 예규는 그렇게 자르면 안 된다. 어느 장, 어느 절, 어느 제목 아래 있는 내용인지가 중요하다. 같은 "제한"이라는 단어라도 "제한경쟁입찰"과 "입찰참가자격 제한"은 전혀 다른 쟁점이다.
그래서 chunk에는 본문만 넣지 않았다.
원문 Source ID
문서 제목
heading path
원문 줄 범위
관련 주제
source path
이런 정보를 함께 넣었다.
그 결과 SRC-001과 SRC-002는 heading 단위의 source chunk 1,277개로 나뉘었다. SRC-001은 580개, SRC-002는 697개였다. 표도 별도로 복원해 source table 753개를 만들었다. 이 중 병합 셀이 있는 표가 406개였다.
이 작업의 의미는 단순히 파일을 많이 만든 것이 아니다. 사용자가 질문했을 때, AI가 "예규 전체"가 아니라 "그 질문과 연결되는 작은 의미 단위"를 집어낼 수 있게 됐다는 뜻이다.
두 번째 문제: embedding은 답변이 아니라 길찾기다
chunking을 한 뒤에는 embedding을 했다. embedding은 문장을 숫자로 바꾸는 작업이다. 숫자로 바뀐 문장들은 서로 얼마나 비슷한지 계산할 수 있다.
여기서 중요한 깨달음이 있었다. embedding은 답변을 만드는 기술이 아니다. 길찾기 기술에 가깝다.
사용자가 "임대차계약 보증금도 수의계약 금액에 넣어야 하나?"라고 물으면, embedding은 그 질문과 비슷한 문서 조각을 찾아준다. 하지만 그 문서 조각이 공식 예규인지, 블로그 해설인지, CSR 과거 사례인지는 별도로 판단해야 한다.
그래서 이 시스템은 세 층으로 나눴다.
1. 찾기: embedding과 vector search로 관련 문서 후보를 찾는다.
2. 확인하기: 원문, source path, heading, 발췌문을 본다.
3. 답하기: 확인된 근거와 해석을 구분해 답변한다.이 구분 덕분에 Ollama나 다른 답변 생성 모델이 필수인지도 분명해졌다. 검색과 원문 확인은 Ollama 없이도 가능하다. 답변을 자연어로 정리할 때만 선택적으로 쓸 수 있다.
세 번째 문제: AI 답변이 아니라 원문을 같이 봐야 한다
시범 질의를 해보자 바로 문제가 드러났다.
그러면 해당 문서의 원문도 같이 출력해줘야하지 않아?맞는 말이었다. 법령과 계약 실무에서는 답변보다 원문이 먼저다. "그럴듯한 설명"보다 "어느 문서 어느 부분에서 나온 말인가"가 더 중요하다.
그래서 답변 방식이 바뀌었다.
이제 지방계약 관련 질문에는 답변 말미에 근거 방식을 붙인다. 여기에는 어떤 검색 방식을 썼는지, 어떤 문서를 근거로 삼았는지, 사용자가 같은 근거를 어떻게 다시 확인할 수 있는지를 적는다.
예를 들어 답변은 이렇게 끝나야 한다.
근거 방식
- 방식: LLM Wiki + OpenViking-style hybrid RAG
- 검색 모드: export 기반 --no-live-ov
- 주요 근거: SRC-002 source chunk, 관련 concept 문서
- 확인 방법: 같은 질문으로 context 검색 재현 가능이 원칙 하나가 답변의 성격을 바꿨다. 이제 AI가 말한 결론만 보는 것이 아니라, 그 결론이 어떤 자료를 보고 나온 것인지 확인할 수 있게 됐다.
네 번째 문제: 공식 근거와 참고 사례를 분리해야 했다
자료가 많아지면 좋은 점도 있지만, 위험도 커진다. CSR 유권해석이나 블로그 글이 검색 1순위로 올라오면, AI가 그것을 공식 근거처럼 사용할 수 있다.
실제로 평가 과정에서 이런 일이 있었다. 제한경쟁입찰이나 복수예비가격 같은 일반 법리 질문에서 MongoDB RAG가 sources/web 사례를 1순위로 올리는 경우가 있었다. 검색 점수만 보면 맞아 보이지만, 답변 생성용 근거로는 위험할 수 있다.
그래서 출처 등급을 나눴다.
출처
역할
sources/docs
공식 원문
sources/source-chunks
공식 원문을 의미 단위로 쪼갠 직접 근거
sources/source-tables
공식 원문 표를 복원한 직접 근거
wiki/concepts
공식 근거를 바탕으로 정리한 위키 개념
sources/web
CSR, 웹 문서, 참고 사례
네이버 블로그
이해를 돕는 해설과 실무 맥락
이 표는 단순 분류가 아니다. 답변의 신뢰도와 위키 편입 여부를 결정하는 기준이다.
다섯 번째 문제: 질문과 답변도 쌓이지만, 아무 답변이나 쌓으면 안 된다
이 시스템의 중요한 특징은 질문과 답변도 다음 검색의 재료가 된다는 점이다.
예를 들어 "재입찰과 재공고입찰의 차이"를 묻고, 공식 근거를 확인해 답변했다면, 그 답변은 다음에도 재사용할 가치가 있다. 그래서 concept 문서로 저장한다.
실제로 이런 문서들이 만들어졌다.
임대차계약 수의계약 요건
보증금 환산과 연액·총액 기준
협상에 의한 계약 참여기술자 변경 조건
부정당업자 제재처분
제한경쟁입찰 제한사유
입찰무효자 복수예비가격 추첨 효력
용지보상 지연 공사중지 계약해지 처리
하지만 여기에도 함정이 있다. 신뢰도가 낮은 답변까지 위키에 넣으면, 다음 검색에서 그 낮은 신뢰도의 답변이 다시 근거처럼 회수된다. 그러면 위키가 똑똑해지는 것이 아니라 자기 말을 반복하는 구조가 된다.
그래서 근거 신뢰도와 저장 게이트를 만들었다.
근거 신뢰도
위키 편입 처리
높음
concept 또는 case로 편입 가능
중상
직접 공식 근거가 확인된 범위에서 편입 가능
보통
authoritative concept 금지, draft 또는 확인 필요 후보
낮음
위키 지식층 편입 금지
매우 낮음
저장하지 않거나 근거 부족 항목으로만 기록
이 기준은 사용자를 위한 안전장치다. "답변을 많이 저장하는 위키"보다 "믿을 수 있는 답변만 승격하는 위키"가 더 중요하기 때문이다.
여섯 번째 문제: Obsidian 그래프가 고립 문서로 가득했다
검색만 잘 되면 될까? 꼭 그렇지는 않았다.
Obsidian Graph View를 보니 문서들이 대부분 고립되어 있었다. 사람 입장에서 보면 지식이 연결되어 있지 않은 것이다. 하지만 원문을 함부로 고치면 안 된다. 법령, 유권해석, 블로그 원문은 보존되어야 한다.
그래서 controlled wikilink 방식을 썼다.
원문 내용을 바꾸지 않고, 자주 등장 하는 키워드나 한글 tag만 제한적으로 [[ ]]로 연결했다. source 문서에는 무리하게 본문을 고치지 않고, 필요한 경우 연결 키워드 섹션이나 source bridge를 사용했다.
이 방식의 장점은 두 가지였다.
첫째, 사람은 Obsidian에서 문서 간 연결을 볼 수 있다.
둘째, AI는 embedding할 때 [[수의계약]] 같은 표현을 일반 텍스트처럼 정규화해 검색 noise를 줄일 수 있다.
그래프를 예쁘게 만들기 위한 작업이 아니라, 사람이 탐색하는 연결성과 AI가 검색하는 의미 단서를 함께 맞추는 작업이었다.
일곱 번째 문제: OpenViking을 붙이면 정말 좋아질까?
어느 정도 검색이 안정되자 OpenViking 이야기가 나왔다. OpenViking은 context를 파일 시스템처럼 다루고, L0/L1/L2 계층으로 필요한 만큼만 불러오는 방식이다. LLM Wiki가 커질수록 매력적인 구조였다.
하지만 바로 갈아타지는 않았다. 먼저 질문했다.
현재 LLM Wiki 안에 OpenViking식 L0/L1 계층을 먼저 구현하는 게 효율적이지 않아?이 판단이 좋았다. 새 도구를 붙이기 전에 현재 위키 안에 L0/L1 계층을 먼저 만들었다.
L0: 짧은 요약, 빠른 후보 탐색용
L1: 문서 범위를 파악하는 개요
L2: 실제 원문 또는 chunk
그 다음 OpenViking runtime도 실험했다. 단, OpenAI API key 없이 운영한다는 조건이 있었다. 그래서 no-API embedding profile을 만들고 테스트했다.
초기 OpenViking no-API 8B pilot은 hit@5가 0.6250이었다. 반면 MongoDB RAG는 같은 평가셋에서 hit@5 1.0000이었다. 즉, OpenViking runtime을 바로 production으로 쓰기에는 부족했다.
여기서 포기하지 않고 방향을 바꿨다. `OpenViking을 단독 검색 엔진으로 쓰기보다, OpenViking식 L0/L1 export, exact keyword, 문서 타입별 우선순위, 공식 근거 boost를 결합한 hybrid wrapper를 만들었다.
그 결과 27개 지방계약 평가셋에서 OpenViking hybrid는 hit@1, hit@3, hit@5, MRR 모두 1.0000을 기록했다.
이 숫자보다 중요한 것은 top source였다. 단순히 맞는 문서가 5위 안에 있는 것이 아니라, 공식 source chunk나 정리된 concept가 상위에 오도록 조정했다.
실제 질의로 신뢰도를 높인 과정
이 시스템은 이론으로만 검증하지 않았다. 실제 질문을 계속 던졌다.
예를 들어 "용역계약에서 수의계약이 허용되는 경우를 모두 찾아서 열거하시오"라는 질문에서는 처음에는 넓은 수의계약 문서와 사례가 섞여 나올 수 있었다. 이후 용역계약 수의계약 허용 사유 concept를 만들고, 관련 SRC-002 source chunk를 상위 근거로 회수하도록 보강했다.
"제한경쟁입찰에서 제한사유는 무엇인가"라는 질문은 "제한"이라는 단어 때문에 입찰참가자격 제한이나 web 사례로 흐를 수 있었다. 그래서 제한경쟁입찰 의도 감지와 source chunk 우선순위를 추가했다.
"입찰참가자격이 없는 자가 복수예비가격을 추첨한 경우 예정가격이 무효인가"라는 질문은 LC-027 평가셋으로 추가했다. 이 질문은 단순 키워드 검색으로는 CSR 사례가 상위에 올 수 있었지만, OpenViking hybrid에서는 입찰무효자 복수예비가격 추첨 효력 concept와 SRC-001, SRC-002 source chunk가 상위에 오도록 개선했다.
마지막으로 "종합쓰레기장 민원 반대와 용지보상 지연으로 공사중지 상태인 경우 계약해지를 어떻게 처리해야 하는가"라는 질문에서는 새 concept를 만들고, 공식 source chunk와 함께 검증했다. 이 답변에는 근거 신뢰도 98/100, 높음이 붙었다. 다만 그 점수는 정답 확률이 아니라 근거 품질 점수라고 명시했다.
이 과정이 핵심이다. 질문을 던질 때마다 위키는 더 똑똑해졌지만, 아무 답변이나 위키에 넣지는 않았다. 공식 근거, 참고 사례, 해석, 신뢰도, 저장 여부를 계속 분리했다.
숫자로 본 변화
항목
결과
네이버 블로그 Markdown 백업
1,028개
CSR 정규화 RAG chunk
11,948개
SRC-001/SRC-002 source chunk
1,277개
SRC-001/SRC-002 source table
753개
L0/L1 context record
8,200개
OpenViking-compatible record
4,152개
지방계약 retrieval 평가셋
27문항
OpenViking hybrid hit@1
1.0000
OpenViking hybrid hit@5
1.0000
OpenViking hybrid MRR
1.0000
qmd hit@1
0.4444
여기서 qmd 수치가 낮다고 해서 qmd가 나쁘다는 뜻은 아니다. qmd는 Markdown 문서 탐색과 관리에 유용하다. 다만 지방계약 답변용 주 검색 backend로 쓰기에는 OpenViking hybrid나 MongoDB RAG가 더 적합하다는 뜻이다.
Before와 After
항목
Before
After
자료 상태
법령, 예규, CSR, 블로그가 흩어져 있음
Source ID와 역할이 붙은 LLM Wiki로 정리
검색 방식
파일명, 키워드, 기억에 의존
chunk, embedding, L0/L1, hybrid retrieval 사용
답변 확인
답변이 맞는지 원문을 다시 찾아야 함
답변 말미에 근거 방식과 주요 근거 표시
출처 구분
공식 근거와 참고 사례가 섞일 수 있음
공식 원문, concept, CSR, 블로그를 층위별로 분리
신뢰도