Jak zbudować własnego agenta AI w 30 minut — krok po kroku z Claude

Własny agent AI, który szuka informacji, zapisuje raporty i działa autonomicznie — brzmi jak science fiction, ale z API Claude'a to dosłownie 30 minut roboty i kilka dziesiątek linii kodu.

Jak zbudować własnego agenta AI w 30 minut — krok po kroku z Claude
Instrukcja obsługi do asystenta, którego możesz zbudować sam. Śrubokręt niewymagany.

Większość ludzi używa AI jak wyszukiwarki. To błąd.

Wpisujesz pytanie, dostajesz odpowiedź, zamykasz kartę. Tymczasem ta sama technologia potrafi samodzielnie przeszukać internet, przeczytać wyniki, podjąć decyzję co zrobić dalej, zapisać raport — i poinformować cię dopiero gdy skończy. Różnica między chatbotem a agentem AI to różnica między kalkulatorem a arkuszem kalkulacyjnym: oba liczą, ale tylko jeden może sam zaplanować budżet.

W tym artykule budujesz prawdziwego agenta — takiego, który wykonuje narzędzia, zarządza pętlą konwersacji i nie wymaga od ciebie żadnej magii poza standardowym kluczem API. Czas: 30 minut. Wymagania wstępne: podstawy Pythona lub TypeScript i konto w Anthropic Console.

Czym właściwie jest agent AI (i czym nie jest)

Agent AI to program, który w pętli wywołuje model językowy, pozwala mu korzystać z narzędzi (funkcji), i powtarza cykl aż model stwierdzi, że zadanie jest skończone. Kluczowe słowo: pętla. Zwykłe wywołanie API to jedno zapytanie i jedna odpowiedź. Agent to wielokrotna wymiana, gdzie model sam decyduje kiedy i które narzędzie uruchomić.

Konkretnie: wysyłasz zapytanie, model odpowiada z blokiem tool_use zamiast tekstu. Ty wykonujesz to narzędzie lokalnie (np. faktycznie szukasz w internecie), odsyłasz wynik jako tool_result, model przetwarza wynik i albo wywołuje kolejne narzędzie, albo kończy z stop_reason == "end_turn".

To jest cały sekret. Nie ma tu żadnej czarnej magii — tylko dobrze zaprojektowana pętla while.

Wybór modelu: ile chcesz zapłacić za inteligencję

Anthropic oferuje trzy modele w różnych punktach cenowych (ceny za milion tokenów, stan na czerwiec 2026):

Model ID Input $/1M Output $/1M Kontekst Kiedy używać
Claude Opus 4.8 claude-opus-4-8 $5,00 $25,00 1M tokenów Złożone zadania wieloetapowe, rozumowanie
Claude Sonnet 4.6 claude-sonnet-4-6 $3,00 $15,00 1M tokenów Codzienna robota, dobry balans cena/jakość
Claude Haiku 4.5 claude-haiku-4-5 $1,00 $5,00 200K tokenów Proste zadania, duże wolumeny, testy

Do nauki i prototypowania zacznij od Sonnet 4.6. Jest wystarczająco sprytny do niemal wszystkiego, a różnica w cenie względem Opusa jest pięciokrotna na wejściu i dziesięciokrotna na wyjściu — przy eksperymentowaniu to ma znaczenie. Opus zostawiasz na produkcję, gdzie naprawdę potrzebujesz głębokiego rozumowania.

Setup w 3 minuty

Zacznij od zainstalowania SDK i ustawienia klucza API. Klucz pobierasz z console.anthropic.com — nigdy nie hardkoduj go w kodzie.

Python:

pip install anthropic

TypeScript/Node:

npm install @anthropic-ai/sdk zod

Klucz ustawiasz jako zmienną środowiskową:

export ANTHROPIC_API_KEY=sk-ant-twój-klucz

