Files
Liti-agent-Development/Caching_설계/Gemini_캐싱설계.md
T

14 KiB

질문

내가 개발하는 인공지능 에이전트는 특정 작업 단계에서 Gemini 모델을 API를 통해 호출하여 사용한다. 이 때, 작작업 2에서 동일한 gemini 모델을 api를 통해 호출하여 작업 1에서 얻은 결과를 input file로 사용한다면, 작업 1의 결과물 파일을 kv-caching 처럼 저장한 후, 작업 2에서 추가적으로 토큰을 사용하지 않고도 gemini 모델이 caching 자료를 hit하여 사용할 수 있는가? 즉, 그렇게 설계를 할 수 있는가?

답변

결론부터 말씀드리면, 네, 완벽하게 가능하며 그러한 구조로 설계하실 수 있습니다. Gemini API는 정확히 질문하신 목적에 부합하는 컨텍스트 캐싱(Context Caching) 기능을 공식적으로 제공하고 있습니다.

다만, "추가적으로 토큰을 전혀 사용하지 않는(비용이 0인)" 것은 아니므로, 비용과 아키텍처 측면에서 이 기능이 에이전트에 어떻게 적용되는지 명확한 현실을 짚어드리겠습니다.

Gemini API의 컨텍스트 캐싱(Context Caching) 작동 원리

에이전트의 워크플로우에서 작업 1의 결과물을 작업 2의 입력으로 재사용할 때, 매번 전체 텍스트나 파일을 다시 API로 전송하여 모델이 처음부터 연산(Attention 연산 등)을 수행하게 하는 것은 시간과 비용 측면에서 매우 비효율적입니다. 컨텍스트 캐싱을 사용하면 이 문제를 해결할 수 있습니다.

  1. 캐시 생성 (작업 1 직후): 작업 1에서 도출된 결과물(텍스트 또는 파일)을 Gemini의 Cache API 엔드포인트를 통해 업로드합니다. 이때 시스템은 해당 컨텍스트를 메모리에 올려두고, 재사용 가능한 고유한 **캐시 이름(Cache Name/ID)**을 반환합니다.
  2. 캐시 참조 (작업 2): 작업 2에서 Gemini API를 호출할 때, 작업 1의 방대한 결과물을 프롬프트에 다시 욱여넣는 대신 이 캐시 ID만 파라미터로 전달합니다.
  3. 결과: 모델은 KV-Cache에 이미 연산되어 저장된 컨텍스트를 즉시 Hit하여 불러오므로, 첫 토큰이 출력될 때까지의 지연 시간(Time-To-First-Token, TTFT)이 비약적으로 단축됩니다.

⚠️ 설계 시 반드시 고려해야 할 제약 및 사실 관계

캐싱을 에이전트 아키텍처에 도입하기 전에 다음 세 가지를 확인하셔야 합니다.

  • 최소 토큰 제약 (매우 중요): 컨텍스트 캐싱은 입력 데이터가 최소 32,768 토큰 이상일 때만 생성할 수 있습니다. 만약 작업 1의 결과물이 단순한 몇 문단이나 몇 페이지 수준의 요약본이라면 캐싱을 사용할 수 없으며, 일반적인 프롬프트 체이닝 방식으로 텍스트를 직접 넘겨주어야 합니다.
  • 과금 구조의 변화: 캐시를 Hit 한다고 해서 토큰 비용이 무료가 되는 것은 아닙니다. 대신 일반 입력 토큰(Input Token) 요금보다 훨씬 저렴한 캐시된 입력 토큰(Cached Input Token) 요금이 적용됩니다. 추가로, 캐시를 메모리에 유지하는 시간(TTL, 기본 1시간)에 비례하여 스토리지 유지 비용이 소액 발생합니다.
  • 적합한 사용처: 에이전트가 처리하는 작업 1의 결과물이나 시스템 프롬프트가 방대한 판례, 수백 페이지의 계약서, 복잡한 규정집처럼 대규모 텍스트일 때 이 아키텍처는 진가를 발휘합니다.

