LLM이 뽑고, 코드가 확인한다 — 플랜엘에 적용한 검증 분리
법령 검색에 AI를 붙이면서 배운 원칙 하나. 추출은 모델에게, 검증은 코드에게 맡기니 환각이 사용자 화면까지 못 왔어요.
법령 검색 서비스 플랜엘을 만들면서 가장 오래 붙잡은 문제가 이거였어요. 모델한테 "이 조건에 맞는 조문 찾아줘"라고 시키면 술술 대답을 해요. 그런데 그 조문 번호가 실제로 존재하는지는 아무도 확인을 안 해요.
모델에게 자기 검증을 시키면 안 되는 이유
처음엔 프롬프트로 해결하려고 했어요. "확실하지 않으면 모른다고 답해줘"를 넣고, 그다음엔 "답변 후에 스스로 검증해줘"까지 붙였고요. 직접 돌려 보니 결과가 오히려 나빠졌어요. 모델이 만들어낸 조문 번호에 대해 모델이 "네, 확인했습니다"를 덧붙이니까요.
같은 시스템이 답을 만들고 그 답을 채점하면, 자기가 만든 실수를 볼 수가 없어요. 사람도 자기 오타는 잘 못 찾잖아요.
자기 검증 구조와 추출·검증 분리 구조의 차이
두 단계로 쪼개기
그래서 파이프라인을 둘로 나눴어요.
1단계는 추출이에요. 사용자 질문에서 키워드·법령명·조문 후보를 뽑아내는 일. 이건 모델이 정말 잘해요. "작년에 바뀐 임대차 관련 조항"이라는 흐릿한 말에서 검색어를 만들어내는 건 정규식으로는 못 하니까요.
2단계는 검증이에요. 뽑힌 조문 번호를 가지고 국가법령정보센터 데이터에 실제로 있는지 코드가 대조해요. if 문 몇 줄이면 되는 일이고, 여기엔 모델이 끼어들지 않아요.
적용해 봤더니 구조가 훨씬 단순해졌어요. 프롬프트에 검증 지시문을 넣을 이유가 사라지니까 프롬프트 자체가 절반으로 줄었고요.
매칭 실패는 조용히 버리지 않기
여기서 한 가지 더 중요한 게 있어요. 대조에 실패한 후보를 그냥 걸러내면 안 돼요.
처음엔 필터링만 했는데, 그러면 "왜 이 검색은 결과가 없지?"를 알 방법이 없어요. 모델이 이상한 걸 뽑고 있는 건지, 데이터가 없는 건지 구분이 안 되니까요. 그래서 매칭 실패한 항목은 unknown 레인으로 따로 빼서 로그에 남기게 했어요.
이걸 며칠 보니까 패턴이 보였어요. 특정 질문 유형에서만 실패가 몰려 있었고, 그건 프롬프트가 아니라 검색 인덱스 쪽 문제였어요. 조용히 버렸으면 영영 못 찾았을 거예요.
- 추출은 모델
- 검증은 코드
- 실패는 unknown으로 격리
어디에 쓸 수 있을까
이 구조는 법령 데이터에만 해당하는 게 아니에요. 대조할 정답지가 있는 모든 곳에 써요.
플랜씨의 금융계산기 쪽도 비슷해요. 대출이자나 연봉 실수령액은 수식이 정해져 있어서 애초에 모델이 계산할 이유가 없어요. 사용자가 "3억 30년 4.5%"라고 말하면 그 숫자를 파싱하는 건 모델이 하고, 계산은 코드가 해요. 그래서 로그인 없이 써도 결과가 늘 같아요.
정리하면 판단 기준은 하나예요. 답이 하나로 정해져 있는 일이면 코드에게 주세요. 모델은 흐릿한 입력을 정리하는 쪽에서만 강해요.
10분이면 나눌 수 있는 구조인데, 이걸 안 나누면 환각이 사용자 화면까지 그대로 올라와요. 저는 그 차이를 세 번쯤 겪고 나서야 배웠어요. 🙂
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.