SDK automatycznie odczyta tę zmienną — nie musisz jej przekazywać jawnie w kodzie. To nie jest kwestia stylu, to kwestia bezpieczeństwa.

Budowa agenta: wersja manualna (żebyś rozumiał co się dzieje)

Zanim użyjesz skrótów, musisz zobaczyć jak pętla działa od środka. Poniższy agent badawczy szuka informacji w sieci i zapisuje wyniki do pliku. Narzędzia są mockami — w produkcji podłączysz prawdziwe API (Brave Search, Serper, Tavily).

import anthropic
import json

client = anthropic.Anthropic()

tools = [
    {
        "name": "web_search",
        "description": "Przeszukaj internet w poszukiwaniu aktualnych informacji. Użyj gdy pytanie dotyczy bieżących danych, cen lub niedawnych wydarzeń.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Zapytanie do wyszukiwarki"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "save_to_file",
        "description": "Zapisz wyniki badań do pliku lokalnego.",
        "input_schema": {
            "type": "object",
            "properties": {
                "filename": {"type": "string"},
                "content": {"type": "string"}
            },
            "required": ["filename", "content"]
        }
    }
]

def execute_tool(name: str, input_data: dict) -> str:
    if name == "web_search":
        # Tu podłączasz prawdziwe API wyszukiwarki
        return f"[Wyniki dla '{input_data['query']}': przykładowe dane]"
    elif name == "save_to_file":
        with open(input_data["filename"], "w", encoding="utf-8") as f:
            f.write(input_data["content"])
        return f"Zapisano do {input_data['filename']}"
    return "Nieznane narzędzie"

def run_agent(user_query: str) -> str:
    messages = [{"role": "user", "content": user_query}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=8000,
            system="Jesteś asystentem badawczym. Szukaj informacji w sieci i zapisuj wyniki do plików gdy użytkownik o to prosi.",
            tools=tools,
            messages=messages
        )
        
        if response.stop_reason == "end_turn":
            return next(b.text for b in response.content if b.type == "text")
        
        if response.stop_reason == "tool_use":
            # WAŻNE: appendujesz cały response.content, nie tylko tekst
            messages.append({"role": "assistant", "content": response.content})
            
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            
            # WSZYSTKIE wyniki w jednej wiadomości — nie rozdzielaj
            messages.append({"role": "user", "content": tool_results})
        else:
            break

    return "Agent zakończył nieoczekiwanie"

result = run_agent("Zbadaj trendy AI z 2025 roku i zapisz podsumowanie do pliku ai_trendy.txt")
print(result)

Trzy błędy, które popełnia każdy na początku

  • Appendowanie tylko tekstu zamiast całego response.content. Bloki tool_use muszą trafić z powrotem do historii wiadomości — bez nich model dostanie orphaned tool_result i zwróci błąd 400.
  • Wysyłanie wyników narzędzi w osobnych wiadomościach. Wszystkie tool_result z jednej rundy muszą być w jednym obiekcie user message.
  • Używanie budget_tokens na Opus 4.8. Ten parametr został usunięty na modelach 4.7 i 4.8 — dostaniesz błąd 400. Zamiast tego użyj thinking: {"type": "adaptive"} bez żadnego budżetu.

Wersja skrócona: Tool Runner automatyzuje pętlę

Jeśli nie potrzebujesz pełnej kontroli nad pętlą, SDK oferuje Tool Runner — wrapper, który obsługuje cały cykl automatycznie. Kod skraca się o połowę:

from anthropic import Anthropic, beta_tool

client = Anthropic()

@beta_tool
def search_web(query: str) -> str:
    """Przeszukaj internet w poszukiwaniu aktualnych informacji.
    
    Args:
        query: Zapytanie do wyszukiwarki.
    """
    return f"Wyniki dla: {query}"

