01
두 도구 소개
먼저, 이 두 도구는 무엇인가
두 도구 모두 처음 듣는 이름일 수 있습니다. 결과를 보기 전에 각각이 무슨 일을 하는 물건인지, 그리고 이 글에 자주 나오는 낱말 몇 개를 먼저 정리합니다.
kordoc — 한글 문서를 Markdown으로 바꾸는 변환기
kordoc은 한국 공공·기업 문서에서 흔한 HWP, HWPX, PDF, DOCX, XLSX 파일을 Markdown 텍스트로 바꿔 주는 오픈소스 명령줄 도구입니다. Node.js 패키지라 npm install -g kordoc 한 줄로 설치하고, kordoc 파일.hwp처럼 부릅니다. 저장소 설명 그대로 "모두 파싱해버리겠다"가 목표입니다. 라이선스는 MIT이고, 2026년 10월 14일 기준 GitHub 별 2,440개에 같은 날 커밋이 있을 만큼 활발합니다.
이 도구가 특별한 지점은 표입니다. 많은 변환기가 표의 칸 경계를 잃고 글자를 한 줄로 이어 붙입니다. kordoc은 칸 병합(rowspan·colspan)까지 포함해 표를 그대로 남기고, RAG용 --format chunks 옵션으로는 문서를 "장·절 경로가 붙은 조각" 단위로 잘라 JSON으로 냅니다. 표는 독립된 조각이 됩니다.
OpenCrab — 문서를 "근거 → 주장 → 지식 그래프"로 올리는 로컬 온톨로지 공장
OpenCrab은 문서를 읽어 그 안의 개념과 관계를 지식 그래프로 만들고, 그 그래프를 AI 에이전트가 MCP로 꺼내 쓰게 해 주는 Python 도구입니다. 설계 원칙이 분명합니다. 근거(evidence)가 없는 주장은 그래프에 올리지 않고, 올라간 주장은 후보 → 검증 → 승격 단계를 거칩니다. 저장은 로컬(SQLite·Chroma·JSON)에 하고, 문서 파싱에는 바로 위의 kordoc을 씁니다. 다만 개념과 관계를 뽑는 "추출" 단계는 공식 저장소 코드 기준으로 Anthropic의 Claude API를 호출합니다. 저장소 README는 MIT 라이선스라고 적었지만 LICENSE 파일은 아직 없습니다.
이 글에서 쓰는 낱말
여섯 개만 추렸습니다. 전체 목록과 관련어는 용어사전에 있고, 뜻이 바뀌면 그곳만 고칩니다.
| 낱말 | 뜻 |
|---|---|
| HWPX / HWP5 | 둘 다 한글 파일입니다. HWPX는 2010년 이후의 XML 기반 표준 형식이고, HWP5는 그 이전부터 쓰던 바이너리 형식입니다. 공공 문서는 아직 HWP5가 더 많습니다(이번 표본의 70%). |
| RAG | 질문을 받으면 먼저 문서에서 관련 조각을 찾아오고, 그 조각을 근거로 AI가 답을 쓰게 하는 방식입니다. 문서가 제대로 잘려 있어야 답에 근거가 붙습니다. |
| 청크 · 벡터 | 청크는 문서를 검색 단위로 자른 조각, 벡터는 그 조각의 뜻을 숫자 배열로 바꾼 것입니다. 벡터가 문서당 하나뿐이면 "이 문서 어딘가"까지만 찾아 줍니다. |
| 온톨로지 · 지식 그래프 | 온톨로지는 "어떤 종류의 것들이 있고 어떤 관계를 맺을 수 있는가"를 정한 규칙, 지식 그래프는 그 규칙대로 실제 대상들을 점과 선으로 이은 결과입니다. |
| MCP | AI 에이전트가 외부 도구·데이터를 표준 방식으로 부르게 해 주는 연결 규약입니다. OpenCrab은 자신을 MCP 서버로 등록합니다. |
| 요구사항 레코드 | 제안요청서의 "요구사항 고유번호 · 명칭 · 정의 · 세부내용 · 산출정보"를 한 묶음으로 뽑은 것입니다. 이 글의 측정 단위입니다. |
02
왜 이 둘인가
왜 하필 이 두 도구를 골랐나
이 연재는 매일 새로 들어오는 GitHub 저장소와 새 기술 가운데 하루 1건을 골라 직접 써 봅니다. 고르는 기준은 네 가지입니다.
- 기준 1지금 막혀 있는 문제와 맞물리는가
소개가 그럴듯한 도구보다, 당장 풀어야 하는 문제에 닿는 도구를 먼저 봅니다. - 기준 2공개 자료로 재현할 수 있는가
결과를 누구나 다시 돌려 볼 수 있어야 합니다. 비공개 문서로는 시험하지 않습니다. - 기준 3설치 비용이 작은가
GPU나 상시 서버가 필요하면 뒤로 미룹니다. 명령 한 줄로 설치되는 것을 우선합니다. - 기준 4주장을 코드로 확인할 수 있는가
README의 설명과 실제 코드가 다를 수 있습니다. 공개 저장소라야 확인이 됩니다.
막혀 있던 문제
공공 제안요청서(RFP)에는 "요구사항 목록"과 "요구사항 상세" 표가 있습니다. 이 표를 "요구사항 1건 = 레코드 1개"로 뽑아야 검색과 비교가 됩니다. 그런데 표준 라이브러리만으로 만든 자체 HWPX 파서는 XML 형식인 HWPX만 읽었습니다. 공개 RFP 155건을 모아 보니 HWP5가 108건(70%)이었습니다. 표본의 7할을 못 읽는 상태였습니다.
두 후보가 걸린 이유
OpenCrab은 "문서 → 근거 → 주장 → 그래프 → MCP"라는 전체 흐름을 이미 구현했다고 소개돼 있었습니다. 직접 만들려던 파이프라인과 모양이 같아 기준 1에 걸렸습니다. 그리고 저장소를 열어 보니 OpenCrab이 문서를 읽는 데 kordoc을 쓰고 있었습니다. kordoc은 HWP5까지 읽는다고 적혀 있었고, 설치가 npm 한 줄이라 기준 3에도 맞았습니다. 그래서 둘을 함께 보되, 푸는 문제가 다르므로 따로 측정하기로 했습니다. kordoc에는 "표를 얼마나 살리는가"를, OpenCrab에는 "로컬에서 세부 근거를 찾아 주는가"를 물었습니다.
03
시험 범위
무엇을 어디까지 돌렸나
문서는 나라장터에 공개된 제안요청서·과업지시서 155건을 썼습니다. 형식은 HWP5 108건, HWPX 37건, PDF 9건, 확장자 없는 파일 1건입니다. 먼저 5건(HWPX 3·HWP5 2)으로 변환 품질을 확인한 뒤 155건 전체로 넓혔습니다. 실제 측정에 들어간 파일은 154건이고, 파일명에 점이 있어 확장자를 잘못 판별한 1건은 건너뛰었습니다.
OpenCrab은 앞 단계의 HWPX 3건만 넣어 로컬 설치·상태 점검·ingest·질의 4건을 확인했습니다. Claude API를 쓰는 추출 단계는 실행하지 않았습니다. 따라서 이 글은 두 제품의 우열이 아니라 2026년 10월 14일에 실행한 특정 경로의 결과입니다.
04
문서 변환
kordoc은 표의 모양을 잃지 않았다
첫 5건에서 HWPX 세부 요구사항 표 120건은 모두 칸 단위로 남았습니다. 고유번호·명칭·정의·세부내용이 서로 다른 칸에 들어 있었고, 표가 문서의 어느 장·절에 있는지 경로도 붙어 있었습니다. HWP5 2건 중 요구사항 체계가 있는 1건은 19건 가운데 18건을 잡았습니다.
HWP5 숫자는 두 개를 같이 봐야 합니다. 목록표의 ID 1,630개를 그대로 분모로 두면 회수율은 75.6%(1,232/1,630)입니다. 그런데 미회수의 대부분은 세 문서에서 나왔고, 그 문서들은 목록표(120개 ID)만 있고 상세표가 아예 없었습니다. 또 다른 두 문서는 목록표 ID가 COR-000 같은 자리표시였습니다. 파서가 놓친 것이 아니라 문서에 없는 것입니다. 이 5건을 빼고 서로 비교할 수 있는 대상만 세면 97.0%(1,232/1,270)로, HWPX의 96.9%와 같은 수준입니다.
| 형식 | 측정 문서 | 핵심 결과 | 해석 |
|---|---|---|---|
| HWPX | 37개 | ID 집합 37/37 일치, 목록 대비 587/606 | 자체 파서와 같은 품질. 글머리 기호는 kordoc이 더 충실 |
| HWP5 | 108개 | 원시 75.6% / 비교 가능 대상 97.0% | 처음으로 읽힘. 미회수는 문서 유형 차이 |
| 9개 | 19/19 | 같은 파이프라인에 넣을 수 있음 |
중간에 한 가지를 고쳤습니다. kordoc은 병합 칸이 없는 표는 Markdown 파이프 표로, 병합이 있으면 HTML 표로 냅니다. 처음 만든 변환 규칙은 HTML만 읽어서 한 문서의 레코드가 1건으로 나왔습니다. 파이프 표를 읽게 하자 62건으로 올랐습니다. 두 형식을 모두 받아야 합니다.
05
검색 품질
검색 결과에는 근거 대신 표지와 목차가 남았다
OpenCrab 로컬 모드에 HWPX 3건을 넣었습니다. 처음 내려받은 약 79MB의 임베딩 모델(all-MiniLM-L6-v2, 영어 모델)을 포함해 41.7초가 걸렸습니다. 저장소를 열어 보니 Chroma에는 문서당 하나씩 모두 3개의 벡터가 있었고, 그래프 노드와 엣지는 0개였습니다.
"개인정보 영향평가", "접속기록 1년 이상 보존", "SER-001 개인정보 보안", "요구사항 고유번호 CSR-01" 네 질문을 던졌지만 매번 같은 문서 3개가 0.87~1.00의 점수로 돌아왔고, 발췌문은 표지와 목차였습니다. 문서 전체가 하나의 벡터가 되면서 세부표의 신호가 묻힌 것입니다. 코드를 보면 청크와 노드·엣지는 Claude API를 부르는 extract 단계에서만 만들어집니다. 그 단계는 문단 3,000자 청크 중 앞 2,500자만 모델에 보냅니다.
이 결과를 OpenCrab 전체의 실패라고 부를 수는 없습니다. 추출 단계를 켜면 청크와 그래프가 생길 수 있습니다. 다만 이번에 필요한 것은 API 없이 로컬에서 바로 쓰는 세부 근거 검색이었고, 시험한 경로는 그 기준을 통과하지 못했습니다.
06
채택 판단
가져갈 것과 멈출 것을 나눴습니다
- 채택kordoc을 공통 변환 계층으로 사용
HWPX·HWP5·PDF를 같은 인터페이스로 읽고, 장·절 경로와 칸 병합 정보를 후속 처리에 넘깁니다. 설치 항목은 Node.js와 kordoc 하나입니다. - 직접 구현요구사항 레코드 추출 규칙
어느 칸이 라벨이고 어느 칸이 값인지, 한 표에 여러 레코드가 쌓였을 때 어디서 끊을지는 문서 특성에 맞춰 따로 둡니다. - 설계만 차용OpenCrab의 품질 게이트와 주장 상태
"근거 참조 무결성 1.00, 깨진 엣지 0" 같은 수치 기준과 후보→검증→승격 상태는 자체 스키마에 옮길 만합니다. - 보류시험한 OpenCrab ingest의 전면 도입
문서 하나당 벡터 하나로는 세부 근거를 찾기 어렵고, 추출은 클라우드 API를 전제합니다. 내부 문서에는 쓸 수 없습니다.
07
한계
이 결과가 말하지 않는 것
시험은 Windows PC 한 대와 공개 제안요청서에 한정했습니다. GPU는 없었습니다. 속도에는 첫 모델 다운로드 시간이 들어 있습니다. kordoc이 함께 내는 페이지 품질 점수는 검증하지 않았고, OpenCrab의 LLM 추출도 실행하지 않았습니다. 제안요청서가 아닌 문서군, 다른 버전, 다른 청크 설정에 그대로 일반화하면 안 됩니다.
재현 범위는 분명합니다. Python 3.12, Node.js 22, kordoc 4.21.31, OpenCrab 0.1.0 메인 브랜치(2026년 10월 6일 커밋)에서 실행했습니다. 이후 버전에서 결과가 달라지면 별도의 재시험 기록으로 잇겠습니다.
08
출처
확인한 근거
제품 설명은 각 공식 저장소의 README와 코드를 기준으로 확인했고, 성능 숫자는 공개 RFP 문서를 이용한 로컬 실행 결과입니다. 개인 메모와 비공개 대화는 이 기사에 쓰지 않았습니다.
- OFFICIAL REPOSITORYkordoc — GitHub (MIT)
- OFFICIAL REPOSITORYOpenCrab — GitHub
- SOURCE CODEOpenCrab extractor.py — 추출 단계가 Claude API를 호출하는 부분
- DESIGN DOCOpenCrab 공장 워크플로 — 품질 게이트 기준
- PUBLIC DATA나라장터 — 공개 제안요청서 155건의 출처
- LOCAL MEASUREMENT공개 RFP 155파일 가운데 154파일 형식별 측정, OpenCrab HWPX 3파일 로컬 검색 · 2026-10-14