XGEN 15일 무료 체험 — 설치 없이 브라우저에서 바로 시작하세요체험 신청
PlateerAI Labs
·Reading Time | 4 min
작성 | 김진수

온톨로지 빌드·검색 개선 대장정 1편 — 자동 빌드에서 LLM을 뺀 이유

온톨로지 자동 빌드에서 LLM을 분리하고, 문서 누락과 한국어 의미 훼손, 값 노드의 가짜 경로를 찾아 바로잡은 과정을 설명합니다.

온톨로지 빌드·검색 개선 대장정 1편 — 자동 빌드에서 LLM을 뺀 이유 — 커버 일러스트Series · 온톨로지 개발기시리즈 11번째 글 · 전체 11편 보기

XGEN의 온톨로지는 지금 이렇게 동작합니다. 문서를 올리면 LLM을 한 번도 부르지 않고 클래스와 개체와 속성 그래프가 생기고, 관계 형성은 사용자가 켠 컬렉션에서만 LLM이 더합니다. 검색은 벡터와 그래프를 한 번에 융합해 LLM을 합성에 한 번만 부르고, 그래프는 PostgreSQL을 정본으로 저장합니다. 이 모양은 처음부터 그린 것이 아닙니다. 기본 빌드에서 LLM을, 검색에서 반복 탐색 루프를, 정본에서 Fuseki를 차례로 빼면서 만들어졌고, 뺄 때마다 그전에 믿고 있던 것이 재보니 달랐습니다. 이 글은 그렇게 정하게 된 것들과, 아직 정하지 못한 것들을 함께 정리합니다.


편집자 노트 · B2B 도입 관점 — 기업용 온톨로지의 첫 과제는 가장 정교한 모델을 고르는 것이 아니라, 문서가 들어올 때마다 예측 가능한 비용과 품질로 같은 과정을 반복하는 것입니다. 실제 고객 환경에서는 문서 양식과 언어, 업무 용어가 계속 달라지고 처리량도 일정하지 않습니다. 이 글은 자동 빌드라는 운영 조건이 설계를 어떻게 바꾸었는지, 그리고 PoC에서 평균 정확도 외에 처리 커버리지·재현성·실패 가시성을 왜 함께 확인해야 하는지 보여줍니다.

온톨로지화가 업로드마다 돌아야 한다는 조건이 설계를 정했습니다

처음 만든 파이프라인은 하향식이었습니다. 역량 질문에서 개념을 뽑아 OWL 스키마를 먼저 만들고 그 스키마로 개체를 추출하는 방식으로, 온톨로지 교과서가 권하는 정석에 가까웠습니다. 문제는 고객사마다 데이터 정의 수준이 달라서 역량 질문이나 스키마를 먼저 써 줄 도메인 전문가를 기대하기 어려웠다는 것입니다. 질문을 LLM이 자동으로 만들게도 해봤지만 질문 단계 자체가 남아 완전 자동화가 되지 않았습니다. 그래서 질문 없이 문서만 넣으면 그래프가 생기는 상향식으로 방향을 잡았습니다.

사전에 정의한 클래스와 관계 체계에 맞춰 추출하는 Ontology-guided 방식도 초기에 검토했습니다. 지금 버전은 FIBO나 schema.org 같은 도메인 시드 온톨로지를 추출 단계에 두지 않습니다. 뒤에서 설명할 되먹임과 후처리가 "온톨로지"라는 말을 쓰긴 하지만, 그것은 외부 온톨로지의 안내라기보다 스스로 만든 스키마를 정리하는 과정이라 Ontology-guided라고 부르기는 어렵다고 보고 있습니다.

상향식으로 잡자 조건이 하나 따라왔습니다. 온톨로지화가 문서 업로드마다 자동으로 돌아야 한다는 것입니다. 사용자가 버튼을 눌러야 그래프가 생기면 대부분의 컬렉션은 그래프 없이 남습니다. 그런데 업로드마다 LLM을 태우면 비용이 감당되지 않습니다. 문서 하나에 청크가 수십 개이고 컬렉션 하나에 문서가 천 개를 넘는 경우가 실제로 있었습니다. 이 조건이 이후의 설계를 거의 다 정했습니다. 공짜여야 하는 것과 비싸도 되는 것을 갈라야 했고, 공짜여야 하는 쪽에서는 LLM을 빼야 했습니다.

