지방계약법령 자료 더미를 믿고 물어볼 수 있는 LLM Wiki로 바꾼 이야기

한 줄 요약

지방계약법령과 행정안전부 예규, 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, 블로그를 층위별로 분리

신뢰도

사용자가 감으로 판단

근거 신뢰도 0~100점과 한계 표시

저장 원칙

유용해 보이면 저장

낮은 신뢰도 답변은 위키 지식층 편입 금지

OpenViking

아이디어 단계

평가셋 기반 hybrid 검색으로 검증

운영 방식

그때그때 실행

HOWTO, schema, workflow, eval report로 반복 가능

이 과정에서 배운 것

첫째, RAG에서 중요한 것은 "AI가 답을 잘한다"가 아니라 "AI가 무엇을 보고 답했는지 확인할 수 있다"는 점이다.

둘째, 법령 분야에서는 자료의 출처 등급이 검색 점수보다 중요하다. 검색 점수가 높아도 CSR 사례는 참고 사례이고, 공식 예규 source chunk는 직접 근거다.

셋째, 블로그 글도 가치가 있다. 다만 공식 근거로 쓰는 것이 아니라, 해설과 맥락을 넓히는 보조 자료로 써야 한다.

넷째, 새 도구는 실제 질문으로 검증해야 한다. OpenViking은 구조적으로 매력적이었지만, no-API runtime 단독 검색은 처음에는 충분하지 않았다. 평가셋이 있었기 때문에 바로 갈아타지 않고 hybrid 방식으로 개선할 수 있었다.

다섯째, 낮은 신뢰도 답변은 저장하지 않는 것도 능력이다. 지식베이스는 많이 쌓는 것보다 잘못된 근거가 다시 검색되지 않게 막는 것이 더 중요하다.

다른 사람이 따라 한다면 이렇게 시작하면 좋다

처음부터 거대한 RAG 시스템을 만들 필요는 없다.

  1. 먼저 자료를 출처별로 나눈다. 공식 원문, 참고 사례, 해설 글을 구분한다.

  2. 긴 문서는 heading 단위로 쪼개고, 원문 위치를 metadata로 남긴다.

  3. 검색 결과에는 답변보다 원문 발췌를 먼저 붙인다.

  4. 실제 업무 질문 20~30개로 평가셋을 만든다.

  5. 검색 결과가 맞는지만 보지 말고, 1순위 문서가 공식 근거인지 확인한다.

  6. 답변이 좋더라도 신뢰도가 낮으면 위키 지식층에 넣지 않는다.

  7. 사용자가 반복해서 묻는 질문만 concept나 case로 승격한다.

이 순서로 가면 AI를 도입하더라도 출처 없는 답변에 끌려가지 않는다.

재사용 가능한 질문 템플릿

원문 근거 확인 요청

다음 질문에 답하되, 답변보다 먼저 관련 원문 근거를 찾아줘. 공식 원문, 정리된 위키 문서, 참고 사례를 구분하고, 답변 말미에 근거 방식과 근거 신뢰도를 표시해줘.
질문: [여기에 질문 입력]

낮은 신뢰도 저장 방지 요청

이 답변이 다음에도 재사용할 만한 지식인지 판단해줘. 근거 신뢰도가 보통 이하라면 concept로 저장하지 말고, 확인 필요 항목이나 후속 조사 과제로만 남겨줘.

법령 자료 RAG 구축 요청

이 폴더의 법령, 행정규칙, 유권해석, 블로그 해설 자료를 출처 등급별로 나누고, 긴 문서는 heading 단위로 chunking해줘. 검색 결과에는 source path, heading, 원문 발췌, 신뢰도 기준이 함께 나오도록 설계해줘.

마무리

이 프로젝트는 "AI에게 법령을 물어보자"에서 시작했지만, 실제로는 "AI가 법령을 어떻게 믿고 찾게 할 것인가"의 문제였다.

법령과 행정규칙, 유권해석, 블로그 해설은 모두 유용하다. 하지만 같은 높이로 섞이면 위험하다. 그래서 자료를 쪼개고, 출처를 붙이고, embedding하고, 검색 결과를 평가하고, 답변마다 신뢰도를 붙였다.

결국 만들어진 것은 멋진 챗봇이 아니라, 질문할수록 더 정리되고, 근거가 약하면 스스로 멈추는 지방계약 LLM Wiki다.

나에게 가장 큰 변화는 이것이었다.

이제 AI에게 묻는다는 말은 "대충 설명해줘"가 아니다.

"내가 가진 자료 중에서, 믿을 수 있는 근거를 먼저 찾고, 그 근거의 한계를 밝힌 다음, 실무적으로 답해줘"라는 뜻이 됐다.

3
2개의 답글

뉴스레터 무료 구독