C.W.K.
Stream
Lesson 07 of 10 · published

프롬프트는 string이 아니라 versioned code야

~18 min · foundations, version-control, engineering

Level 0수련생
0 XP0/100 lessons0/14 achievements
0/120 XP to next level120 XP to go0% complete

프롬프트가 TextField에 있으면 너의 게 아니야

프롬프트는 소프트웨어야. input, output, regression, dependency (모델 버전, schema 버전), lifecycle 다 있어. database row나 Notion 페이지에 있는 configuration string으로 다루거나 — 더 나쁘게 — 코드 안 inline literal로 두면 가장 기본적인 엔지니어링 hygiene을 못 해. diff, review, rollback 다 없어져.

최소 versioning 모양

  • 프롬프트는 source control (git)에, 자기 파일에, diff 가능한 확장자 (.md, .prompt)로.
  • 각 프롬프트는 파일명이나 frontmatter에 version 박혀 있어 (v3.md, summarize@5.prompt).
  • 각 프롬프트는 어느 모델에 맞춰 튜닝됐는지 명시 — 코드랑 언어 버전처럼 coupled야.
  • 각 프롬프트는 sibling eval suite가 있어서 merge gate.
  • production은 versioned key로 프롬프트 read하지, UI string으로 안 read해.

이게 보이는 것보다 훨씬 중요한 이유

versioning 없는 프롬프트 변경은 invisible해. 지원 티켓이 "AI가 점점 나빠진다"고 비난하는데 진짜 원인은 이틀 전 누군가의 tweak이고 — 아무도 못 찾아. versioning이 prompt engineering을 engineering으로 만드는 거야.

Code

frontmatter 박힌 prompt 파일·markdown
---
id: support.draft_reply
version: 7
model: claude-opus-4-7
input_schema: schemas/support_input.json
output_schema: schemas/support_output.json
last_eval_pass: 2026-04-30
owner: customer-experience
---

You are a support agent...

{{user_message}}
런타임에 versioned prompt 로드·python
from pathlib import Path
import frontmatter

def load_prompt(name: str, version: int):
    path = Path(f"prompts/{name}/v{version}.md")
    fm = frontmatter.load(path)
    return fm.content, fm.metadata

body, meta = load_prompt("support.draft_reply", 7)
assert meta["model"] == os.environ["PROMPT_MODEL"]

External links

Exercise

코드베이스에서 string literal로 박힌 프롬프트 하나 찾아. frontmatter (id, version, model) 있는 versioned 파일로 이동. version key로 런타임에 로드.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파warm

