AI Engineering Academy · Lekcja

Zarządzanie stanem między krokami wykonywania

Zachowuj zmienne, ramki danych i zaimportowane biblioteki między kolejnymi krokami wykonywania kodu, aby agent mógł korzystać z wcześniejszych wyników bez ponownego uruchamiania obliczeń.

Lekcja 3 z 413 kroki

Zarządzanie stanem między krokami wykonywania to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Problem bezstanowości

Każde wykonanie kontenera Docker rozpoczyna pracę z nowym interpreterem Pythona. Zmienne zdefiniowane w iteracji 1 nie istnieją w iteracji 2. Ta bezstanowość zmusza agenta do ponownego obliczania lub wczytywania wszystkiego od początku przy każdym etapie wykonywania — chyba że zostanie wdrożona jawna strategia zarządzania stanem, która zachowuje i odtwarza stan między iteracjami. Bez niej analizy wieloetapowe są niemożliwe.

# 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!

Trwałe zapisywanie stanu w plikach

Najprostszym i najbardziej przenośnym podejściem jest zapisywanie stanu w plikach. Na końcu każdego bloku kodu agent zapisuje ramki danych, słowniki lub inne obiekty w plikach w katalogu przestrzeni roboczej. W następnej iteracji wczytuje je ponownie. Format Parquet idealnie nadaje się do ramek danych, JSON do słowników, a pickle do dowolnych obiektów Pythona (choć pickle pochodzący z niezaufanego kodu stanowi zagrożenie bezpieczeństwa).

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)

Uczenie agenta zapisywania stanu w plikach