@beta_tool  
def save_file(filename: str, content: str) -> str:
    """Zapisz treść do pliku lokalnego.
    
    Args:
        filename: Nazwa pliku do utworzenia.
        content: Zawartość tekstowa.
    """
    with open(filename, "w", encoding="utf-8") as f:
        f.write(content)
    return f"Zapisano {filename}"

runner = client.beta.messages.tool_runner(
    model="claude-sonnet-4-6",
    max_tokens=8000,
    system="Jesteś asystentem badawczym.",
    tools=[search_web, save_file],
    messages=[{"role": "user", "content": "Zbadaj trendy AI i zapisz raport do report.txt"}]
)

for message in runner:
    for block in message.content:
        if block.type == "text":
            print(block.text)

Dekorator @beta_tool automatycznie generuje input_schema z docstringa i type hintów. Tool Runner sam obsługuje kolejne rundy wymiany z modelem aż do end_turn. Minusem: masz mniejszą kontrolę nad logiką — np. nie możesz przerwać pętli po określonej liczbie kroków bez własnego wrappera.

TypeScript? Też spokojnie

Ekwiwalent w TypeScript z użyciem betaZodTool i biblioteki Zod do walidacji schemy:

import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";

const client = new Anthropic();

const searchWeb = betaZodTool({
  name: "search_web",
  description: "Przeszukaj internet. Użyj gdy pytanie dotyczy aktualnych danych.",
  inputSchema: z.object({
    query: z.string().describe("Zapytanie wyszukiwarki"),
  }),
  run: async ({ query }) => {
    return `Wyniki wyszukiwania dla: ${query}`;
  },
});

const saveFile = betaZodTool({
  name: "save_file",
  description: "Zapisz treść do pliku lokalnego.",
  inputSchema: z.object({
    filename: z.string(),
    content: z.string(),
  }),
  run: async ({ filename, content }) => {
    const fs = await import("fs/promises");
    await fs.writeFile(filename, content, "utf-8");
    return `Zapisano ${filename}`;
  },
});

const finalMessage = await client.beta.messages.toolRunner({
  model: "claude-sonnet-4-6",
  max_tokens: 8000,
  system: "Jesteś asystentem badawczym.",
  tools: [searchWeb, saveFile],
  messages: [{ role: "user", content: "Zbadaj trendy AI i zapisz do report.txt" }],
});

for (const block of finalMessage.content) {
  if (block.type === "text") console.log(block.text);
}

Ile to kosztuje w praktyce

Policzmy konkretnie. Założenie: 10 rozmów z agentem dziennie, każda to średnio 5 000 tokenów na wejściu i 1 000 tokenów na wyjściu (realistyczne dla prostego agenta badawczego).

Bez cache, model Sonnet 4.6:

  • Input: 10 × 5 000 × $3/1M = $0,15/dzień = ~$4,50/miesiąc
  • Output: 10 × 1 000 × $15/1M = $0,15/dzień = ~$4,50/miesiąc
  • Łącznie: ~$9/miesiąc

Z prompt cachingiem (system prompt 4 000 tokenów, powtarzany w każdej rundzie):

  • Pierwsze zapisy cache: 4 000 × $3,75/1M = $0,015 (jednorazowo co 5 minut TTL)
  • Odczyty cache: 4 000 × $0,30/1M = $0,0012 za każde wywołanie
  • Oszczędność na systemowym prompcie: ~90%

Żeby skorzystać z cachingu, dodajesz cache_control do bloku systemowego:

system=[{
    "type": "text",
    "text": "Twój długi system prompt...",
    "cache_control": {"type": "ephemeral"}
}]

Minimalna wielkość prefiksu do cachowania: 2 048 tokenów dla Sonnet 4.6, 4 096 dla Opus 4.8. Przy krótkich system promptach caching nie zadziała — warto o tym wiedzieć zanim zaczniesz się dziwić, że oszczędności nie ma.

