Odbudowa agenta w stylu Claude Code przy użyciu LangChain Deep Agents
Jak działa system agenta kodującego, od podstawowego pętla po narzędzia, planowanie, pamięć, podagenty oraz zatwierdzenia w izolowanym środowisku, odbudowywany krok po kroku za pomocą DeepAgents.
Źródła: **Build Your Own Claude Code Using Langchin: A Deepdive Into LangChain’s Deep Agents**
Agenct kodujący to nie chatbot
Latwo wyobrazić sobie agenta kodującego jako inteligentniejszego chatbota, ale to wyobrażenie jest błędne. Możliwości Claude Code nie pochodzą wyłącznie od modelu – pochodzą od systemu sterującego stworzonego wokół modelu. Ten system to tzw. płaszczyzna sterowania: niewidzialny mechanizm, który dostarcza modelowi narzędzi do czytania plików, środowisko do uruchamiania kodu, pamięć tego, co już zrobił, oraz listę kontrolną pomagającą utrzymać kierunek działania. Bez tego systemu model językowy jest jedynie generatorem tekstu; z nim ten sam model zachowuje się jak zdolny, autonomiczny inżynier oprogramowania.
Celem jest zbadanie, w jaki sposób działa agent w stylu Claude Code, krok po kroku, oraz odbudowa każdego elementu za pomocą biblioteki deepagents od LangChain. Pod koniec ta struktura powinna stać się zrozumiała, ponieważ sami ją stworzymy.
Pętla w centrum
Sesja rozpoczyna się od wiadomości użytkownika. Może to być szybkie pytanie dotyczące zawartości folderu lub złożone zadanie, takie jak przebudowa systemu logowania i aktualizacja jego testów. Model czyta tę wiadomość przed wyborem dalszych działań, a stamtąd ścieżka rozwidla się. Gdy potrzebna jest akcja, model żąda narzędzia, otrzymuje wynik i wraca do początku – ten cykl jest identyczny zarówno w przypadku zadania składającego się z jednego kroku, jak i z pięćdziesięciu. Gdy model odpisze zwykłym tekstem bez żadnego żądania narzędzia, pętla zostaje przerwana, a użytkownik widzi rezultat. Brak żądania użycia narzędzia jest dokładnie sygnałem, że agent uważa swoją pracę za zakończoną.
Pole „decyduje o następnym kroku” łączy trzy fazy opisane w badaniach nad agentami: zbieranie kontekstu poprzez czytanie plików, działanie poprzez edycję i wykonywanie poleceń oraz weryfikację poprzez przeprowadzanie testów. Nie działają one jako oddzielne tryby – są to po prostu różne wywołania narzędzi realizowane w ramach jednego pętla.
Istnieje jeden celowy zmiany trybu, mianowicie tryb planowania. Zazwyczaj agent planuje i działa jednocześnie. W trybie planowania przed pętlą znajduje się brama decyzyjna. Agent bada sytuację i opracowuje propozycję, nie zmieniając niczego: pliki są czytane, a pomysły rozwijane, ale edycje i polecenia czekają, aż użytkownik da zgodę, dopiero wtedy uruchamia się normalna pętla. Dzięki temu stanowi to zabezpieczenie przy dużych lub ryzykownych zadaniach. Nie jest to to samo co lista zadań, którą agent utrzymuje dla siebie przy każdym zadaniu, ponieważ tryb planowania to pauza inicjowana przez użytkownika i wymagająca jego wyraźnej aprobaty.
Diagram nadal pomija mechanizmy związane z pętlą: kompresję przy wypełnieniu pamięci, podagenty uruchamiające własne prywatne pętle oraz warstwę uprawnień i sandboxu znajdującą się pomiędzy „uruchomieniem narzędzia” a rzeczywistym systemem. Żaden z tych elementów nie modyfikuje pętli – one ją otaczają.
Wewnątrz tego układu znajdują się cztery funkcje, które utrzymują pętlę w stabilności. Planowanie sprawia, że agent najpierw myśli; narzędzia pełnią rolę jego rąk. Zarządzanie kontekstem zapobiega temu, by długotrwałe zadania przepełniały pamięć agenta. Podagenty przenoszą zadania poboczne do swoich prywatnych pętli, dzięki czemu główny wątek pozostaje wolny od zbędnych obciążeń. One zaopatrują pętlę w niezbędne elementy, ale nigdy jej nie przepisują.
Pod tym wszystkim znajduje się warstwa, która ma największe znaczenie. Gdy agent chce wpłynąć na rzeczywisty świat poprzez edycję pliku, uruchomienie polecenia lub korzystanie z sieci, działanie to nie dociera bezpośrednio do maszyny. Najpierw przechodzi przez zasady uprawnień i środowisko sandbox, a żaden inny element bezpieczeństwa w projekcie nie ma tak dużego znaczenia. Wyobraźmy sobie próbę zabezpieczenia agenta poprzez dodanie do jego instrukcji linijki typu „proszę, nie usuwaj niczego ważnego”. To jest jedynie notatka, którą model może zignorować, źle zrozumieć lub o którą można się spierać. Prawdziwa ochrona znajduje się poza modelem: reguła uprawnień, która kategorycznie odrzuca żądanie użycia narzędzia, oraz środowisko sandbox systemu operacyjnego, które zatrzymuje niebezpieczne polecenia bez względu na preferencje modelu.
Sam silnik
Zanim sięgniemy po bibliotekę, przydatne jest zobaczenie silnika bez żadnych dodatków. To kompletny, działający cykl agenta.
def run_agent_loop(client, user_message, tools, tool_functions, max_turns=20):
messages = [{"role": "user", "content": user_message}]
for _ in range(max_turns):
# 1. Ask the model what to do next.
response = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=messages
)
messages = [*messages, {"role": "assistant", "content": response.content}]
# 2. Plain text and no tool request? The job is done.
tool_uses = [b for b in response.content if b.type == "tool_use"]
if not tool_uses:
return "".join(b.text for b in response.content if b.type == "text")
# 3. Run each requested tool and hand the results back. 4. Repeat.
results = [
{"type": "tool_result", "tool_use_id": call.id,
"content": tool_functions[call.name](**call.input)}
for call in tool_uses
]
messages = [*messages, {"role": "user", "content": results}]
raise RuntimeError(f"agent loop did not finish within {max_turns} turns")
Składa się z około trzydziestu linijek, a reszta projektu to ten pętla z dodatkami. Silniejsze narzędzia trafiają do tool_functions. Planista zapisuje notatki w messages. Kompresor przycinają messages, gdy się rozrastają. Podagent to po prostu ta sama funkcja wywołana ponownie z nowym, pustym списkiem messages. Sprawdzenie uprawnień znajduje się pomiędzy modelem żądającym narzędzia a linijką, która je wykonuje. Ograniczenie max_turns to ten element paranoi, który zachowuje każde prawdziwe rozwiązanie, ponieważ model uwięziony w pętli nieustannie wydaje pieniądze.
Każdy agent programistyczny ma to bijące serce – pętlę agenta, która przekształca mówienie w działanie – i jest ona niemal żenująco prosta:
- Zapytaj model o to, co ma zrobić dalej.
- Model albo odpowiada w zwykłym tekście, albo prosi o użycie narzędzia.
Czytanie pliku, wykonywanie polecenia oraz edycja kodu przechodzą przez ten sam cykl. Wyobraź sobie wykonawcę, który otrzymuje zadanie wraz z listą kontrolną: sprawdza, co należy zrobić dalej, wykonuje to, sprawdza wynik, ponownie dokonuje wyboru i zgłasza wyniki dopiero wtedy, gdy lista jest pusta. Pętla pełni rolę osądu tego wykonawcy. Może się również skalować – proste pytanie może wymagać jednego kroku, podczas gdy złożona refaktoryzacja obejmuje dziesiątki wywołań narzędzi, a nikt nie musi z góry ustalać liczby kroków.
Budowanie tego za pomocą Deep Agents
Nie ma potrzeby ręcznego pisania pętli while. Stworzenie Deep Agenta automatycznie dostarcza działającą pętlę. Zacznij od zainstalowania biblioteki oraz dostawcy modeli.
pip install deepagents langchain-anthropic
Następnie utwórz najprostszego możliwego agenta.
from deepagents import create_deep_agent
def get_weather(city: str) -> str:
"""Get the weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[get_weather],
system_prompt="You are a helpful assistant.",
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
)
Ta wywołanie create_deep_agent zwraca agenta, w którym pętla jest już przygotowana, a .invoke() uruchamia dokładnie opisany powyżej cykl. Narzędzie to nie zależy od tego, jaki model znajduje się pod spodem – czy to OpenAI, Google, czy Anthropic. Pętla pozostaje taka sama.
Narzędzia to ręce
Gdzie pętla jest silnikiem, tam narzędzia są rękami. Bez nich model może jedynie mówić. Z dobrymi narzędziami może czytać kod, go modyfikować i sprawdzać wyniki. Prawdziwe narzędzia do programowania dzielą się na cztery rodziny:
- Narzędzia do czytania przeglądają zawartość bez żadnych zmian:
read_fileotwiera plik,globznajduje pliki według wzoru nazwy, na przykład wszystkie pliki.py, agrepszuka w plikach określonego tekstu. Początkujący często mylą te dwa ostatnie narzędzia, aleglobznajduje pliki według nazwy, agrepwedług zawartości.
write_file tworzy nowe pliki, a edit_file modyfikuje część istniejącego pliku.task, przekazuje podzadanie agentowi pomocniczemu.Dlaczego nie pozwolić agentowi używać cat do czytania i sed do edycji? Może to zrobić, ale specjalnie stworzone narzędzia mają przewagę pod dwoma względami. Po pierwsze, chodzi o zarządzanie zasobami: dedykowane narzędzie do czytania może sprawdzić rozmiar pliku przed jego załadowaniem i go skrócić w razie potrzeby, podczas gdy zwykły cat wyświetla całą zawartość, co może przekroczyć limit kontekstu. Po drugie, uprawnienia są bardziej przejrzyste: wywołanie znanej funkcji edit_file informuje system dokładnie o tym, co się dzieje, dzięki czemu może on zastosować reguły bezpieczeństwa, natomiast dowolne polecenie w shellu stanowi nieprzejrzyste i ryzykowne rozwiązanie.
Istnieje tu zabawny detal. Modele zostały nauczone na podstawie publicznej sieci internetowej pełnej odpowiedzi z Stack Overflow, w których używane są cat i sed, więc nawet te zaawansowane z przyzwyczajenia sięgają po surowy shell. Prompty systemowe muszą je stanowczo przypominać, aby używały swoich prawdziwych narzędzi, a nie skrótów shella.
Deep Agents dostarcza pełny zestaw narzędzi wbudowanych: planowanie, narzędzia do systemu plików, wykonywanie zadań i delegowanie. Narzędzia niestandardowe są dodawane jako funkcje ozdobione specjalnymi elementami.
import subprocess
from langchain_core.tools import tool
MAX_OUTPUT_CHARS = 20_000 # roughly 5k tokens
@tool
def run_tests(path: str = ".") -> str:
"""Run the project's pytest suite and return its output.
Output is truncated to the last 20k characters (failures appear at the
end). The run is killed after 5 minutes.
"""
try:
result = subprocess.run(["pytest", path], capture_output=True,
text=True, check=False, timeout=300)
except subprocess.TimeoutExpired:
return "pytest timed out after 300s"
output = result.stdout + result.stderr
if len(output) > MAX_OUTPUT_CHARS:
return ("[... output truncated to the last 20,000 characters ...]\n"
+ output[-MAX_OUTPUT_CHARS:])
return output
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[run_tests], # merged in alongside the built-ins
system_prompt="You are a coding assistant. Always run tests after editing.",
)
Dekorator @tool umożliwia uruchomienie zwykłej funkcji przez model. Jej dokumentacja ma duże znaczenie, ponieważ model traktuje ją jak instrukcję określającą, kiedy i w jaki sposób używać danego narzędzia.
Planowanie przed działaniem
Gdy podasz złożone żądanie prostemu modelowi, ten często działa chaotycznie: jedna wywołanie tu, drugie tam, bez żadnego ogólnego planu, czasami krążąc w kółko. Dobrzy agenci programistyczni unikają tego, opracowując plan przed jakimkolwiek działaniem. Tworzą uporządkowaną listę zadań, która dzieli pracę na jasno określone kroki, a następnie realizują je po kolei, oznaczając każdy element jako w toku lub zakończony. To jak kucharz, który czyta cały przepis przed włączeniem kuchenki, aby nie wymyślać czwartego kroku, gdy trzeci już się przypala.
Pod spodem kryje się sprytny trik inżynieryjny. Podczas długiej sesji pierwotny cel zostaje pogrzebany pod stronami wyników narzędzi, a model traci go z oczu. Aby temu przeciwdziałać, system wstawia aktualną listę z zadaniami z powrotem do rozmowy po wywołaniach narzędzi, tak jak kolega z pracy może przesunąć listę kontrolną z powrotem po biurku jako przypomnienie.
W Deep Agents jest to wbudowane narzędzie write_todos. Nie trzeba nic konfigurować, ponieważ domyślny prompt systemu uczy agenta planowania przed działaniem oraz aktualizowania listy w trakcie całego procesu.
Zarządzanie kontekstem
Tutaj znajduje się najtrudniejsza inżynieria. Każdy model ma okno kontekstowe, czyli ustalony limit pamięci roboczej. Kilka dużych plików w połączeniu z kilkoma poleceniami szybko je wypełniają, a gdy dojdzie do przepełnienia, agent zaczyna pomijać różne elementy, nawet instrukcje, od których zaczął. Agenci programistyczni radzą sobie z tym za pomocą dwóch strategii:
- Pliki pełnią rolę zewnętrznej pamięci. Zamiast wprowadzać ogromny wynik wyszukiwania do czatu, agent zapisuje go na dysku i przechowuje tylko nazwę pliku, otwierając go ponownie, gdy potrzebuje szczegółów.
- Gdy rozmowa zbliża się do limitu, aktywuje się procedura kompresji. System zatrzymuje pętlę, skraca starsze wymiany informacji, przechowuje kluczowe fakty w długoterminowej pamięci i usuwa przepełnioną krótkoterminową pamięć.
Pomyśl o pisaniu obszernego raportu: nikt nie może przechowywać wszystkich informacji w pamięci. Robisz notatki, organizujesz dane i trzymasz jednostronicowy streszczenie na biurku. Obie te funkcje są wbudowane w Deep Agents, który zapewnia wirtualny system plików do przechowywania dużych ilości danych oraz automatyczne narzędzie do tworzenia streszczeń, więc zarządzanie pamięcią istnieje od chwili utworzenia agenta.
Podagenty: dziel i rządź
Część zadań jest zbyt skomplikowana na jedną rozmowę. Poszukiwanie w ogromnej bazie kodu może wymagać przeczytania pięćdziesięciu plików, a te pliki nie powinny zapełniać pamięci głównego agenta. Rozwiązaniem są subagenty – główny agent uruchamia pomocnika, powierza mu konkretne zadanie, na przykład znalezienie lokalizacji logiki autoryzacji, a pomocnik pracuje w swoim oddzielnym oknie kontekstowym. Po zakończeniu pracy przesyła on zwięzły podsumowanie. Podobnie postępuje menedżer, który deleguje zadania badawcze młodszemu pracownikowi i otrzymuje jednoakapitowe podsumowanie, dzięki czemu jego praca pozostaje uporządkowana.
Istnieje również zabezpieczenie: subagentom surowo zabrania się uruchamiania własnych subagentów. W przeciwnym razie zdezorientowany agent mógłby stworzyć nieskończoną łańcuchową strukturę pomocników, co doprowadziłoby do nadmiernego zużycia zasobów API. Ścisły limit głębokości zapewnia bezpieczeństwo przy delegowaniu zadań.
Deep Agents zawiera wbudowany narzędzie task do tworzenia pomocników. Specjalistyczne narzędzia, takie jak dedykowany wyszukiwacz kodu, są opisane za pomocą prostych słowników.
code_searcher = {
"name": "code-searcher",
"description": "Searches the codebase to find where specific logic lives. "
"Use this for any open-ended 'where is X?' question.",
# ⚠️ NOTE: the key is `system_prompt`, NOT `prompt` (see correction below).
"system_prompt": "You are an expert at navigating codebases. Use the grep "
"and glob tools to locate relevant files, then report a "
"concise summary of what you found and where. Do not make "
"any edits.",
}
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[run_tests],
system_prompt="You are a coding assistant.",
subagents=[code_searcher],
)
Główny agent wie teraz o istnieniu specjalisty, co pozwala mu mieć czystą pamięć do prawdziwej pracy nad kodowaniem.
Bезpieczeństwo i zatwierdzenie przez człowieka
Na tym etapie agent może edytować pliki i wykonywać polecenia w shellu na rzeczywistej maszynie, co wymaga chwili zastanowienia. Entuzjastyczny agent może usunąć niewłaściwe pliki lub uruchomić coś destruktywnego. Siła bez hamulców to prawdziwe zagrożenie. Agenci zajmujący się kodowaniem wprowadzają kontrole na dwóch poziomach:
- Listy dozwolenia i zasady odrzucenia umożliwiają automatyczne wykonywanie bezpiecznych działań, takich jak czytanie plików, podczas gdy destruktywne działania, takie jak usuwanie plików, wymagają zatwierdzenia.
- Prośby o zatwierdzenie, nazywane również interwencją człowieka, sprawiają, że agent zatrzymuje się i pyta przed podjęciem ryzykownych działań. Użytkownik zatwierdza, edytuje lub odrzuca działanie, a następnie pętla jest kontynuowana.
Główną zasadą bezpieczeństwa agentów jest to, że prompt nie może stanowić bariery bezpieczeństwa. Polecenie modelowi, by nie usuwał niczego ważnego, to uprzejma prośba, a nie bariera, ponieważ model może ją łatwo ominąć poprzez halucynacje. Egzekwowanie tych zasad musi odbywać się poza modelem, w specjalnym narzędziu kontrolnym. Zasada uprawnień może odrzucić żądanie użycia określonego narzędzia, a środowisko sandbox może zablokować niebezpieczne polecenia w systemie operacyjnym, bez względu na to, jak bardzo model chce je uruchomić. Z tego powodu narzędzie wykonywania potrzebuje odpowiedniego backendu. Uprzejmość to nie bezpieczeństwo.
Deep Agents umieszcza swoje ograniczenia w tym narzędziu kontrolnym. Dostęp do środowiska shell wymaga backendu, a wbudowany mechanizm przerwania w LangGraph zatrzymuje wykonywanie operacji ryzykownych.
from deepagents import create_deep_agent
# Import path for backends can vary by version - check the current
# "Backends" page in the Deep Agents docs.
from deepagents.backends import LocalShellBackend
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
system_prompt="You are a coding assistant working inside this project.",
backend=LocalShellBackend(), # enables the `execute` shell tool
)
Jeśli zostanie zażądane narzędzie dostępne tylko po autoryzacji, pętla zostaje zatrzymana, a wynik zawiera payload __interrupt__, który określa planowaną akcję oraz dostępne opcje: approve, edit, reject lub respond. Po podjęciu decyzji wykonywanie kontynuuje się.
from langgraph.types import Command
result = agent.invoke({"messages": [...]}, config)
result["__interrupt__"] # the pending action + allowed decisions — show your user
# You decide; the loop picks up exactly where it paused:
agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config)
# ... or {"type": "reject"} — the tool is never run, and the model is told so.
Prawdziwe ograniczenia powinny być określone w ustawieniach, a nie tylko w instrukcjach.
Pamięć między sesjami
Domyślnie agent traci wszystko, gdy rozmowa się zamyka. Rzeczywiście przydatny asystent powinien pamiętać konwencje kodowania projektu, to, co podoba się użytkownikowi, oraz to, gdzie przerwał pracę wczoraj. Do tego służą dwa mechanizmy. Funkcja checkpointingu przechowuje dokładny stan agenta, dzięki czemu długa zadanie może przetrwać przerwę i kontynuować pracę od tego samego punktu. Pamięć długoterminowa przechowuje informacje, które przetrwają każdą pojedynczą rozmowę – na przykład to, że w projekcie odstępy między elementami są czterema spacjami.
Ponieważ Deep Agents działa na LangGraph, możliwość przechowywania danych wynika z integracji mechanizmu checkpointingu.
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[run_tests],
system_prompt="You are a coding assistant.",
checkpointer=InMemorySaver(), # remembers state within a session
)
# A "thread_id" ties messages together into one ongoing conversation.
config = {"configurable": {"thread_id": "project-alpha"}}
agent.invoke(
{"messages": [{"role": "user", "content": "Start refactoring the auth module."}]},
config=config,
)
# Later, same thread_id - the agent remembers the earlier turn:
agent.invoke(
{"messages": [{"role": "user", "content": "Now update the tests too."}]},
config=config,
)
InMemorySaver działa tylko przez czas trwania programu. Aby zapewnić trwałość danych pomimo restartów, zastąp go wskaźnikiem stanu przechowywanym w bazie danych.
Wszystko w jednym agencie
Wszystkie elementy połączone razem tworzą jeden agenta: podstawowy pętla działania wraz z dostosowanymi narzędziami, mechanizmem planowania, obsługą kontekstu, pomocniczym podagentem, interfejsem oraz zapisanym stanem – łącznie siedem części.
from deepagents import create_deep_agent
from deepagents.backends import LocalShellBackend
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
# --- A custom tool (Part 2) — token-budgeted, see Part 2 for the full body ---
@tool
def run_tests(path: str = ".") -> str:
"""Run the project's pytest suite and return its (truncated) output."""
import subprocess
try:
result = subprocess.run(["pytest", path], capture_output=True,
text=True, check=False, timeout=300)
except subprocess.TimeoutExpired:
return "pytest timed out after 300s"
output = result.stdout + result.stderr
return output if len(output) <= 20_000 else "[... truncated ...]\n" + output[-20_000:]
# --- A specialized subagent (Part 5) — note the `system_prompt` key ---
code_searcher = {
"name": "code-searcher",
"description": "Finds where specific logic lives in the codebase. "
"Use for open-ended 'where is X?' questions.",
"system_prompt": "You navigate codebases using grep and glob, then report a "
"concise summary of what you found. You never make edits.",
}
# --- A system prompt that teaches good behavior (Parts 2 & 3) ---
SYSTEM_PROMPT = """You are a careful coding assistant.
Workflow:
1. Plan the task as a to-do list before doing anything.
2. Use your built-in read, grep, and glob tools to explore - never the raw shell
equivalents like cat or grep.
3. Make focused edits.
4. ALWAYS run the tests after editing, and fix anything that breaks.
5. Delegate broad codebase searches to the code-searcher subagent.
"""
# --- Assemble the agent (Parts 1, 4, 6, 7 handled by the harness) ---
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6", # Part 1: the loop, model-agnostic
tools=[run_tests], # Part 2: custom hands
system_prompt=SYSTEM_PROMPT, # Parts 2 & 3: behavior + planning
subagents=[code_searcher], # Part 5: delegation
backend=LocalShellBackend(root_dir=".", virtual_mode=False), # Part 6: shell access
interrupt_on={"execute": True, # Part 6: the brakes —
"write_file": True, "edit_file": True}, # approval before anything destructive
checkpointer=InMemorySaver(), # Part 7: memory across turns
)
# Part 4 (context management) and built-in planning come on automatically.
config = {"configurable": {"thread_id": "my-project"}}
result = agent.invoke(
{"messages": [{"role": "user", "content": "The login tests are failing. Fix them."}]},
config=config,
)
print(result["messages"][-1].content)
Mniej niż sto linijek kodu tworzy agenta programistycznego, który planuje, wyszukuje, edytuje, uruchamia testy, przekazuje zadania pomocnikom, pozostaje w środowisku izolowanym i utrzymuje kontekst. Jego struktura odpowiada strukturze Claude Code: zwykły pętla otoczona narzędziami, mechanizmami planowania, zarządzania pamięcią, delegacji oraz zabezpieczeniami znajdującymi się poza modelem.