기본 빌드에서 LLM을 0회로 내렸습니다

빌드를 두 갈래로 나눴습니다. 기본 빌드는 문서 구조에서 개체를 뽑는 단계이고 LLM을 부르지 않습니다. 표와 행 덤프는 격자에서 행을 개체로, 머리글과 캡션을 유형과 속성으로 읽습니다. 산문에서는 "A, B 등의 C" 같은 한국어 Hearst 패턴으로 상하위를 잡고, 복합명사의 머리로 상위 개념을 세웁니다. "내부감사규정"의 머리는 "규정"입니다. 이름의 경계는 형태소 규칙으로 판정합니다. 이 단계는 업로드 직후 자동으로 돌고, 문서 두 개짜리 컬렉션이면 30초 안에 끝납니다. 정형 데이터는 따로 갑니다. CSV와 외부 DB 조회 결과는 테이블을 클래스로, 컬럼을 속성으로, 외래키를 관계로 옮기는 결정론적 매핑이라 여기서도 LLM은 없습니다.

관계 형성 빌드는 그 위에 얹는 선택 단계입니다. 사용자가 컬렉션에서 켠 경우에만 300청크 그룹마다 LLM을 한 번 불러 이미 뽑힌 개체 사이의 관계를 주어·술어·목적어로 추출합니다. 켠 시점에 이미 쌓여 있던 문서에도 소급해서 돕니다. 처음에는 그룹마다 독립적으로 뽑게 했는데 실측하니 실제 관계 22개에 술어 이름이 16종에서 19종이 나왔습니다. "소속", "소속규정", "속함"이 세 종류로 갈리는 식입니다. 그래서 이미 유도된 개체 목록과 이미 쓰인 술어 이름을 다음 그룹의 참고로 되먹입니다. 되도록 같은 이름을 재사용하도록 안내하되 허용 집합을 강제하지는 않습니다. 강제하면 새 종류의 관계가 들어올 자리가 없어집니다. 이 되먹임은 EDC 계열 연구의 스키마 되먹임과 같은 취지입니다.

[문서 업로드]     [청킹·임베딩]     [기본 빌드]            [관계형성 빌드]
PDF·HWP·DOCX·CSV → 청크 + 벡터  →  LLM 0회 결정론    →  LLM, 토글 켠 컬렉션만
외부 DB SELECT                    표·형태소·Hearst       이미 뽑힌 개체 사이 관계
                                        ↓                      ↓
                                  PostgreSQL 그래프 (정본)
                                  노드 · 엣지 · 노드→출처 청크

기본 빌드에서 지킨 원칙이 하나 더 있습니다. 판정 근거는 형태소 태그, 원문 등장, 코퍼스 통계 세 가지뿐이고 도메인 단어 목록이나 예시 문자열을 코드에 두지 않습니다. 고객사 도메인마다 목록을 유지할 수 없기도 하지만, 목록이 있으면 그 도메인의 중요한 일상어가 함께 지워지기 때문입니다. 금융에서 "한도"는 일반어 목록에 들어갈 법한 말이지만 그 도메인에서는 핵심 개념입니다. 흔한 낱말을 거를 때도 단어 목록 대신 형태소 사전이 빈도 순으로 매겨둔 번호를 씁니다. 사전에 없는 말이 아주 작은 번호를 받는다는 것을 알아내기 전까지는 이 방식도 "포토레지스트"나 "스테퍼" 같은 도메인 용어를 흔한 말로 오인했습니다.

이렇게 LLM을 빼놓고 나니 결과가 좋아졌는지 나빠졌는지를 다른 방식으로 확인해야 했습니다. 그리고 확인해보니 문제는 LLM을 뺀 자리가 아니라 전혀 다른 곳에 있었습니다.

빌드가 문서를 500개까지만 읽고 있었습니다

"빌드가 왜 이렇게 느리냐"는 말에서 시작했습니다. 문서 1,186개짜리 컬렉션의 빌드 이력 102건이 거의 전부 164초, 청크 10,056개, 트리플 54,809개로 똑같았습니다. 11.6분 간격으로 같은 빌드가 무한히 반복되고 있었습니다.

