ИИ-агенты Google Sheets — Автоматизация таблиц | QantCore
📊

ИИ-агенты для Google Sheets

Подключаем ИИ-агента к Google Sheets: автоматическое заполнение, классификация данных, генерация отчётов на естественном языке и умный парсинг через Google Sheets API + GPT-4o.

intermediate ⏱ 15 мин
📊 Google Sheets Данные · Формулы API ИИ-Агент Чтение данных из Sheets LLM: анализ / генерация Запись результатов обратно Google Sheets API v4 Инструменты агента 📖 read_range(range) ✏️ write_range(range, data) Результат Отчёт в Sheets Классификация Графики service_account.json gspread / google-api-python-client OAuth 2.0 · Service Account Автообновление

# 1. Настройка Google Sheets API и сервисного аккаунта

Первый шаг — получить доступ к Google Sheets API. Создаём проект в Google Cloud Console, включаем Google Sheets API и Google Drive API, создаём сервисный аккаунт, скачиваем JSON-ключ. Затем даём сервисному аккаунту доступ к нужной таблице (Editor) через кнопку «Share» в интерфейсе Sheets. Для работы используем библиотеку gspread — она удобнее google-api-python-client.

# 1. Установка зависимостей
pip install gspread google-auth openai pandas

# 2. Аутентификация через сервисный аккаунт
import gspread
from google.oauth2.service_account import Credentials

# Путь к JSON-ключу сервисного аккаунта
SERVICE_ACCOUNT_FILE = "/opt/secrets/gsheets-service-account.json"
SCOPES = [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive.file"
]

creds = Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
gc = gspread.authorize(creds)

# 3. Открываем таблицу (по URL или ID)
# ID таблицы берём из URL: https://docs.google.com/spreadsheets/d/{SHEET_ID}/
SHEET_ID = "1BxiMVs0XRA5nFMjKvBdBZjgmUUqptlbs74OgvE2upms"
sheet = gc.open_by_key(SHEET_ID).sheet1
print(f"✅ Подключено: {sheet.title}, строк: {sheet.row_count}")

# 2. Инструменты чтения и записи диапазонов

Базовые инструменты агента: read_range читает произвольный диапазон ячеек и возвращает данные в виде списка словарей или сырого массива. write_range записывает данные в указанный диапазон. Это фундамент, на котором строятся все остальные возможности — классификация, генерация отчётов, заполнение пропусков.

def read_range(sheet, range_str: str, as_dict=True) -> list:
    """Читает диапазон: 'A1:D100' → список словарей [{col: val}, ...]"""
    data = sheet.get(range_str)
    if not as_dict or len(data) < 2:
        return data
    headers = data[0]
    return [{headers[i]: row[i] if i < len(row) else "" 
             for i in range(len(headers))} for row in data[1:]]

def write_range(sheet, range_str: str, data: list):
    """Записывает данные в диапазон. data — список списков."""
    sheet.update(range_name=range_str, values=data, value_input_option="USER_ENTERED")
    return {"status": "ok", "range": range_str, "rows": len(data)}

# Пример: чтение таблицы заказов
# A: order_id | B: customer | C: amount | D: status | E: category
orders = read_range(sheet, "A1:E500")
print(f"Загружено заказов: {len(orders)}")
# Вывод: Загружено заказов: 487

# 3. ИИ-классификация данных в таблице

Одна из самых востребованных задач — автоматическая классификация строк. Например, категоризация отзывов клиентов по тональности, определение темы обращения в службу поддержки или классификация товаров по типу. Агент читает столбец, отправляет батчами в LLM и записывает результаты в соседний столбец.

from openai import OpenAI
client = OpenAI()
BATCH_SIZE = 20  # Строк за один вызов LLM

def classify_sentiment(reviews: list) -> list:
    """Классифицирует тональность отзывов батчами"""
    results = []
    for i in range(0, len(reviews), BATCH_SIZE):
        batch = reviews[i:i+BATCH_SIZE]
        numbered = "\n".join(f"{j+1}. {r}" for j, r in enumerate(batch))
        prompt = f"""Определи тональность каждого отзыва.
Варианты: positive, negative, neutral.
Верни ТОЛЬКО JSON-массив: ["positive", "negative", ...]

{numbered}"""
        resp = client.chat.completions.create(
            model="gpt-4o-mini", temperature=0,
            messages=[{"role": "user", "content": prompt}],
            response_format={"type": "json_object"}
        )
        batch_results = json.loads(resp.choices[0].message.content)
        results.extend(batch_results.get("sentiments", batch_results))
    return results

