Технология Context Caching: Архитектура и сценарии экономии

35 минут Урок 16

Введение: Проблема "Безумного Библиотекаря"

Добро пожаловать в четвертый модуль. Сегодня мы разберем технологию, которая меняет правила игры в работе с большими языковыми моделями (LLM) — Context Caching (Кеширование контекста).

Чтобы понять ценность этой технологии, давайте представим работу с LLM через аналогию. Представьте, что вы приходите в библиотеку к гениальному библиотекарю, чтобы задать вопрос по сложной книге (например, "Война и мир"). Но у этого библиотекаря есть особенность: у него нет долгосрочной памяти на содержание разговора.

В классической архитектуре (без кеширования), каждый раз, когда вы задаете уточняющий вопрос, вы вынуждены:

  1. Забирать книгу у библиотекаря.
  2. Выходить из комнаты.
  3. Снова заходить, вручать ему книгу заново.
  4. Ждать, пока он её прочитает с первой до последней страницы.
  5. И только потом задавать вопрос.

Это модель stateless (без сохранения состояния). При контекстном окне в 1 или 2 миллиона токенов, повторная отправка и обработка всего массива данных становится:

  • Дорогой: Вы платите за обработку одних и тех же вводных токенов снова и снова.
  • Медленной: Модели требуется время (Time To First Token), чтобы «переварить» огромный контекст.

Context Caching в Gemini API решает эту проблему. Это механизм, позволяющий сохранить состояние обработанных токенов (Key-Value pairs в механизме внимания трансформера) на серверах Google и ссылаться на них по уникальному идентификатору. В нашей аналогии: вы оставляете книгу библиотекарю один раз, и в следующий раз просто говорите: «Помнишь ту книгу на столе? Открой страницу 42».

Архитектура Context Caching: Под капотом

Давайте углубимся в техническую часть. Что именно мы кешируем? Многие ошибочно полагают, что кешируется просто текст. Если бы это было так, выигрыш в производительности был бы минимальным.

На самом деле, Gemini API кеширует KV-Cache (Key-Value Cache) слоев внимания (attention layers) модели.

Как это работает:

  1. Токенизация: Текст превращается в токены.
  2. Эмбеддинг: Токены превращаются в векторы.
  3. Проход через слои (Inference): Модель вычисляет взаимосвязи между всеми токенами. Это самая вычислительно затратная часть. Для каждого токена генерируются матрицы Keys и Values, которые определяют, на что этот токен «обращает внимание».

При обычном запросе эти вычисления выбрасываются после ответа. При Context Caching эти матрицы сохраняются в высокоскоростной памяти (обычно VRAM или специализированные тензорные хранилища TPU).

Когда вы делаете запрос к кешированному контенту, модель пропускает фазу обработки входного контекста и сразу переходит к генерации ответа, используя готовые матрицы внимания. Это снижает латентность (задержку) на порядки, особенно для контекстов более 100k токенов.

Жизненный цикл кеша:

  • Создание: Вы передаете контент (текст, файлы, видео) и задаете TTL (Time To Live).
  • Хранение: Контент получает уникальное имя ресурса (например, cachedContents/123...).
  • Использование: Вы отправляете промпт, указывая не текст, а ссылку на кеш.
  • Истечение: По истечении TTL кеш удаляется, если он не был обновлен.

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

# Настройка API ключа
genai.configure(api_key=os.environ['GOOGLE_API_KEY'])

# 1. Загрузка большого документа (например, техническая спецификация)
# В реальной жизни это может быть PDF на 500 страниц
path_to_file = 'apollo_11_flight_plan.pdf'
file_ref = genai.upload_file(path_to_file)

# Ожидание завершения обработки файла
import time
while file_ref.state.name == "PROCESSING":
    time.sleep(2)
    file_ref = genai.get_file(file_ref.name)

# 2. Создание кеша
# Мы задаем TTL (время жизни) на 1 час
cache = caching.CachedContent.create(
    model='models/gemini-1.5-flash-001', # Указываем совместимую модель
    display_name='apollo_flight_plan_cache',
    system_instruction='Ты эксперт по полетам NASA. Отвечай точно и технически грамотно.',
    contents=[file_ref],
    ttl=datetime.timedelta(minutes=60)
)

print(f"Кеш создан: {cache.name}")

# 3. Использование кеша
# Обратите внимание: мы не передаем файл снова!
model = genai.GenerativeModel.from_cached_content(cached_content=cache)

response = model.generate_content("Какова была последовательность действий при посадке на Луну?")
print(response.text)

Экономика кеширования: Когда это выгодно?

Внедрение Context Caching — это не только про скорость, но и про оптимизацию бюджета. Однако, у этой технологии есть своя модель ценообразования, которую нужно понимать, чтобы не уйти в минус.