추천하는 에이전트 아키텍처 플로우

이러한 특성을 바탕으로 에이전트를 설계하신다면 다음과 같은 파이프라인이 됩니다.

  1. 에이전트 작업 1 실행: Gemini API 호출 \rightarrow 대규모 결과물 파일(.txt, .pdf 등) 생성.
  2. 캐시 매니저 개입: 에이전트 시스템이 해당 파일을 File API로 업로드 후, CacheManager를 통해 캐시를 생성하고 Cache ID를 보관.
  3. 에이전트 작업 2 실행: 프롬프트(작업 2의 세부 지시사항) + Cache ID를 조합하여 Gemini API 재호출 \rightarrow 고속으로 추론 완료.
  4. 캐시 정리: 작업 2가 완전히 종료되면 메모리 누수나 불필요한 과금을 막기 위해 API를 통해 명시적으로 캐시를 삭제(Delete).

이 컨텍스트 캐싱 기능을 에이전트의 Python 코드에 직접 구현해 보실 수 있도록 관련 API 호출 로직과 예제 코드를 작성해 드릴까요?


질문

그렇다면, “작업 1의 결과물 파일을 KV-caching처럼 저장해 두고, 작업 2에서 추가 토큰 없이 동일 Gemini 모델이 그것을 hit하여 쓰게 설계할 수 있는가?”

답변

질문하신 내용에 대해 아주 엄밀하게 답변드리자면, **"아키텍처 구조로는 정확히 그렇게 설계할 수 있지만, 비용 측면에서 추가 토큰 소모가 '0'이 되는 것은 아니다"**가 정확한 사실입니다.

말씀하신 아이디어를 실제 시스템으로 구현할 때 마주하게 될 현실적인 팩트들을 짚어드리겠습니다.

1. 구조적 가능성: KV-Caching 원리의 적용 (가능)

Gemini API가 제공하는 컨텍스트 캐싱(Context Caching) 기능이 바로 정확히 말씀하신 KV-cache 원리로 작동합니다.

작업 1의 결과물 파일을 Cache API를 통해 업로드하면, 모델은 연산(Attention)을 마친 상태로 해당 텍스트의 정보를 서버 메모리에 보관합니다. 작업 2에서 이 캐시 ID를 호출하면, 모델은 텍스트를 처음부터 다시 읽고 연산하는 과정을 생략(Hit)하고 즉시 추론을 시작하므로 답변 생성 속도가 비약적으로 빨라집니다.

2. 비용의 현실: "추가 토큰 없이" (불가능)

이 부분이 설계 시 가장 주의하셔야 할 오해입니다. 캐시를 Hit 한다고 해서 작업 2에서 입력 토큰 비용이 완전히 무료가 되는 것은 아닙니다.

  • 캐시된 토큰 요금: 일반적인 입력 토큰(Input Token)을 매번 전송하는 것보다 크게 할인된 '캐시된 입력(Cached Input)' 요금이 부과됩니다. (추가 비용이 0이 아니라, 대폭 절감되는 개념입니다.)
  • 유지 비용: KV-cache 상태를 메모리에 들고 있어야 하므로, 캐시를 살려두는 시간(TTL)에 비례하여 시간당 스토리지 보관 비용이 소액 발생합니다.

3. 시스템 설계의 결정적 제약: 최소 토큰 수

이 아키텍처를 에이전트에 도입하기 위해 반드시 넘어야 할 기술적 허들이 있습니다. 바로 최소 32,768 토큰 제한입니다.

  • 작업 1에서 산출된 결과물 파일의 크기가 32,768 토큰(대략 영문 기준 24,000단어 이상)을 넘지 않으면, Gemini API는 캐싱 생성을 아예 거부합니다.
  • 따라서 작업 1의 결과가 짧은 텍스트라면 캐싱 구조를 쓸 수 없으며, 기존처럼 작업 2의 프롬프트에 텍스트를 직접 삽입(프롬프트 체이닝)해야 합니다.