# Пайплайн: читаем столбец B (отзывы), классифицируем, пишем в столбец F
data = read_range(sheet, "A1:F500")
reviews = [row.get("review", "") for row in data]
sentiments = classify_sentiment(reviews)
# Записываем результаты в столбец F (Sentiment), начиная с F2
output = [[s] for s in sentiments]
write_range(sheet, f"F2:F{1+len(output)}", output)
print(f"✅ Классифицировано: {len(sentiments)} отзывов")

# 4. Генерация сводок и отчётов на естественном языке

Агент читает данные таблицы и генерирует текстовый отчёт на естественном языке: сводка продаж за период, анализ трендов, выявление аномалий. Результат записывается в отдельный лист «Reports» с автоформатированием. Используем gspread formatting для красивого вывода.

def generate_report(sheet, data_range="A1:E500") -> str:
    """Генерирует текстовый отчёт по данным таблицы"""
    data = read_range(sheet, data_range)
    if not data:
        return "Нет данных для отчёта"
    
    # Собираем базовую статистику
    headers = list(data[0].keys())
    row_count = len(data)
    sample = json.dumps(data[:10], ensure_ascii=False, indent=2)
    
    prompt = f"""Проанализируй данные таблицы и напиши краткий отчёт на русском.

Заголовки столбцов: {headers}
Всего строк: {row_count}
Образец данных (первые 10 строк):
{sample}

Напиши отчёт в формате:
## 📊 Сводка
(2-3 предложения общих выводов)

## 📈 Ключевые метрики
(числовые показатели если есть)

## ⚠️ Аномалии
(что выглядит необычно)

## 💡 Рекомендации
(2-3 практических совета)"""
    
    resp = client.chat.completions.create(
        model="gpt-4o", temperature=0.4,
        messages=[{"role": "user", "content": prompt}]
    )
    report = resp.choices[0].message.content
    
    # Создаём лист Reports и записываем отчёт
    try:
        report_sheet = sheet.spreadsheet.worksheet("Reports")
    except:
        report_sheet = sheet.spreadsheet.add_worksheet("Reports", 100, 5)
    
    lines = report.split("\n")
    report_sheet.update("A1", [[line] for line in lines])
    return report

# Вывод:
# ## 📊 Сводка
# За отчётный период обработано 487 заказов на сумму 2.4 млн руб...
# ## 📈 Ключевые метрики
# Средний чек: 4 928 руб. Конверсия: 3.2%...

# 5. Умное заполнение пропусков и очистка данных

LLM-агент отлично справляется с задачей заполнения пропусков в таблицах. Он анализирует контекст строки и соседних строк, чтобы предсказать наиболее вероятное значение. Это работает для категорий, описаний товаров, тегов и других текстовых полей, где простой автозаполнитель бессилен.

def fill_missing_values(sheet, column_letter: str, context_columns: list):
    """Заполняет пустые ячейки в колонке на основе контекста других колонок"""
    # Читаем всю таблицу
    all_data = sheet.get_all_records()
    headers = list(all_data[0].keys()) if all_data else []
    
    # Находим строки с пустыми значениями в целевой колонке
    target_col_name = None
    col_idx = ord(column_letter) - ord('A')
    if col_idx < len(headers):
        target_col_name = headers[col_idx]
    
    updates = []
    for row_idx, row in enumerate(all_data, start=2):
        current_val = row.get(target_col_name, "") if target_col_name else ""
        if current_val and current_val.strip():
            continue  # Не пустое — пропускаем
        
        # Формируем контекст из соседних колонок
        context = {k: v for k, v in row.items() if k in context_columns}
        prompt = f"""Заполни пропущенное значение в колонке '{target_col_name}'.
Контекст строки: {json.dumps(context, ensure_ascii=False)}
Верни ТОЛЬКО значение, без пояснений."""
        
        resp = client.chat.completions.create(
            model="gpt-4o-mini", temperature=0.2,
            messages=[{"role": "user", "content": prompt}]
        )
        predicted = resp.choices[0].message.content.strip()
        updates.append((row_idx, predicted))
        print(f"Строка {row_idx}: '{current_val}' → '{predicted}'")
    
    # Применяем обновления
    for row_idx, value in updates:
        sheet.update_acell(f"{column_letter}{row_idx}", value)
    
    return {"filled": len(updates)}

# 6. Telegram-бот как интерфейс к Google Sheets