원인은 SDK의 조회 함수 기본값이 500건이라는 것이었습니다. 빌드가 문서를 읽는 네 경로 전부가 상한을 넘기지 않았습니다. 실제 문서는 1,186개, 실제 청크는 29,506개인데 빌드가 본 청크는 10,056개, 그러니까 앞의 문서 500개 몫만이었습니다. 두 가지 결과가 따라왔습니다. 문서 686개, 청크로는 66%가 온톨로지에 영영 들어가지 않았습니다. 검색은 벡터 저장소를 직접 보니 잡히는데 그래프에는 없어서, 같은 컬렉션인데 문서에 따라 근거가 있고 없고가 갈렸습니다. 그리고 백필 스윕이 멈추지 않았습니다. 빌드된 청크 수가 전체 청크 수보다 작으면 다시 빌드하는데, 빌드가 500개 문서만 보는 한 그 격차는 절대 좁혀지지 않습니다.

이 결함이 오래 살아남은 이유는 조용했기 때문입니다. 빌드는 매번 성공했고 품질 점수도 나왔습니다. 그래서 고칠 때 상한을 명시하는 것으로 끝내지 않고, 상한에 닿으면 경고를 남기게 했습니다. 조용히 잘리면 왜 문서가 빠졌는지 아무도 못 찾습니다. 회귀 방지로는 테스트 하나가 빌드 경로가 SDK를 직접 부르지 않는다는 것을 소스에서 검사합니다. 한 곳만 고치면 나머지 경로로 되살아나기 때문입니다.

이 무렵 같이 잡은 것들도 결이 같았습니다. 산문이 표로 오인돼 한 문서의 개체 32개가 전부 "신청을", "정한다.", "document_type" 같은 조각이었던 일이 있었는데, 표 판정이 "줄마다 단어 수가 일정하면 표"였기 때문입니다. 품질이 줄의 단어 수라는 우연에 걸려 있었던 것입니다. 형태소 태그에서 명사만 이어 붙여 "현물환거래"가 "현물"과 "거래"로, "준법감시인"이 "준법감시"로 잘리던 일도 있었습니다. 이름이 틀리면 관계도 틀려서 실제로 "준법감시 → 감시 → 법규준수"로 주어가 바뀐 관계가 만들어져 있었습니다. 업로드 때 청크 앞에 붙는 검색용 머리말이 관계 추출과 답변 생성 프롬프트에 그대로 실려 400자 청크 기준 31%를 차지하던 일도 있었습니다.

표준 레시피가 반대 뜻을 같은 것으로 합쳤습니다

이름을 합치는 문제에서는 남들이 하는 방식을 먼저 봤습니다. 최근 증분 지식그래프 구현들은 새 개체가 들어올 때 그래프 전체를 훑지 않고 문자 3-gram과 MinHash로 후보를 좁힌 뒤 자카드 유사도 0.9 이상이면 같은 것으로 봅니다. 널리 쓰이는 레시피라 그대로 가져오면 될 것 같았습니다.

가져오기 전에 저희 라벨로 돌려봤습니다. 실제 컬렉션 두 곳의 라벨 2,880개와 2,906개에 적용하니 형태소 이름 키로 이미 합쳐지는 쌍 외에 새로 잡는 쌍이 12건이었고, 그중 세 건이 뜻이 반대였습니다. "개선 가능성 低"와 "개선 가능성 高"가 유사도 1.00으로 같은 것이 됐고, "입장처리 前"과 "입장처리 後", "초급Ⅰ 프로그램"과 "초급Ⅱ 프로그램"도 그랬습니다.