요약하자면:

특히 구상 중이신 에이전트가 방대한 판례 원문, 수십 페이지에 달하는 법률 문서, 또는 복잡한 계약서 전체를 작업 1의 결과물로 다루고 이를 기반으로 작업 2를 수행해야 한다면, 이 캐싱 아키텍처는 속도와 비용 면에서 에이전트의 성능을 극대화하는 최고의 설계 방식입니다.

현재 에이전트가 처리하는 '작업 1의 결과물'은 대략 어느 정도의 텍스트 분량(페이지 수나 단어 수)을 예상하고 계신가요? 분량에 따라 캐싱 도입이 적절한지 판단해 드릴 수 있습니다.


질문

“작업 1 → 작업 2 파이프라인을 Context Caching 친화적으로 설계하는 구체적 API 아키텍처” 를 Python 예제로 작성하라.

답변

작업 1의 산출물을 Context Caching을 통해 작업 2로 매끄럽게 넘기고, 불필요한 비용 낭비를 막기 위해 캐시 생명주기(TTL)를 관리하는 구체적인 파이프라인 아키텍처입니다.

Python의 공식 google-generativeai SDK를 활용하여, 에이전트가 YAML 프롬프트를 기반으로 동작하는 상황을 가정한 설계 예제입니다.

Context Caching 파이프라인 Python 예제

Python

import os
import datetime
import yaml
import google.generativeai as genai
from google.generativeai import caching

# API 키 설정
genai.configure(api_key=os.environ["GEMINI_API_KEY"])

class LegalAIAgentPipeline:
    def __init__(self, model_name="models/gemini-1.5-pro-002"):
        # 캐싱을 지원하는 Pro 모델 지정
        self.model_name = model_name
        self.cache = None

    def run_task1(self, task1_yaml_prompt: dict, input_data: str) -> str:
        """
        작업 1: 원본 데이터를 입력받아 대규모 분석 결과물 생성
        """
        print("--- [작업 1] 대규모 데이터 추론 및 결과물 생성 중 ---")
        model = genai.GenerativeModel(self.model_name)
        
        # YAML에서 프롬프트 추출
        instruction = task1_yaml_prompt.get('instruction', '')
        prompt = f"{instruction}\n\n[입력 데이터]\n{input_data}"
        
        response = model.generate_content(prompt)
        return response.text

    def create_context_cache(self, task1_result: str, system_instruction: str):
        """
        작업 1의 결과를 KV-Cache에 적재하여 작업 2를 위한 준비
        (주의: task1_result가 32,768 토큰 이상이어야 생성 가능)
        """
        print("--- [캐시 생성] 작업 1 결과물을 서버 메모리에 적재 ---")
        
        # TTL(Time-To-Live)을 짧게 설정하여 스토리지 유지 비용을 최소화
        self.cache = caching.CachedContent.create(
            model=self.model_name,
            display_name="task1_legal_analysis_cache", # 캐시 식별자
            system_instruction=system_instruction,     # 에이전트의 절대 규칙 부여
            contents=[task1_result],
            ttl=datetime.timedelta(minutes=15),        # 15분 후 자동 삭제되도록 설계
        )
        print(f"✅ 캐시 생성 완료: {self.cache.name}")
        return self.cache

    def run_task2(self, task2_yaml_prompt: dict) -> str:
        """
        작업 2: 적재된 캐시를 Hit하여 추가 입력 토큰(Input Token) 전송 없이 고속 추론
        """
        print("--- [작업 2] 캐시 Hit 및 후속 지시사항 수행 ---")
        if not self.cache:
            raise ValueError("참조할 캐시가 존재하지 않습니다.")

        # 캐시된 컨텍스트를 기반으로 작업 2 전용 모델 인스턴스 생성
        cached_model = genai.GenerativeModel.from_cached_content(
            cached_content=self.cache,
            # 에이전트가 멋대로 작동하지 않도록 온도(Temperature)를 낮춤
            generation_config=genai.GenerationConfig(
                temperature=0.2,
            )
        )

        # 작업 2의 세부 지시사항만 프롬프트로 전달
        # (작업 1의 방대한 결과물은 이미 캐시에 있으므로 전송하지 않음)
        instruction = task2_yaml_prompt.get('instruction', '')
        
        # TTFT(첫 토큰 도달 시간)가 대폭 단축되며 답변 생성
        response = cached_model.generate_content(instruction)
        return response.text

    def cleanup(self):
        """
        파이프라인 종료 후 캐시를 명시적으로 삭제하여 불필요한 과금 방지
        """
        if self.cache:
            self.cache.delete()
            print("🧹 리소스 정리: 캐시가 정상적으로 삭제되었습니다.")

