AI가 한글 문서를 못 여는 진짜 이유, 그리고 그걸 푼 방법
핵심 요약
- AI 도구가 .hwp를 다루지 못하는 건 모델 성능 문제가 아니라 형식 문제입니다. 모델이 좋아져도 저절로 해결되지 않습니다.
- 막혀 있던 지점은 '읽기'가 아니라 '쓰기'였습니다. 문서를 읽는 건 서버에서 이미 되고 있었고, 양식 구조를 파악하고 채우고 저장하는 일이 전부 브라우저 안에 묶여 있었습니다.
- 엔진을 서버용으로 다시 만드는 대신, 화면 없는 브라우저를 서버에서 돌려 그 안의 엔진을 그대로 호출하는 쪽을 택했습니다. 예상보다 두 자릿수 빨랐습니다.
- 도구를 설계하며 정한 원칙은 '글은 AI가 쓰고, 우리는 문서만 다룬다'였습니다. 초안 생성은 일부러 열지 않았습니다.
요즘 개발자들은 터미널에서 AI에게 일을 시킵니다. 코드를 고치고, 파일을 읽고, 커밋까지 맡깁니다. 그런데 정부지원사업 신청서를 앞에 두면 그 흐름이 끊깁니다. 안내문에 붙은 양식 파일이 .hwp이기 때문입니다. AI에게 그 파일을 건네면 아무것도 하지 못합니다.
이건 모델이 똑똑하지 않아서가 아닙니다. 형식이 닫혀 있어서입니다. 그리고 형식 문제는 모델이 한 세대 좋아진다고 저절로 풀리지 않습니다. 누군가 그 형식을 다룰 수 있는 통로를 열어줘야 합니다.
이 글은 그 통로를 만든 기록입니다. 처음에 예상한 어려움과 실제로 막혀 있던 지점이 달랐고, 그 차이가 설계 전체를 바꿨습니다.
막혀 있던 건 읽기가 아니었다
처음에는 당연히 '읽기'가 문제일 거라고 생각했습니다. AI가 파일을 못 여니까 못 다루는 거라고요. 그런데 우리 코드를 살펴보니 읽기는 이미 서버에서 되고 있었습니다. 문서에서 글자와 표를 뽑아내는 일은 별도의 처리 경로로 오래전부터 동작하고 있었어요.
실제로 막혀 있던 건 그 다음이었습니다. 양식의 어느 칸이 작성란인지 파악하고, 거기에 내용을 넣고, 다시 원래 형식의 파일로 저장하는 일. 이 세 가지가 전부 브라우저 안에서만 동작하고 있었습니다.
이유는 제품 구조에 있었습니다. 우리 편집기는 사용자가 문서를 열면 편집 엔진이 그 사람의 브라우저로 내려가 거기서 돌아갑니다. 타이핑에 즉각 반응해야 하니 자연스러운 선택이었죠. 서버는 완성된 파일을 받아 보관만 합니다.
그런데 AI 에이전트가 우리에게 요청을 보낼 때는 브라우저가 없습니다. 서버끼리 대화하는 것뿐입니다. 엔진이 없으니 채울 방법도 없었습니다.
이 지점이 설계를 갈랐다
- 'AI가 한글을 못 읽는다'가 문제였다면 파서를 하나 더 붙이면 끝났을 겁니다.
- 실제 문제가 '편집 능력이 브라우저에 묶여 있다'였기 때문에 해결책의 모양이 완전히 달라졌습니다.
- 문제를 정확히 짚는 데 쓴 시간이 가장 값졌습니다.
엔진을 옮기는 대신, 작업장을 서버에 지었다
선택지는 둘이었습니다. 편집 엔진을 서버에서도 돌아가게 다시 만들거나, 브라우저 자체를 서버에 올리거나.
앞의 길은 정공법처럼 보이지만 몇 달짜리입니다. 우리 엔진에는 그동안 쌓인 기능이 마흔 개 넘게 얹혀 있습니다. 표 안의 표를 다루고, 셀 안에 들어앉은 개체를 피해 글자를 넣고, 삽입한 텍스트의 크기를 주변과 맞추는 것 같은 자잘하고 까다로운 것들입니다. 그걸 서버용으로 다시 만든다는 건 그 시행착오를 통째로 반복한다는 뜻입니다.
그래서 뒤의 길을 택했습니다. 브라우저는 화면 없이도 돌아갑니다. 창을 띄우지 않고, 사람이 마우스로 누르는 대신 코드가 명령을 보냅니다. 기계를 작업장 밖으로 꺼내는 대신, 서버 안에 창문 없는 작업장을 하나 더 짓고 로봇팔을 넣은 셈입니다.
먼저 이게 되는지부터 확인했습니다. 화면 없는 브라우저를 띄우고, 문서를 밀어 넣고, 구조를 뽑고, 칸을 채우고, 저장된 바이트를 회수해 다시 열어보는 것까지. 반나절짜리 실험이었고, 결과는 예상과 달랐습니다.
예상보다 두 자릿수 빨랐다
브라우저를 서버에서 돌린다고 하면 대개 '느리겠네'라고 생각합니다. 저도 그랬습니다. 문서 하나에 몇 초는 걸릴 거라 보고, 그 전제로 처리량과 비용을 계산하고 있었습니다.
실제로 재보니 아니었습니다. 브라우저를 처음 띄우는 데는 시간이 걸리지만 그건 한 번뿐이고, 일단 준비된 뒤에는 문서 하나를 열고 구조를 뽑고 채우고 저장하는 왕복이 밀리초 단위로 끝났습니다.
| 단계 | 걸린 시간 |
|---|---|
| 문서 열기 | 228ms |
| 양식 구조 추출 (영역 112개 · 표 21개) | 48ms |
| 본문·표 셀 채우기 | 81ms |
| 저장 | 8ms |
| 저장한 파일 재확인 | 40ms |
| 지면 이미지 렌더링 | 50ms |
연속으로 열 번을 반복해도 회당 50밀리초 안팎에서 흔들리지 않았고, 오류는 없었습니다. 실제 모집 중인 공고에 붙은 양식으로도 확인했습니다 — 공고 한 건의 11페이지짜리 신청 양식을 열어 표 열아홉 개의 구조를 그대로 인식했습니다.
이 숫자가 설계를 한 번 더 바꿨습니다. 느릴 거라 가정하고 준비하던 복잡한 장치들이 전부 불필요해졌거든요. 문서를 메모리에 붙들어 두고 관리할 필요 없이, 요청이 올 때마다 다시 열어도 되는 구조로 단순해졌습니다. 그러자 처리 도중 무언가 죽었을 때 문서가 유실되는 시나리오 자체가 사라졌습니다.
측정이 설계를 단순하게 만든다
- 성능을 재는 이유는 보통 느린 곳을 찾기 위해서지만, 이번엔 반대였습니다.
- 빠르다는 걸 확인하고 나니 미리 만들어 두려던 복잡한 장치를 지울 수 있었습니다.
- 추측으로 설계했다면 쓰지도 않을 캐시와 상태 관리를 붙였을 겁니다.
무엇을 열고, 무엇을 닫아둘 것인가
통로가 뚫리자 다음 질문이 왔습니다. AI에게 무엇을 할 수 있게 해줄 것인가.
가장 유혹적인 선택은 '초안 생성'을 그대로 열어주는 것이었습니다. 우리에겐 이미 양식을 읽고 항목마다 내용을 채워 넣는 기능이 있으니까요. 도구 하나로 감싸면 끝입니다.
그런데 그렇게 하면 AI가 버튼 누르는 기계가 됩니다. 결과물의 품질이 우리가 미리 짜둔 지시문에 갇혀버려요. 사용자가 쓰는 AI가 우리보다 그 회사를 더 잘 알고 있는데도 말입니다.
그래서 반대로 정했습니다. 글은 사용자의 AI가 쓴다. 우리는 한글 문서를 다루는 부분만 연다. 문서를 열고, 구조를 알려주고, 지정한 자리에 넣고, 검증하고, 원래 형식으로 돌려주는 것까지입니다.
도구를 만들며 지킨 원칙이 세 가지 있습니다.
- 채우는 위치는 반드시 지정받는다. 도구가 알아서 빈 칸을 찾아 채우지 않습니다. 양식에서 비어 있다고 다 작성란은 아니거든요 — 안내문 자리, 머리글 자리, 구분선이 섞여 있습니다. 실제로 초기 실험에서 안내문 칸에 글자가 들어간 적이 있었고, 그게 이 원칙을 만들었습니다. 어디에 무엇을 쓸지는 문맥을 읽은 AI가 판단하는 게 맞습니다.
- 표를 깨뜨리면 저장하지 않는다. 채우기 전후로 문서의 구조를 비교합니다. 표가 사라지거나 칸 수가 줄었으면 그 작업을 되돌리고 이유를 알려줍니다. 사람이 눈으로 보고 있지 않은 상황이라 이 그물이 없으면 망가진 파일이 조용히 나갑니다.
- 내보낼 때 한 번 더 연다. 저장한 결과를 다시 열어 정상인지 확인한 뒤에야 사용자에게 전달합니다. 예전에 저장된 파일이 한글에서 열리지 않는 사고를 겪은 적이 있어서, 그 검사를 아예 경로에 박아 넣었습니다.
한 가지 더, 눈에 잘 안 띄지만 중요한 결정이 있었습니다. 완성된 문서 파일을 대화에 실어 보내지 않는 것입니다. 60킬로바이트짜리 한글 파일을 텍스트로 바꿔 대화에 넣으면 2만 토큰 가까이 잡아먹습니다. 사용자가 쓸 수 있는 맥락을 우리가 파일 하나로 날려버리는 셈이죠. 그래서 파일은 별도 통로로 주고받고, 대화에는 결과 요약만 남깁니다.
대신 AI가 결과를 눈으로 확인할 수는 있게 했습니다. 지정한 페이지를 이미지로 렌더링해 돌려주는 도구를 뒀습니다. 채운 내용이 실제 지면에서 어떻게 보이는지, 칸을 잘못 짚지는 않았는지 스스로 점검할 수 있습니다. 사람이 중간에서 봐주지 않는 흐름이라, AI에게 눈을 주는 게 생각보다 큰 차이를 만듭니다.
아직 안 된 것들
여기까지가 지금 상태입니다. 도구는 동작하고, 실제 공고 양식으로 처음부터 끝까지 통과하는 것도 확인했습니다. 하지만 아직 외부에 열려 있지는 않습니다.
남은 건 두 가지입니다. 하나는 이 처리기를 상시 서비스로 띄우는 일이고, 다른 하나는 요금 정책을 정하는 일입니다. 두 번째가 생각보다 까다롭습니다. 기존 기능들은 우리가 AI 사용료를 부담하는 구조라 그 원가를 기준으로 값을 매겼는데, 이 통로에서는 사용료를 사용자가 이미 내고 있습니다. 우리 쪽 원가 구조가 아예 다릅니다. 기존 기준을 그대로 가져오면 과하게 받게 됩니다.
그래서 값을 정하지 못한 채로 만들되, 나중에 정책이 바뀌어도 코드를 헤집지 않도록 값 결정을 한 곳에 모아뒀습니다. 정해지면 그 파일만 고치면 됩니다.
genDOC MCP는 준비 중입니다. 어떤 도구가 제공되는지, 연결은 어떻게 하는지 미리 확인하고 공개 알림을 받아보세요.
MCP 소개 보기JulianProduct Owner
genDOC의 프로덕트 오너이자 개발자. 공고 데이터 파이프라인부터 문서 편집기까지 제품을 직접 만들고 있습니다.
이 글은 Julian이 작성하고 genDOC 팀이 함께 검수했습니다.