원인은 분명했습니다. 3-gram 정규화가 영숫자와 한글이 아닌 문자를 버립니다. 영어 이름에서는 버려도 되는 문자가 한국어 실무 문서에서는 뜻을 가르는 유일한 글자였습니다. 한자와 로마숫자 말입니다. 저희가 쓰던 형태소 키는 한자를 명사로 남기기 때문에 低와 高가 키에 남아 두 이름이 갈립니다. 지금 방식이 이 데이터에서는 표준 레시피보다 안전했습니다. 같은 조사에서 이름의 엔트로피로 퍼지 병합 여부를 가르는 게이트도 확인했는데, 한국어에서는 그 값이 길이의 대용품이라 밋밋한 머리글과 진짜 클래스를 구분하지 못했습니다. 건진 것은 하나였습니다. 따옴표 변형이 세 노드로 갈려 있었는데, 이름 앞뒤의 기호만 키에서 빼면 붙습니다. 가운데 기호는 ℃나 %처럼 뜻이 있으니 남깁니다.

값을 노드로 두면 가짜 경로가 생겼습니다

속성값을 어디에 둘지도 이 시기에 정했습니다. 처음에는 값도 노드로 두려 했습니다. 그래프니까 전부 노드와 엣지로 표현하는 것이 자연스러워 보였습니다. 그런데 "2024년"이나 "서울"처럼 같은 값을 가진 무관한 개체들이 값 노드를 거쳐 2홉 이웃이 되면서 가짜 경로가 생겼습니다. 서울에 있는 지점과 서울에서 열린 행사가 연결되는 식입니다. 그래서 속성값은 노드가 아니라 개체에 붙는 값 목록으로 둡니다.

정형 데이터에서 200행을 넘는 사실 테이블을 스키마만 두는 것도 같은 이유입니다. 행을 전부 개체로 만들면 기준 개체를 찾으려고 거대한 사실 기록을 함께 읽어야 하고, 수치는 어차피 SQL이 더 정확합니다. 추론기도 두지 않았습니다. RDFS나 OWL 추론, SHACL 검증을 쓰면 저장소가 특정 그래프 데이터베이스에 묶입니다. 판정은 전부 빌드 시점 파이썬에서 끝내고 저장은 평범한 노드와 엣지 표로 유지한다는 방침을 세웠습니다. 부모 클래스의 속성을 한 단계 물질화하는 것과 품질 지표를 산출하는 것까지가 빌드 시점에 하는 일입니다.


B2B 도입을 검토한다면

이 단계에서 고객이 먼저 합의할 것은 어떤 모델을 쓸 것인가보다 기본 파이프라인이 무엇을 보장하는가입니다. 입력 문서 수와 처리 문서 수를 대조할 수 있는지, 같은 입력을 재실행했을 때 결과가 일관적인지, 업무 용어의 반대 의미와 숫자·단위가 보존되는지, LLM 보강이 실패해도 기본 결과를 사용할 수 있는지를 확인해야 합니다.

운영 지표에는 평균 정확도 외에도 전체 문서 처리율, 누락·상한 도달 경고, 재처리 결과의 차이, 업무 핵심 용어의 회귀 테스트가 포함되어야 합니다. 안정적인 그래프를 만들었다고 해서 곧바로 좋은 검색이 되는 것은 아닙니다. 다음 편에서는 그래프 탐색과 벡터 검색을 어떤 질문에 배치하고, 이를 실제 서비스의 지연·저장·갱신 구조로 어떻게 연결했는지 살펴봅니다.


다음 편 → 온톨로지 빌드·검색 개선 대장정 2편 — 여덟 번의 탐색을 한 번의 융합으로

시리즈 전체 보기 → 온톨로지 개발기

#온톨로지#지식 그래프#자동화#데이터 품질#XGEN

자주 묻는 질문

온톨로지 기본 빌드에서 LLM을 왜 뺐나요?
모든 문서 업로드마다 자동으로 실행하려면 비용과 지연, 결과의 재현성을 관리할 수 있어야 했기 때문입니다. 표 구조·형태소·Hearst 패턴을 이용한 기본 빌드는 결정론적으로 처리하고, LLM 관계 형성은 사용자가 켠 컬렉션에만 선택적으로 적용합니다.
빌드 성공만 확인하면 전체 문서가 처리됐다고 볼 수 있나요?
아닙니다. 조회 상한이나 페이지네이션 문제로 일부 문서만 처리되어도 작업 자체는 성공으로 끝날 수 있습니다. 입력 문서·청크 수와 실제 처리 수를 대조하고, 상한 도달과 누락을 별도 경고와 지표로 남겨야 합니다.
블로그 목록으로