Управление состоянием между этапами выполнения
Сохраняйте переменные, таблицы данных и импортированные библиотеки между несколькими этапами выполнения кода, чтобы агент мог использовать предыдущие результаты без повторного выполнения прежних вычислений.
«Управление состоянием между этапами выполнения» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Проблема отсутствия состояния
Каждое выполнение контейнера Docker запускает новый интерпретатор Python. Переменные, определённые в итерации 1, не существуют в итерации 2. Из-за этого отсутствия состояния агент вынужден заново вычислять или загружать всё с нуля на каждом этапе выполнения — если только Вы не реализуете явную стратегию управления состоянием, позволяющую сохранять и восстанавливать его между итерациями. Без этого многоэтапный анализ невозможен.
# Iteration 1 - works fine
df = pd.read_csv('data.csv') # df is in memory
df_cleaned = df.dropna()
print('Rows after cleaning:', len(df_cleaned))
# Iteration 2 - NEW container, df_cleaned is GONE
result = df_cleaned.groupby('category').sum() # NameError: df_cleaned is not defined
print(result) # This will fail!Сохранение состояния в файлах
Самый простой и универсальный подход — сохранять состояние в файлах. В конце каждого блока кода агент записывает DataFrame, словари и другие объекты в файлы рабочего каталога. Следующая итерация загружает их обратно. Формат Parquet идеально подходит для DataFrame, JSON — для словарей, а pickle — для произвольных объектов Python (хотя pickle из ненадёжного кода представляет угрозу безопасности).
import pandas as pd
import json
from pathlib import Path
WORKSPACE = Path('/workspace')
# Iteration 1: process and SAVE
df = pd.read_csv(WORKSPACE / 'raw_data.csv')
df_cleaned = df.dropna().reset_index(drop=True)
df_cleaned.to_parquet(WORKSPACE / 'cleaned.parquet') # save for next iteration
stats = {'rows': len(df_cleaned), 'columns': list(df_cleaned.columns)}
with open(WORKSPACE / 'stats.json', 'w') as f:
json.dump(stats, f)
print('Saved cleaned data:', len(df_cleaned), 'rows')
# Iteration 2: LOAD and continue
df_cleaned = pd.read_parquet(WORKSPACE / 'cleaned.parquet') # restore state
with open(WORKSPACE / 'stats.json') as f:
stats = json.load(f)
result = df_cleaned.groupby('category')['value'].sum()
print(result)Обучение агента работе с состоянием в файлах
Недостаточно просто реализовать поддержку состояния в файлах на уровне инфраструктуры — LLM также должна знать об этом соглашении и последовательно ему следовать. Включите в системный запрос явные инструкции: после вычисления результата, который понадобится позже, всегда сохраняйте его под описательным именем файла, а в начале каждой итерации перед продолжением работы всегда загружайте файлы, созданные на предыдущих этапах.
STATE_MANAGEMENT_INSTRUCTIONS = '''
State persistence rules:
- You have a persistent workspace at /workspace/
- After computing any result you will need later, SAVE it to /workspace/
- DataFrames: use .to_parquet('/workspace/name.parquet')
- Dicts/lists: use json.dump to /workspace/name.json
- Text: write to /workspace/name.txt
- At the start of each code block, LOAD the files you need from previous steps
- Use clear, descriptive filenames like 'cleaned_data.parquet', not 'tmp1.parquet'
- When listing files, use: import os; print(os.listdir('/workspace/'))
'''Состояние на основе ядра с постоянными процессами Python
Альтернатива состоянию в файлах — поддерживать постоянный процесс Python (например, ядро Jupyter), работающий между итерациями, и передавать в него каждый блок кода с помощью exec(). Переменные, определённые в одной итерации, остаются доступными в следующей. Этот подход быстрее и естественнее, но требует запуска постоянного процесса для каждого сеанса агента, а не временных контейнеров.
import jupyter_client
class PersistentKernel:
def __init__(self):
km, self.kc = jupyter_client.manager.start_new_kernel(kernel_name='python3')
self.kc.wait_for_ready(timeout=30)
print('Kernel started')
def execute(self, code: str, timeout=60) -> tuple[str, str]:
msg_id = self.kc.execute(code)
outputs, errors = [], []
while True:
msg = self.kc.get_iopub_msg(timeout=timeout)
if msg['msg_type'] == 'stream':
if msg['content']['name'] == 'stdout':
outputs.append(msg['content']['text'])
else:
errors.append(msg['content']['text'])
if msg['msg_type'] == 'status' and msg['content']['execution_state'] == 'idle':
break
return ''.join(outputs), ''.join(errors)
def shutdown(self):
self.kc.shutdown()
# Variables persist across execute() calls!
kernel = PersistentKernel()
kernel.execute('x = 42') # x is now defined in kernel
output, _ = kernel.execute('print(x)') # prints 42 - state persists!
kernel.shutdown()Управление состоянием с помощью объекта состояния
На уровне оркестратора агента (вне песочницы) поддерживайте явный объект состояния, отслеживающий текущее состояние работы агента: какие файлы созданы, что вычислено, на каком этапе находится агент и какова конечная цель. Передавайте сводку этого объекта состояния LLM на каждой итерации, чтобы она всегда понимала своё место в общей задаче.
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ExecutionState:
task: str
iteration: int = 0
workspace_files: list[str] = field(default_factory=list)
computed_values: dict[str, Any] = field(default_factory=dict)
completed_steps: list[str] = field(default_factory=list)
last_output: str = ''
def to_context_summary(self) -> str:
return f'''Current task: {self.task}
Iteration: {self.iteration}
Completed steps: {', '.join(self.completed_steps) or 'None yet'}
Workspace files: {', '.join(self.workspace_files) or 'None yet'}
Key values: {self.computed_values}
Last output: {self.last_output[:500]}'''
def after_execution(self, output: str, step_name: str):
import os
self.workspace_files = os.listdir('/workspace')
self.completed_steps.append(step_name)
self.last_output = output
self.iteration += 1Внедрение контекста состояния в запросы
Каждый раз, когда Вы обращаетесь к LLM за следующим блоком кода, добавляйте текущий контекст состояния в сообщение пользователя. Это даёт LLM точную информацию о доступных файлах, уже выполненных вычислениях и том, что ещё необходимо сделать. Без этого контекста LLM может попытаться заново создать уже существующие файлы или пропустить этапы, которые уже завершены.
def build_iteration_prompt(task: str, state: ExecutionState, last_observation: str) -> str:
return f'''ORIGINAL TASK: {task}
CURRENT STATE:
{state.to_context_summary()}
LAST EXECUTION OUTPUT:
{last_observation}
What is the next step? Write Python code to continue.
Remember:
- Load data from workspace files if needed
- Save any results you will need in future steps
- If the task is complete, say TASK COMPLETE and summarize the result'''
# Use in the main loop
for step_num in range(max_iterations):
context = build_iteration_prompt(task, state, last_observation)
response = llm.complete(messages + [{'role': 'user', 'content': context}])
code = extract_code_block(response)
if not code:
break # done
output, err = execute_in_sandbox(code)
state.after_execution(output, step_name=f'step_{step_num}')
last_observation = format_observation(output, err)Работа с большими промежуточными данными
Агенты анализа данных часто создают большие промежуточные наборы данных, повторная загрузка которых на каждой итерации требует значительных ресурсов. Применяйте отложенную загрузку: загружайте только данные, необходимые для текущего этапа. Используйте столбцовые форматы, такие как Parquet, поддерживающие эффективное чтение отдельных столбцов. Для действительно больших наборов данных (от 100 МБ) поддерживайте постоянное ядро, чтобы DataFrame оставался в памяти между итерациями и не преобразовывался в последовательную форму и обратно при каждом запуске.
# Good: load only needed columns
df = pd.read_parquet('/workspace/full_data.parquet', columns=['date', 'revenue', 'region'])
# Bad: load everything even if you only need 2 columns
# df = pd.read_parquet('/workspace/full_data.parquet') # loads 50 columns you don't need
# Good: filter early before loading full dataset
df = pd.read_parquet('/workspace/full_data.parquet', filters=[('region', '=', 'EMEA')])
# For very large files, tell the LLM about data shape upfront
df_info = {'shape': (1_000_000, 50), 'size_mb': 850, 'columns': [...]}
# Include df_info in state so LLM plans accordinglyСоздание контрольных точек для длительных задач
Для задач, охватывающих множество итераций, реализуйте создание контрольных точек: периодически сохраняйте полное состояние агента (завершённые этапы, файлы рабочего пространства, текущий прогресс) в надёжном хранилище, чтобы после прерывания задачу можно было продолжить. Это особенно важно для длительных заданий анализа данных, которые могут занимать более 30 минут и быть прерваны из-за проблем с сетью, ошибок API или перезапуска сервера.
import json
import time
def checkpoint_state(state: ExecutionState, checkpoint_id: str, db):
data = {
'task': state.task,
'iteration': state.iteration,
'completed_steps': state.completed_steps,
'workspace_files': state.workspace_files,
'timestamp': time.time()
}
db.execute(
'INSERT INTO agent_checkpoints (id, state) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET state=excluded.state',
(checkpoint_id, json.dumps(data))
)
print(f'Checkpoint saved at iteration {state.iteration}')
def resume_from_checkpoint(checkpoint_id: str, db) -> ExecutionState | None:
row = db.execute('SELECT state FROM agent_checkpoints WHERE id = ?', (checkpoint_id,)).fetchone()
if not row:
return None
data = json.loads(row[0])
state = ExecutionState(task=data['task'])
state.iteration = data['iteration']
state.completed_steps = data['completed_steps']
print(f'Resumed from iteration {state.iteration}')
return stateОчистка состояния после завершения
В рабочих пространствах агентов накапливаются файлы, и со временем они могут занять много места. Всегда реализуйте этап очистки, запускаемый после завершения или сбоя задачи: удаляйте промежуточные файлы (cleaned.parquet, tmp_output.csv) и сохраняйте только конечные файлы, которые нужны пользователю. Для облачного хранилища задайте политики жизненного цикла, автоматически удаляющие файлы рабочего пространства по истечении срока хранения.
import shutil
from pathlib import Path
INTERMEDIATE_PATTERNS = ['*.parquet', 'tmp_*.csv', 'step_*.json', 'debug_*.txt']
FINAL_OUTPUT_PATTERNS = ['report.pdf', 'final_*.csv', 'summary.json']
def cleanup_workspace(workspace_dir: str, keep_final=True):
workspace = Path(workspace_dir)
final_outputs = []
if keep_final:
for pattern in FINAL_OUTPUT_PATTERNS:
final_outputs.extend(workspace.glob(pattern))
# Move final outputs to output directory
output_dir = workspace.parent / 'outputs'
output_dir.mkdir(exist_ok=True)
for f in final_outputs:
shutil.move(str(f), str(output_dir / f.name))
# Delete the workspace
shutil.rmtree(workspace_dir)
print(f'Workspace cleaned up. Kept {len(final_outputs)} output files.')
return [str(f) for f in final_outputs]Версионирование состояния для отладки
Когда агент, работающий с кодом, выдаёт неверные результаты, необходимо проследить историю его выполнения, чтобы найти источник ошибки. Реализуйте версионирование состояния, сохраняя снимок рабочего пространства после каждой итерации. Это позволяет воспроизвести выполнение агента, изучить промежуточные состояния и определить точную итерацию, на которой произошла ошибка. Для экономии места сохраняйте только разницу (изменённые файлы).
Контекстное окно и внешнее состояние
В проектировании агентов, работающих с кодом, существует принципиальное противоречие между размещением состояния в контекстном окне LLM (быстро доступно, но ограничено) и во внешнем хранилище (практически не ограничено, но требует явного управления). Оптимальная стратегия такова: храните данные текущего этапа в контексте, большие промежуточные результаты — в файлах, цель задачи и общий план — в контексте, а исходные данные всегда — на диске. LLM управляет задачей, а файловая система — данными.
Быстрая проверка
Проверьте, насколько хорошо Вы поняли управление состоянием между этапами выполнения кода из этого урока.
Итоги урока
В этом уроке Вы узнали, что сохранение состояния в файлах с использованием Parquet и JSON — наиболее универсальный способ обмена состоянием между временными контейнерами; постоянные ядра устраняют затраты на повторную загрузку, сохраняя работающий процесс Python между итерациями; а внедрение контекста состояния даёт LLM точную информацию о доступных файлах и завершённых этапах на каждой итерации. Далее мы создадим полноценного агента анализа данных.
Часто задаваемые вопросы
Урок «Управление состоянием между этапами выполнения» бесплатный?
Да — полный текст урока «Управление состоянием между этапами выполнения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Управление состоянием между этапами выполнения»?
Сохраняйте переменные, таблицы данных и импортированные библиотеки между несколькими этапами выполнения кода, чтобы агент мог использовать предыдущие результаты без повторного выполнения прежних вычи… Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Управление состоянием между этапами выполнения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Цикл выполнения кода
- Изоляция с Docker и RestrictedPython
- Управление состоянием между этапами выполнения
- Создание агента для анализа данных