콘텐츠로 이동

프롬프트 조합과 KV-Cache 최적화

에이전트 시스템에서 프롬프트는 단일 문자열이 아닙니다. 여러 소스에서 동적으로 조립됩니다. 잘 설계된 프롬프트 조합 시스템은 다음 특성을 가집니다.

  • 모듈성: 각 섹션이 독립적으로 교체 가능
  • 우선순위: 컨텍스트 한계에 가까워질 때 무엇을 유지할지 결정
  • 캐시 친화성: KV-Cache 히트율을 최대화하는 순서 배치

OpenDev 연구에서 제안된 방식은 프롬프트 섹션에 **우선순위 번호(10~95)**를 부여하여 조합 순서와 컨텍스트 압박 시 생략 순서를 결정합니다.

interface PromptSection {
priority: number; // 10(필수) ~ 95(선택)
content: string;
cacheable: boolean; // KV-Cache 대상 여부
provider?: 'openai' | 'anthropic' | 'google'; // 특정 제공자 전용
}
function assemblePrompt(
sections: PromptSection[],
maxTokens: number
): string {
// 우선순위 오름차순 정렬 (낮을수록 중요)
const sorted = [...sections].sort((a, b) => a.priority - b.priority);
let result = '';
let tokenCount = 0;
for (const section of sorted) {
const sectionTokens = estimateTokens(section.content);
if (tokenCount + sectionTokens > maxTokens) {
// 우선순위 높은(숫자 큰) 섹션부터 생략
continue;
}
result += section.content + '\n\n';
tokenCount += sectionTokens;
}
return result;
}
우선순위섹션 종류설명
10시스템 역할 정의절대 생략 불가
20안전 가이드라인필수 제약 사항
30현재 태스크 설명핵심 지시
50관련 도구 목록태스크 관련 도구만
70최근 대화 히스토리압박 시 요약으로 대체
85참고 예시공간 있을 때만 포함
95상세 배경 지식선택적 보강

**KV-Cache(Key-Value Cache)**는 트랜스포머 어텐션 메커니즘에서 이미 계산한 키-값 쌍을 재사용하는 기술입니다. 같은 프롬프트 prefix가 반복될 때 재계산을 건너뜁니다.

Manus의 실측 데이터에 따르면:

캐시 미스 토큰 비용: $3.00 / 1M tokens
캐시 히트 토큰 비용: $0.30 / 1M tokens
──────────────────────────────────────
비용 절감: 10배 (90% 감소)
// 나쁜 예: 자주 변하는 내용이 앞에 있으면 캐시 무효화
const prompt = `
현재 시각: ${new Date().toISOString()} // 매번 변함 → 캐시 미스
당신은 코드 리뷰 전문가입니다.
[안전 가이드라인 500 토큰...]
[코드베이스 컨텍스트 2000 토큰...]
코드: ${userCode}
`;
// 좋은 예: 안정적인 내용을 앞에, 동적 내용을 뒤에
const prompt = `
당신은 코드 리뷰 전문가입니다. // 캐시 가능 ✓
[안전 가이드라인 500 토큰...] // 캐시 가능 ✓
[코드베이스 컨텍스트 2000 토큰...] // 캐시 가능 ✓
현재 시각: ${new Date().toISOString()} // 동적
코드: ${userCode} // 동적
`;

대화 히스토리를 수정하지 않고 항상 끝에 추가합니다. 중간 메시지를 편집하거나 삭제하면 그 이후의 모든 캐시가 무효화됩니다.

class AppendOnlyContext {
private messages: Message[] = [];
// 메시지 추가만 허용, 수정/삭제 없음
append(message: Message): void {
this.messages = [...this.messages, message];
}
// 압축이 필요할 때: 오래된 메시지를 요약으로 대체
compactOldMessages(keepRecent: number): void {
if (this.messages.length <= keepRecent) return;
const toSummarize = this.messages.slice(0, -keepRecent);
const summary = summarize(toSummarize);
// 요약 이후 메시지는 그대로 유지 → 캐시 보존
this.messages = [
{ role: 'system', content: `[이전 대화 요약]: ${summary}` },
...this.messages.slice(-keepRecent),
];
}
}

Anthropic API는 cache_control 파라미터로 캐시 구간을 명시적으로 지정합니다.

const response = await anthropic.messages.create({
model: 'claude-opus-4-5',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: systemPrompt,
cache_control: { type: 'ephemeral' }, // 캐시 브레이크포인트
},
{
type: 'text',
text: userMessage, // 이 이후는 캐시 미적용
},
],
},
],
});

도구 목록이 바뀌면 시스템 프롬프트 캐시가 깨집니다. Manus가 사용하는 해결책은 **도구 제거 대신 로짓 마스킹(logit masking)**입니다.

모든 도구를 프롬프트에 유지하되, 디코딩 단계에서 특정 도구 호출에 해당하는 토큰의 로짓을 -∞으로 설정하여 선택 불가능하게 만듭니다. 이렇게 하면 시스템 프롬프트는 변경 없이 유지되어 캐시가 보존됩니다.

프롬프트 조합은 우선순위를 부여한 모듈 단위로 설계하고, KV-Cache를 최대화하려면 안정적인 내용을 앞에, 동적 내용을 뒤에 배치해야 합니다. Append-only 컨텍스트 관리와 명시적 캐시 브레이크포인트를 조합하면 동일한 기능을 최대 10배 낮은 비용으로 운영할 수 있습니다.