한 문장으로 이 프로젝트가 뭘 하는지부터.
OpenKB = "문서를 검색하는 게 아니라, 문서를 한 번 위키로 컴파일해 두고 그 위키에 물어보는 시스템." 보통의 RAG는 질문이 올 때마다 문서 조각을 찾아 와 답을 만든다. 다음 질문이 오면 처음부터 다시 찾는다. OpenKB는 순서를 뒤집는다 — 문서를 넣는 시점에 LLM이 요약·개념 페이지·개체 페이지·상호 링크를 미리 만들어 마크다운 위키로 쌓아 두고, 질문은 그 위키를 상대로 받는다. 지식이 매번 재발견되는 게 아니라 누적된다.
일반 RAG는 질문을 받을 때마다 서가로 달려가는 사서다. 매번 처음부터 찾으니 어제 찾은 것도 기억에 안 남고, 서로 다른 책이 같은 주제를 다룬다는 사실도 발견되지 않는다.
OpenKB는 책이 들어올 때마다 미리 읽고 정리해 두는 사서다. 요약을 쓰고, 개념마다 카드를 만들고, "이 개념은 저 책에도 나온다"고 링크를 걸어 둔다. 질문이 오면 그 정리본을 편다. 정리 비용을 입고 시점에 한 번 치르고, 조회 때마다 그 이득을 회수하는 구조다.
PageIndex가 이걸 한다. 벡터 DB를 세울 필요가 없고, 왜 그 대목을 골랐는지 설명이 남는 게 장점이다.발상의 출처가 분명하다. README가 안드레이 카파시(Andrej Karpathy)의 글을 근거로 든다 — LLM이 요약·개념 페이지·상호 참조를 만들고 그걸 자동으로 유지한다면, 지식은 질의마다 재유도되는 대신 시간이 갈수록 쌓인다는 아이디어다. OpenKB는 그 구상을 CLI로 구현한 것이고, 만든 곳은 PageIndex를 내놓은 VectifyAI다.
별 4.2천 개 — RAG 도구가 널린 판에서 무엇이 달랐나.
RAG를 붙이려면 보통 Pinecone·Qdrant·Milvus 중 하나를 세우고, 임베딩 모델을 고르고, 청크 크기를 튜닝한다. OpenKB는 그 층을 통째로 뺐다. 긴 문서는 PageIndex가 목차 트리로 색인하고, LLM이 그 트리를 읽어 필요한 가지로 내려간다. 운영자 입장에서 관리할 데이터베이스가 하나도 없다는 게 이 프로젝트의 가장 큰 판매 논리다.
컴파일된 위키는 .md 파일과 [[상호 링크]]뿐이다. 그래서 Obsidian으로 바로 열려 그래프 뷰가 그려지고, grep이 먹고, git으로 버전 관리가 되고, 폴더째 복사하면 그게 백업이다. 특정 벤더의 저장 포맷에 갇히지 않는다는 뜻이기도 하다.
위키가 한 덩어리가 아니다. 예제 위키를 열어 보면 summaries/(문서별 요약) · concepts/(개념 카드) · entities/(사람·조직·장소·제품) · explorations/(저장한 질의 결과)로 갈린다. 최근 업데이트에서는 위키 페이지가 구글의 Open Knowledge Format(OKF) 규격을 따르게 됐다 — 만든 지식을 다른 도구와 주고받을 길을 연 것이다.
이게 특이한 기능이다. openkb skill new <이름> "<의도>"를 실행하면 위키에 쌓인 지식을 증류해 배포 가능한 에이전트 스킬(SKILL.md + references/)을 만든다. 만들고 끝이 아니라 skill validate로 형식을 검사하고 skill eval로 의도한 프롬프트에서 실제로 발동하는지까지 시험한다. 레포에 실제 예제(transformer-attention)가 들어 있다.
짧은 문서는 markitdown으로 마크다운화하면서 이미지를 pymupdf로 뽑아 인라인 배치하고, 20쪽 넘는 긴 PDF는 PageIndex가 트리 색인과 함께 그림을 추출한다. 답변이 도표를 근거로 인용할 수 있다는 뜻이다.
RAG는 질의할 때 돈이 들고, OpenKB는 문서를 넣을 때 크게 든다. 요약·개념 추출·개체 추출·링크 정리가 전부 모델 호출이다. 문서가 많고 질문이 적으면 손해고, 문서가 안정적이고 같은 자료에 계속 물어보는 상황이면 이득이다. 도입 전에 "우리는 어느 쪽인가"를 먼저 답해야 한다.
파이썬 14개 의존성 — 전부 버전이 고정돼 있다.
먼저 눈에 띄는 게 의존성 전부가 ==로 못 박혀 있다는 점이다. >=가 하나도 없다. LLM 파이프라인은 라이브러리 미세 변경에도 출력이 흔들려서, 재현성을 위해 범위 지정을 포기한 선택으로 읽힌다.
| 영역 | 패키지 | 맡은 일 |
|---|---|---|
| 핵심 색인 | pageindex==0.3.0.dev3 | 긴 문서를 트리로 색인 — 이 프로젝트의 심장이자 같은 회사의 다른 제품 |
| 문서 변환 | markitdown[docx,pptx,xlsx,xls] | Word·PPT·Excel → 마크다운. 마이크로소프트가 만든 변환기 |
| 웹 추출 | trafilatura==2.0.0 | URL을 넣으면 광고·메뉴를 걷어내고 본문만 추출 |
| 모델 호출 | litellm + openai | litellm이 100여 개 프로바이더를 한 인터페이스로 흡수 |
| 에이전트 | openai-agents==0.17.3 | 질의·채팅 에이전트 루프 |
| CLI | click + rich + prompt_toolkit | 명령 14개 + 대화형 채팅 화면 |
| 파일 감시 | watchdog | raw/에 파일이 떨어지면 자동 컴파일 |
| 동시성 안전 | portalocker | 여러 프로세스가 같은 KB를 건드리지 못하게 파일 잠금 |
| 깨진 JSON 복구 | json-repair | 모델이 뱉은 불완전한 JSON을 고쳐 파싱 |
이 둘은 실제로 굴려 본 사람만 넣는 의존성이다. json-repair는 "모델이 JSON을 자주 깨뜨린다"는 현실을, portalocker는 "watch 모드와 수동 add가 동시에 돌 수 있다"는 현실을 각각 인정한 흔적이다. 데모용 프로젝트에는 없고 운영해 본 프로젝트에만 있다.
frontend/에 TSX 76개가 있고, openkb-web 명령으로 지식 워크벤치를 띄운다. 브라우저에서 KB를 훑고, 문서를 올려 컴파일하고, 질의·채팅을 스트리밍으로 받는다. openkb/api.py(785줄) + api_helpers.py(776줄)가 그 뒤를 받친다.
두 층 — 위키 기반(foundation)과 생성기(generators).
위키가 데이터베이스가 아니라 파일이라는 결정이 이 프로젝트의 모든 성질을 낳는다. Obsidian 호환도, grep도, git 이력도, 백업도 여기서 자동으로 따라온다. 대신 대가도 분명하다 — 수만 개 문서로 커지면 파일 시스템이 병목이 되고, 동시 쓰기를 portalocker로 직접 막아야 한다.
README가 표로 못 박은 분기다. 20쪽 미만이면 markitdown으로 마크다운화해 LLM이 전문을 읽고, 20쪽 이상이면 PageIndex가 트리 색인을 만들어 LLM이 트리를 타고 내려간다. 코드에서는 _is_long_doc()이 그 판단을 한다. 결과물(요약 + 개념)은 어느 경로든 같은 모양이라, 입력이 길든 짧든 위키 쪽에서는 차이를 몰라도 된다.
재미있는 대목이다. 위키를 어떻게 조직할지가 코드에 하드코딩된 게 아니라 wiki/AGENTS.md라는 마크다운 파일에 적혀 있고, 그게 곧 LLM에게 주는 유지보수 지침서다. 사용자가 이 파일을 고치면 위키 구조 자체가 바뀐다. recompile --refresh-schema가 그 스키마까지 갱신한다.
도서관의 분류 규칙집을 사서의 머릿속이 아니라 벽에 붙여 둔 셈이다. 규칙을 바꾸고 싶으면 사서를 다시 교육시키는 게 아니라 벽에 붙은 종이를 고쳐 쓰면 된다. 프롬프트를 코드 밖으로 꺼내 사용자가 편집할 수 있는 자산으로 만든 패턴이다.
openkb lint(649줄)가 있다는 게 이 프로젝트의 성숙도를 보여 준다. 구조 검사(색인과 실제 파일이 어긋나지 않았나)와 지식 건강 검사를 함께 돌린다. 문서가 계속 들어오는 위키는 링크가 끊기고 개념이 중복되며 내용이 서로 모순되기 쉬운데, 그걸 사람이 눈으로 잡을 수 없다는 현실을 인정한 장치다.
openkb remove <문서>는 원본만 지우는 게 아니라 그 문서에서 파생된 위키 페이지·이미지·레지스트리 항목·PageIndex 상태를 함께 정리한다. --dry-run으로 미리 볼 수 있고, --keep-raw·--keep-empty로 남길 것을 고른다. 파생물이 많은 시스템에서 지우기가 넣기보다 어렵다는 걸 아는 설계다.
파이썬 1만 9천 줄 — 어디부터 열까.
| 대상 | 왜 |
|---|---|
examples/commands/sample-wiki/ | 코드보다 먼저 결과물을 본다. 개념 페이지가 어떻게 생겼고 링크가 어떻게 걸리는지 5분이면 감이 온다 |
openkb/agent/compiler.py | 문서 → 위키 변환의 전 과정. 어떤 프롬프트로 무엇을 뽑는지가 전부 여기 있다 |
openkb/lint.py | "지식이 건강한가"를 기계로 어떻게 재는지 — 다른 데서 보기 힘든 코드다 |
이 레포에서 실제로 가져갈 것.
RAG 개선이라 하면 보통 청킹 전략·리랭커·하이브리드 검색을 만진다. OpenKB는 계산을 언제 하느냐를 바꿨다. 소프트웨어에서 흔한 트레이드오프(색인 vs 스캔, 컴파일 vs 인터프리터)를 지식 검색에 적용한 사례라, RAG를 붙여 본 사람일수록 얻는 게 많다.
wiki/AGENTS.md와 prompts/skill_create.md가 그 예다. 프롬프트를 문자열 리터럴로 코드에 박으면 사용자가 못 고치고 diff도 안 읽힌다. 파일로 빼면 버전 관리·리뷰·사용자 커스터마이징이 한 번에 열린다.
json-repair로 깨진 출력 복구, _run_compile_with_retry로 재시도, portalocker로 동시성 차단, _staging_dir_for·_snapshot_add_paths로 실패 시 되돌리기. LLM은 확률적으로 실패하므로 파이프라인 전체가 그 전제 위에 서야 한다는 걸 코드로 보여 준다.
skill new → skill validate(형식) → skill eval(발동 여부). 에이전트 스킬을 쓰는 사람은 많지만 그 스킬이 원하는 상황에서 실제로 불려 나오는지 시험하는 코드는 흔하지 않다. skill/evaluator.py가 그 실물이다.
넣기는 쉽고 지우기는 어렵다. _build_remove_plan → _execute_remove_plan으로 계획을 먼저 만들고 미리 보여 준 뒤 실행하는 구조는, 파생물이 얽힌 어떤 시스템에도 그대로 옮길 수 있는 패턴이다.
가벼운 대신 API 비용이 든다.
| 항목 | 필요 | 메모 |
|---|---|---|
| 파이썬 | ≥ 3.10 | pip install openkb 한 줄 |
| 모델 | LLM 프로바이더 키 1개 이상 | litellm 경유라 OpenAI·Anthropic·로컬 등 폭넓게 가능 |
| GPU | 불필요 | 임베딩·벡터 연산이 없으니 로컬 GPU가 쓸 데가 없다 |
| 디스크 | 원본 + 위키 + 추출 이미지 | 위키는 텍스트라 작지만 PDF에서 뽑은 이미지가 자리를 차지한다 |
| 웹 UI | 추가 요구 없음 | openkb-web이 같은 프로세스에서 뜬다 |
| 실질 비용 | 넣을 때의 토큰 | 긴 PDF 한 편이 요약·개념·개체·링크로 여러 번 호출된다 |
정리하면 인프라 요구는 거의 없고 비용은 전부 API로 간다. 벡터 DB를 세울 필요가 없다는 게 곧 "서버 비용이 API 비용으로 옮겨 갔다"는 뜻이기도 하다. 문서 100편을 한 번 넣고 1년간 물어볼 거라면 유리하고, 매일 문서가 쏟아지는데 질문은 가끔이면 불리하다.
난이도별 다섯 개.
examples/commands/sample-wiki/를 Obsidian으로 열어 그래프를 본다. 그다음 내 PDF 서너 편으로 openkb init → openkb add를 돌려 같은 구조가 나오는지 본다.
openkb query "질문"과 openkb chat에서 같은 질문을 하고 답과 인용을 비교한다. --save로 저장한 뒤 wiki/explorations/에 뭐가 남는지 확인한다.
규칙 파일에 "모든 개념 페이지에 반대 개념 항목을 넣어라" 같은 조항을 추가하고 openkb recompile --all --refresh-schema를 돌린다.
내 위키에서 openkb skill new my-skill "…"로 스킬을 만들고 skill eval을 돌린다. 발동이 안 되면 SKILL.md의 description을 고쳐 다시 잰다.
openkb/lint.py를 읽고 "고아 개념 페이지(어느 요약에서도 링크되지 않은 개념)"를 잡는 검사를 넣는다.
4주.
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | 쓰면서 감 잡기 | 예제 위키 읽기 → 내 문서 컴파일 → query·chat·visualize 다 돌려 보기. 어떤 문서 종류에서 개념 추출이 잘/못 되는지 기록 |
| 2주차 | 컴파일러 읽기 | agent/compiler.py를 따라가며 요약·개념·개체·링크가 각각 어떤 프롬프트로 나오는지 정리. 프롬프트 하나를 고쳐 결과 변화를 관찰 |
| 3주차 | 벡터리스 검색 | PageIndex 저장소로 넘어가 트리 색인과 트리 탐색이 어떻게 도는지 본다. 임베딩 방식과의 장단을 문서로 정리 |
| 4주차 | 지식 유지보수 | lint.py + mutation.py + remove 경로. "계속 자라는 지식 베이스를 어떻게 썩지 않게 하나"를 주제로 내 규칙을 추가 |
비교 대상으로 ai-memory(같은 카탈로그에 있다)를 나란히 읽으면 좋다. 둘 다 "LLM이 마크다운 위키를 유지한다"는 카파시식 발상에서 출발했는데, OpenKB는 내가 넣은 문서를, ai-memory는 에이전트가 일한 기록을 재료로 삼는다. 같은 아이디어의 두 갈래를 붙여 보면 설계 선택이 선명해진다.
이 문서에 나온 말들.
| 용어 | 뜻 |
|---|---|
| 지식 컴파일 (compile) | 문서를 넣는 시점에 LLM이 요약·개념·링크를 미리 만들어 두는 것. OpenKB의 핵심 동사 |
| PageIndex | 같은 팀이 만든 벡터리스 색인 엔진. 문서를 목차 트리로 만들고 LLM이 그 트리를 타고 내려간다 |
| 벡터리스 (vectorless) | 임베딩·유사도 대신 구조와 추론으로 찾는 방식. 벡터 DB가 필요 없다 |
| 개념 페이지 / 개체 페이지 | 여러 문서에 걸친 주제를 모은 카드 / 사람·조직·장소·제품을 모은 카드 |
| exploration | 질의 결과를 위키에 저장한 것. 다음 질문의 재료가 된다 |
| OKF | Open Knowledge Format. 구글이 제안한 지식 공유 규격. 위키 페이지가 이 형식을 따른다 |
| Skill Factory | 위키에서 배포 가능한 에이전트 스킬을 증류해 내는 기능 |
| lint | 코드 검사기에서 빌린 말. 여기선 지식의 구조·건강을 검사한다 |
| litellm | 수많은 LLM 프로바이더를 하나의 호출 인터페이스로 감싸는 라이브러리 |
| markitdown | Word·PPT·Excel 등을 마크다운으로 바꾸는 마이크로소프트 변환기 |
| portalocker | 여러 프로세스가 같은 파일·폴더를 동시에 건드리지 못하게 잠그는 라이브러리 |
원문으로 더 깊이.
examples/commands/sample-wiki/ (컴파일된 위키 실물) · examples/skills/transformer-attention/ (스킬 산출물)