Skip to content
Wróć do bloga
Open Source

Predykcyjne AI: Daj swojemu agentowi laboratorium Docker do uruchamiania modeli

Vstorm · · 8 min czytania
Spis treści

Każde demo agenta AI pokazuje to samo: “zapytaj swojego agenta o dane.” Agent odpytuje bazę danych, podsumowuje wyniki, może tworzy wykres.

Ale spróbuj poprosić go o uruchomienie regresji wielomianowej na 24 miesiącach danych sprzedażowych i prognozę na następne 6 miesięcy. Nagle twój agent oparty na chacie uderza w ścianę. Nie może zrobić import sklearn. Nie może wykonać Pythona. Może rozumować o tym, jakiego modelu użyć, ale nie może go faktycznie uruchomić.

Oczywista poprawka: daj mu środowisko Pythona. Ale jest pytanie, o którym nikt nie mówi - czy to środowisko powinno być domyślnym runtimem agenta (jak Claude Code), czy raczej narzędziem, które agent wybiera, kiedy chce?

Zbudowaliśmy demo, które odpowiada na to pytanie.

TL;DR

  • Wzorzec “environment as a tool” pozwala agentowi AI selektywnie używać sandboxa Docker tylko wtedy, gdy jest potrzebny, unikając narzutu przy prostych zapytaniach.
  • Podejście delegacji do sub-agenta utrzymuje głównego agenta w czystości - opisuje co przewidzieć, a sub-agent wymyśla jak napisać kod Pythona.
  • Strukturalne wyjście Pydantic do wykresów bije generowanie obrazów - wyślij dane, pozwól frontendowi renderować z Chart.js.
  • Strumieniowanie WebSocket z agent.iter() daje wgląd w czasie rzeczywistym w tekst, wywołania narzędzi i wyniki.
  • Najtrudniejsza część to integracja frontendowa - łączenie serii wykresów z różnymi zakresami dat, przechwytywanie wyjść narzędzi i strumieniowanie przez WebSocket.

Demo: Agent do analityki predykcyjnej

Zbudowaliśmy pełnowymiarową aplikację demo - asystenta analitycznego opartego na chacie, który potrafi:

  1. Odpytywać dane - filtrować i agregować miesięczne dane sprzedażowe (3 produkty, 3 regiony, 24 miesiące)
  2. Uruchamiać predykcje - sub-agent pisze i wykonuje Python (sklearn, pandas) wewnątrz izolowanego kontenera Docker
  3. Generować wykresy - strukturalne wyjście Pydantic renderowane jako wykresy liniowe Chart.js w przeglądarce

Główny agent ma trzy narzędzia. Dwa są proste (zapytaj JSON, zwróć dane wykresu). Trzecie jest interesujące - uruchamia sub-agenta, który ma pełny dostęp do sandboxa Docker.

Architektura: Środowisko jako narzędzie

Oto kluczowa decyzja projektowa: sandbox Docker jest narzędziem, a nie domyślnym środowiskiem agenta.

analytics_agent: Agent[AnalyticsDeps, str] = Agent(
"openai:gpt-4.1",
deps_type=AnalyticsDeps,
)

Główny agent to zwykły agent Pydantic AI. Nie żyje wewnątrz Dockera. Ma trzy narzędzia zarejestrowane z @analytics_agent.tool:

  • query_data - czyta plik JSON, filtruje rekordy, zwraca wyniki. Docker nie jest potrzebny.
  • predict - tworzy sub-agenta z dostępem do Dockera, deleguje zadanie predykcji.
  • generate_chart - zwraca strukturalny LineChartData (model Pydantic), który frontend renderuje jako wykres Chart.js.

Agent decyduje kiedy użyć Dockera. Jeśli zapytasz “pokaż mi łączną sprzedaż według produktu”, wywołuje query_data - szybko, bez narzutu. Jeśli zapytasz “przewidź sprzedaż Widget Alpha na następne 6 miesięcy”, wywołuje predict - który uruchamia sub-agenta wewnątrz Dockera.

Narzędzie Predict: Sub-agent z laboratorium Docker

To jest główny wzorzec. Narzędzie predict nie wykonuje kodu samo - deleguje do sub-agenta, który ma pełny dostęp do kontenera Docker:

