Agentic AI — Niech Ollama pisze i uruchamia Twój Python, w trybie lokalnym
Lokalny generator kodu AI typu agent: Ollama pisze kod w Pythonie, uruchamia go i odczytuje wynik w pętli agenta. Wymagania wstępne, struktura projektu oraz pełny proces budowania.
Źródła: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator
To przewodnik pokazuje, jak odtworzyć proces od surowców do gotowego systemu dla: Agentic AI — umożliwiającego Ollamie pisanie i uruchamianie kodu w Pythonie, oraz lokalnego generatora kodu. Główny nacisk kładziony jest na konkretne kroki operacyjne, wyraźne sprawdzenia oraz kod, który można bez żadnych domysłów wkleić do repozytorium. Na etapie przeglądu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania.
Co budujemy
Gdy przechodzisz przez etap „Co budujemy”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co się dzieje w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas trwania oraz koszt tokena lub zapytania. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z wersji demonstracyjnej do środowisk współdzielonych. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
Wymagania wstępne
Gdy przechodzisz przez etap Wstępne wymagania, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
ollama serve
ollama pull llama3.1:8b
pip install ollama
Struktura projektu
Podczas prace nad etapem planowania projektu najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponowne, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dopinane później. Zapisuj identyfikator żądania, identyfikator modelu oraz opóźnienie przy każdej próbie połączenia. Bez tych danych błędy dostawcy występujące sporadycznie wyglądają jak błędy aplikacji. Podczas pracy nad etapem planowania projektu najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.
╔═══════════╦════════════════════════════════════════════════════╗
║ File ║ Role ║
╠═══════════╬════════════════════════════════════════════════════╣
║ main.py ║ CLI, work-dir prompt / --dir, starts the loop ║
║ agent.py ║ Chat loop, tool execution, fake tool-call handling ║
║ tools.py ║ Read / write / run, plus a soft ~/repo path limit ║
║ config.py ║ Model name, iteration cap, system prompt ║
╚═══════════╩════════════════════════════════════════════════════╝
Najpierw konfiguracja (config.py)
Etap konfiguracji w Config first config py działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API.
MODEL_NAME = "llama3.1:8b"
MAX_ITERATIONS = 15
SYSTEM_PROMPT = (
"You are an autonomous local AI assistant with file tools. "
"When creating or editing code, you MUST call write_local_file with the COMPLETE source in content. "
"Never write empty files. Never create a placeholder file first. "
"Always use relative filenames (for example 'calculator.py'). "
"Do not invent absolute paths like /repo/.... "
"Never describe or simulate tool calls in text, XML, JSON, or markdown - only use real tool calls. "
"Keep calling tools until the task is actually done; then reply with a short plain-text summary."
)
Narzędzia (tools.py)
Narzędzia typu py stage działają najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny poufnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, aby operatorzy mogli je sprawdzić bez konieczności przeglądania całej struktury. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed uruchomieniem pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API.
read_local_file
Faza readlocalfile funkcjonuje najlepiej, gdy jest traktowana jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Zdokumentuj zarówno prawidłowy przebieg procesu, jak i ścieżkę przywracania do stanu poprzedniego. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Zabezpiecz interpreter oraz plik blokujący zależności przed rozpoczęciem wykonywania pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API. Faza readlocalfile funkcjonuje najlepiej, gdy jest traktowana jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj przypadki cichego, częściowego ukończenia zadania.
def read_local_file(filepath: str) -> str:
"""Reads and returns the contents of a local file.
Args:
filepath: The path to the file to read. Must be under ~/repo.
"""
try:
with open(_resolve_tool_path(filepath), "r") as f:
return f.read()
except Exception as e:
return f"Error reading file: {str(e)}"
write_local_file
W fazie writelocalfile należy zdefiniować dane wejściowe, osobę odpowiedzialną za tę czynność oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić tę czynność na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Należy oddzielić budowanie klienta od pętli komunikacji, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.
def write_local_file(filepath: str, content: str) -> str:
"""Writes or overwrites text content to a local file.
Args:
filepath: Target filename or path. Must be under ~/repo.
content: The text content to write inside the file. Must not be empty.
"""
try:
if content is None or not str(content).strip():
return (
"Error writing file: content is empty. "
"Call write_local_file again with the full file source in content."
)
path = _resolve_tool_path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
f.write(content)
return f"Successfully wrote file to {path} ({len(content)} bytes)"
except Exception as e:
return f"Error writing file: {str(e)}"
run_python_script
Dla etapu runpythonscript należy zdefiniować dane wejściowe, osobę odpowiedzialną za ten krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny tajemnic oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Należy oddzielić proces tworzenia klienta od pętli przekazywania wiadomości, aby można było zmieniać dostawców bez konieczności przepisywania automatu stanu rozmowy.
def run_python_script(filepath: str) -> str:
"""Executes a local Python file and returns STDOUT and STDERR.
Args:
filepath: The path to the Python file to run. Must be under ~/repo.
"""
try:
path = _resolve_tool_path(filepath)
result = subprocess.run(
["python3", str(path)],
capture_output=True,
text=True,
timeout=10,
cwd=str(_work_dir),
)
return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
except Exception as e:
return f"Execution error: {str(e)}"
Rejestracja
W fazie rejestracji należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości nieudanych należą do samego produktu, a nie są elementami dodatkowej optymalizacji. Należy oddzielić budowę klienta od pętli przekazywania wiadomości, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanów rozmowy. W fazie rejestracji należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.
REGISTERED_TOOLS = {
"read_local_file": read_local_file,
"write_local_file": write_local_file,
"run_python_script": run_python_script,
}
Ograniczenia ścieżki miękkiej w katalogu ~/repo
Pracując nad ograniczeniami ścieżki miękkiej na etapie realizacji, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez takiego śladu przerywane błędy dostawcy wyglądają jak błędy aplikacji.
ALLOWED_ROOT = (Path.home() / "repo").resolve()
_work_dir = ALLOWED_ROOT
def _under_repo(path: Path) -> Path:
resolved = path.expanduser().resolve()
try:
resolved.relative_to(ALLOWED_ROOT)
except ValueError as exc:
raise PermissionError(
f"Path '{resolved}' is outside allowed root '{ALLOWED_ROOT}'."
) from exc
return resolved
Pętla agenta (agent.py)
Gdy pracujesz nad etapem agenta w pętli The agent loop, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
# ollama api call
response = ollama.chat(
model=MODEL_NAME,
messages=messages,
tools=tools_list, # the Python functions from REGISTERED_TOOLS
)
message = response["message"]
messages.append(message)
tool_calls = message.get("tool_calls")
Jak wyglądają rzeczywiste calls narzędzia
Gdy przechodzisz przez etap „What a real toolcalls”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji. Gdy przechodzisz przez etap „What a real toolcalls”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań.
{
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"function": {
"name": "write_local_file",
"arguments": {
"filepath": "hello.py",
"content": "print('hi')\n"
}
}
}
]
}
}
for call in tool_calls:
func_name = call["function"]["name"]
func_args = call["function"]["arguments"]
if func_name in REGISTERED_TOOLS:
observation = REGISTERED_TOOLS[func_name](**func_args)
else:
observation = f"Error: Tool '{func_name}' is not registered."
messages.append({
"role": "tool",
"name": func_name,
"content": str(observation),
})
Jak wygląda fałszywy tool_call
Etap „Jak wygląda fałszywy tool_call” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres badania. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Różnice między laptopem a środowiskiem CI to najczęstsza przyczyna ukrytych awarii w demonstracjach API.
I'll create the file now.
<tool_call>
{"name": "write_local_file", "arguments": {"filepath": "hello.py", "content": "print('hi')"}}
</tool_call>
CONTINUE_NUDGE = (
"That response was not a valid tool call. "
"Call write_local_file now with the full file contents. "
"Do not write empty content. Do not describe the tool call in text."
)
if not tool_calls:
content = message.get("content") or ""
if _looks_like_fake_tool_call(content):
messages.append({"role": "user", "content": CONTINUE_NUDGE})
continue # next iteration; yes, this burns a turn
print(f"\n[Agent Completed Task]:\n{content}")
return
Oczyszczanie kontekstu
Etap przycinania kontekstu działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny zapis transkrypcji, jeden przypadek awarii oraz notatkę o cofnięciu zmian przed rozszerzeniem zakresu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API.
def prune_context(messages: list, max_history: int = 6) -> list:
if len(messages) <= max_history + 2:
return messages
return messages[:2] + messages[-max_history:]p
CLI (main.py)
Etap CLI main py działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny zapis transkrypcji, jeden przypadek awarii oraz notatkę o cofnięciu zmian przed rozszerzeniem zakresu.
python main.py --dir demos/hello 'Create hello.py that prints Hello and run it to verify.'
Uruchamianie
cd ~/repo/python-agent
python main.py 'Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.'
Files will be written under: /home/<user_name>/repo
Enter a directory under ~/repo for the program (relative path like 'demos/factorial', or absolute under ~/repo): calculator
Work directory: /home/<user_name>/repo/calculator
Starting agent with goal: Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.
[Iter 1] Executing Tool: write_local_file({'content': "import tkinter as tk\n...", 'filepath': 'calculator.py'})
-> Successfully wrote file to ... (N bytes)
[Agent Completed Task]:
This is a complete working single-file tkinter calculator with digit buttons 0-9, + - * / = C. The expression and result are shown in the entry field at the top. The 'C' button clears the entry field.
cd ~/repo/calculator
python calculator.py
Lista kontrolna operacyjna
Literatura pokrewna
- Praktyczne notatki: Budowanie aplikacji AI agencyjnej z lokalnym LLM — bez chmury, bez API — Szczegółowy przewodnik po Praktycznych notatkach: Budowanie aplikacji AI agencyjnej z lokalnym LLM — bez chmury, bez API: kontrakty, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.
- Praktyczne notatki: Semantyczne cacheowanie dla agentów AI: Jak stworzyć swoje aplikacje LLM — Szczegółowy przewodnik po Praktycznych notatkach: Semantyczne cacheowanie dla agentów AI: Jak stworzyć swoje aplikacje LLM: kontrakty, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.