Агентський ШІ — нехай Ollama пише та виконує ваш Python, локально
Місцевий генератор коду для штучного інтелекту-агента: Ollama пише код на Python, запускає його та читає результат у циклі агента. Попередні вимоги, структура проекту та повний процес компіляції.
Джерела: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator
У цьому посібнику описано процес створення системи від сировини до готового продукту для: Agentic AI — що дозволяє Ollama писати та виконувати ваш Python-код, а також для локального генератора коду. Основна увага приділяється крокам виконання, чітким перевіркам та коду, який можна просто додати до репозиторію без необхідності здогадуватися щодо його призначення. На етапі огляду необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Виконавці мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись здогадатися про прихований стан системи. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте безповідомного часткового завершення роботи.
Що ми створюємо
Під час роботи над етапом «Що ми створюємо» спочатку запишіть умови використання: необхідні вхідні дані, сигнал про успішне виконання та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Запишіть час виконання та витрати на токени або запити поруч із результатами функціоналу. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Фіксуйте ідентифікатор запиту, ідентифікатор моделі та час затримки при кожному виклику. Без цих записів періодичні помилки постачальника виглядають як баги програмного забезпечення.
Попередні вимоги
Під час виконання етапу передумов спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Записуйте ідентифікатор запиту, ідентифікатор моделі та час відгуку для кожного виклику. Без цих записів періодичні помилки постачальника виглядають як баги додатку.
ollama serve
ollama pull llama3.1:8b
pip install ollama
Структура проекту
Під час роботи над етапом планування проекту спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Документуйте як шлях успішної роботи, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки. Зафіксуйте ідентифікатор запиту, ідентифікатор моделі та час виконання кожного запиту. Без цих даних періодичні помилки постачальника виглядають як баги програми. Під час роботи над етапом планування проекту спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Назвіть всі елементи, визначте критерії успіху та не допускайте безповідомних часткових завершень.
╔═══════════╦════════════════════════════════════════════════════╗
║ 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 ║
╚═══════════╩════════════════════════════════════════════════════╝
Спочатку конфігурація (config.py)
Етап конфігурації Config first config py працює найкраще, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний запис роботи, один випадок збою та примітку про скасування змін перед розширенням обсягу завдань. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуальний контроль витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ. Фіксуйте версію інтерпретатора та файли блокування залежностей перед тим, як пояснювати принцип роботи циклів. Розбіжності між ноутбуком та середовищем CI є найпоширенішою причиною прихованих збоїв у демонстраціях 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."
)
Інструменти (tools.py)
Інструменти типу py stage працюють найкраще, коли їх розглядають як вимірювану поверхню. Збережіть один ідеальний зразок виконання, один випадок збою та запис про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, щоб оператори могли їх перевіряти, не читаючи весь код. Забезпечте фіксацію версії інтерпретатора та файлу з інформацією про залежності перед початком використання циклів. Розбіжності між ноутбуком та системою CI є найпоширенішою причиною прихованих збоїв під час демонстрацій API.
read_local_file
Етап readlocalfile працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку щодо скасування змін перед розширенням обсягу. Документуйте як успішний, так і відновлювальний сценарії. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Фіксуйте версію інтерпретатора та файли блокування залежностей перед початком використання циклів. Розбіжності між ноутбуком та середовищем CI є найпоширенішою причиною прихованих збоїв у демонстраціях API. Етап readlocalfile працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку щодо скасування змін перед розширенням обсягу. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте прихованого часткового виконання.
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
На етапі writelocalfile необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Розділіть створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без переписування машини станів розмови.
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
Для етапу runpythonscript необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись здогадатися про прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь граф. Необхідно розділити процес створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без переписування машини станів розмови.
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)}"
Реєстрація
На етапі реєстрації необхідно визначити вхідні дані, власника кроку та критерії завершення ще до змін у коді. Оператори мають мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Необхідно розділити процес створення клієнта від циклу обробки повідомлень, щоб можна було замінити постачальників без переписування машини станів розмови. На етапі реєстрації необхідно визначити вхідні дані, власника кроку та критерії завершення ще до змін у коді. Оператори мають мати можливість знову виконати крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте безповідомного часткового завершення роботи.
REGISTERED_TOOLS = {
"read_local_file": read_local_file,
"write_local_file": write_local_file,
"run_python_script": run_python_script,
}
Обмеження м’якого шляху під ~/repo
Під час роботи над обмеженнями м’якого шляху на етапі розробки спочатку складіть перелік вимог: необхідні вхідні дані, сигнал про успіх та наслідки часткової невдачі. Такий перелік допоможе зберегти чесність у подальших змінах коду. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли шлях переходить від демо-середовища до спільних. Фіксуйте ідентифікатор запиту, ідентифікатор моделі та час затримки після кожного виклику. Без цих записів періодичні помилки постачальника можуть здаватися багами програми.
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
Цикл агента (agent.py)
Під час роботи над етапом агента у циклі The agent loop спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Зберігайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Записуйте ідентифікатор запиту, ідентифікатор моделі та час відгуку для кожного виклику. Без цих записів періодичні помилки постачальника виглядають як баги додатку.
# 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")
Як виглядають справжні виклики_інструментів
Під час роботи над етапом «Що таке справжній інструмент», спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки. Фіксуйте ідентифікатор запиту, ідентифікатор моделі та час виконання кожного виклику. Без цих записів періодичні помилки постачальника виглядають як баги програми. Під час роботи над етапом «Що таке справжній інструмент», спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як договір між вхідними даними та перевіреними результатами. Назвіть всі елементи, визначте критерії успіху та не допускайте безповідомних часткових завершень.
{
"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),
})
Як виглядає фальшивий tool_call
Етап «Як виглядає фальшивий tool_call» найкраще працює, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Візуалізація витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Закріпіть інтерпретатор та файл з параметрами залежностей перед тим, як пояснювати принцип роботи циклу. Розбіжності між ноутбуком та середовищем CI є найпоширенішою причиною прихованих збоїв у демонстраціях 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
Обрізка контексту
Етап обрізки контексту працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Фіксуйте версію інтерпретатора та файл з блокуванням залежностей перед тим, як навчати алгоритм циклу. Розбіжності між ноутбуком та системою CI є найпоширенішою причиною прихованих збоїв у демонстраціях 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)
Етап CLI main py працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи.
python main.py --dir demos/hello 'Create hello.py that prints Hello and run it to verify.'
Запуск
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