Revenant: 죽은 파이썬 라이브러리를 위해 영매팅을 했더니, 테스트를 통과하며 돌아왔다
요약
Revenant는 버려진 오픈 소스 프로젝트의 역사를 분석하고 되살리는 도구입니다. 이 도구는 커밋, 이슈 등 모든 기록을 수집하여 '유령'에게 질문하게 하고 출처 기반 답변을 얻습니다. 이를 통해 테스트 통과 0개였던 `psf/requests-html` 라이브러리를 Python 환경에서 30/30으로 복원하는 성과를 보여주었습니다.
핵심 포인트
- Revenant는 버려진 오픈 소스 프로젝트의 모든 이력을 수집합니다.
- 프로젝트에 질문하면, 기록을 바탕으로 출처가 명시된 1인칭 답변을 얻습니다.
- 샌드박스 환경에서 테스트를 실행하여 실제 코드 복원 및 검증이 가능합니다.
본 글은 Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine에 제출하는 글입니다.
요약 (TL;DR): Revenant는 버려진 오픈 소스 프로젝트를 위해 영매팅(séance)을 진행합니다. 이 도구는 죽은 레포지토리(repo)의 역사를 질문할 수 있는 기억으로 변환하며, 프로젝트가 모든 문장을 인용하며 1인칭으로 답변하게 만듭니다. 그런 다음 샌드박스(sandbox)에서 부검을 실시하고, 프로젝트를 테스트 검증된 수정 사항 하나씩 되살립니다. 첫 번째 환자였던
psf/requests-html은 테스트 통과 0개에서 Python 3.9–3.13 환경에서 30/30으로 돌아왔습니다. 라이브는 **revenant-zaq3.onrender.com**에서 확인 가능합니다.
내가 만든 것 (What I Built)
오늘날 파이썬(Python) 환경에서 import requests_html을 실행하면 오류가 발생합니다. Kenneth Reitz의 "HTML Parsing for Humans"는 마지막 업데이트가 2019년 2월에 이루어졌습니다. 이 프로젝트의 이슈 트래커(issue tracker)에 올라온 최신 스레드들은 모두 같은 내용을 담고 있습니다: "이 프로젝트가 죽은 건가?", "더 이상 임포트되지 않는다", "관리자가 있나요?"
제가 섬뜩하다고 느끼는 부분은 이 프로젝트의 목소리가 사라지지 않았다는 점입니다. 관리자들이 작성한 모든 커밋 메시지(commit message), 이슈, 리뷰 및 문서 페이지들이 여전히 남아있습니다. 다만, 그것을 말해줄 사람이 아무도 없는 상태인 것이죠.
그래서 '기계 속 유령(Ghost in the Machine)' 프로젝트를 위해 저는 Revenant를 만들었습니다. 이는 버려진 오픈 소스 소프트웨어를 위한 영매팅이며, 세 가지 의식으로 구성되어 있습니다:
- 소환(Summon). 프로젝트의 전체 이력(커밋(commits), 이슈(issues), PRs, 댓글(comments), 리뷰(reviews), 문서(docs) 및 폐기된 문서 사이트의 아카이브 사본)을 검색 가능한 메모리로 수집합니다.
- 유령에게 질문하기(Ask the ghost). 프로젝트에 질문을 합니다. 유령은 _자신의 기록을 바탕으로 1인칭 시점_에서 답변하며, 모든 문장은 출처가 된 이슈, 커밋 또는 문서의 링크를 명시합니다. 기록에 아무것도 없을 경우, _"영혼들은 침묵하고 있습니다."_라고 말합니다.
- 부검 및 부활(Autopsy and resurrection). 네트워크 연결이 없는 샌드박스에서 프로젝트 자체 테스트를 실행하고, 무엇이 이 프로젝트를 죽게 했는지 명시하는 **사망 진단서(death certificate)**를 작성한 다음, 한 번에 작은 수정 사항을 시도합니다. 프로젝트 자체의 테스트가 각 수정 사항이 유지될지 결정합니다. 통과하는 테스트를 망가뜨리는 모든 수정 사항은 되돌려집니다.
그 과정에서 유령은 자체 테스트 스위트 내 8년 된 미스터리를 해결하는 데 도움을 주었습니다. 그 이야기는 The Spirit of It에 담겨 있습니다.
데모(Demo)
실시간 영매팅 방: revenant-zaq3.onrender.com
- requests-html의 차트, 사망 진단서 및 부활 타임라인을 엽니다.
- _"lxml 5.2로 requests_html을 가져오는 것이 왜 실패하나요?"_와 같은 질문을 유령에게 합니다.
- 달 버튼을 클릭하여 어두운 영매팅 방과 양피지 느낌의 라이트 테마 사이를 전환합니다. 두 테마 모두 WCAG AA 대비 기준에 맞춰 테스트되었습니다.
이것은 Render의 무료 티어에서 실행되므로, 오랫동안 아무도 방문하지 않았다면 첫 로딩 시 약 30~60초가 소요되며 시스템이 깨어납니다. 호스팅된 유령은 Backboard의 무료 미리보기 모델을 사용하며, 이는 속도 제한(rate-limited)이 있을 수 있습니다. "영매팅이 중단되었습니다"라는 메시지가 보이면 나중에 다시 시도해 보세요.
[
사망 진단서(The death certificate). 여기에는 이슈 트래커의 목격자들과 코드 외부의 생체 신호가 기록되어 있습니다. Travis CI는 작동하지 않습니다 (travis-ci.org는 2021년에 폐쇄됨). 문서 도메인은 TLS에 실패하며, 마지막 Wayback 스냅샷은 등록업체의 "도메인 정지(Domain Suspension)" 페이지입니다. PyPI는 2019년 이후로 릴리스가 없습니다.

유령에게 묻기(Asking the ghost). 각 문장은 인용 확인(citation check)을 통해 판결을 받습니다. 이 답변은 호스팅된 모델에서 실시간으로 가져온 것입니다. 두 번째 문장은 사실이지만, 인용한 주석에 핵심 단어가 포함되어 있지 않기 때문에 **보류(withheld)**되었습니다. 이 검사는 의도적으로 엄격합니다 (자세한 내용은 아래 참조).
기록에 관련 내용이 아무것도 없을 때, 유령은 어떤 모델도 호출되기 전에 거부하므로, 모델이 꾸며낼 것이 없습니다:
$ revenant ask psf/requests-html "사워도우 빵 레시피는 무엇인가요?"
영혼들은 침묵합니다. 제 기록에는 그것에 대해 말하는 것이 아무것도 없습니다.
(관련 증거 검색 안 됨) gemma4:e4b: 호출되지 않음
**부활(The resurrection)**은 RESURRECTION_LOG.md의 실제 실행 내용을 요약한 것입니다:
initial: passed 0, errors 2
lxml-html-clean: kept (passing 0 -> 24)
pytest-asyncio-event-loop: REVERTED (regressed test_bare_render, test_browser_process, test_render)
...
3번째 줄을 보세요. 첫 번째 pytest-asyncio 수정 시도는 통과하던 테스트 3개를 망가뜨렸기 때문에, Revenant는 그것을 폐기했습니다. 이벤트 루프(event-loop) 수정이 적용된 후, 같은 치료법은 테스트 스위트(test suite)를 27개에서 30개로 늘렸습니다. 이 '유지 또는 되돌리기(keep-or-revert)' 규칙이야말로 Revenant의 가장 중요한 부분입니다. 이것이 부활 과정에서 좀비 코드(zombie)가 돌아오는 것을 막아줍니다.
이 게시물의 모든 숫자는 8 vCPU, 16GB RAM, 그리고 GPU가 없는 CPU 전용 머신에서 실행된 demo/에 저장된 실행 결과에서 가져온 것입니다.
코드
버려진 오픈 소스 소프트웨어를 위한 영매팅(A seance for abandoned open-source software): 코드에 깃든 유령에게 왜 그런지 물어보고, 그 코드를 부활시키세요. 로컬 오픈 웨이트 모델.
Revenant
버려진 오픈 소스 소프트웨어를 위한 영매팅.
프로젝트 시작일: Oct 27, 2026.
죽은 저장소(repository)라도 목소리가 남아 있습니다. 수천 개의 커밋, 이슈, 리뷰, 그리고 그것을 살아있게 유지했던 사람들에 의해 작성된 문서들까지 말입니다. Revenant는 그 목소리를 모아 프로젝트의 '유령(ghost)'과 대화할 수 있게 하고, 무엇이 실제로 이 프로젝트를 죽게 했는지 알아내기 위해 **부검(autopsy)**을 수행한 다음, **부활(resurrection)**을 시도합니다. 이는 한 번에 작은 검증된 수정 사항들로 이루어지며, 각각은 프로젝트 자체의 테스트 스위트(test suite)에 의해 판단됩니다.
첫 번째 환자는 psf/requests-html (최종 릴리스 0.10.0, 2019년 2월)입니다. 오늘날의 Python 버전에서는 아예 임포트되지도 않습니다.
| 이전 | 이후 |
|---|---|
| 테스트 스위트 (Python 3.13, 최신 종속성) | |
| 0 passed, 2 collection errors | 30 passed, 0 failed |
| ... | |
| … |
파이썬 3.11 이상 버전에서 작동하며, MIT 라이선스를 따르고 웹 UI는 표준 라이브러리만을 사용합니다. 이 프로젝트는 88개의 단위 테스트(unit tests)를 가지고 있습니다. 모델은 로컬에서 Ollama를 통해 실행되거나, 호스팅되는 오픈 웨이트 모델을 사용하고 싶다면 Backboard를 통해 실행할 수 있습니다. 모든 파트너 통합 기능은 선택 사항이며 환경 변수(environment variable)로 활성화됩니다. 동일한 읽기 전용 영매팅 공간을 로컬에서 실행하려면:
git clone https://github.com/Phantom9869/Revenant && cd Revenant && pip install -e .
REVENANT_HOME=deploy/bundle REVENANT_EMBED_MODEL=none revenant serve --port 8765
구축 과정 (How I Built It)
Revenant는 몇 가지 오픈 웨이트 모델을 중심으로 구축된 작고 지루한(boring) 부분들의 파이프라인입니다. 각 모델은 하나의 좁은 임무를 맡으며, 모델이 아닌 다른 무언가가 그 작업의 정확성을 검사합니다.
summon ──► memory (SQLite FTS5 + EmbeddingGemma) ──► ask: Gemma 4 E4B ──► citation check ──► answer / refusal
│
└──► autopsy (bubblewrap, no network) ──► death certificate ──► resurrect:
...
| 역할 (role) | 모델 (model) | 실행 위치 (where it runs) |
|---|---|---|
| Memory (embeddings) | EmbeddingGemma 300M | local, Ollama |
| ... |
메모리: 프로젝트 자체의 지식 기반
revenant summon은 레포지토리를 복제하고 온디스크 캐시(on-disk cache)를 사용하여 GitHub API를 수확하며, 속도 제한(rate limits) 이후에도 재개할 수 있습니다. 2,810개의 문서가 있는 requests-html의 경우: 462개의 커밋(commits), 693개의 이슈(issues), 235개의 PR(Pull Request), 1,368개의 댓글(comments), 21개의 리뷰(reviews), 그리고 29개의 문서 페이지(doc pages)가 있습니다. 이들은 FTS5 인덱스 및 EmbeddingGemma 벡터와 함께 SQLite에 저장됩니다 (CPU에서 약 157초 소요). 검색(Retrieval)은 BM25와 코사인 유사도 결과를 상호 역순위 융합(reciprocal rank fusion)으로 병합합니다.
문서 사이트가 작동하지 않아, Revenant는 Browserbase를 통해 2019년의 Wayback 스냅샷을 가져와 이를 주입(ingest)합니다. 이제 이 유령은 자신의 잃어버린 문서를 [archive:html.python-requests.org@2019-09-15]와 같이 인용할 수 있습니다.
모델이 말한다. 코드가 허용되는 말을 결정한다.
사실을 지어내는 유령은 쓸모없는 것보다 더 나쁩니다. 왜냐하면 그것이 실제 유지 관리자들의 입에 말을 넣어주기 때문입니다. 그래서:
- 근거가 없으면 아무것도 언급하지 않음(No evidence, no séance). 최적 기록의 코사인 유사도(cosine similarity) 점수가 0.30 미만이면 모델을 호출하지 않고 거부한다.
- 보여준 것만 인용하라(Only cite what you were shown). 검색되지 않은 기록에 대한 인용은 제거된다.
- 모든 문장이 검사된다(Every sentence is checked).
citecheck.py는 해당 문장의 모든 하드 디테일(버전, 날짜, SHA, 이슈 번호, 식별자 등)이 인용된 기록에 단어 그대로 나타나고, 그리고 그 기록이 문장의 다른 충분한 단어를 공유할 때만 문장을 통과시킨다. 실패하는 문장은 보류된다. 아무것도 통과하지 못하면 답변은 거부(refusal)가 된다.
이 검사는 실제 환각(hallucination) 때문에 존재한다. 초기 답변은 2019년 문서 스냅샷에 "0.9.0" 세부 정보를 부여했지만, 해당 정보는 그 어디에도 없다. 새로운 검사를 거치자 그 문장은 보류된다.
솔직한 단점: 이는 단어를 일치시킬 뿐, 의미를 이해하지 못한다. 스크린샷에 있는 실제 ImportError 문장이나 다른 실행에서 나온 그럴듯하게 정확한 pip install requests-html==0.9.0 문장처럼 거짓 음성(false negatives)이 존재하며, 부정문이나 의역도 판단할 수 없다. 나는 잘못된 문장을 통과시키기보다는 참인 문장을 보류하는 것을 선호하지만, 이것은 진실의 오라클(truth oracle)은 아니다.
테스트가 모델이 아닌 심판이다
오토프시(autopsy)는 환자 자신의 테스트 명령어(Pipfile에서 가져옴)를 네트워크 연결이 없는 bubblewrap 샌드박스에서 실행한다. 이는 JUnit XML을 구문 분석하고, 실패를 알려진 사망 원인과 일치시키며, 사용자들이 동일한 오류를 보고했던 문제들, 즉 **증인(witnesses)**의 흔적을 메모리에서 검색한다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