@analytics_agent.tool
async def predict(
ctx: RunContext[AnalyticsDeps],
task_description: str,
) -> str:
"""Run a prediction using Python in a Docker sandbox."""
sandbox = ctx.deps.sandbox
# Write sales data into the Docker container
sandbox.write("/workspace/sales_data.json", data_content)
# Create a sub-agent with Docker tools
console_toolset = create_console_toolset(
include_execute=True,
require_write_approval=False,
require_execute_approval=False,
)
sub_agent: Agent[SandboxDeps, str] = Agent(
"openai:gpt-4.1",
system_prompt="You are a data science code executor...",
deps_type=SandboxDeps,
toolsets=[console_toolset],
)
result = await sub_agent.run(
f"Perform this prediction task:\n\n{task_description}",
deps=SandboxDeps(backend=sandbox),
)
return result.output

Co dzieje się krok po kroku:

  1. Główny agent otrzymuje: “Przewidź sprzedaż Widget Alpha na następne 6 miesięcy”
  2. Główny agent wywołuje predict(task_description="...")
  3. Dane sprzedażowe są zapisywane do kontenera Docker pod /workspace/sales_data.json
  4. Tworzony jest nowy sub-agent z create_console_toolset() - dający mu ls, read, write, execute i inne operacje plikowe
  5. Sub-agent pisze skrypt Pythona używając pandas + sklearn
  6. Sub-agent wykonuje skrypt wewnątrz Dockera
  7. Wyniki płyną z powrotem do głównego agenta, który wyjaśnia je użytkownikowi

Sub-agent nie ma pojęcia, że jest sub-agentem. Widzi tylko system prompt mówiący “jesteś wykonawcą kodu data science” i narzędzia do czytania/pisania/wykonywania plików. Sandbox Docker jest kompletnie przezroczysty.

Strukturalne wykresy z Pydantic

Trzecie narzędzie - generate_chart - demonstruje strukturalne wyjście. Zamiast zwracać surowy tekst, zwraca model Pydantic:

class DataPoint(BaseModel):
x: str # e.g. "2024-01"
y: float
class ChartSeries(BaseModel):
name: str
data_points: list[DataPoint]
class LineChartData(BaseModel):
title: str
x_label: str
y_label: str
series: list[ChartSeries]

Narzędzie generate_chart pobiera parametry wykresu od LLM i zwraca zserializowany LineChartData ze specjalnym prefiksem (CHART_DATA:). Serwer przechwytuje ten prefiks w strumieniu WebSocket i wysyła go do frontendu jako wiadomość chart_data:

if result_str.startswith(CHART_DATA_PREFIX):
chart_json = result_str[len(CHART_DATA_PREFIX):]
await websocket.send_json(
{"type": "chart_data", "data": json.loads(chart_json)}
)

Frontend odbiera to i renderuje z Chart.js. Żadnych obrazów, żadnego base64, żadnego matplotlib - tylko strukturalne dane płynące od agenta do przeglądarki.

Pytanie o środowisko: Narzędzie vs. Domyślne

To jest pytanie projektowe, które wspomniałem na początku. Są dwa sposoby na danie agentowi środowiska do wykonywania kodu:

Opcja A: Środowisko jako narzędzie (co zbudowaliśmy) Agent żyje poza Dockerem. Ma narzędzie predict, które deleguje do sub-agenta wewnątrz Dockera. Agent decyduje kiedy go użyć.

Opcja B: Domyślne środowisko (jak Claude Code) Agent żyje wewnątrz Dockera. Każda komenda, którą uruchamia, każdy plik, który czyta - wszystko jest w sandboxie. Środowisko jest zawsze dostępne.

Kiedy każde z nich ma sens:

Środowisko jako narzędzieDomyślne środowisko
Najlepsze doZadania domenowe (predykcje, analiza danych, code review)Agenci do kodowania ogólnego przeznaczenia
Kontrola agentaAgent decyduje kiedy użyć sandboxaAgent zawsze działa w sandboxie
NarzutPłaci koszt Dockera tylko gdy potrzebnyZawsze uruchomiony
ElastycznośćMoże swobodnie mieszać narzędziaWszystko przechodzi przez sandbox
ZłożonośćWymaga wzorca delegacji sub-agentaProstsze - agent po prostu ma narzędzia

Dla naszego demo analityki predykcyjnej, Opcja A jest wyraźnie właściwa. Agent głównie odpowiada na pytania o dane (Docker nie jest potrzebny) i uruchamia Dockera tylko gdy musi wykonać kod sklearn. Uczynienie Dockera domyślnym środowiskiem dodałoby niepotrzebne opóźnienie do każdej interakcji.

Ale dla agenta kodującego jak Claude Code, Opcja B ma sens - całym zadaniem agenta jest czytanie, pisanie i wykonywanie kodu. Środowisko jest produktem.

