Миграция легаси-кода: Паттерны рефакторинга под Gemini 3

50 минут Урок 5

Введение: Почему ваш код для LLM устаревает быстрее, чем вы думаете

Добро пожаловать на урок по миграции легаси-кода. В мире традиционной разработки термин «legacy» обычно относится к коду, написанному годы назад. В сфере Generative AI год — это эпоха, а полгода — поколение. Код, который вы писали для PaLM или ранних версий Gemini 1.0, сегодня может быть не просто неоптимальным, а архитектурно ошибочным при работе с Gemini 3.

Что мы будем называть «легаси» в контексте этого урока?

  • Монолитные промпты: Попытки запихнуть все инструкции, примеры (few-shot) и контекст в одно сообщение пользователя.
  • Ручной парсинг JSON: Использование регулярных выражений (Regex) для извлечения данных из ответа модели.
  • Stateless-цепочки: Передача всей истории диалога в каждом запросе без использования механизмов кэширования контекста.
  • Игнорирование мультимодальности: Использование OCR-библиотек (вроде Tesseract) перед отправкой текста в модель, вместо того чтобы скармливать изображение напрямую.

Переход на Gemini 3 — это не просто смена URL в API. Это смена парадигмы с «текст-в-текст» на «мультимодальный агентный поток». В этом уроке мы разберем конкретные паттерны рефакторинга, которые сделают ваше приложение быстрее, дешевле и умнее.

Паттерн №1: Декомпозиция Системных Инструкций

Раньше, чтобы заставить модель вести себя определенным образом (например, как техподдержка), мы писали в начале промпта: «Ты — полезный ассистент, отвечай вежливо...». В Gemini 3 архитектура четко разделяет System Instructions и User Content.

Проблема старого подхода:
Модель могла «забыть» свою роль, если диалог становился слишком длинным, или пользователь мог легко переопределить поведение (jailbreak), просто написав «Забудь все инструкции выше».

Решение в Gemini 3:
Использование параметра system_instruction на уровне инициализации модели. Это создает более устойчивое поведение и экономит токены в каждом последующем запросе, так как система обрабатывает инструкцию отдельно от динамического контекста.

python
import google.generativeai as genai
import os

# --- LEGACY (Плохой паттерн) ---
# Роль смешана с пользовательским вводом
def get_legacy_response(user_input):
    model = genai.GenerativeModel('gemini-1.0-pro')
    prompt = f"""
    Ты опытный Python-разработчик. Отвечай только кодом.
    Не давай пояснений, если их не просят.
    
    Вопрос пользователя: {user_input}
    """
    response = model.generate_content(prompt)
    return response.text

# --- GEMINI 3 REFACTORING (Хороший паттерн) ---
# Четкое разделение System Instructions
def get_modern_response(user_input):
    # Инструкция задается один раз при инициализации
    model = genai.GenerativeModel(
        'gemini-3-pro',
        system_instruction="Ты опытный Python-разработчик. Отвечай только кодом. Не давай пояснений, если их не просят."
    )
    
    # Запрос содержит ТОЛЬКО задачу
    response = model.generate_content(user_input)
    return response.text

Паттерн №2: От Regex к Controlled Generation (JSON Mode)

Одной из самых больших болей разработчиков была попытка получить от LLM структурированные данные. Мы писали в промптах: «Верни ответ в формате JSON, не добавляй markdown, убедись, что ключи называются так-то...». А потом писали сложные try-except блоки и регулярные выражения, чтобы почистить ответ, потому что модель все равно добавляла приветствие в начале.

Gemini 3 поддерживает Controlled Generation (или JSON Mode с принудительной схемой) на нативном уровне. Это означает, что вы можете передать схему данных (Schema), и модель гарантированно вернет валидный JSON, соответствующий этой схеме, или ошибку.

Это позволяет удалить тонны кода валидации и повторных запросов (retry logic).

python
import typing_extensions as typing
import json

# Определение желаемой структуры ответа
class Recipe(typing.TypedDict):
    name: str
    ingredients: list[str]
    calories: int

# --- LEGACY APPROACH ---
# Ненадежный, требует парсинга строки
legacy_prompt = "Придумай рецепт завтрака и верни JSON с полями name, ingredients, calories."
# ... далее следует код с json.loads() внутри try-catch блока ...

