한국어 RAG 실전

조사, 긴 문서, 변하는 값, 부분 근거

품앗이가 협동조합의 상담 AI와 매장 AI에 자료를 물리며 겪은 일을 다른 조직이 다시 쓸 수 있게 옮긴 글입니다. RAG는 AI가 답하기 전에 우리 자료에서 관련 대목을 찾아 함께 읽게 하는 방식입니다. 묶음 전체는 조직 AI 만들기에서 볼 수 있습니다.

요지

한국어 자료로 RAG를 만들면 영어 자료에서는 없던 함정을 만납니다. 조사가 낱말 끝에 붙어 같은 낱말을 다른 낱말로 만들고, 긴 문서의 주제어가 흔한 낱말에 밀립니다. 검색이 반쯤만 맞으면 모델은 받은 반쪽 위에서 나머지를 그럴듯하게 지어냅니다. 그래서 이 노드는 검색을 고치는 법과 함께, 변하는 값을 어떻게 넣고 다른 분야의 자료를 어떻게 걸러 들이는지를 적습니다.

1. 조사가 정확한 맞춤을 깨뜨린다

작업 기록에 따르면 「영농형태양광이 뭔가요?」라는 질문이 같은 주제의 「영농형 태양광 사업에 대해 알려줘」보다 검색 점수가 6배 가까이 낮게 나온 일이 있습니다. 어절 단위로 자르면 조사가 붙은 「영농형태양광이」가 한 토큰이 되는데, 본문에는 조사 없는 「영농형태양광」이 203번 나와서 정확히 맞지 않았던 것입니다. 불용어 목록은 따로 떨어진 「의」, 「가」 같은 토큰만 거르고, 명사에 붙은 조사는 거르지 못합니다.

형태소 분석기 같은 무거운 의존성을 들이지 않고, 가벼운 조사 떼기를 질문과 문서 양쪽에 똑같이 적용해서 풀었고, 그때 지킨 것은 세 가지입니다.

  • 긴 조사부터 맞춰 보아서, 「으로」를 「로」보다 먼저 봅니다.
  • 두 글자 명사는 보호해서 「농가」, 「복지」, 「제도」가 「농」, 「복」, 「제」로 깨지면 안 됩니다.
  • 영문과 숫자 토큰은 건드리지 않습니다.

그 결과 약했던 질문 하나는 점수가 4.3배 올랐고, 잘되던 질문은 순위가 그대로였습니다. 이 문제를 찾은 것도 상담 기록을 진단한 덕분이었습니다. 문답 기록을 들여다보지 않았다면 농민들이 붙여 쓰기로 묻는 질문을 검색이 놓친다는 사실을 몰랐을 것입니다.

긴 문서에서 주제어가 흔한 낱말에 밀리는 일도 있었습니다. 표가 많아 길어진 주제 문서의 주제어가, 「주민」이라는 낱말이 170번 나오는 다른 문서에 밀려 검색 상위에 들지 못했습니다. 본문에 키워드를 채워 넣는 대신 제목, 곧 파일 이름에 든 낱말에 가산점을 주었습니다. 드문 제목어에는 크게, 흔한 제목어에는 작게 주려고 낱말의 희소도에 비례하게 했습니다. 주제어를 넣은 질문 네 개가 모두 0개에서 4개로 맞게 바뀌었고, 다른 질문은 나빠지지 않았습니다. 파일 이름을 주제로 지어 두면 모든 문서가 저절로 이 덕을 봅니다. 이런 변경을 할 때는 고치기 전에 대표 질문들의 상위 세 개 결과를 떠 두고 고친 뒤와 대조합니다.

AI가 DB 조회문을 직접 만들어 돌렸는데 결과가 0건이면, 「데이터가 없어요」로 끝내지 않습니다. 기록에 남은 사례에서는 생산자 이름 칸을 지점 칸으로 잘못 읽어 0건이 나왔습니다. 원래 조회문과 「0건이 나왔다, 무엇이 잘못됐을까」를 모델에 다시 주고, 고친 조회문을 다시 돌리는 일을 최대 세 번 되풀이하게 합니다. 질문 유형이 새로 나올 때마다 스키마 힌트를 하나씩 덧대는 것은 끝이 없는 두더지 잡기입니다. 0건 자체를 의심하는 습관은 측정의 함정에서도 다룹니다.

2. 변하는 값은 변한다고 적어서 넣는다

견적이나 시세처럼 수시로 변하는 값은 고정된 사실을 담는 온톨로지가 아니라 마크다운 지식 문서에 넣습니다. 관계 세 쌍으로 굳혀 넣으면 값이 바뀌는 순간 거짓이 되기 때문입니다. 기록에 남은 처방은 문서마다 세 가지를 갖추는 것입니다.

  1. 문서 머리에 변동성, 기준 시점, 출처의 성격을 적습니다.
  2. 본문 맨 위에 가격 변동 고지를 둡니다. 기준 시점의 참고값이고, 수시로 변하며, 현장 실사와 여러 업체 견적이 필요하고, 확정 가격으로 인용하지 말라는 내용입니다.
  3. 「답할 때는 반드시 변동 고지와 여러 업체 견적 권고로 마무리하라」는 답변 지침을 넣습니다.

출처의 성격도 정직하게 적어야 하는데, 블로그나 AI가 종합한 안내문이면 「참고 시세」라고 쓰고, 파일 이름에 SAMPLE이 붙은 견적서면 「예시 견적, 실제 계약가 아님」이라고 씁니다. 출처를 흐리고 단가를 사실처럼 넣으면 AI는 그 숫자를 확정된 값처럼 말합니다.

