📈

AI-агент для анализа данных: pandas + LLM

Создание data-аналитика на базе LLM: генерация SQL-запросов, pandas-скриптов, визуализация графиков. Автоматический анализ CSV-файлов и поиск инсайтов.

intermediate ⏱ 20 мин
Архитектура AI Data Analyst Agent 👤 Запрос на русском "Покажи топ-5" LLM Router Классификация запроса sql | pandas | viz 🗄️ SQL Generator 🐼 Pandas Script 📊 Matplotlib ⚡ Sandbox Executor exec() в изоляции + сбор stdout/stderr Data Sources: CSV / SQLite / PostgreSQL (read-only connection) | df.head() schema auto-discovery Output: pandas DataFrame → JSON summary / PNG chart / Markdown table LLM видит результат → уточняет запрос при необходимости

# 1. Установка и загрузка данных

Устанавливаем pandas, matplotlib, openai и sqlite3. Загружаем тестовый CSV (продажи интернет-магазина) и создаём SQLite-базу для демонстрации SQL-возможностей агента. Показываем авто-определение схемы таблицы.

# Установка
pip install pandas matplotlib openai sqlite3-tools

import pandas as pd
import sqlite3, json, os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# Загружаем CSV (продажи магазина)
df = pd.read_csv("sales_2025.csv")
print(f"Загружено: {len(df)} строк, {len(df.columns)} колонок")
print(f"Колонки: {list(df.columns)}")
print(f"Типы:\n{df.dtypes}")

# Создаём SQLite базу для SQL-запросов
conn = sqlite3.connect(":memory:")
df.to_sql("sales", conn, index=False)

# Авто-схема для LLM
schema = pd.io.sql.get_schema(df, "sales")
print(f"\nСхема:\n{schema}")
    

# 2. Ядро агента: классификация запроса и генерация кода

Ключевой компонент — LLM определяет тип запроса (SQL, pandas или визуализация) и генерирует исполняемый код. Системный промпт содержит схему данных и примеры. Используем gpt-4o для точной генерации кода.

SYSTEM_PROMPT = """Ты — AI Data Analyst. Твоя задача — анализировать данные.
На вход подаётся вопрос на русском и схема данных.
Верни JSON с полями:
- type: "sql" | "pandas" | "viz"
- code: исполняемый код Python (для sql — строка с запросом)
- explanation: краткое описание что делает код (на русском)

Правила:
1. Для SQL: переменная conn уже есть (sqlite3.Connection)
2. Для pandas: переменная df уже есть (pd.DataFrame)
3. Для viz: используй matplotlib, сохраняй в result.png
4. ВСЕГДА присваивай результат в переменную result
5. Никаких input(), только обработка данных
"""

def generate_analysis_code(question: str, schema_info: str) -> dict:
    """LLM генерирует код анализа по вопросу."""
    response = client.chat.completions.create(
        model="gpt-4o",
        response_format={"type": "json_object"},
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": 
                f"Схема:\n{schema_info}\n\nВопрос: {question}"
            }
        ]
    )
    return json.loads(response.choices[0].message.content)

# Пример: пользователь спрашивает про топ-5 товаров
result = generate_analysis_code(
    "Покажи топ-5 товаров по выручке за 2025 год",
    schema
)
print(json.dumps(result, indent=2, ensure_ascii=False))
    

# 3. Безопасный исполнитель кода (Sandbox)

Код, сгенерированный LLM, нельзя выполнять бесконтрольно. Создаём sandbox-исполнитель с ограниченным globals, таймаутом и чёрным списком опасных вызовов. Результат захватывается через stdout и переменную result.

import sys, io, traceback
from contextlib import redirect_stdout, redirect_stderr

# Чёрный список опасных вызовов
FORBIDDEN = {"__import__", "eval", "exec", "compile", "open",
            "subprocess", "os.system", "shutil"}

