코드베이스에 숨겨진 프롬프트를 위한 package.lock
요약
blogus는 코드 내에 흩어진 AI 프롬프트를 의존성처럼 관리할 수 있게 돕는 도구입니다. 기존 코드를 스캔하여 프롬프트를 추출하고, 버전 관리 및 해시 잠금(lock)을 통해 프로덕션 환경의 프롬프트 일관성을 보장합니다.
핵심 포인트
- 기존 코드를 재작성할 필요 없이 스캔만으로 프롬프트 자동 추출
- 프롬프트를 .prompt 파일로 변환하여 이름, 모델 설정, 변수 타입 관리
- prompts.lock 파일을 통해 콘텐츠 해시 기반의 버전 잠금 기능 제공
- 소스 코드 내 인라인 문자열을 버전 관리된 프롬프트 호출로 자동 동기화
프롬프트는 의존성 (dependencies)입니다. 우리는 단지 그것을 의존성처럼 취급하기를 거부할 뿐입니다. 프로덕션 (production) LLM 애플리케이션은 프롬프트 문자열이 수십 개의 파일에 흩어져 있고, 인라인 (inline)으로 연결되어 있으며, 해당 기능을 마지막으로 만진 사람에 의해 수정됩니다. 어떤 프롬프트가 존재하는지, 어떤 버전이 라이브 상태인지, 또는 summarize.py에 있는 것이 평가 (eval) 시 사용된 것과 일치하는지 아무도 모릅니다. 우리는 라이브러리를 패치 (patch) 단위까지 버전 관리합니다. 하지만 우리의 프롬프트는 content = "Summarize: " + text와 같은 식과 무관심한 태도로 방치됩니다.
blogus는 AI 프롬프트를 위한 package.lock입니다. 이 도구는 이미 코드에 존재하는 프롬프트를 추출하고, 의존성처럼 버전을 관리하며, 콘텐츠 해시 (content hashes)로 잠금 (lock) 처리하고, 변경 사항을 소스 파일에 다시 동기화합니다. 슬로건은 정확히 이렇습니다: "AI 프롬프트를 위한 package.lock."
핵심 아이디어: 마이그레이션(migration)하지 말고 발견하라
대부분의 프롬프트 관리 도구는 먼저 프롬프트를 자신들의 시스템으로 옮길 것을 요구합니다. 그러한 마이그레이션이 바로 그 도구들이 채택되지 못하는 이유입니다. blogus는 이를 뒤집습니다. 기존 코드를 스캔하여 프롬프트가 이미 존재하는 LLM 호출을 찾아내므로, 도입 과정은 재작성이 아닌 스캔이 됩니다.
$ blogus scan
Found 3 LLM API calls:
...
저 출력 결과가 화면 하나에 담긴 전체 피치 (pitch)입니다. Python과 JavaScript 전반에 걸친 호출을 찾아냈고, 제공자 (provider)를 식별했으며, 모든 호출을 버전 관리되지 않은 상태로 표시했습니다. 당신은 아무것도 옮기지 않았습니다. 단지 당신이 무엇을 가지고 있는지 알아냈을 뿐입니다.
워크플로우 (workflow)
blogus는 당신이 이미 의존성에 대해 생각하는 방식과 깔끔하게 매칭되는 5단계 루프로 실행됩니다: 추출 (extract), 버전 관리 (version), 잠금 (lock), 동기화 (sync), 검증 (verify).
버전 관리는 발견된 프롬프트를 프런트 매터 (front matter)가 포함된 .prompt 파일로 변환하여, 프롬프트가 이름, 모델 설정 (model config), 그리고 타입이 지정된 변수 (typed variables)를 가진 실제 아티팩트 (artifact)가 되도록 합니다:
# prompts/summarize.prompt
---
name: summarize
...
잠금 (Locking)은 prompts.lock을 생성하며, 이는 이 시스템을 단순한 텍스트 파일 폴더가 아닌 의존성 시스템으로 만드는 메커니즘입니다:
# prompts.lock
prompts:
summarize:
...
각 프롬프트는 sha256 콘텐츠 해시(content hash)와 해당 프롬프트가 잠긴(locked) 커밋(commit) 정보를 가집니다. 이제 "운영 환경(production)에 어떤 버전의 summarize 프롬프트가 있는가"라는 질문에 대해 추측이 아닌, diff를 통해 확인할 수 있는 답변을 얻을 수 있습니다.
동기화 작동 방식
이 시스템을 단순한 린터(linter) 이상으로 만드는 단계는 fix입니다. 이 명령은 소스 코드 내에 인라인 문자열(inline string)을 포함하는 대신, 버전 관리된 프롬프트로부터 로드하도록 소스 코드를 재작성합니다:
# Before
content = "Summarize: " + text
...
인라인 문자열은 프롬프트 이름과 해당 해시가 주석으로 달린 load_prompt 호출로 변경됩니다. 이 주석이 코드와 락 파일(lock file) 사이의 연결 고리 역할을 합니다. 일단 주석이 생성되면, CI(지속적 통합) 환경에서 blogus verify를 실행하여 코드가 잠긴 프롬프트로부터 벗어났을 경우 빌드를 실패하게 만들 수 있습니다:
$ blogus verify || exit 1
이것이 핵심적인 가치입니다. 코드 주석의 해시는 락 파일의 해시와 일치해야 합니다. 누군가 인라인 사용 방식을 수정하거나, 다시 잠그는(re-lock) 과정 없이 .prompt 파일을 변경하면, verify가 머지(merge) 전에 이를 잡아냅니다. 이는 의존성(dependencies)에 대한 락파일 체크와 동일한 계약(contract)을 모델 동작을 실제로 구동하는 프롬프트에 적용한 것입니다.
CLI는 해당 루프를 중심으로 예상 가능한 인터페이스를 제공합니다: 목록을 보여주는 scan, init, prompts, 변수와 함께 프롬프트를 실행하는 exec <name>, 효과를 평가하는 analyze, 테스트 케이스를 생성하는 test, 잠금 기능을 수행하는 lock, 검증을 위한 verify, 버전 관리되지 않은 프롬프트를 찾는 check, 그리고 fix가 있습니다. 또한 웹 UI(8000 포트에서 uvx --with blogus[web] blogus-web 실행)와 대화형 TUI 데모도 제공됩니다. 설치는 uv add blogus를 사용하거나, 설치 없이 바로 시도하려면 uvx blogus scan을 사용하세요.
적합하지 않은 경우
몇 가지 솔직한 한계점입니다.
먼저 이 도구가 무엇을 볼 수 있는지부터 살펴보겠습니다. 스캐너(scanner)는 Python 및 JavaScript/TypeScript 파일 내의 OpenAI SDK 호출(openai.chat.completions.create)과 Anthropic SDK 호출(anthropic.messages.create)을 감지합니다. 이것이 감지 범위(detection surface)입니다. 만약 귀하의 호출이 LiteLLM, Bedrock, 프로바이더 라우터(provider router), 또는 자체적인 얇은 래퍼(thin wrapper)를 거치거나, 서비스가 Go 또는 Rust로 작성되었다면, scan은 아무것도 찾아내지 못할 것이며 이 도구의 모든 가치가 사라지게 됩니다. 빈 스캔 결과로 아이디어를 판단하기 전에, 귀하의 스택이 해당 범위 내에 있는지 먼저 확인하십시오.
analyze 명령은 LLM 기반의 효과성 점수 산정(effectiveness scoring)을 수행하며, test 명령은 테스트 케이스를 생성합니다. 이는 숨겨진 세부 사항이 아니라 시그니처(signature)에 명시되어 있습니다: analyze는 --judge-model 인자를 받습니다. 즉, 모델이 귀하의 프롬프트를 채점하는 것입니다. LLM-as-judge(판사로서의 LLM) 방식은 노이즈가 발생할 수 있으므로, 해당 점수를 차단 기준이 아닌 조사 신호로 취급하십시오. blogus의 신뢰할 수 있는 부분은 결정론적(deterministic)인 핵심 기능인 scan, lock, verify입니다. 해시 계약(hash contract)은 정확합니다. 품질 점수는 본질적으로 퍼지(fuzzy)하므로, 이 두 가지를 서로 다른 기준으로 다루어야 합니다.
fix 단계는 소스 파일을 수정합니다. 인라인 문자열(inline strings)을 load_prompt 호출로 다시 쓰는 것은 코드 변환(code transformation)이며, 모든 코드 수정 도구(codemod)는 예외 케이스(edge case)에서 오류를 범할 수 있습니다. 수정 사항을 미리 볼 수 있는 blogus fix --dry-run 명령이 있으므로, 실제 실행 전에 반드시 사용해야 합니다. 깨끗한 워킹 트리(working tree)에서 실행하여 차이점(diff)을 검토하고, PR(Pull Request)을 통해 반영하십시오. 이 도구는 커밋되지 않은 변경 사항이 있는 상태에서 실행하는 도구가 아닙니다.
또한, 이 도구는 앱에 load_prompt 런타임 의존성(runtime dependency)을 도입합니다. 이제 프롬프트는 런타임에 배포되고 로드되어야 하는 .prompt 파일에 저장됩니다. 이는 버전 관리를 위한 합리적인 교환(trade)이지만, 분명한 트레이드오프입니다. 만약 프롬프트가 사소하고 절대 변하지 않는다면, 락파일(lockfile)을 사용하는 절차는 불필요한 오버헤드입니다. 이 도구의 가치는 프롬프트의 개수와 프롬프트가 얼마나 자주 변하는지에 따라 결정됩니다.
그리고 이 도구는 프롬프트 텍스트와 모델 설정 (model config)을 관리합니다. 모델이 실제로 반환하는 결과물까지 관리하는 것은 아닙니다. 잠금(locked) 처리되고 검증된 프롬프트라 할지라도, 모델 제공업체가 동일한 이름으로 모델을 업데이트한 후에는 여전히 더 나쁜 답변을 생성할 수 있습니다. blogus는 제공업체의 계약이 아닌, 여러분 측의 계약을 고정(pins)하는 것입니다.
핵심 요약 (Takeaways)
- 프롬프트는 의존성 (dependencies)이며, 프롬프트가 노후화되는 이유는 아무도 이를 의존성처럼 취급하지 않기 때문입니다. 콘텐츠 해시 (content hash)와 잠금 파일 (lockfile)을 결합하면 "어떤 버전이 실행 중인가"라는 문제를 해결할 수 있습니다.
- 마이그레이션 (migrate) 대신 제자리에서 발견 (Discover-in-place)하는 방식이 이 도구가 실제로 채택될 수 있는 이유입니다. 첫 번째 명령은 재작성 (rewrite)이 아니라 스캔 (scan)입니다.
- CI에서의
verify단계가 핵심적인 보상입니다. 잠금된 프롬프트로부터 발생하는 코드 드리프트 (Code drift)는 의존성 잠금 확인 (dependency lock check)과 마찬가지로 빌드를 실패하게 만듭니다. - LLM 기반의 분석 (analyze) 및 테스트 (test) 기능보다 결정론적 핵심 기능 (deterministic core: 스캔, 잠금, 검증)을 더 신뢰하십시오. 신뢰도와 기준이 서로 다릅니다.
만약 여러분의 코드베이스에 누군가가 암기할 수 있는 것보다 더 많은 프롬프트가 있다면, blogus scan은 그 개수가 얼마나 되는지 5초 만에 알아낼 수 있는 방법입니다. 코드와 CLI 참조는 여기에서 확인할 수 있습니다: https://github.com/Skelf-Research/blogus
누군가의 스캔 결과에서 가장 높은 프롬프트 개수가 얼마가 나올지 진심으로 궁금합니다. 마음껏 테스트해 보시고, 이슈 (issues) 제보도 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기