3. 부분 근거 위에서 지어낸다: 「실시간 조회」라는 말은 검증이 아니다

한 상담 AI가 「법제처 실시간 조회」라고 내세우며 시공업체 자격 요건 여섯 항목을 모두 틀리게 답한 일이 있습니다. 허가와 등록을 헷갈렸고, 자본금과 인원 숫자가 틀렸고, 없는 조문을 댔고, 아직 시행되지 않은 법을 현행 근거로 들었습니다.

기록이 찾은 원인은 두 가지였습니다. 첫째, 법령의 별표를 고를 때 질문의 키워드로 점수를 매겼는데, 정작 답이 들어 있는 「등록기준」 별표는 질문에 그 낱말이 없어서 0점으로 빠졌습니다. 둘째, 모델은 받은 부분 근거 위에서 나머지를 그럴듯하게 지어냈고, 「실시간 조회」라는 머리말이 답 전체를 검증된 것처럼 보이게 했습니다.

그래서 아래 세 가지로 고쳤습니다.

  • 자격이나 등록을 묻는 질문에는 등록기준 같은 별표를 강제로 포함합니다.
  • 프롬프트에 「인용하는 조문, 별표, 수치, 법령 이름은 넣어 준 원문에 실제로 있는 것만 쓰고, 없으면 ‘추가 확인 필요’라고 쓰라」는 규칙을 넣습니다.
  • 「실시간 조회」 같은 말로 답을 부풀리지 않습니다.

AI의 법령 답변은 원문과 대조해서 검증합니다. AI에게 법 원문을 읽히는 방법은 법제처 API로 AI에게 법 원문을 읽히기에 있습니다.

4. 다른 분야의 자료를 가져올 때: 원래 분야의 「적용」이 섞여 있다

매장 AI를 위해 만든 협동조합 자료를 에너지 상담 AI에 다시 쓴 일이 있는데, 질의응답 쌍에 「○○생협도 몬드라곤처럼…」 같은 원래 분야의 적용 문장이 붙어 있었고, 원문 끝에는 「○○생협 시사점」 절이 있었습니다. 그 기록에 남은 정제 순서는 아래와 같습니다.

  1. 가공한 질의응답보다 원문을 먼저 씁니다. 원문 쪽이 적용 문장 없이 순수한 사실에 더 가깝습니다.
  2. 「시사점」 절이 시작하는 곳부터 끝까지 잘라 냅니다.
  3. 원래 분야의 고유한 낱말이 든 줄은 통째로 지웁니다. 사실 일부를 잃더라도 오염을 막는 편이 낫습니다.
  4. 변환한 뒤 그 고유한 낱말이 0건인지 검색합니다. 출처를 적는 메타 문구에도 그 낱말을 쓰지 않습니다. 「○○ 시사점 제외」라고 적었다가 검사에 걸린 일이 있습니다.

자료마다 오염된 정도가 달랐으므로 전수 확인합니다. 같은 기록에서 이론을 다룬 생성 질의응답은 오히려 깨끗했고, 사례 자료의 질의응답과 원문 끝 시사점 절에 적용 문장이 섞여 있었습니다.

생성 모델로 질의응답 데이터를 만들 때 지킬 것도 기록에 남아 있습니다. 「JSON 하나는 한 줄에 끝낸다」를 지시에 분명히 적고, 읽히지 않는 줄은 버리고 유효한 것만 저장하고, 출처 칸을 두고, 온도를 낮게 두고, 자료별로 20건씩 나눠 만듭니다.

일곱 줄로 줄이면

  1. 한국어 검색은 조사를 떼고 맞추되, 긴 조사부터 보고 두 글자 명사와 영문은 보호한다.
  2. 긴 문서가 밀리면 본문을 채우지 말고 제목 낱말에 가산점을 준다.
  3. 바꾸기 전에 대표 질문의 상위 결과를 떠 두고 대조한다.
  4. 0건이면 조회문을 의심하고 스스로 고쳐 다시 돌리게 한다.
  5. 변하는 값은 기준 시점, 변동 고지, 답변 지침과 함께 넣는다.
  6. 넣어 준 원문에 없는 조문과 숫자는 쓰지 말라고 프롬프트에 적는다.
  7. 다른 분야의 자료는 원래 분야의 적용 문장을 걸러 낸 뒤 들인다.

도구 메모

아래는 기록에 남은 경량 조사 떼기의 뼈대이고, 질문과 문서를 토큰으로 자르는 함수 안에서 양쪽에 똑같이 씁니다.

_JOSA_MULTI = ("으로서", "이라는", "에서의", "으로", "에서", "이란", "까지", "부터", "처럼")
_JOSA_SINGLE = ("이", "가", "을", "를", "은", "는", "의", "에", "와", "과", "도", "로", "만", "요")
 
def _stem(tok):
    if not tok or not ("가" <= tok[0] <= "힣"):   # 영문·숫자 보호
        return tok
    for j in _JOSA_MULTI:                          # 긴 조사 먼저
        if len(tok) - len(j) >= 2 and tok.endswith(j):
            return tok[:-len(j)]
    if len(tok) >= 3:                              # 두 글자 명사 보호
        for j in _JOSA_SINGLE:
            if tok.endswith(j):
                return tok[:-1]
    return tok

관련 노드


작성: 품앗이 · 2026-10-22 · 협동조합 상담 AI·매장 AI의 검색 기록 기준