Nie można po prostu zaimplementować stanu opartego na plikach w infrastrukturze — LLM musi również znać tę konwencję i konsekwentnie jej używać. W wiadomości systemowej należy umieścić wyraźne instrukcje: po obliczeniu wyniku, który będzie potrzebny później, zawsze zapisuj go pod opisową nazwą pliku, a na początku każdej iteracji zawsze wczytuj pliki utworzone w poprzednich krokach, zanim przejdziesz dalej.

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/'))
'''

Stan oparty na jądrze z trwałymi procesami Pythona

Alternatywą dla stanu opartego na plikach jest utrzymywanie trwałego procesu Pythona (takiego jak jądro Jupyter) między iteracjami i wstrzykiwanie do niego każdego bloku kodu za pomocą exec(). Zmienne zdefiniowane w jednej iteracji pozostają dostępne w następnej. To podejście jest szybsze i bardziej naturalne, ale wymaga uruchomienia trwałego procesu dla każdej sesji agenta zamiast kontenerów efemerycznych.

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()

Zarządzanie stanem za pomocą obiektu stanu

W warstwie orkiestratora agenta (poza piaskownicą) należy utrzymywać jawny obiekt stanu, który śledzi bieżący status pracy agenta: utworzone pliki, wykonane obliczenia, aktualny krok oraz cel końcowy. W każdej iteracji należy przekazywać LLM podsumowanie tego obiektu stanu, aby agent zawsze wiedział, na jakim etapie całego zadania się znajduje.

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

Wstrzykiwanie kontekstu stanu do promptów

Za każdym razem, gdy wywołujesz LLM w celu wygenerowania kolejnego bloku kodu, wstrzyknij bieżący kontekst stanu do wiadomości użytkownika. Dzięki temu LLM otrzymuje dokładne informacje o dostępnych plikach, wykonanych obliczeniach i zadaniach, które nadal trzeba wykonać. Bez tego kontekstu LLM może próbować odtworzyć już istniejące pliki albo pominąć kroki, które zostały już ukończone.

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)

Obsługa dużych danych pośrednich

Agenci do analizy danych często tworzą duże zbiory danych pośrednich, których ponowne wczytywanie w każdej iteracji jest kosztowne. Należy stosować leniwe ładowanie: wczytywać tylko dane potrzebne w bieżącym kroku. Warto używać formatów kolumnowych, takich jak Parquet, które obsługują wydajne odczyty wybranych kolumn. W przypadku naprawdę dużych zbiorów danych (100 MB lub więcej) należy utrzymywać trwałe jądro, aby ramka danych pozostawała w pamięci między iteracjami, zamiast za każdym razem ją serializować i deserializować.

# 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

Punkty kontrolne dla zadań długotrwałych

W przypadku zadań obejmujących wiele iteracji należy wdrożyć zapisywanie punktów kontrolnych: okresowo zapisywać pełny stan agenta (ukończone kroki, pliki przestrzeni roboczej, bieżący postęp) w trwałym magazynie, aby można było wznowić zadanie po przerwaniu. Jest to szczególnie ważne w przypadku długotrwałych zadań analizy danych, które mogą trwać ponad 30 minut i zostać przerwane przez problemy z siecią, błędy API lub ponowne uruchomienie serwera.

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

Czyszczenie stanu po ukończeniu

W przestrzeniach roboczych agentów gromadzą się pliki, a ich rozmiar może z czasem znacznie wzrosnąć. Zawsze należy wdrożyć etap czyszczenia uruchamiany po ukończeniu lub niepowodzeniu zadania: usuwać pliki pośrednie (cleaned.parquet, tmp_output.csv) i pozostawiać tylko pliki wynikowe, na których zależy użytkownikowi. W przypadku magazynu chmurowego należy ustawić zasady cyklu życia, które automatycznie usuwają pliki przestrzeni roboczej po upływie okresu przechowywania.

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]

Wersjonowanie stanu na potrzeby debugowania

Gdy agent kodu generuje nieprawidłowe wyniki, trzeba prześledzić historię jego wykonania, aby znaleźć przyczynę. Należy wdrożyć wersjonowanie stanu, zapisując migawkę przestrzeni roboczej po każdej iteracji. Umożliwia to odtworzenie wykonania agenta, przeanalizowanie stanów pośrednich i wskazanie dokładnej iteracji, w której wystąpił błąd. Aby oszczędzać miejsce, należy przechowywać tylko różnice (zmienione pliki).

Okno kontekstu a stan zewnętrzny

W projektowaniu agentów kodu występuje fundamentalne napięcie między umieszczaniem stanu w oknie kontekstu LLM (dostępne natychmiast, ale ograniczone) a przechowywaniem go w zewnętrznym magazynie (nieograniczone, ale wymagające jawnego zarządzania). Optymalna strategia to: przechowywać dane bieżącego kroku w kontekście, duże wyniki pośrednie w plikach, cel zadania i plan wysokiego poziomu w kontekście, a surowe dane zawsze na dysku. LLM zarządza zadaniem, a system plików — danymi.

Szybki test

Sprawdź swoją wiedzę na temat zarządzania stanem między kolejnymi etapami wykonywania kodu omówionego w tej lekcji.

Podsumowanie lekcji

W tej lekcji dowiedziałeś się, że: trwałe zapisywanie stanu w plikach z użyciem Parquet i JSON to najbardziej przenośny sposób współdzielenia stanu między efemerycznymi wykonaniami kontenerów; trwałe jądra eliminują koszt ponownego wczytywania, utrzymując aktywny proces Pythona między iteracjami; a wstrzykiwanie kontekstu stanu zapewnia LLM dokładną wiedzę o dostępnych plikach i ukończonych krokach w każdej iteracji. Następnie zbudujemy kompletnego agenta do analizy danych.

Bezpłatny start

Ucz się Python dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
30
Lekcje
120

Często zadawane pytania

Czy lekcja „Zarządzanie stanem między krokami wykonywania” jest bezpłatna?

Tak — pełny tekst „Zarządzanie stanem między krokami wykonywania” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Zarządzanie stanem między krokami wykonywania”?

Zachowuj zmienne, ramki danych i zaimportowane biblioteki między kolejnymi krokami wykonywania kodu, aby agent mógł korzystać z wcześniejszych wyników bez ponownego uruchamiania obliczeń. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Zarządzanie stanem między krokami wykonywania”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Pętla wykonywania kodu
  2. Izolowanie za pomocą Docker i RestrictedPython
  3. Zarządzanie stanem między krokami wykonywania
  4. Budowanie agenta do analizy danych
← Powrót do AI Engineering Academy