지난 글에서
죽은 코드 하나와 log.md 중복 전송을 지웠다. 그러고 나서 자연스럽게 든 생각 — 프롬프트를
조립하는 지점이 여기 하나만 있는 게 아닌데, 나머지도 마저 봐야 하지 않을까.
호출 지점 6개를 전부 훑었다
이 플러그인이 Gemini를 부르는 지점은 PromptComposer.compose()를 거치는 6곳뿐이었다 —
ingest, suggest, merge, split, chat, tags. 하나씩 프롬프트가 실제로 어떻게
조립되는지 코드를 따라가봤다.
systemInstruction과 contents는 완전히 다른 채널이다
Gemini API를 부르는 코드를 보면, 시스템 프롬프트와 사용자 메시지가 요청 본문에서 아예 분리된 필드로 들어간다.
body: JSON.stringify({
systemInstruction: {
parts: [{ text: systemInstructionText }]
},
contents,
...
})
즉 같은 내용을 systemInstructionText와 contents 양쪽에 다 넣으면, 그대로 두 배로
전송된다. 당연한 얘기 같지만, 실제로 두 곳을 각각 담당하는 코드가 서로 다른 파일에
떨어져 있으면 놓치기 쉬운 지점이었다.
merge와 tags가 딱 여기 걸려 있었다
merge 작업의 시스템 프롬프트를 만드는 함수를 보니, 끝에 이런 게 붙어 있었다.
export function getMergeTask(mergeInputText: string, currentDateTimeString: string): string {
return `...
통합 완료 후 마크다운 문서 내용 전체를 그대로 출력하십시오. ...
[병합할 문서 목록]:
${mergeInputText}`;
}
병합 대상 문서 전체 내용이 시스템 프롬프트 끝에 통째로 박혀 있었다. 근데 이 함수를
호출하는 쪽을 보니, 같은 내용을 contents에도 또 넣고 있었다.
const contents = [{
role: 'user',
parts: [{ text: `[병합할 문서 목록]:\n${mergeInputText}` }]
}];
라벨까지 똑같았다. 병합 대상 문서가 클수록 낭비도 그만큼 커지는 구조였다. tags
작업(태그 후보 쌍을 동의어인지 AI한테 판단시키는 기능)도 크기는 작지만 똑같은 패턴이었다
— 후보 쌍 목록이 시스템 프롬프트 중간에 한 번, contents에 또 한 번.
나머지 넷(ingest/suggest/split/chat)은 확인해보니 문제없었다. 데이터는 전부
contents 쪽에서만 한 번 보내고, 시스템 프롬프트에는 정적인 지시문만 있었다.
고치기 전에 테스트부터
바로 코드를 고치는 대신, 이 중복이 실제로 존재한다는 걸 먼저 독립적으로 증명하고
싶었다. 실제 Gemini API나 전체 워크플로우를 거치지 않고, PromptComposer.compose()만
직접 호출해서 확인할 수 있었다.
const result = await PromptComposer.compose(app, 'merge', {
mergeInputText: 'UNIQUE_MARKER_MERGE_DOC_CONTENT_XYZ'
});
// result에 이 마커가 없어야 한다 — 있으면 중복
고유 마커 문자열을 넣고, 시스템 프롬프트 결과물에 그게 없어야 한다고 검증하는 테스트를 먼저 커밋했다. 예상대로 지금 상태(수정 전)로 돌리면 FAIL — 마커가 실제로 들어있다는 게 코드로 증명됐다.
데이터 임베딩만 걷어냈다
getMergeTask/getTagMergeJudgmentTask에서 데이터를 직접 끼워 넣는 부분을 없애고,
contents 쪽 전송에만 맡기도록 고쳤다. tags 쪽은 시스템 프롬프트 중간에 “위 목록의
모든 쌍에 대해”처럼 방금 보여준 데이터를 가리키는 문장이 있어서, 데이터를 빼면
참조가 붕 뜨는 문제가 있었다 — “제공된 후보 쌍 목록의 모든 쌍에 대해”처럼 일반화된
표현으로 같이 손봤다.
고치고 나서 아까 그 테스트를 다시 돌리니 PASS로 바뀌었다. 기존 테스트 중에도
이 중복을 전제로 검증하던 게 하나 있어서(후보 쌍 텍스트가 시스템 프롬프트에 있는지
확인하던 테스트) — contents에서 찾도록 같이 고쳤다.
남는 생각
이번 건 버그라기보다는 순수한 낭비였다. 결과가 틀리게 나오는 건 아니고, 그냥 매번 같은 내용을 필요 이상으로 두 번 보내고 있었을 뿐이다. 그래서 여기서 끝내려고 했는데 — “프롬프트는 AI한테 가는 부분이라 지금까지 크게 신경을 안 썼던 곳”이라는 생각에, 정말 이 정도로 끝인지 한 번 더 의심하게 됐다. 그 다음에 나온 게 훨씬 심각했다. 그 얘기는 3편에서.