Реализация паттернов ReAct и Chain-of-Thought через API
Введение: От простой генерации к мышлению
Добро пожаловать в четвертый модуль курса. До этого момента мы взаимодействовали с Gemini API преимущественно в режиме «вопрос-ответ». Вы отправляли промпт, модель возвращала текст. Это работает отлично для творческих задач или суммаризации, но когда дело доходит до сложных многосоставных задач, требующих логики, математики или взаимодействия с внешним миром, «плоская» генерация часто дает сбой.
Почему это происходит? Стандартная языковая модель предсказывает следующий токен на основе вероятности. У нее нет встроенного «черновика» для вычислений или паузы на размышление. Если вы попросите её решить сложную математическую задачу сразу, она может выдать правдоподобный, но неверный ответ (галлюцинацию).
Сегодня мы изучим два фундаментальных паттерна, которые превращают LLM из генератора текста в интеллектуального агента:
- Chain-of-Thought (CoT): Цепочка рассуждений. Мы учим модель «думать вслух» перед тем, как дать ответ.
- ReAct (Reasoning + Acting): Комбинация рассуждений и действий. Модель не только думает, но и использует инструменты (Tools) для получения недостающей информации, анализирует результат и продолжает работу.
Эти методики — основа современных автономных агентов. Давайте разберем, как реализовать их с помощью Gemini 3 API.
Часть 1: Chain-of-Thought (CoT) — Думаем шаг за шагом
Паттерн Chain-of-Thought был предложен исследователями Google (Wei et al., 2022). Идея гениально проста: если заставить модель разбить задачу на логические шаги, точность решения сложных задач возрастает в разы.
Zero-Shot CoT vs Few-Shot CoT
Существует два основных способа активировать этот режим:
- Zero-Shot CoT: Вы просто добавляете в конец промпта магическую фразу: «Let's think step by step» (Давай подумаем шаг за шагом). Это заставляет модель сгенерировать рассуждение перед ответом.
- Few-Shot CoT: Вы передаете модели несколько примеров (few-shot), где в поле ответа содержится подробное решение, а не только результат.
В Gemini 3, благодаря огромному контекстному окну и улучшенным возможностям reasoning, часто достаточно грамотного системного промпта.
import google.generativeai as genai
import os
# Настройка API (предполагается, что ключ в переменных окружения)
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-pro') # Используем Pro для сложных рассуждений
# Пример 1: Стандартный запрос (может ошибиться в сложной логике)
standard_prompt = """
У Роджера 5 теннисных мячей. Он покупает еще 2 банки теннисных мячей.
В каждой банке по 3 мяча. Сколько мячей у него теперь?
Ответ:
"""
# Пример 2: Chain-of-Thought Prompting
cot_prompt = """
У Роджера 5 теннисных мячей. Он покупает еще 2 банки теннисных мячей.
В каждой банке по 3 мяча. Сколько мячей у него теперь?
Давай подумаем шаг за шагом.
"""
response_std = model.generate_content(standard_prompt)
response_cot = model.generate_content(cot_prompt)
print(f"--- Стандартный ответ ---\n{response_std.text}")
print(f"\n--- CoT Ответ ---\n{response_cot.text}")
# В CoT ответе вы увидите структуру:
# 1. У Роджера было 5 мячей.
# 2. 2 банки * 3 мяча = 6 новых мячей.
# 3. 5 + 6 = 11 мячей.
# Ответ: 11.
Часть 2: Паттерн ReAct — Рассуждение и Действие
Если CoT — это «внутренний монолог», то ReAct (Yao et al., 2023) — это способность модели взаимодействовать с внешним миром. Название происходит от Reasoning + Acting.
В классическом ReAct агент работает в цикле:
- Thought (Мысль): Агент анализирует текущую ситуацию и решает, что нужно сделать.
- Action (Действие): Агент выбирает инструмент (функцию) и аргументы для него.
- Observation (Наблюдение): Агент получает результат выполнения функции (например, JSON от API погоды).
- Повтор: Агент снова думает, достаточно ли у него информации для финального ответа, или нужно еще одно действие.
Интеграция с Gemini Function Calling
В экосистеме Gemini паттерн ReAct реализуется нативно через механизм Function Calling. Вам не нужно писать сложные регулярные выражения для парсинга текста (как это делалось в ранних версиях LangChain). Вы передаете описания функций модели, и она возвращает структурированный вызов функции, когда решает действовать.
Однако, ответственность за цикл выполнения лежит на вас (или на фреймворке). Gemini возвращает название функции, вы её исполняете, и результат отправляете обратно в модель.
import google.generativeai as genai
from google.generativeai.types import FunctionDeclaration, Tool
# --- 1. Определение инструментов (Tools) ---
# Функция для получения погоды (Mock)
def get_current_weather(location: str, unit: str = "celsius"):
"""Возвращает текущую погоду в заданном месте."""
# В реальности здесь был бы запрос к API
print(f"[TOOL] Запрос погоды для: {location}")
if "москв" in location.lower():
return {"temperature": 22, "condition": "sunny", "location": "Москва"}
elif "лондон" in location.lower():
return {"temperature": 15, "condition": "rainy", "location": "Лондон"}
else:
return {"temperature": 20, "condition": "cloudy", "location": location}
# Функция для поиска цен на авиабилеты (Mock)
def search_flights(origin: str, destination: str):
"""Ищет авиабилеты между городами."""
print(f"[TOOL] Поиск билетов: {origin} -> {destination}")
return {"price": 350, "currency": "USD", "airline": "GeminiAir"}
# Создаем словарь инструментов для выполнения
tools_map = {
'get_current_weather': get_current_weather,
'search_flights': search_flights
}
# Инициализация инструментов для Gemini
tools = [
get_current_weather,
search_flights
]
# --- 2. Инициализация модели с инструментами ---
model = genai.GenerativeModel(
model_name='gemini-1.5-pro',
tools=tools
)
# --- 3. Реализация ReAct Цикла ---
def run_react_agent(prompt):
# Включаем автоматический режим function calling (auto-execution) или ручной.
# Для обучения реализуем "Ручной" цикл (chat mode), чтобы видеть шаги.
chat = model.start_chat(enable_automatic_function_calling=True)
# Примечание: В Python SDK `enable_automatic_function_calling=True`
# скрывает от нас ReAct цикл, выполняя его "под капотом".
# Но для понимания сути, давайте посмотрим, как это выглядит для пользователя:
response = chat.send_message(prompt)
return response.text
# Запуск агента
user_query = "Какая сейчас погода в Москве и сколько стоит улететь оттуда в Лондон?"
print(f"User: {user_query}")
# SDK автоматически выполнит цикл Thought -> Action -> Observation
result = run_react_agent(user_query)
print(f"Agent: {result}")
# Если бы мы делали это ВРУЧНУЮ (без enable_automatic_function_calling=True),
# код выглядел бы так:
# 1. response = chat.send_message(prompt)
# 2. while response.parts[0].function_call:
# 3. call = response.parts[0].function_call
# 4. tool_result = tools_map[call.name](**call.args)
# 5. response = chat.send_message(tool_result) # Отправляем Observation
Глубокое погружение: Анатомия ReAct цикла
Давайте разберем, что происходит «под капотом» примера выше, если бы мы реализовывали цикл вручную (что часто нужно для кастомной логики или отладки).
Шаг 1: Первичный промпт
Пользователь спрашивает: «Стоит ли мне брать зонтик в Лондоне?».
Шаг 2: Генерация мысли и вызова (Thought + Action)
Gemini анализирует промпт и свои инструменты. Она понимает, что не знает текущую погоду. Она возвращает специальный объект FunctionCall с именем get_current_weather и аргументом {"location": "London"}.
Шаг 3: Исполнение (Execution)
Ваш код перехватывает этот объект. Вы находите соответствующую функцию Python, запускаете её. Допустим, она возвращает JSON: {"condition": "rainy"}.
Шаг 4: Наблюдение (Observation)
Критический момент. Вы отправляете этот JSON обратно в чат-сессию. Для модели это выглядит как сообщение от роли function (или tool). Теперь контекст модели выглядит так:
User: Стоит ли брать зонтик в Лондоне?
Model: (FunctionCall: get_current_weather(London))
Tool: {"condition": "rainy"}Шаг 5: Финальный ответ (Reasoning)
Получив наблюдение, Gemini снова запускает процесс генерации. Она видит, что идет дождь, и формирует финальный ответ: «Да, в Лондоне сейчас дождливо, зонтик вам пригодится».
Важно: Это и есть суть ReAct. Модель приостанавливает генерацию текста, ждет данных из внешнего мира, и только потом продолжает рассуждение.
Создайте простого 'Финансового Агента'. Реализуйте функцию (mock) `get_stock_price(ticker)`, которая возвращает цену акции. Задайте агенту вопрос, требующий сравнения: 'Что дороже: одна акция Apple или две акции Microsoft?'. Используйте режим `enable_automatic_function_calling=True` для простоты.
Лучшие практики и подводные камни
При создании агентов на базе ReAct и Gemini стоит учитывать несколько нюансов:
- Четкие Docstrings: Модель выбирает инструмент исключительно по его описанию. Описывайте функции максимально подробно: что она делает, какие аргументы принимает, в каком формате.
- Обработка ошибок: Что если API погоды вернет ошибку 500? Ваш код должен перехватить исключение и вернуть модели сообщение об ошибке (например,
{"error": "Service unavailable"}). Умная модель (как Gemini 1.5 Pro) сможет прочитать ошибку и попробовать другой путь или сообщить пользователю о проблеме, вместо того чтобы «упасть». - Защита от зацикливания: Иногда модель может застрять, вызывая одну и ту же функцию бесконечно. Всегда ограничивайте количество итераций цикла (например, макс. 5 вызовов инструментов на один запрос).
- System Instructions: Используйте системные инструкции, чтобы задать «персону» агента. Например: «Ты — полезный помощник. Всегда проверяй факты с помощью инструментов перед ответом. Если информации недостаточно, задавай уточняющие вопросы пользователю».
В чем ключевое различие между паттерном Chain-of-Thought (CoT) и ReAct?