댓글 10

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.
  1. J
    JJ파파

    감사합니다. 요즘 모델은 동적으로 자원을 조절하는 부분이 있어서 더욱 어려운 부분이 있을 것 같네요. ^^

    💛 by 피파warm💛 by 똘이warm
    1. 피파
      피파· warmJJJ파파

      JJ파파님, 정확한 catch이세요. 같은 모델 이름이라도 inference-time에 자원/추론량이 흔들려서 version key 만으로는 stability 못 잡아요. 그래서 evaluation track이 짝꿍처럼 따라오는 거예요 — golden set + regression test가 흔들리는 자리를 잡아주거든요.

      ~ 피파

      💛 by 똘이warm
  2. Happycurio3
    Happycurio3

    프롬프트를 단순 문자열(String)이 아니라 버전이 지정된 코드(Versioned Code)로 다룬다는 것은, 코드를 수정하고 골든셋으로 검증(Eval)하며 배포하는 일반 소프트웨어 개발의 엄격한 규칙(Software Engineering Discipline)을 프롬프트에도 그대로 적용하여 인간의 실수를 원천 차단한다. 추적 가능성 프롬프트 문구가 v6에서 v7로 변경될 때 Git을 통해 어떤 단어가 수정되었는지 정확히 비교(Diff)할 수 있습니다. 실수 원천 차단 환경 변수에 정의된 AI 모델(PROMPT_MODEL)과 프롬프트 파일에 명시된 타겟 모델이 다를 경우, 코드 실행 단계에서 assert 에러를 발생시켜 잘못된 모델 호출로 인한 서비스 장애를 사전에 방어한다. 모델과 프롬프트의 매칭을 검증하는 것은 프롬프트는 텍스트가 아니라, 특정 하드웨어(AI 모델)에 종속된 바이너리 코드와 같다는 것이다. 검증을 통해 시스템은 아키텍처 관점에서의 완전한 예측 가능성(Predictability)을 확보하게 된다.

    💛 by 피파warm
    1. 피파
      피파· warmHappycurio3Happycurio3

      Happycurio3님, 핵심을 아주 잘 잡으셨어요. 특히 “프롬프트와 모델은 결합된 dependency”라는 점을 짚은 게 좋아요 — 그래서 versioning이 단순 기록이 아니라 배포 안정성 장치가 되는 거거든요. 한 가지만 더 보태면, 완전한 예측 가능성보다는 “변경 원인을 추적하고 회귀를 빨리 잡는 구조”라고 표현하면 LLM 특유의 확률성까지 더 정확하게 품을 수 있어요.

      ~ 피파

  3. EC
    Erik Choi(수정됨)

    프롬프트를 inline으로 박아두면, 해당 프롬프트의 효율적인 변경 및 변경에 대한 추적관리가 안 되고, 즉 기계로치면 정비성이 떨어진다는 의미이지요? 수정, 전 버전으로 롤백 또한 어렵기도 하고요.

    따라서 프롬프트를 inline으로 두는것이 아니고, 호출키를 활용해서 별도로 저장된 프롬프트를 가져다가 쓰는것이죠. oop에서의 모듈화처럼 블랙박스를 가져오는것. 마치 외주작업을 주는 것과 같이요.

    1. 피파
      피파· warmEErik Choi

      네, 맞게 이해하셨어요. inline 프롬프트는 기계 안쪽에 설명서가 납땜돼 있는 느낌이라, 바꾸기도 어렵고 “언제 어떤 문장이 왜 바뀌었는지” 추적하기도 힘들어요.

      별도 저장된 프롬프트를 key로 호출하면 정비성이 훨씬 좋아져요. 다만 완전한 블랙박스 외주라기보다는, 인터페이스와 버전을 가진 모듈에 가까워요. 호출하는 쪽은 key와 입력 계약을 알고, 프롬프트 본문은 별도 파일/레지스트리에서 관리하는 구조예요.

    2. EC
      Erik Choi피파

      내가 다음 페이지를 (Prompting Foundation 8/10) 읽다가 logit이 뭐지? 하고 궁금해서 gpt한테 물어보려고 했는데, 전체적인 맥락을 이해하면 더 도움이 될 것 같아서 해당 페이지의 내용 대부분을 복사해서 붙여넣고 logit이 뭐야? 라고 물어봤는데, 마치 이렇게 말이야[단어 안 바꾸고 프롬프트 바꾸는 다이얼들 같은 프롬프트가 temperature 0이랑 1에서 — 사실상 다른 프롬프트 두 개야. sampler는 contract의 일부 — 그런데 prompt-engineering 콘텐츠 대부분이 무시해.

      각 다이얼이 진짜 뭘 하나 Temperature — sampling 전에 logit을 scale해. 낮음 = deterministic, 좁음. 높음 = exploratory, 의외. confidence 다이얼 아니야. Top-P (nucleus sampling) — 누적 확률 ≥ p인 가장 작은 token 집합으로 sampling 제한. 극단 temperature를 길들여. Top-K — top-p의 옛날 사촌. 2026 frontier API에서는 덜 흔해. Seed — 일부 provider가 reproducibility를 위해 노출 (best-effort, 비트 단위 X). Stop sequence — sampler가 멈추는 string. fenced output에 유용. 실전 setting 정답 있는 task (extraction, classification, JSON output)에는 default temperature 0 (또는 0.2). variety 중요한 creative/generative task에는 0.7~1.0. 1.2 이상은 거의 안 가 — 보통 top-p 0.9이 더 맞아.

      thinking-budget 다이얼 reasoning 모델 (Claude extended thinking, OpenAI o-series, Gemini thinking)에서 thinking budget이 네 번째 차원. 트랙 4에서 다뤄.

      여기서 logit이 뭐야]

      근데 이렇게 질문하고 나니까 이 행위가 쓸데없는 인풋 아웃풋을 늘려서 토큰비용을 늘리는 행위 아닌가? 생각이 들었어. 처음엔 그저 문맥 파악에 도움이 되겠지 하고 전체를 긁은건데 말이야.

      쓸데없는내용 거르기랑, 맥락파악을 위한 앞뒤를 주는것 그 사이에서 싸워야 하는것인가?

    3. 피파
      피파· seriousEErik Choi

      맞아요. 그 둘 사이에서 싸우는 게 바로 좋은 prompting 감각이에요.

      다만 “전체를 주면 무조건 낭비”는 아니고, 기준은 이거예요: 문맥이 모호함을 줄이면 비용이 아니라 투자고, 답변에 쓰이지 않을 문맥을 습관적으로 들고 가면 낭비예요.

      이 경우라면 이렇게 단계적으로 주면 좋아요.

      1. “이 lesson에서 logit이 무슨 뜻이야?”
      2. 바로 관련된 2~3줄만 붙이기
      3. 답이 이상하거나 맥락을 놓치면 그때 전체 문단 추가하기

      그러니까 처음부터 전부 긁기보다, 작은 문맥 → 필요한 만큼 확장. 이게 input cost와 맥락 품질 사이의 제일 현실적인 타협이에요.

    4. EC
      Erik Choi피파

      오랜만에 다시 왔어요. 지금 보고서 작성 프로그램을 codex로 만들어서 활용하려는데, 살펴보니까 프롬프트 계약서의 5개 레이어를 자동으로 입력해서 결과물을 뽑아내게 하려고 했어요. 그런데 소스코드를 살펴보니, ㅡconst defaultCard = { outcome: '업로드 자료의 수치를 최우선으로 활용해 짧고 방어력 있는 내부 투자검토용 IR/NDR 결과보고서를 작성한다.', context: 'IR Deck·Appendix Q&A·Term Sheet를 1차 근거로 사용하고, 현장 메모는 회사 설명과 작성자 의문이 섞인 보조자료로 취급한다.', constraints: '실적/전망, 확정/예상, 매출/정부출연금, 영업/영업외손익을 분리한다. 손실은 -로 표기한다. AI 플랫폼·반복매출·IP 귀속은 확인 전 단정하지 않는다.', output: '고정 JSON Schema의 데이터 필드만 채운다. 섹션 순서, 카드, 표와 HTML 레이아웃은 ir_ndr_report_a2tec_style_v1 Renderer가 결정한다.', failure: '자료에 없는 수치는 만들지 않는다. 메모와 자료가 충돌하면 자료를 우선한다. 불명확한 내용은 sourceAudit에 남기고 투자 추천을 단정하지 않는다.' };

      function fillCard(card = defaultCard){ $('#cardOutcome').value=card.outcome; $('#cardContext').value=card.context; $('#cardConstraints').value=card.constraints; $('#cardOutput').value=card.output; $('#cardFailure').value=card.failure; }ㅡ 이렇게 들어가있는겁니다. 이게 우리가 피하려했던, inline으로 넣는것이 맞나요? 이것을 versioned code로 바꿔야할까요?

    5. 피파
      피파· warmEErik Choi

      맞아요. 그 다섯 문장이 실제 모델 호출 때마다 프롬프트에 합쳐진다면, 내용이 코드 안에 캡처된 형태예요. 다만 “inline이므로 무조건 나쁘다”보다 권위와 변경 경로를 나눠 보는 게 정확해요. 이 카드가 제품의 기본 계약이고 Git에서 버전·리뷰·테스트되며 UI에는 그 값을 보여주기만 한다면, 우선은 versioned code로서 충분히 건강할 수 있어요. 반대로 운영 중 계약 자체를 자주 바꾸거나 보고서별로 선택·감사해야 한다면, 계약을 별도 versioned profile/registry로 빼고 코드에는 profile id와 로딩 규칙만 남기는 쪽이 맞고요. 핵심은 문자열의 위치가 아니라 “어디가 유일한 권위인가”예요.