# ==========================================
# 실행 시나리오 예시
# ==========================================
if __name__ == "__main__":
    agent = LegalAIAgentPipeline()
    
    # 1. 작업 1용 프롬프트와 방대한 원본 데이터 (가정)
    task1_prompt = {"instruction": "다음 텍스트에서 주요 쟁점과 인과관계를 추출하여 상세한 마크다운 리포트로 작성하라."}
    dummy_large_input = "방대한 텍스트 원본 데이터..." * 10000 
    
    # 실제 환경에서는 아래 주석을 해제하여 사용
    # task1_output = agent.run_task1(task1_prompt, dummy_large_input)
    
    # 테스트를 위해 task1_output을 가상의 대규모 텍스트(32k 토큰 이상)로 가정
    task1_output = "작업 1에서 도출된 상세한 분석 결과물..." * 6000 
    
    system_rule = "너는 특화된 AI 에이전트이다. YAML에 명시된 워크플로우를 절대적으로 준수한다."
    
    try:
        # 2. 캐시 매니저를 통해 작업 1 결과를 메모리에 고정
        agent.create_context_cache(task1_output, system_rule)
        
        # 3. 작업 2 실행 (입력 데이터 전송 없이 프롬프트만 전달)
        task2_prompt = {
            "instruction": "<thinking> 태그를 사용하여 작업 1 결과물의 논리적 허점을 3단계로 검토하고, 최종 <final_response>에 결과를 출력하라."
        }
        final_result = agent.run_task2(task2_prompt)
        print("\n[작업 2 최종 결과]\n", final_result)
        
    except Exception as e:
        print(f"오류 발생 (작업 1의 결과가 32,768 토큰 미만이면 캐시 생성에 실패합니다): {e}")
    finally:
        # 4. 메모리 누수 및 유지 비용 발생을 막기 위해 반드시 캐시 삭제
        agent.cleanup()

아키텍처 설계 시 핵심 포인트

  1. from_cached_content의 활용: 작업 2에서 모델 객체를 생성할 때, 일반적인 GenerativeModel이 아닌 from_cached_content 메서드를 사용합니다. 이것이 바로 질문하신 **"KV-Cache Hit"**를 구현하는 API의 핵심입니다.
  2. system_instruction의 캐싱: create_context_cache를 호출할 때 에이전트의 역할과 강제 규칙(System Instruction)도 함께 캐싱할 수 있습니다. 이렇게 하면 작업 2에서 모델이 자기 멋대로 작동하는 이탈 현상을 더욱 강력하게 억제할 수 있습니다.
  3. 명시적인 cleanup(): 캐시는 지정된 TTL(예: 15분)이 지나면 자동 삭제되지만, 작업 2가 1분 만에 끝났다면 남은 14분 동안 스토리지 유지 비용이 미세하게 발생합니다. 에이전트 파이프라인의 finally 블록에서 즉시 캐시를 삭제하도록 설계하는 것이 가장 경제적입니다.