Чтобы пользователи могли взаимодействовать с таблицами через естественный язык, обернём агента в Telegram-бота. Пользователь пишет «Покажи топ-10 клиентов по выручке за май» — бот через агента читает Google Sheets, анализирует данные через LLM и возвращает ответ.

import asyncio
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters

# Конфигурация инструментов для LLM-агента
TOOLS_DEFINITION = [
    {
        "type": "function",
        "function": {
            "name": "sheets_read_range",
            "description": "Читает данные из Google Sheets по диапазону (A1:C50)",
            "parameters": {
                "type": "object",
                "properties": {
                    "range": {"type": "string", "description": "Диапазон, например 'Лист1!A1:D100'"}
                },
                "required": ["range"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "sheets_write_range",
            "description": "Записывает данные в Google Sheets",
            "parameters": {
                "type": "object",
                "properties": {
                    "range": {"type": "string"},
                    "data": {"type": "array", "items": {"type": "array"}}
                },
                "required": ["range", "data"]
            }
        }
    }
]

async def handle_message(update: Update, context):
    user_message = update.message.text
    # ReAct-цикл с доступом к Google Sheets
    messages = [
        {"role": "system", "content": "Ты — ИИ-агент Google Sheets. Отвечай на русском, кратко."},
        {"role": "user", "content": user_message}
    ]
    # ... ReAct-цикл с function calling (как в гайде #1) ...
    # Выполняем tools, возвращаем ответ пользователю
    await update.message.reply_text(reply)

app = Application.builder().token("TELEGRAM_BOT_TOKEN").build()
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
print("🤖 Telegram-бот для Google Sheets запущен")
app.run_polling()

# 7. Автоматический парсинг и структурирование неструктурированных данных

Часто в таблицы попадают неструктурированные данные: адреса одной строкой, описания товаров в свободной форме, логи. Агент парсит такие данные и раскладывает по отдельным колонкам. Например, из строки «г. Москва, ул. Тверская, д. 12, кв. 45, индекс 125009» извлекает город, улицу, дом, квартиру, индекс.

def parse_unstructured_column(sheet, source_col: str, target_cols: list, schema: dict):
    """Парсит неструктурированные данные из source_col в target_cols по схеме.
    schema = {"Город": "string", "Улица": "string", "Дом": "string", ...}"""
    data = sheet.get_all_records()
    source_idx = ord(source_col) - ord('A')
    headers = list(data[0].keys()) if data else []
    source_col_name = headers[source_idx] if source_idx < len(headers) else source_col
    
    results = []
    for row in data:
        raw_text = row.get(source_col_name, "")
        schema_str = json.dumps(schema, ensure_ascii=False)
        prompt = f"""Извлеки структурированные данные из текста.
Схема полей: {schema_str}
Текст: {raw_text}
Верни ТОЛЬКО JSON-объект с полями согласно схеме."""
        resp = client.chat.completions.create(
            model="gpt-4o-mini", temperature=0,
            messages=[{"role": "user", "content": prompt}],
            response_format={"type": "json_object"}
        )
        parsed = json.loads(resp.choices[0].message.content)
        results.append([parsed.get(col, "") for col in target_cols])
    
    # Записываем результат в целевые колонки
    start_col_letter = chr(ord(source_col) + 1)  # Следующая после source
    end_col_letter = chr(ord(start_col_letter) + len(target_cols) - 1)
    range_str = f"{start_col_letter}2:{end_col_letter}{1+len(results)}"
    sheet.update(range_str, results)
    return {"parsed": len(results), "fields": target_cols}

# Пример: парсим адреса из столбца C в столбцы D-H
schema = {"Город": "string", "Улица": "string", "Дом": "string", "Квартира": "string", "Индекс": "string"}
parse_unstructured_column(sheet, "C", ["Город", "Улица", "Дом", "Квартира", "Индекс"], schema)
# Вывод: {'parsed': 487, 'fields': ['Город', 'Улица', 'Дом', 'Квартира', 'Индекс']}
✅ Итог

Мы интегрировали ИИ-агента с Google Sheets API через сервисный аккаунт и библиотеку gspread. Агент умеет: читать и записывать произвольные диапазоны, классифицировать данные батчами через LLM (тональность, категории, теги), генерировать текстовые отчёты на естественном языке с автоматическим созданием листа Reports, заполнять пропуски в данных на основе контекста, парсить неструктурированные строки в структурированные колонки, и работать как Telegram-бот для общения с таблицами на естественном языке. Следующий шаг — добавить триггеры на изменение таблицы (Google Apps Script webhook) для полностью автономной работы агента без ручного запуска.