# --- GEMINI 3 REFACTORING ---
# Гарантированная структура
model = genai.GenerativeModel(
    'gemini-3-pro',
    generation_config={
        "response_mime_type": "application/json",
        "response_schema": Recipe # Передаем класс напрямую
    }
)

response = model.generate_content("Придумай высокобелковый завтрак")

# Теперь мы можем быть уверены в структуре
recipe_data = json.loads(response.text)
print(f"Блюдо: {recipe_data['name']}, Калории: {recipe_data['calories']}")

Паттерн №3: Внедрение Context Caching

В старых версиях API, если вы строили RAG (Retrieval Augmented Generation) систему, вы каждый раз отправляли огромные куски документации в промпте. Это:

  1. Дорого: Вы платите за одни и те же входные токены при каждом запросе.
  2. Медленно: Увеличивается Time to First Token (TTFT).

Gemini 3 вводит понятие Context Caching. Если у вас есть статический контекст (например, база знаний компании, книга, кодовая база), вы можете загрузить его один раз, получить токен кэша и ссылаться на него в последующих запросах.

Когда применять рефакторинг с кэшированием?
Если ваш системный промпт или контекст превышает 32k токенов и используется многократно. Экономия может достигать 90% стоимости.

python
from google.generativeai import caching
import datetime

# Представим, что у нас есть огромный текст мануала
huge_manual = "...текст на 100,000 токенов..."

# Создаем кэш (живет 1 час)
cache = caching.CachedContent.create(
    model='models/gemini-3-pro',
    display_name='tech_manual_v1',
    system_instruction='Ты эксперт по этому мануалу. Отвечай точно по тексту.',
    contents=[huge_manual],
    ttl=datetime.timedelta(minutes=60),
)

# Инициализируем модель, подключенную к кэшу
model_with_cache = genai.GenerativeModel.from_cached_content(cached_content=cache)

# Теперь запросы обрабатываются молниеносно, так как контекст уже "в голове" модели
response = model_with_cache.generate_content("Как сбросить настройки устройства?")
print(response.usage_metadata) # Увидите cached_content_token_count

Паттерн №4: Мультимодальность вместо OCR-костылей

Классический легаси-пайплайн для работы с документами выглядел так:
PDF -> Библиотека PyPDF/OCR -> Текст -> LLM.

Этот подход теряет структуру документа, форматирование, информацию из диаграмм и графиков. Gemini 3 является мультимодальной нативной моделью. Это значит, что она «видит» документ так же, как человек.

Задача рефакторинга:
Удалить промежуточные слои обработки текста. Загружать PDF, видео или аудио напрямую через File API. Это не только упрощает код, но и значительно повышает качество ответов, так как модель видит визуальный контекст (например, где находится подпись или печать).

Упражнение

Рефакторинг легаси-бота для анализа резюме.<br><br>Ниже представлен псевдокод устаревшего обработчика. Ваша задача — переписать его, используя современные практики Gemini 3:<br>1. Заменить промпт-инжиниринг на System Instruction.<br>2. Заменить ручной парсинг JSON на `response_schema`.<br>3. (Опционально) Представить, как бы вы убрали OCR, если бы на вход подавался PDF.<br><br>ИСХОДНЫЙ КОД (LEGACY):<br>```python<br>def analyze_cv(cv_text):<br> prompt = f"""<br> Проанализируй этот текст резюме: {cv_text}<br> Вытащи имя кандидата и список навыков.<br> Верни ответ СТРОГО в формате: {{"name": "Ivan", "skills": ["Python"]}}<br> Не пиши ничего кроме JSON.<br> """<br> raw_response = model.generate(prompt)<br> # Очистка от markdown ```json<br> clean_json = raw_response.replace("```json", "").replace("```", "")<br> return json.loads(clean_json)<br>```

Вопрос

При миграции на Gemini 3, в каком случае вам СЛЕДУЕТ использовать Context Caching?

Заключение

Миграция легаси-кода на Gemini 3 — это процесс удаления лишнего. Вы удаляете код валидации, удаляете сложные инструкции из промптов, удаляете внешние библиотеки для OCR и парсинга.

Ваш код становится «тоньше», но функциональнее, потому что вы перекладываете когнитивную нагрузку и управление состоянием на архитектуру API. В следующем уроке мы поговорим о том, как превратить эти статические вызовы в полноценных автономных агентов.