옵시디안 플러그인 이름을 고민하다가, 같은 컨셉(Andrej Karpathy의 LLM
Wiki)을 구현한 플러그인이 이미 여럿 있다는 걸 알게 됐다. 검색 요약만
보고 넘어가는 대신, 여섯 개를 전부 GitHub에서 직접 클론해서 소스
코드를 읽었다. 첫 번째는 그중 가장 크고 활발한 obsidian-llm-wiki
(green-dalii, 플러그인 id는 karpathywiki)다.
GitHub 지표 — 551스타, 활발히 유지보수 중
9월 2일, GitHub API로 직접 확인하니 551스타·68포크, 전날에도 커밋될
만큼 활발했다. 릴리스 30개, 최신 버전은 1.27.0. ko-fi 후원 링크가 있는
걸 보면 진지하게 이어가는 1인 프로젝트로 보인다.
임베딩·벡터DB가 없다 — 대신 그래프 위 PPR
가장 큰 차이는 검색 방식이다. 이 플러그인은 RAG(임베딩+벡터 검색)를
아예 안 쓴다. 대신 노트에서 엔티티·개념을 추출해서 [[wiki-link]]
그래프를 만들고, 그 그래프 위에서 Personalized PageRank(PPR)로
검색한다 — 렉시컬 매칭 → 키워드 생성 → 로컬 스캔 → 시맨틱 폴백 →
PPR 확장까지 5단계 캐스케이드로 답을 찾아간다. 자체 벤치마크로는
순수 kNN(임베딩 유사도만 쓰는 방식) 대비 27.1% vs 24.1%로 우위라고
주장하고 있었다.
지금 만들고 있는 플러그인은 정반대로 RAG(임베딩) 기반이다. 우열의 문제라기보다는 서로 다른 트레이드오프다 — RAG는 구현이 단순하고 벡터 검색 라이브러리만 있으면 되지만, 이론적으로 “의미적으로 비슷해 보이는데 실제로는 무관하다”는 위험이 있다. 그래프+PPR은 관계가 명시적으로 링크로 존재해야 잡히니까 그런 위험은 구조적으로 줄어들지만, 링크가 안 걸린 진짜 관련 정보는 놓칠 수 있다.
main.ts를 쪼개는 방식 — 믹스인 패턴
main.ts가 453줄인데, 실제 로직은 대부분 main-commands/ 폴더
아래 파일(ingest-commands.ts, schema-commands.ts, connection- commands.ts 등)로 나뉘어 있다. 그리고 이걸 Object.assign (LLMWikiPlugin.prototype, ingestCommands, schemaCommands, ...)로
플러그인 클래스 프로토타입에 갖다 붙이는 믹스인 패턴을 쓴다.
TypeScript interface LLMWikiPlugin extends IngestMethods, SchemaCommandsMethods {} 같은 선언 병합도 같이 써서, 파일은 여러
개로 나뉘어 있는데 코드에서는 this.ingestSomething()처럼 한
클래스의 메서드인 것처럼 접근할 수 있게 만들어뒀다. 파일을 잘게
쪼개고 싶은데 매번 컨트롤러 객체를 거쳐 호출하는 번거로움은 피하고
싶을 때 쓸 만한 패턴이다.
API 키를 OS 키체인에 저장한다
Obsidian의 app.secretStorage API를 써서 API 키를 OS 키체인에
저장한다. 예전 버전에서 평문으로 data.json에 저장하던 사용자를
위한 마이그레이션 코드도 있는데, 이게 꽤 신중하게 짜여 있다 — 평문
키를 임시 필드에 스테이징해두고, 실제로 키체인 쓰기가 성공한
뒤에야 원본 평문 필드를 지운다. 쓰기가 실패하면 평문이 그대로
남아있어서 다음 로드 때 마이그레이션이 다시 시도된다. 마이그레이션
도중 뭔가 실패해도 키를 아예 잃어버리는 경우가 없게 만든 설계다.
배운 점
가장 크고 활발한 프로젝트답게, 검색 방식(그래프+PPR)부터 코드 구조(믹스인)까지 나름의 이유가 있는 선택들이었다. 특히 API 키 마이그레이션을 2단계로 쪼갠 부분은, “설정 스키마가 바뀔 때 사용자 데이터를 잃지 않는다”는 문제를 실제로 진지하게 다뤄본 사람의 코드라는 인상을 줬다.