Skip to content
게으른 엔지니어의 기술 블로그
Go back

프롬프트에서 뺄 거 찾다가 2편 — 데이터를 두 번 보내고 있었다

지난 글에서 죽은 코드 하나와 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,
    ...
})

즉 같은 내용을 systemInstructionTextcontents 양쪽에 다 넣으면, 그대로 두 배로 전송된다. 당연한 얘기 같지만, 실제로 두 곳을 각각 담당하는 코드가 서로 다른 파일에 떨어져 있으면 놓치기 쉬운 지점이었다.

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편에서.


Share this post:

Previous Part
정교한 해결책을 설계하다가, 이미 있던 걸 뒤늦게 알아챘다
Next Part
프롬프트에서 뺄 거 찾다가 3편 — 사실은 한 달째 지침이 하나도 안 가고 있었다