사용자가 쓴 한 문장은 모델에 보낸 전체 입력이 아니다
슬라이드 재생성에서 사용자가 “색을 바꿔 달라”고 입력해도 모델은 그 문장만 받지 않는다. 원본 제목·본문, 페이지 역할, 덱의 디자인, 참조 이미지 정책과 활성 템플릿을 합친 입력을 받는다. 자유 지시만 저장하면 결과가 달라졌을 때 실제로 어떤 문자열을 보냈는지 되찾기 어렵다.
먼저 슬라이드 생성·재생성이 PromptsService에서 DB 템플릿을 조회하고, 없으면 기본 템플릿을 렌더하도록 통합했다. 이후 공통 컴파일러에 작업별 템플릿 고정과 최종 전송 문자열 기록을 추가했다. 전송 모델 일치와 잘못된 블록 복구는 후속 변경에서 보완했다.
최초 생성, 일괄 생성과 단일 재생성이 각각 조립하면 같은 원본도 경로에 따라 다른 정책으로 해석할 수 있다. AI Commons의 프롬프트 컴파일러는 공통 컴파일러와 작업별 템플릿 묶음, 최종 문자열 기록을 연결했다. 후속 변경은 전송 모델의 일치와 잘못된 원본 블록 복구까지 보완했다.
프롬프트 조립은 하나로 모으고 변경 신호는 원본 그대로 둔다
순수 함수 compileSlideImagePrompt가 제목·본문, 서버가 정한 역할·언어·비율, 덱 디자인·스타일, 페이지 지시문과 출처를 받는다. 정책, 제목, 본문, 덱 디자인, 페이지 방향과 렌더 조건의 여섯 블록을 만들고 이전 이미지를 재사용하는 경로에는 참조 정책을 더한다.
참조 정책은 이전 결과와 원본 입력의 차이를 기준으로 고른다. 새 자유 지시나 사용자가 쓴 페이지 지시의 변경은 user_edit, 그 밖의 본문·지시 변경은 content_update, 변경 없음은 style_preserve다. 최초 생성과 재생성의 공통 내용은 같은 규칙으로 조립한다.
단일 재생성은 다른 제어를 위해 hasContentChange를 false로 바꾸는 경로가 있었다. 이 값을 참조 정책에도 쓰면 본문을 고쳤는데 style_preserve가 된다. 변경 전의 차이를 별도 changeSignals에 보존하고 정책 분류는 그 값을 읽는다. 실행 편의를 위해 덮어쓴 플래그와 입력의 사실을 분리한 것이다.
5페이지 뒤에 활성 버전이 바뀌어도 한 작업의 입력은 유지한다
페이지마다 활성 템플릿을 다시 읽으면 생성 도중 관리자가 버전을 바꾼 경우 한 덱의 앞뒤가 갈라진다. 작업 시작 시 image-generate와 image-regenerate의 활성 상세를 각각 한 번 읽고 모든 페이지에 전달한다. 선택한 상세의 modelEngine도 함께 전달한다.
템플릿만 고정하고 이미지 서비스가 전송 직전에 모델을 다시 조회하면 기록된 묶음과 실제 모델이 달라질 수 있다. 그래서 최종 문자열을 직접 넘기는 것과 모델을 명시하는 것을 한 계약으로 묶었다. 아래는 변수 이름과 주변 필드를 줄인 예시다.
const compiled = compileSlideImagePrompt(input, deps);
await imageScheduler.executeGenerate({
prompt: compiled.prompt,
modelEngine: selectedBundleModel,
// 나머지 요청 필드는 생략
});
// 이미지 버전의 renderedPrompt에도 compiled.prompt를 저장한다. 활성 버전 변경은 다음 작업부터 반영된다. 작업 중 일관성을 선택한 대가로 현재 작업의 남은 페이지는 새 버전을 즉시 쓰지 않는다. 묶음이 없는 별도 호출에는 경고 후 재조회하는 폴백도 있다. 모든 진입점이 항상 최초 스냅샷을 갖는다는 보장은 그 폴백까지 확장할 수 없다.
잘못된 템플릿에 원본을 덧붙이기만 하면 두 번 그릴 수 있다
편집 가능한 템플릿에서 제목·본문 자리표시자가 빠지거나 태그가 틀리면 원본이 누락될 수 있다. 렌더러가 오류 때 원래 템플릿을 돌려주는 경우에는 치환되지 않은 괄호가 그대로 남는다. 컴파일러는 실제 원본 블록의 태그 쌍과 내용을 확인해 누락·손상을 보정한다.
닫는 태그가 없는 본문 뒤에 정상 블록을 추가하기만 하면, 기존 위치의 본문과 새 블록의 본문이 중복될 수 있다. 후속 변경은 다루는 손상 형태를 제자리에서 교체하고 중복 블록을 걷은 뒤, 자리가 없는 블록만 추가한다. 치환 콜백을 사용해 원본의 $&나 $1이 문자열 치환 문법으로 해석되지 않게 하고, 원래 들여쓰기와 제목의 공백도 보존한다.
이 보정은 생성 경로를 유지하면서 원본 존재를 지키는 선택이다. 렌더 실패와 자리표시자 누락 이유는 경고로 남는다. 모든 템플릿 오류를 거부하거나 시각적 결과를 검증하지는 않는다. 블록 구분과 “본문은 데이터”라는 문장은 모델에게 전달하는 지시이며, 프롬프트 인젝션을 차단하는 보안 경계는 아니다.
전송한 문자열을 다시 렌더하지 않고 그대로 저장한다
공통 생성 함수는 컴파일 결과를 최초 생성의 prompt 또는 편집의 editInstructions로 전달한다. 이미지 서비스는 이미 컴파일한 문자열을 다시 템플릿 렌더링하지 않는다. 이미지 버전의 renderedPrompt에도 그 문자열을 저장한다. 사용자 자유 지시는 기존 prompt 필드에 별도로 남긴다.
| 기록 | 설명하는 입력 |
|---|---|
| prompt | 사용자가 입력한 자유 지시 |
| renderedPrompt | 컴파일을 마쳐 실제 전송한 문자열 |
| 템플릿 키·버전 태그와 compilerVersion | 어떤 템플릿과 조립 규칙을 사용했는가 |
| referenceMode · referencePolicy | 참조 여부와 이전 이미지 유지 정책 |
| requestFingerprint | 최종 문자열·비율·모델·참조 이미지 바이트를 선택해 비교한 값 |
지문은 최종 프롬프트, 비율, 선택 모델과 참조 이미지의 전체 SHA-256을 연결해 다시 해시하고 앞 16자리를 쓴다. 같은 지문의 새 생성도 허용하고 새 버전을 만든다. 중간 산출물 재사용의 지문과 달리 캐시나 중복 거부의 키가 아니다.
이 값에는 webSearch와 모든 공급자 옵션이 들어 있지 않다. 전체 HTTP 요청의 정규화된 해시, 공급자 결과의 출처 증명이나 충돌 없는 식별자로 읽을 수 없다. 빈 모델 값은 공급자 기본값을 사용하므로, 같은 빈 값의 지문만으로 미래의 기본 모델까지 고정하지도 못한다. 정확한 프롬프트 저장에는 원본 본문을 포함한 텍스트 저장 비용이 따른다.
문자열의 재현성과 이미지의 품질은 따로 검증한다
컴파일러 테스트는 원본의 줄바꿈·들여쓰기·특수문자 보존과 누락·손상 블록의 복구를 검사한다. 배선 테스트는 최초 생성과 편집에 보낸 문자열이 컴파일 결과와 같은지, renderedPrompt가 전송값과 같은지 확인한다. 본문 변경 신호를 덮어쓴 단일 경로도 회귀 조건에 넣는다.
10페이지 생성 테스트는 키당 조회가 한 번인지 확인하고, 5페이지 저장 뒤 활성 버전을 v2로 바꾼다. 남은 페이지도 최초 템플릿과 model-v1을 사용하는지, 지문이 그 전송 입력과 맞는지 비교한다. 공급자와 작업 배선은 모킹돼 있어 살아 있는 모델의 응답을 확인하는 테스트는 아니다.
세 가지 예시 시나리오는 실제 TemplateService로 컴파일하고 문서의 프롬프트 코드 블록·확인 문장과 비교한다. 문서 전체의 공백까지 스냅샷으로 고정하지 않아 편집 형식과 입력 계약을 분리한다. 이런 검증으로 “무엇을 보냈는가”를 재현할 수 있어도 결과의 충실도와 가독성이 좋아지는지는 별도 평가가 필요하다. 원문 보존과 슬라이드 가독성의 비교가 그 품질 경계를 다룬다.