Проблема, от которой хочется выть: миграция данных с галлюцинирующим AI
Представьте: вы работаете в Remote (платформа для найма и управления удаленными сотрудниками). К вам приходит новый клиент с 500-мегабайтным CSV-файлом. В нем данные о 50 000 сотрудниках из их старой системы. Нужно все это перенести в вашу платформу.
Типичный сценарий до 2025 года: бросаете файл в GPT-4.5 или Claude 3.7. Ждете. Получаете код для миграции. Запускаете. И... бац. Ошибка в строке 12 345. Потому что модель не увидела эту строку - контекстное окно закончилось на 10 000-й. Или она "придумала" структуру данных, которой нет в файле. Или просто проигнорировала специфичные для клиента поля.
Галлюцинации в миграции данных - это не просто "ой, ошибка". Это потерянные зарплаты, некорректные налоговые расчеты, юридические риски. Одна ошибка в поле "налоговый резидент" может стоить компании десятки тысяч долларов штрафов.
Remote столкнулся с этим в 2025 году. Их команда онбординга тратила часы на ручную проверку миграций. Инженеры писали кастомные скрипты под каждого клиента. Масштабировать это было невозможно.
Решение, которое работает: Code Execution Agent на LangGraph
Вместо того чтобы просить LLM "проанализируй файл и напиши код", Remote пошел другим путем. Они создали агента, который сам исполняет код для анализа данных. Не генерирует предположения. Не "помнит" файл в контексте. Он реально открывает файл, читает его, анализирует - и только потом принимает решения.
Архитектура, которая не ломается
Вот как это выглядит технически (на основе LangGraph 1.8, актуальной на январь 2026):
from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
file_path: str
analysis_results: dict
migration_plan: list
executed_code: list
errors: list
# Узлы графа
async def analyze_file(state: AgentState):
# ВМЕСТО того чтобы передавать файл в промпт
# Агент генерирует и исполняет код для анализа
code = """
import pandas as pd
import json
df = pd.read_csv('{{file_path}}', nrows=1000)
result = {
'columns': list(df.columns),
'dtypes': dict(df.dtypes),
'sample': df.head(3).to_dict('records'),
'null_counts': df.isnull().sum().to_dict()
}
print(json.dumps(result))
"""
# Код исполняется в sandbox
result = execute_in_sandbox(code, state['file_path'])
return {"analysis_results": result}
async def plan_migration(state: AgentState):
# LLM получает РЕЗУЛЬТАТЫ анализа, а не файл
# Планирует миграцию на основе реальных данных
prompt = f"""На основе анализа:
{state['analysis_results']}
Создай план миграции в систему Remote."""
# ... генерация плана
return {"migration_plan": plan}
# Сборка графа
graph_builder = StateGraph(AgentState)
graph_builder.add_node("analyze", analyze_file)
graph_builder.add_node("plan", plan_migration)
graph_builder.add_edge("analyze", "plan")
graph_builder.add_edge("plan", END)
graph = graph_builder.compile()
Что здесь важно? Файл никогда не попадает в промпт целиком. LLM видит только результаты анализа - структуру, типы данных, примеры строк. Это решает проблему контекстного окна раз и навсегда.
Шаг за шагом: как работает агент миграции
1 Загрузка и предварительный анализ
Агент не спрашивает "что в файле?". Он запускает код, который:
- Определяет размер файла и кодировку
- Читает первые 1000 строк для анализа структуры
- Определяет типы данных в каждой колонке
- Ищет потенциальные проблемы: дубликаты, невалидные значения, отсутствующие обязательные поля
Все это - через исполнение Python-кода в изолированной среде. Результаты - чистые данные, а не предположения LLM.
2 Сопоставление полей с целевой системой
Вот где начинается магия. У Remote есть сложная схема данных: сотрудники, контракты, выплаты, налоговые формы. Агент должен понять, какое поле исходного файла куда мапится.
Старый подход: LLM пытается угадать. Новый подход: агент исполняет код, который:
# Пример кода, который генерирует и исполняет агент
def find_best_match(source_field, target_fields):
"""Находит лучшее соответствие между полями."""
# Использует эмбеддинги и семантическое сравнение
# Но ВАЖНО: сравнение происходит на реальных данных из файла
# Не на названиях полей, а на примерах значений
# Берет 100 случайных значений из source_field
sample_values = get_field_sample(source_field, n=100)
# Для каждого целевого поля вычисляет "похожесть"
matches = []
for target in target_fields:
similarity = calculate_semantic_similarity(
sample_values,
target['expected_patterns']
)
matches.append((target['name'], similarity))
return max(matches, key=lambda x: x[1])
Это детерминировано. Это проверяемо. Это не зависит от "настроения" LLM сегодня.
3 Валидация и обработка исключений
Самый болезненный этап. В файле клиента может быть что угодно: даты в формате "DD/MM/YYYY", "MM-DD-YY", "January 15, 2025". Зарплаты в долларах, евро, иенах. Налоговые коды, которые существуют только в одной стране.
Агент Remote обрабатывает это так:
- Для каждого проблемного поля генерирует код обработки
- Исполняет его на выборке данных
- Проверяет результаты на валидность
- Если ошибок больше порога - просит человеческое вмешательство
Ключевая метрика: "автономность агента". Remote отслеживает, какой процент миграций агент завершает без человеческого вмешательства. В январе 2026 этот показатель достиг 87% - для файлов до 1 ГБ со стандартной структурой.
Технические нюансы, о которых молчат в блогах
Sandbox - не просто Docker
Многие думают: "запущу код в контейнере и все". Remote пошел дальше:
- Ограничение памяти: 512 МБ на процесс
- Таймауты: 30 секунд на анализ, 2 минуты на полную обработку
- Белый список библиотек: pandas, numpy, но не requests, не subprocess
- Мониторинг системных вызовов: блокировка любых сетевых операций
Потому что один недобросовестный клиент (или одна ошибка LLM) может сгенерировать код import os; os.system('rm -rf /'). Шутка, которая стоит карьеры.
Управление контекстом: не только токены
Проблема контекстного окна - это не только "сколько токенов". Это еще и "какая информация действительно нужна".
Remote использует подход, похожий на тот, что описан в нашей статье "Когда память кончается: как заставить локальный AI помнить больше 8К токенов". Но с twist: вместо того чтобы сжимать контекст, они его структурируют.
| Что в контексте | Как хранится | Когда используется |
|---|---|---|
| Структура файла | JSON schema | Всегда |
| Примеры данных | 5 строк из каждой колонки | При маппинге полей |
| Правила трансформации | Генерируемый код | При исполнении |
| Ошибки и исключения | Отдельный лог | Для отладки и репортов |
Галлюцинации: лечим, а не маскируем
Главный трюк Remote: они не пытаются сделать LLM "менее галлюцинирующей". Они принимают, что галлюцинации будут всегда. И строят систему вокруг этого.
Каждый шаг, где LLM что-то предлагает ("это поле - это дата найма"), немедленно проверяется исполнением кода:
# LLM предлагает: "field_X - это дата найма, формат DD/MM/YYYY"
# Агент не верит на слово. Он проверяет:
validation_code = """
import pandas as pd
from datetime import datetime
try:
# Пробуем распарсить
sample = df['field_X'].dropna().head(100)
parsed = pd.to_datetime(sample, format='%d/%m/%Y', errors='coerce')
# Сколько успешно?
success_rate = (parsed.notna().sum() / len(sample)) * 100
# Все даты в разумном диапазоне? (2000-2026)
valid_years = parsed.dt.year.between(2000, 2026).sum()
year_rate = (valid_years / parsed.notna().sum()) * 100
return {
'is_date': success_rate > 90,
'success_rate': success_rate,
'valid_year_rate': year_rate
}
except Exception as e:
return {'error': str(e), 'is_date': False}
"""
Если проверка проваливается - LLM получает feedback и пробует снова. Это цикл с обратной связью, а не одноразовый запрос.
Ошибки, которые повторяют все (и как их избежать)
Ошибка 1: Доверять LLM с числами
LLM прекрасно генерирует текст. С числами - катастрофа. Особенно с большими числами, процентами, вычислениями.
Как НЕ надо: "LLM, посчитай среднюю зарплату в файле"
Как НАДО: "LLM, сгенерируй код, который посчитает среднюю зарплату. Исполни его. Верни результат."
Ошибка 2: Игнорировать edge cases в данных
В миграции данных edge cases - это не исключения. Это правило. Файл на 50 000 строк? В 50 из них будет что-то странное.
Remote решает это через "статистическую валидацию":
- Анализирует распределение значений в каждом поле
- Ищет выбросы статистическими методами (IQR, z-score)
- Для выбросов - отдельная обработка или запрос к человеку
Ошибка 3: Не планировать откат
Самая страшная ошибка в миграции: начали процесс, что-то пошло не так, а откатиться нельзя.
Агент Remote для каждого шага миграции генерирует и тестирует код отката. Перед тем как изменить данные в целевой системе, он убеждается, что может вернуть все как было.
Что это дает на практике?
Цифры от Remote (январь 2026):
- Время онбординга клиента: с 3-5 дней до 4-8 часов
- Ошибки в миграции: с 15-20% ручных проверок до 2-3%
- Масштабируемость: один инженер может вести 10 параллельных миграций вместо 1
- Качество данных: 99.8% точность маппинга полей (против 85-90% у ручного подхода)
Но самое важное - предсказуемость. Клиент знает: через 6 часов его данные будут в системе. Без сюрпризов. Без "ой, мы нашли проблему, нужно еще 2 дня".
Можно ли повторить? (Спойлер: да, но...)
Архитектура Remote открыта. LangGraph - публичная библиотека. Идея проста. Но devil in details.
Вам понадобится:
- Надежный sandbox для исполнения кода. Не просто Docker. Система с мониторингом ресурсов, таймаутами, изоляцией сети.
- Продвинутое управление контекстом. Не просто "вот весь файл в промпте". Структурированное хранение разных типов информации. Возможно, вам пригодится наш гайд по Middleware в LangChain 1.0.
- Система валидации каждого шага. LLM всегда будет галлюцинировать. Ваша работа - ловить эти галлюцинации до того, как они станут проблемой.
- План для edge cases. 5% данных всегда будут странными. У вас должен быть процесс: автоматическая обработка → полуавтоматическая → ручная.
Что дальше? Будущее агентов исполнения кода
Remote уже экспериментирует с next-level фичами:
- Мультимодальные агенты: загрузка не только CSV, но и PDF с контрактами, сканы паспортов, скриншоты старых систем. Агент анализирует все вместе.
- Инкрементальные миграции: не весь файл сразу, а потоковое обновление. Новые сотрудники добавляются автоматически.
- Обратное маппирование: из системы Remote обратно в формат клиента. Для отчетов, аудитов, экспорта.
Но самая интересная возможность - самообучающиеся агенты. Каждая миграция создает "память": какие поля сложные, какие преобразования работают, где чаще всего ошибки. Следующий агент учится на этом. Это уже не просто выполнение задачи. Это эволюция процесса.
Кстати, если вам интересна тема долговременной памяти для AI-агентов, посмотрите нашу статью "Системы долговременной памяти для LLM". Там есть паттерны, которые Remote как раз тестирует для своих агентов.
И последнее: не ждите, пока ваша LLM станет "умнее" или "менее галлюцинирующей". Это как ждать, пока дождь перестанет быть мокрым. Примите ограничения. Постройте систему вокруг них. Как сделал Remote.
Потому что в 2026 году лучший AI-агент - не тот, который никогда не ошибается. А тот, который свои ошибки находит и исправляет сам. До того, как их заметит клиент.