Realne wyniki

Oto co demo faktycznie produkuje. Zapytaj go o “analizę sezonowych wzorców Widget Beta i predykcję na następne 12 miesięcy”:

Sub-agent wybrał wygładzanie wykładnicze Holta-Wintersa (odpowiednie dla danych sezonowych), uruchomił je wewnątrz Dockera i zwrócił strukturalne predykcje. Główny agent następnie wywołał generate_chart z danymi historycznymi i prognozowanymi jako osobne serie.

Cały przepływ - od wiadomości użytkownika do wyrenderowanego wykresu - odbywa się przez pojedyncze połączenie WebSocket ze strumieniowaniem w czasie rzeczywistym tekstu, wywołań narzędzi i danych wykresów.

Protokół strumieniowania WebSocket

Serwer używa agent.iter() z Pydantic AI do strumieniowania w czasie rzeczywistym. Każdy token modelu, wywołanie narzędzia i wynik narzędzia jest strumieniowany do frontendu:

async with analytics_agent.iter(
user_message, deps=deps, message_history=message_history,
) as run:
async for node in run:
if Agent.is_model_request_node(node):
# Stream text deltas and tool call deltas
async with node.stream(run.ctx) as stream:
async for event in stream:
if isinstance(event, PartDeltaEvent):
if isinstance(event.delta, TextPartDelta):
await ws.send_json({
"type": "text_delta",
"content": event.delta.content_delta
})
elif Agent.is_call_tools_node(node):
# Stream tool execution events
...

Frontend pokazuje karty narzędzi, które rozwijają się, żeby pokazać argumenty i wyniki, tekst strumieniowany token po tokenie i wykresy renderowane inline - wszystko przez jeden WebSocket.

Jeden gotcha: Chart.js multi-serie z różnymi zakresami

Natrafiliśmy na ciekawy bug podczas developmentu. Przy rysowaniu “Historical” (2024-01 do 2025-12) obok “Forecast” (2026-01 do 2026-06), Chart.js używał tylko etykiet z pierwszej serii. Punkty prognozy były mapowane na historyczne daty.

Rozwiązanie: połącz wszystkie unikalne etykiety x ze wszystkich serii, a potem użyj mapy lookup per seria z null dla brakujących dat:

const allLabels = [...new Set(
chartData.series.flatMap((s) => s.data_points.map((dp) => dp.x))
)].sort();
const datasets = chartData.series.map((s, i) => {
const lookup = new Map(s.data_points.map((dp) => [dp.x, dp.y]));
return {
label: s.name,
data: allLabels.map((x) => lookup.get(x) ?? null),
spanGaps: false,
// ...styling
};
});

Mała rzecz, ale to ten rodzaj buga, który sprawia, że twoja prognoza wygląda kompletnie źle, podczas gdy dane są faktycznie poprawne.

Kluczowe wnioski

  • “Środowisko jako narzędzie” to właściwy wzorzec, gdy twój agent tylko czasami potrzebuje wykonania kodu. Nie płać narzutu Dockera przy każdej interakcji.
  • Delegacja do sub-agenta utrzymuje głównego agenta w czystości. Główny agent opisuje co przewidzieć. Sub-agent wymyśla jak napisać kod Pythona.
  • Strukturalne wyjście Pydantic do wykresów bije generowanie obrazów. Wyślij dane, pozwól frontendowi renderować. Łatwiejsze do stylowania, interaktywne i bez blobów base64.
  • Strumieniowanie WebSocket z agent.iter() daje wgląd w czasie rzeczywistym w to, co agent robi - tekst, wywołania narzędzi i wyniki.
  • Najtrudniejsza część to nie agent - to integracja frontendowa (łączenie serii wykresów z różnymi zakresami dat, przechwytywanie wyjść narzędzi, strumieniowanie przez WebSocket).

Wypróbuj sam

pydantic-ai-backend - sandbox Docker, console toolset i abstrakcje backendowe dla agentów Pydantic AI

Pełne demo jest w examples/predictive_analytics/:

Terminal window
pip install pydantic-ai-backend[docker,console]
export OPENAI_API_KEY=your-key
uvicorn examples.predictive_analytics.server:app --port 8000
Udostępnij artykuł

Powiązane artykuły

Gotowy, żeby wdrożyć swoją aplikację AI?

Wybierz frameworki, wygeneruj projekt gotowy do produkcji i wdróż. 75+ opcji, jedna komenda, zero długu konfiguracyjnego.

Potrzebujesz pomocy przy budowie agentów AI?