Używasz ChatGPT codziennie. Wklejasz tekst, dostajesz tekst z powrotem, czujesz się produktywnie. Ale na każdej konferencji technologicznej w 2026 roku pada jedno słowo: agenty. Twój PM mówi 'potrzebujemy agenta". CTO mówi 'agenty to przyszłość". LinkedIn tonie w esejach o agentach. A ty siedzisz i myślisz: 'Nawet nie wiem, co to znaczy."
I tu jest luka. Chatbot czeka, aż napiszesz, i odpisuje — jak SMSowanie z mądrym kumplem. Agent AI to co innego. Agent ma cel, sam dobiera narzędzia, obsługuje błędy i pracuje dopóki nie skończy zadania albo stwierdzi, że się nie da. Nikt go nie prowadzi za rączkę. To różnica między zadaniem komuś pytania a zatrudnieniem kogoś do wykonania roboty.
Dzisiaj wieczorem zamykasz tę lukę. Zbudujesz prawdziwego agenta — w Pythonie, od zera, bez frameworków — który przeszukuje sieć, analizuje informacje, podejmuje decyzje i zapisuje raport na dysku. Przed snem będziesz rozumieć dokładny wzorzec, na którym działają Claude Code, Codex, Devin i każdy inny produkt agentowy za $200/miesiąc. Sam wzorzec to jakieś 30 linijek.
Co budujemy
Research Agent, który:
- Przyjmuje od ciebie temat
- Przeszukuje internet w poszukiwaniu informacji
- Czyta i analizuje to, co znajduje
- Pisze ustrukturyzowane podsumowanie
- Zapisuje wynik do pliku
To nie jest zabawka. To ta sama architektura, co w produkcyjnych agentach — tool use, pętle rozumowania, strukturalny output. Jedyna różnica między tym a 'produkcyjnym agentem" to obsługa błędów i skala.
Krok 1: Przygotuj projekt (10 minut)
mkdir research-agent && cd research-agent
python3 -m venv venv
source venv/bin/activate
pip install anthropic httpx
Dwie zależności:
- anthropic — SDK Pythona (Software Development Kit — gotowa biblioteka do komunikacji z API Claude'a)
- httpx — do wykonywania zapytań HTTP z Pythona
Potrzebujesz jeszcze klucza API Anthropic — w zasadzie hasła, które pozwala twojemu kodowi rozmawiać z Claude'em. Zdobędziesz go na console.anthropic.com. Nowe konta dostają $5 darmowych kredytów, co wystarczy na setki uruchomień tego agenta.
export ANTHROPIC_API_KEY=sk-ant-...
touch agent.py
Krok 2: Zdefiniuj narzędzia (15 minut)
Agent bez narzędzi to po prostu chatbot. Narzędzia to funkcje, które pozwalają agentowi wchodzić w interakcję ze światem — przeszukiwać sieć, czytać pliki, wywoływać API (sposób, w jaki programy krzyczą do siebie przez internet — coś jak SMSowanie maszyna-maszyna).
Naszemu agentowi damy dwa narzędzia:
# agent.py
import anthropic
import httpx
import json
import os
from datetime import datetime
client = anthropic.Anthropic()
MODEL = "claude-haiku-4.5"
# Definicje narzędzi — mówią Claude'owi, co ma do dyspozycji
tools = [
{
"name": "web_search",
"description": "Search the web for information on a topic. Returns results with titles, URLs, and snippets.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
},
{
"name": "save_file",
"description": "Save text content to a file on disk.",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "Name of the file to save"
},
"content": {
"type": "string",
"description": "Content to write to the file"
}
},
"required": ["filename", "content"]
}
}
]
Te definicje działają jak menu w restauracji. Claude czyta opisy i sam decyduje, kiedy użyć którego narzędzia — nie wpisujesz kolejności na sztywno. Część input_schema używa JSON Schema — standardowego formatu do opisywania struktury danych, żeby Claude wiedział, jakie parametry przyjmuje każde narzędzie. Tak, opisujesz format danych za pomocą kolejnego formatu danych. Witamy w programowaniu.
Krok 3: Zaimplementuj narzędzia (15 minut)
Definicje mówią Claude'owi, co istnieje. Teraz piszemy kod, który faktycznie się uruchomi, gdy Claude wywoła narzędzie. Tu guma spotyka się z asfaltem — a dokładniej, tu twoje piękne abstrakcje spotykają się z brzydką rzeczywistością parsowania HTML jak w 2003:
def execute_tool(tool_name: str, tool_input: dict) -> str:
"""Execute a tool and return the result as a string."""
if tool_name == "web_search":
return do_web_search(tool_input["query"])
elif tool_name == "save_file":
return do_save_file(tool_input["filename"], tool_input["content"])
else:
return f"Error: Unknown tool '{tool_name}'"
def do_web_search(query: str) -> str:
"""Search using DuckDuckGo's HTML endpoint. No API key needed."""
try:
response = httpx.get(
"https://html.duckduckgo.com/html/",
params={"q": query},
headers={"User-Agent": "ResearchAgent/1.0"},
timeout=10.0,
)
response.raise_for_status()
text = response.text
results = []
parts = text.split('class="result__snippet"')
for part in parts[1:6]: # Grab up to 5 results
snippet_end = part.find("</a>")
if snippet_end > 0:
snippet = part[:snippet_end]
clean = snippet.replace("<b>", "").replace("</b>", "")
clean = clean.split(">")[-1] if ">" in clean else clean
if clean.strip():
results.append(clean.strip())
if results:
return "Search results:\n" + "\n".join(
f"- {r}" for r in results
)
return f"Search completed but no clear results for: {query}"
except Exception as e:
return f"Search error: {str(e)}"
def do_save_file(filename: str, content: str) -> str:
"""Save content to the output directory."""
os.makedirs("output", exist_ok=True)
filepath = os.path.join("output", filename)
try:
with open(filepath, "w") as f:
f.write(content)
return f"File saved successfully: {filepath}"
except Exception as e:
return f"Error saving file: {str(e)}"
Wyszukiwanie korzysta z HTMLowego endpointu DuckDuckGo — bez klucza API, bez rejestracji, za darmo. Parsowanie HTML trzyma się na taśmie klejącej i optymizmie (skrobiemy surowy markup strony zamiast używać porządnego feeda danych), ale działa. Na produkcji podmieniłbyś to na Brave Search API (2000 darmowych zapytań/miesiąc) albo self-hosted SearXNG.
Krok 4: Zbuduj pętlę agenta (20 minut)
To serce całości. Każdy produkt agentowy z ładnym landing page'em i wyceną na $50M uruchamia jakąś wersję tych 30 linijek:
def run_agent(topic: str, max_turns: int = 10) -> str:
"""Run the research agent on a topic."""
print(f"\n{'='*60}")
print(f"Research Agent — Topic: {topic}")
print(f"{'='*60}\n")
system_prompt = """You are a research agent. Your job is to research a topic
thoroughly and produce a well-structured summary.
Your process:
1. Search for information on the topic (multiple searches with different angles)
2. Analyze what you find
3. Write a comprehensive research summary
4. Save the summary to a file
Be thorough — do at least 3 different searches to cover the topic well.
Be critical — evaluate sources and note conflicting information.
When done, save the final summary as a markdown file.
Current date: """ + datetime.now().strftime("%Y-%m-%d")
messages = [
{
"role": "user",
"content": f"Research this topic and produce a detailed summary: {topic}"
}
]
# The agent loop
for turn in range(max_turns):
print(f"--- Turn {turn + 1} ---")
response = client.messages.create(
model=MODEL,
max_tokens=4096,
system=system_prompt,
tools=tools,
messages=messages,
)
print(f"Stop reason: {response.stop_reason}")
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_id = block.id
print(f" Tool: {tool_name}")
print(f" Input: {json.dumps(tool_input, indent=2)[:200]}")
result = execute_tool(tool_name, tool_input)
print(f" Result: {result[:200]}...")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_id,
"content": result,
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
elif response.stop_reason == "end_turn":
final_text = ""
for block in response.content:
if hasattr(block, "text"):
final_text += block.text
print(f"\nAgent completed in {turn + 1} turns.")
return final_text
return "Agent did not complete within turn limit."
Rozbijmy tę pętlę, bo to cała sztuczka magiczna:
- Wyślij zadanie do Claude'a wraz z definicjami narzędzi
- Claude myśli i decyduje, czy użyć narzędzia, czy odpowiedzieć końcowym tekstem
- Jeśli tool_use — uruchamiamy narzędzie i odsyłamy wynik jako nową wiadomość
- Claude widzi wynik i decyduje o następnym ruchu
- Powtarzaj aż Claude zwróci
end_turn— czyli skończył
Kluczowy insight: nie zakodowałeś na sztywno 'najpierw szukaj, potem analizuj, potem pisz". Claude sam ustala workflow na podstawie zadania. To właśnie odróżnia agenta od skryptu. Skrypt wykonuje twoje instrukcje. Agent wykonuje swoje własne.
Pole stop_reason jest kluczowe. API Claude'a zwraca albo "tool_use" (chcę wywołać narzędzie) albo "end_turn" (skończyłem). Twoja pętla po prostu sprawdza, które z nich, i reaguje odpowiednio.
Krok 5: Dodaj punkt wejścia (5 minut)
Nudna część. Ale nawet nudne części muszą istnieć, bo inaczej nic nie zadziała — lekcja, której połowa repozytoriów z demami AI na GitHubie wciąż nie odrobiła:
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
topic = " ".join(sys.argv[1:])
else:
topic = input("Enter research topic: ")
result = run_agent(topic)
print(f"\n{'='*60}")
print("Research complete. Check the output/ directory.")
print(f"{'='*60}")
Krok 6: Uruchom (5 minut)
python agent.py "current state of MCP protocol adoption in 2026"
Obserwuj terminal. Zobaczysz, jak agent sam przechodzi przez problem:
============================================================
Research Agent — Topic: current state of MCP protocol adoption in 2026
============================================================
--- Turn 1 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP model context protocol adoption 2026"}
Result: Search results: - The MCP ecosystem has grown...
--- Turn 2 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP servers enterprise production 2026"}
Result: Search results: - Amazon Bedrock AgentCore...
--- Turn 3 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP protocol limitations challenges 2026"}
Result: Search results: - Stateful sessions fight with...
--- Turn 4 ---
Stop reason: tool_use
Tool: save_file
Input: {"filename": "mcp-research-2026.md", "content": "# MCP Protocol..."}
Result: File saved successfully: output/mcp-research-2026.md
--- Turn 5 ---
Stop reason: end_turn
Agent completed in 5 turns.
Pięć tur. Trzy wyszukiwania, jeden zapis pliku, jedno końcowe podsumowanie. Nikt mu nie kazał szukać z różnych kątów — sam tak postanowił. To jest agencja, nie skryptowanie.
Krok 7: Uczyń go mądrzejszym (30 minut)
Podstawowy agent działa. Teraz dodajmy trzy ulepszenia, które zmienią go z dema w coś, czego faktycznie będziesz używać.
Pamięć między sesjami
Teraz każde uruchomienie zaczyna od zera. Dajmy agentowi prostą pamięć — plik JSON (ustrukturyzowany format tekstowy, który programy mogą łatwo czytać i zapisywać), przechowujący wcześniejsze tematy badań:
from pathlib import Path
MEMORY_FILE = "memory.json"
def load_memory() -> list:
"""Load previous research topics and findings."""
if Path(MEMORY_FILE).exists():
with open(MEMORY_FILE) as f:
return json.load(f)
return []
def save_memory(topic: str, summary: str):
"""Save this research session to memory."""
memory = load_memory()
memory.append({
"date": datetime.now().isoformat(),
"topic": topic,
"summary": summary[:500],
})
memory = memory[-20:] # Keep last 20 entries
with open(MEMORY_FILE, "w") as f:
json.dump(memory, f, indent=2)
Wstrzyknij pamięć do system promptu — tekstu instrukcji, który kształtuje zachowanie Claude'a:
memory = load_memory()
if memory:
memory_context = "\n\nPrevious research sessions:\n"
for m in memory[-5:]:
memory_context += f"- [{m['date'][:10]}] {m['topic']}: {m['summary'][:100]}...\n"
system_prompt += memory_context
Teraz agent wie, co badał wcześniej. Może odwoływać się do poprzednich wyników, unikać duplikujących się wyszukiwań i budować na wcześniejszej pracy.
Narzędzie do myślenia
To jest trik. Dodaj narzędzie, które nie robi dosłownie nic:
tools.append({
"name": "think",
"description": "Use this tool to think through your approach before acting. Write out your reasoning and what you need to find out next.",
"input_schema": {
"type": "object",
"properties": {
"thought": {
"type": "string",
"description": "Your reasoning and plan"
}
},
"required": ["thought"]
}
})
W executorze narzędzi po prostu zwraca potwierdzenie:
elif tool_name == "think":
print(f" Thinking: {tool_input['thought'][:300]}")
return "Thought recorded. Continue with your plan."
Po co dodawać narzędzie, które nic nie robi? Bo daje agentowi ustrukturyzowane miejsce do rozumowania zanim zacznie działać. Bez niego Claude od razu skacze do wywołań narzędzi. Z nim — zatrzymuje się, planuje i dopiero wykonuje — dając zauważalnie lepsze wyniki. Anthropic dokumentuje tę technikę w swoim przewodniku po tool use, a produkcyjne agenty na tym polegają.
Odporność na błędy
def execute_tool_safe(tool_name: str, tool_input: dict) -> str:
"""Execute a tool with automatic retries."""
for attempt in range(3):
try:
result = execute_tool(tool_name, tool_input)
if "error" in result.lower() and attempt < 2:
print(f" Retry {attempt + 1}...")
continue
return result
except Exception as e:
if attempt < 2:
print(f" Error, retrying: {e}")
continue
return f"Tool failed after 3 attempts: {str(e)}"
Zapytania HTTP padają. API leżą. Timeouty się zdarzają. Produkcyjny agent ponawia próby zanim się podda. Trzy podejścia z fallbackiem to absolutne minimum.
Finalna struktura
research-agent/
├── agent.py # ~150 linii Pythona
├── memory.json # Tworzony automatycznie, przechowuje historię sesji
├── output/ # Tworzony automatycznie, przechowuje raporty
│ └── *.md
└── requirements.txt # anthropic, httpx
Poniżej 200 linii. Dwie zależności. Zero frameworków.
Dlaczego bez frameworka?
Możesz się zastanawiać: 'Czemu nie użyć LangChain albo LlamaIndex?" (Oba to popularne frameworki Pythona, które dodają gotowe abstrakcje wokół wywołań LLM.)
Bo pętla agenta powyżej to 30 linii. LangChain dodałby 15 zależności i trzy warstwy abstrakcji na ten sam rezultat.
Użyj frameworka, gdy:
- Potrzebujesz 10+ narzędzi ze skomplikowaną logiką routingu
- Potrzebujesz pamięci konwersacji skalującej się na tysiące użytkowników
- Potrzebujesz wielu agentów koordynujących to samo zadanie
- Przerosłeś prosty Python i potrzebujesz czyjejś architektury
Odpuść framework, gdy:
- Budujesz swojego pierwszego agenta
- Twój agent ma 2–5 narzędzi
- Chcesz rozumieć każdą linijkę tego, co się uruchamia
- 'Działa i rozumiem to" bije 'działa i ufam abstrakcji"
Stan na marzec 2026 — dokumentacja Anthropic SDK pokazuje ten sam surowy wzorzec pętli, który właśnie zbudowaliśmy. Oficjalna rekomendacja to startować bez frameworka.
Co zbudowałeś dziś wieczorem
Zróbmy inwentaryzację:
- Działający agent AI — przyjmuje cel, realizuje go autonomicznie
- Tool use — agent wywołuje zewnętrzne systemy (wyszukiwarka, plikowy I/O)
- Pętla rozumowania — Claude decyduje o kolejnej akcji na podstawie wyników, a nie na sztywno zakodowanego skryptu
- Pamięć — agent pamięta poprzednie sesje i buduje na nich
- Obsługa błędów — ponawia próby zanim się podda
- Persystencja wyników — rezultaty lądują w prawdziwych plikach na twoim dysku
To ta sama podstawowa architektura co Claude Code, Devin, OpenAI Codex i każdy inny produkt agentowy. Mają lepsze narzędzia, więcej obsługi błędów i większe okna kontekstu — ilość tekstu, którą model 'widzi" naraz, jak jego pamięć robocza. Ale pętla jest identyczna z tym, co właśnie napisałeś.
Dokąd dalej
Rozumiesz już fundamentalny wzorzec. Wszystko inne to inżynieria nakładana na wierzch:
- Więcej narzędzi — kalkulator, web scraper, łącznik do bazy danych, executor kodu
- Lepsza pamięć — wektorowe bazy danych (systemy przechowujące tekst według znaczenia, nie tylko słów kluczowych) do semantycznego przeszukiwania poprzednich sesji
- Równoległe wywołania narzędzi — uruchamiaj wiele wyszukiwań naraz zamiast sekwencyjnie
- Systemy wielu agentów — drugi agent sprawdza pracę pierwszego, jak code review
- Integracja MCP — Model Context Protocol, standard łączenia agentów AI z zewnętrznymi narzędziami, jak USB, ale dla źródeł danych
Nie uczysz się czyjegoś frameworka, który za pół roku może nie istnieć. Uczysz się wzorca. Tego samego wzorca, który działał w 2024, działa w 2026 i będzie działał w 2028 — bo podstawowa mechanika (model decyduje → narzędzie wykonuje → wynik wraca) jest tym, jak działają wszystkie systemy agentowe, niezależnie od tego, jaką marketingową nazwę noszą.
Branża 'agentów AI" chce, żebyś uwierzył, że budowanie agentów wymaga doktoratu i rundy finansowania na $100M. Wymaga zrozumienia jednej pętli: wywołaj model, sprawdź czy chce narzędzia, uruchom narzędzie, odeślij wynik, powtórz. To tyle. Cała reszta to inżynieria wokół tej pętli — a teraz wiesz wystarczająco, żeby tę inżynierię robić samodzielnie.