Jeśli twój agent przetwarza dane wsadowo (analityka, bulk research, nie wymaga odpowiedzi w czasie rzeczywistym) — Batch API daje 50% zniżki na wszystkie tokeny. Zero zmian w logice, tylko inny endpoint.

Zasady, których złamanie zwróci błąd 400

API Claude'a jest dość restrykcyjne i kilka reguł egzekwuje twardo:

  • Nie używaj jednocześnie temperature i top_p. Na modelach Claude 4.x to zwraca błąd 400 — wybierz jedno albo żadne.
  • Nie używaj budget_tokens na Opus 4.8. Parametr usunięty na serii 4.7/4.8. Dla rozszerzonego myślenia użyj thinking: {"type": "adaptive"}.
  • Nagłówek anthropic-version: 2023-06-01 jest wymagany przy bezpośrednich wywołaniach HTTP. SDK dodaje go automatycznie — martwisz się o to tylko jeśli piszesz własne requesty curl lub fetch.
  • Maksymalnie 128 narzędzi per definicja agenta. Spokojnie wystarczy — jeśli potrzebujesz więcej, to sygnał, że agent robi za dużo rzeczy naraz.
  • Ustrukturyzowane wyjście JSON deklarujesz przez output_config: {format: ...} — parametr output_format jest przestarzały i może być ignorowany.

Co dalej po "Hello World" agenta

Kiedy masz działającą pętlę, naturalne rozszerzenia to:

Prawdziwe narzędzia zamiast mocków

Podłącz Brave Search API (podstawowy plan od $3/miesiąc) albo Tavily (pierwsze 1 000 zapytań miesięcznie gratis). Wymiana mocka na prawdziwe API to podmiana jednej funkcji execute_tool.

Pamięć między sesjami

Domyślnie agent nie pamięta poprzednich rozmów. Najprostsza pamięć: zapisz historię messages do JSONa i ładuj przy starcie. Zaawansowana: wektorowa baza danych (Qdrant, Chroma) z semantic search po przeszłych wątkach.

Multi-agent z handoff

Jeden agent-koordynator wywołuje wyspecjalizowane pod-agenty przez narzędzia. Okno kontekstowe 1M tokenów na Sonnecie 4.6 i Opus 4.8 jest wystarczające na bardzo długie łańcuchy rozumowania — 200K tokenów Haiku 4.5 możesz poczuć przy złożonych zadaniach.

Podsumowanie praktyczne

  • Zbudowanie podstawowego agenta zajmuje mniej czasu niż skonfigurowanie LangChain — natywne API Anthropic jest wystarczająco proste do bezpośredniego użycia.
  • Pętla agenta to trzy kroki: wyślij zapytanie → sprawdź stop_reason → jeśli tool_use, wykonaj narzędzie i wyślij tool_result z powrotem; powtarzaj aż end_turn.
  • Do nauki używaj Sonnet 4.6 ($3/$15 za 1M tokenów) — Opus 4.8 zostawiasz na produkcję gdzie naprawdę potrzebujesz maksymalnej inteligencji.
  • Prompt caching to łatwe 90% oszczędności na powtarzających się system promptach — jeden dodatkowy blok JSON w konfiguracji.
  • Trzy pułapki do zapamiętania: nie mieszaj temperature z top_p, nie używaj budget_tokens na Opus 4.8, appenduj zawsze pełny response.content a nie tylko tekst.
  • Koszt prostego agenta w produkcji to realnie $9-20/miesiąc przy rozsądnym użyciu z cachingiem — nie $200, które straszy większość osób przy pierwszym spojrzeniu na cennik.

Agent AI to nie projekt na kwartał. To 30 minut do pierwszego działającego prototypu i kilka dni do czegoś, co możesz faktycznie używać. Klucz API masz, Python masz, czas startować.

$ udostępnij X in
Piotr Olszewski
Piotr Olszewski

Piszę maistry.pl — AI po polsku, bez ściemy. Codziennie o 18:18.