Обычно стоимость складывается из трех компонентов:

  1. Стоимость записи (Cache creation): Обычно равна или чуть дешевле стандартной стоимости входных токенов. Вы платите за первичную обработку.
  2. Стоимость хранения (Storage): Оплата за количество токенов в час. Это «аренда» памяти на серверах Google.
  3. Стоимость чтения (Input tokens with cache): Это самая важная часть. Цена за использование кешированных токенов значительно ниже (часто в 4-5 раз), чем передача обычных токенов.

Точка безубыточности (Breakeven Point)

Кеширование не выгодно для разовых запросов. Если вы отправляете книгу и задаете 1 вопрос — обычный запрос будет дешевле (так как нет платы за хранение в час).

Золотое правило: Кеширование окупается, если вы планируете сделать несколько запросов к одному и тому же контексту или если контекст используется интенсивно в течение короткого времени.

Пример расчета (условные единицы):
Предположим, у вас есть контекст 1М токенов.
Обычный запрос: $5 за запрос.
Кешированный запрос: $4 (запись) + $1/час (хранение) + $1 (чтение).

  • 1 запрос без кеша: $5.
  • 1 запрос с кешем: $6 (дороже).
  • 10 запросов без кеша: $50.
  • 10 запросов с кешем за час: $4 (запись) + $1 (хранение) + $10 (чтение) = $15. Экономия более 3х раз!

Основные сценарии использования (Use Cases)

Давайте разберем конкретные паттерны проектирования, где Context Caching является необходимым.

1. Разговор с документацией (Chat with Docs)

Это классический RAG, но без векторной базы данных для среднего размера документов. Если у вас есть юридический договор, мануал к оборудованию или книга, и пользователь ведет длительный диалог:

  • Без кеша: История чата растет, плюс в каждом запросе вы снова шлете весь документ в system prompt.
  • С кешем: Документ загружается один раз. В промпт летит только текущий вопрос и короткая история сообщений.

2. Few-Shot Learning с огромной базой примеров

Чтобы заставить модель писать код в специфическом стиле вашей компании, вы можете захотеть показать ей 500 примеров идеального кода. Это займет 500k токенов.

  • Загружаем примеры в кеш.
  • Теперь модель «обучена» вашему стилю (в рамках контекста) за копейки при каждом следующем запросе генерации кода.

3. Агенты с сохранением состояния

В мульти-агентных системах часто есть «Глобальный контекст» — описание мира, доступных инструментов, правил поведения, текущего состояния проекта. Вместо того чтобы каждый агент передавал это состояние друг другу в теле запроса, они могут обращаться к единому cachedContent объекту, который обновляется (пересоздается) только при значимых изменениях.

4. Анализ видео и аудио

Видео-файлы потребляют огромное количество токенов. Анализ часового видеопотока покадрово. Закешировав видеофайл, вы можете задавать десятки вопросов («Кто вошел в кадр на 10-й минуте?», «Опиши эмоции на 20-й минуте») с минимальной задержкой.

python
# Пример обновления TTL и управления кешем
# Важно: сам контент кеша неизменяем (immutable). 
# Если нужно изменить текст - создаем новый кеш. 
# Но мы можем продлевать жизнь текущему.

import datetime

def manage_cache_lifecycle(cache_name):
    # Получаем ссылку на существующий кеш
    try:
        existing_cache = caching.CachedContent.get(cache_name)
        print(f"Текущий TTL: {existing_cache.expire_time}")
        
        # Продлеваем жизнь еще на 2 часа, так как сессия пользователя активна
        new_ttl = datetime.timedelta(hours=2)
        existing_cache.update(ttl=new_ttl)
        print(f"TTL обновлен. Новое время истечения: {existing_cache.expire_time}")
        
        return existing_cache
        
    except Exception as e:
        print(f"Кеш не найден или истек: {e}")
        return None

# Хорошей практикой является удаление кеша, если сессия явно завершена пользователем
def cleanup_session(cache_name):
    try:
        cache = caching.CachedContent.get(cache_name)
        cache.delete()
        print("Ресурсы освобождены.")
    except:
        pass
Упражнение

Сценарий: Вы разрабатываете бота технической поддержки для автосервиса. У вас есть 3 мануала по ремонту автомобилей (суммарно 800,000 токенов). Бот должен отвечать на вопросы механиков в течение рабочей смены (8 часов). Ваша задача: Написать псевдокод или код на Python, который инициализирует кеш с этими мануалами на 8 часов и показывает, как обрабатывать входящие запросы от механиков.

Вопрос

Представьте, что вы создали кеш контекста длиной 1 миллион токенов с TTL 1 час. Вы сделали 5 запросов к этому кешу. Что произойдет, если вы захотите изменить одно предложение в исходном тексте контекста?