def safe_execute(code: str, context: dict) -> dict:
    """Выполняет код в sandbox-окружении."""
    # Проверка на запрещённые вызовы
    for bad in FORBIDDEN:
        if bad in code:
            return {"error": f"Запрещённый вызов: {bad}"}

    namespace = {**context, "__builtins__": {}}
    stdout = io.StringIO()

    try:
        with redirect_stdout(stdout), redirect_stderr(stdout):
            exec(code, namespace)

        result = namespace.get("result", None)
        # Конвертируем DataFrame в dict
        if isinstance(result, pd.DataFrame):
            result = json.loads(result.to_json(orient="records"))
        elif isinstance(result, pd.Series):
            result = json.loads(result.to_json())

        return {
            "success": True,
            "result": result,
            "stdout": stdout.getvalue()[:2000]
        }
    except Exception as e:
        return {
            "success": False,
            "error": f"{type(e).__name__}: {e}",
            "traceback": traceback.format_exc()[:500]
        }
    

# 4. Сборка агента: вопрос → код → исполнение → ответ

Объединяем все компоненты в единого агента. Пользователь задаёт вопрос на русском, LLM генерирует код, sandbox исполняет, результат форматируется в читаемый ответ. При ошибке код отправляется на доработку.

def analyze(question: str, max_retries: int = 2) -> str:
    """Главный метод AI Data Analyst."""
    context = {
        "df": df,
        "conn": conn,
        "pd": pd,
        "json": json
    }

    for attempt in range(max_retries):
        plan = generate_analysis_code(question, schema)
        print(f"\n🔍 Тип: {plan['type']}")
        print(f"📝 Пояснение: {plan['explanation']}")

        # Для SQL-запросов оборачиваем в pd.read_sql
        code = plan["code"]
        if plan["type"] == "sql" and "pd.read_sql" not in code:
            code = f"result = pd.read_sql_query({code}, conn)"

        # Исполняем
        exec_result = safe_execute(code, context)

        if exec_result["success"]:
            # Формируем ответ через LLM
            return format_response(question, exec_result, plan)
        else:
            print(f"⚠️ Ошибка: {exec_result['error']}")
            # Дорабатываем с сообщением об ошибке
            question += f"\n(Ошибка: {exec_result['error']}, исправь код)"

    return "❌ Не удалось выполнить анализ после нескольких попыток"

def format_response(q: str, exec_r: dict, plan: dict) -> str:
    """LLM форматирует результат в читаемый ответ."""
    data_str = json.dumps(exec_r["result"], ensure_ascii=False)
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "system",
            "content": "Преврати JSON-результат в читаемый ответ на русском. Добавь числовые итоги и выводы."
        }, {
            "role": "user",
            "content": f"Вопрос: {q}\nРезультат: {data_str}"
        }]
    )
    return response.choices[0].message.content

# Тест агента
answer = analyze("Какова средняя выручка по месяцам? Покажи тренд.")
print(f"\n📊 Результат:\n{answer}")
    

# 5. Визуализация: графики через matplotlib

Когда пользователь просит график, LLM генерирует matplotlib-код. Агент исполняет его в sandbox и сохраняет результат как PNG. Картинку можно отобразить в UI или сохранить для отчёта. Добавляем поддержку русских шрифтов.

import matplotlib
matplotlib.use("Agg")  # безголовый режим
import matplotlib.pyplot as plt

# Настройка русских шрифтов
plt.rcParams['font.family'] = 'DejaVu Sans'

def analyze_with_viz(question: str):
    """Версия с поддержкой графиков."""
    context = {
        "df": df, "conn": conn,
        "pd": pd, "plt": plt, "json": json
    }

    plan = generate_analysis_code(question, schema)

    if plan["type"] == "viz":
        # Добавляем сохранение графика
        code = plan["code"]
        if "savefig" not in code:
            code += "\nplt.savefig('result.png', dpi=150, bbox_inches='tight')"

        exec_result = safe_execute(code, context)
        print(f"📊 График сохранён: result.png")
        return exec_result

    return analyze(question)

# Пример: круговая диаграмма продаж по категориям
analyze_with_viz(
    "Построй круговую диаграмму распределения продаж по категориям товаров"
)
    
✅ Итог

AI Data Analyst превращает вопросы на естественном языке в полноценный анализ данных с SQL, pandas и визуализацией. Агент понимает контекст ваших данных, самостоятельно выбирает правильный инструмент и выполняет вычисления в изолированном sandbox. Решение легко встраивается в Jupyter-ноутбуки, дашборды и Slack-ботов. Для production-использования добавьте кэширование частых запросов и подключение к PostgreSQL/ClickHouse.

🔗 Полезные ссылки

🐼 Pandas Docs 🤖 OpenAI API 📊 Matplotlib