
Document.Bot 구축하기: 실제 문서(PDF/Office)를 위한 로컬 우선(Local-First) AI 워크스페이스
요약
폴더 단위의 문서를 로컬에서 인덱싱하여 검색하고 질문할 수 있는 로컬 우선(Local-First) AI 워크스페이스 'Document.Bot'의 구축 과정을 다룹니다. Electron, LangChain, LangGraph, LanceDB를 활용한 데스크톱 앱 아키텍처와 설계 철학을 설명합니다.
핵심 포인트
- 폴더 전체를 워크스페이스로 간주하는 로컬 우선 접근 방식
- Electron, React, LangChain, LangGraph, LanceDB 기술 스택 활용
- 단순 채팅을 넘어 검색과 소스 검토를 핵심 기능으로 통합
- 데스크톱 앱 기반의 로컬 인덱싱을 통한 효율적인 문서 관리
대부분의 AI 문서 워크플로우는 여전히 문서 중심이 아닌 채팅 중심으로 설계된 것처럼 느껴집니다.
PDF를 업로드합니다. 질문을 합니다. 또 다른 파일을 업로드합니다. 다시 질문합니다. 답변이 유용해 보이면, 여전히 원래 소스로 돌아가 해당 구절을 확인하고, 문맥을 다른 문서로 복사한 뒤, 내일 똑같은 과정을 반복해야 합니다.
이러한 루프는 단일 파일의 경우에는 괜찮습니다. 하지만 작업이 폴더 단위로 이루어질 때는 무너집니다. PDF, Word 파일, 스프레드시트, Markdown 노트, 참고 자료, 내보내기 파일, 초안, 그리고 절반만 완성된 연구 자료들이 폴더에 담겨 있을 때 말이죠.
저는 다른 가정을 바탕으로 Document.Bot을 구축했습니다. 바로 '폴더가 워크스페이스(Workspace)다'라는 가정입니다.
Document.Bot은 문서 작업이 많은 업무를 위한 로컬 우선(Local-First) AI 워크스페이스입니다. PDF, Word 파일, 스프레드시트, Markdown 및 노트가 담긴 폴더를 연결하면, 앱이 이를 로컬에서 인덱싱(Indexing)합니다. 그러면 채팅창에 파일을 반복해서 업로드할 필요 없이, 워크스페이스 전체에 걸쳐 검색하고, 원본 소스를 검토하며, AI에게 질문하고, 출처가 뒷받침되는 검토 가능한 결과물을 초안으로 작성할 수 있습니다.
아키텍처는 이러한 제품의 약속을 따릅니다. Document.Bot은 Electron, React, LangChain, LangGraph, 그리고 LanceDB로 구축된 macOS 및 Windows용 데스크톱 앱입니다.
이 글은 왜 이러한 선택들이 제품에 적합했는지, 경계는 어디인지, 그리고 검색(Retrieval)과 소스 검토를 보이지 않는 배관(Plumbing)이 아닌 핵심 제품 기능으로 다루는 AI 앱을 구축하며 무엇을 배웠는지에 대한 빌더 노트(Builder note)입니다.
왜 데스크톱으로 시작했는가
많은 AI 제품들이 브라우저에서 시작됩니다. 많은 카테고리에서 그것은 올바른 결정입니다. 배포가 더 간단하고, 업데이트가 즉각적이며, 협업을 중앙 집중화하기가 더 쉽기 때문입니다.
Document.Bot은 달랐습니다.
사람들이 중요하게 생각하는 문서들은 이미 그들의 기기에 있습니다. 그것들은 동기화된 폴더, 어지러운 프로젝트 디렉토리, 다른 도구로부터의 내보내기(export) 파일, 이메일 첨부 파일, 오래된 클라이언트 폴더, 그리고 로컬 아카이브(local archives)에 존재합니다. 사용자에게 이러한 파일들을 웹 앱에 반복적으로 업로드하도록 요구하는 것은 마찰(friction)을 일으키고 즉각적으로 신뢰 문제를 제기합니다.
데스크톱은 앱이 사용자의 파일과 더 자연스러운 관계를 맺을 수 있게 해줍니다.
로컬 폴더와 직접 작업할 수 있습니다. 파일을 제자리에서 감시하고 인덱싱(indexing)할 수 있습니다. 원본 문서를 열고 사용자의 기존 폴더 구조를 보존할 수 있습니다. 사용자가 원격 호출(remote calls)이 필요한 모델이나 제공업체를 선택하지 않는 한, 로컬 문서 처리(local document processing)를 로컬에 유지할 수 있습니다.
이는 문서 중심의 작업에서 매우 중요한데, 사용자가 단순히 "답변"만을 원하는 것이 아니기 때문입니다. 그들은 답변이 어디에서 왔는지 알고 싶어 합니다. 그들은 출처를 검토하고 싶어 합니다. 생성된 초안을 원본 파일과 비교하고 싶어 합니다. 그들은 워크스페이스가 임시 업로드 상자처럼 느껴지는 것이 아니라, 자신의 자료에 기반을 두고 있다는 느낌을 받기를 원합니다.
Electron은 현대적인 웹 UI 스택을 사용하여 빠르게 움직이면서도, macOS와 Windows 모두를 위한 실제 데스크톱 제품을 만들고 싶었기에 실질적인 선택이었습니다.
로컬 애플리케이션 셸(Shell)로서의 Electron
Document.Bot에서 Electron은 단순히 웹 페이지를 감싸는 래퍼(wrapper)가 아닙니다. 그것은 제품 UI와 로컬 머신 사이의 경계입니다.
메인 프로세스(Main process)는 로컬 서비스들을 소유합니다: 앱 라이프사이클(app lifecycle), 파일 시스템 액세스(filesystem access), 인덱싱 오케스트레이션(indexing orchestration), 데이터베이스 액세스(database access), IPC, 패키징 관련 사항, 그리고 OS 통합입니다. 렌더러(Renderer)는 제품 경험을 소유합니다: 워크스페이스, 검색 UI, 문서 창(document panes), 채팅, 설정, 그리고 리뷰 흐름입니다.
그러한 분리는 중요합니다.
렌더러(renderer)가 로컬 기능들을 무분별하게 가져다 쓰는 특권적인 주머니가 되어서는 안 됩니다. 렌더러는 시스템 계층(system layer)과 명확한 인터페이스를 가진 React 애플리케이션처럼 동작해야 합니다. 메인 프로세스(main process)는 프리로드 브릿지(preload bridge)와 IPC를 통해 좁게 범위가 지정된 작업들을 노출할 수 있습니다: 폴더 선택, 인덱싱된 메타데이터 읽기, 검색 실행, 문서 열기, 인덱싱 작업 시작 또는 모니터링 등입니다.
패턴은 대략 다음과 같습니다:
- Electron 메인 프로세스 (main process): 로컬 서비스, 파일 시스템 (filesystem), 앱 라이프사이클 (app lifecycle), IPC, 패키징 (packaging)
- 프리로드 계층 (preload layer): 특권 코드가 있는 계층과 UI 사이의 제한된 브릿지
- React 렌더러 (renderer): 워크스페이스 UI, 소스 검사, 채팅 및 리뷰 흐름
- 로컬 인덱싱 계층 (local indexing layer): 문서 메타데이터, 청크 (chunks), 임베딩 (embeddings), 검색 상태 (retrieval state)
- AI 오케스트레이션 계층 (AI orchestration layer): 모델 호출, 도구 (tools), 에이전트 상태 (agent state), 워크플로우 제어
빌드 도구의 경우, 앱은 메인, 프리로드, 렌더러 번들에는 electron-vite를 사용하고, 데스크톱 패키징에는 electron-builder를 사용합니다. 패키징 경로는 매우 중요한데, Document.Bot이 네이티브 및 플랫폼 민감형 구성 요소에 의존하기 때문입니다: 로컬 데이터베이스, PDF 도구, OCR/인덱싱 헬퍼, 모델/인덱싱 런타임(runtimes)은 개발 단계뿐만 아니라 앱이 패키징된 후에도 예측 가능하게 동작해야 합니다.
이러한 아키텍처는 앱이 단순히 웹사이트인 척하지 않으면서도 UI의 생산성을 유지해 줍니다. 또한 보안 태세(security posture)를 더 쉽게 추론할 수 있게 해줍니다. 문서 에이전트가 유용해지기 위해 임의의 셸(shell) 권한을 가질 필요는 없습니다. 사실, 에이전트에게 기본적으로 광범위한 로컬 권한을 부여하는 것은 유용한 어시스턴트를 불편한 제품으로 만드는 가장 빠른 방법 중 하나입니다.
Document.Bot의 역할은 사용자가 문서를 다루는 것을 돕는 것이지, AI가 기기 내부를 돌아다니게 방치하는 것이 아닙니다.
제품 표면을 위한 React
React는 스택의 명백한 부분이지만, 여전히 제품의 형태를 결정짓습니다.
Document.Bot은 상태를 가진 UI (stateful UI)가 매우 많습니다: 연결된 폴더, 인덱싱 상태, 검색 결과, 파일 및 소스 태그, 문서 뷰어, 채팅 스레드, 리뷰 패널, 모델 설정, 그리고 계정 관련 인터페이스 등이 포함됩니다. React는 UI가 수많은 작은 상태 기반 인터페이스 (state-driven surfaces)의 조합으로 이루어지기 때문에 이러한 종류의 애플리케이션에 매우 적합합니다.
핵심적인 디자인 선택은 인터페이스가 "단순한 채팅"에 머물러서는 안 된다는 점입니다.
채팅은 유용하지만, 문서 작업에는 메시지 박스 이상의 것이 필요합니다. 사용자는 코퍼스 (corpus)를 볼 수 있어야 합니다. 질문을 하기 전에 검색을 해야 합니다. 답변을 받은 후에는 소스 파일을 검토해야 합니다. 어떤 파일이 사용되었는지, 어떤 청크 (chunks)가 검색되었는지, 그리고 출력된 결과가 검토 가능한 수준인지 알아야 합니다.
이것이 바로 UI가 단순한 대화가 아닌 워크스페이스 (workspace)를 중심으로 구축된 이유입니다.
검색, 소스 태그, 뷰어 창, 그리고 리뷰 흐름은 부차적인 기능이 아닙니다. 이것들은 사용자가 신뢰를 구축하는 방식입니다. 생성된 답변은 근거가 되는 파일과 연결되어 있고, 사용자가 주장(claim)에서 원본 소스로 바로 이동할 수 있을 때 훨씬 더 유용합니다.
이는 또한 인터페이스 내에서 AI의 역할도 변화시킵니다. 모델이 제품의 전부가 아닙니다. 모델은 브라우징, 필터링, 검사, 초안 작성 및 검증을 포함하는 워크플로 (workflow) 내부의 하나의 레이어입니다.
로컬 검색을 위한 LanceDB
앱이 폴더 우선 (folder-first) 방식으로 전환되면, 로컬 인덱싱 (local indexing)이 중심이 됩니다.
Document.Bot은 모델에게 답변을 요청하기 전에 문서를 파싱 (parse)하고, 유용한 청크로 분할하며, 메타데이터를 저장하고, 관련 자료를 검색해야 합니다. LanceDB는 데스크톱 앱과 함께 실행될 수 있고 로컬 문서 청크, 메타데이터 및 임베딩 (embeddings)에 대한 검색을 지원할 수 있기 때문에 이 작업에 적합합니다.
현재 아키텍처에서 Document.Bot의 QMD 검색 레이어는 LanceDB 기반의 저장소를 사용합니다. 이는 인덱싱된 워크스페이스에 대해 전문 검색 (full-text search), 벡터 검색 (vector search), 그리고 하이브리드 검색 (hybrid retrieval) 경로를 지원하면서도, 제품에 노출되는 검색/도구 API를 안정적으로 유지합니다.
중요한 제품 아이디어는 간단합니다: 모델이 워크스페이스 전체에 가장 먼저 닿는 요소가 되어서는 안 된다는 것입니다.
AI가 답변하기 전에, 앱은 문제를 좁혀야 합니다. 로컬 인덱스 (local index)를 검색하고, 관련 청크 (chunks)를 찾으며, 소스 메타데이터 (source metadata)를 보존하고, 사용자가 검토할 수 있을 만큼 검색 프로세스를 충분히 가시화해야 합니다.
또한 로컬 인덱스는 모델 제공자 (model provider)를 공식 문서 기록 시스템 (system of record)으로 만들지 않으면서도, 연결된 폴더에 대한 앱의 지속적인 메모리를 제공합니다. 원본 파일은 신뢰할 수 있는 유일한 원천 (source of truth)으로 남습니다. 인덱스는 해당 파일들을 다시 구축할 수 있는 표현 (representation)입니다.
이러한 차이는 매우 중요합니다.
인덱스는 오래될 수 있습니다. 파서 (parsers)는 개선될 수 있습니다. 임베딩 모델 (embedding models)은 변경될 수 있습니다. 파일 형식에는 예외 케이스가 존재합니다. 만약 인덱스를 다시 구축 가능한 캐시 (rebuildable cache)로 취급한다면, 앱은 이러한 변화로부터 복구할 수 있습니다. 하지만 인덱스를 표준 문서 저장소 (canonical document store)로 취급한다면, 작은 실수조차 훨씬 더 큰 비용을 초래하게 됩니다.
이러한 시스템을 구축하며 얻은 교훈 중 하나는 이것입니다: 인덱싱을 지속 가능하게 유지하되, 신성불가침한 것으로 만들지는 마십시오.
앱은 불필요한 작업을 반복하지 않기 위해 충분한 상태 (state)를 기억해야 하지만, 항상 원본 폴더로부터 다시 구축하는 것이 가능해야 합니다. 원본 파일은 AI 워크플로 (workflow) 외부에서도 검토 가능한 상태로 유지되어야 합니다.
탐색용 텍스트와 문서 뷰의 분리
문서 인덱싱 과정에는 또 다른 실질적인 교훈이 숨겨져 있습니다: 추출된 텍스트 (extracted text)와 고충실도 문서 뷰 (high-fidelity document views)는 서로 다른 목적을 수행한다는 점입니다.
검색 (retrieval)을 위해 앱에는 탐색용 텍스트 (discovery text)가 필요합니다. 청킹 (chunking), 검색, 임베딩, 그리고 인용을 수행할 수 있을 만큼 충분히 깨끗하고 구조화된 텍스트가 필요합니다. 이 텍스트가 소스 문서의 모든 시각적 세부 사항을 완벽하게 재현할 필요는 없습니다.
사용자 검토를 위해서는 앱에 고충실도 뷰 (high-fidelity views)가 필요합니다. 사용자는 원본 PDF 레이아웃, 표, 서식, 페이지 구조, 스프레드시트 셀, 주석 또는 주변 맥락을 확인해야 할 수도 있습니다. 검토 경험은 인덱싱을 위해 추출된 텍스트에만 의존할 수 없습니다.
이 두 계층을 분리함으로써 제품은 더욱 개선됩니다.
검색 계층(Retrieval layer)은 검색 품질, 청크 경계(Chunk boundaries), 메타데이터(Metadata), 모델 컨텍스트(Model context)를 최적화할 수 있습니다. 반면, 보기 계층(Viewing layer)은 인간의 검토(Human inspection)를 위해 최적화될 수 있습니다. 사용자가 소스(Source)를 클릭했을 때, 단순히 진공 상태의 고립된 청크를 보여주는 것이 아니라 원래의 결과물(Artifact)로 다시 안내해야 합니다.
이것은 제가 어떤 문서 AI 앱에서도 반복해서 강조할 설계 원칙 중 하나입니다. 즉, 검색(Retrieval)만으로는 충분하지 않습니다. 소스 검토(Source inspection) 또한 제품의 일부입니다.
AI 워크플로우를 위한 LangChain 및 LangGraph
Document.Bot은 AI 계층이 단 하나의 프롬프트(Prompt) 이상이기 때문에 LangChain과 LangGraph를 사용합니다.
제공자(Provider) 선택, 모델 선택, 검색 도구, 문서 컨텍스트, 에이전트 상태(Agent state), 장기 실행 워크플로우(Long-running workflows), 그리고 앱이 일시 중지하거나 재개하거나 사용자가 중간 결과를 검토하도록 해야 하는 지점들이 존재합니다.
LangChain은 제공자 및 모델 추상화(Abstraction)에 유용합니다. 이는 모든 상호작용을 특정 제공자의 API 형태에 맞춰 하드코딩하지 않고도, 앱이 다양한 모델 백엔드(Model backends)에서 작동할 수 있는 방법을 제공합니다.
LangGraph는 워크플로우가 상태 유지(Stateful)가 되는 시점에 유용합니다. 문서 어시스턴트는 종종 "모델에 메시지 전송" 이상의 구조를 필요로 합니다. 정보를 검색하고, 소스를 바탕으로 추론하며, 후속 질문을 던지고, 출력을 초안 작성하고, 이를 수정하며, 상태를 보존하고, 중단 후 재개하는 과정이 필요할 수 있습니다.
이 지점에서 그래프 기반 오케스트레이션(Graph-based orchestration)이 적합합니다. 이를 통해 앱은 AI 워크플로우를 단일한 불투명한 호출(Opaque call)이 아니라, 상태를 가진 일련의 단계(Steps)로 모델링할 수 있습니다.
데스크톱 소프트웨어에서는 체크포인팅(Checkpointing)과 중단 가능성(Interruptibility)이 중요합니다. 사용자는 노트북을 닫습니다. 인덱싱(Indexing)에는 시간이 걸립니다. AI 호출은 실패할 수 있습니다. 문서 작업은 단순한 채팅 응답보다 더 오래 실행될 수 있습니다. 앱은 전체 워크플로우를 잃지 않고 이를 처리할 수 있어야 합니다.
Document.Bot은 단일 모델 호출(model call) 이후에도 스레드 상태(thread state)가 유지될 수 있도록 로컬 체크포인터(local checkpointer)를 사용합니다. 이는 엔지니어링 측면의 트레이드오프(tradeoffs)를 동반합니다. 즉, 체크포인트 저장소(checkpoint storage)를 유지, 제한 및 복구할 수 있어야 합니다. 하지만 장시간 실행되는 문서 작업을 수행하는 데스크톱 앱의 경우, 내구성이 있는 상태(durable state)는 단순한 프레임워크의 세부 사항이 아니라 제품의 일부입니다.
제품의 경계(product boundary)는 기술적 경계만큼이나 중요합니다. Document.Bot은 문서 편집을 맹목적으로 자율화(autonomous)하려고 시도하지 않습니다. 목표는 검토 가능하고 소스에 기반한(source-backed) 결과물을 만드는 것입니다. AI는 검색, 요약, 비교 및 초안 작성을 도울 수 있지만, 판단과 최종 결정을 위해 사용자가 루프 안에 머물러야(stay in the loop) 합니다.
모델 선택을 명시적으로 만들기
AI 앱이 저지를 수 있는 실수 중 하나는 너무 많은 것을 숨기는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
