Агентные ИИ — позвольте Ollama писать и запускать ваш Python локально
Местный генератор кода на основе ИИ-агента: Ollama пишет код на Python, запускает его и считывает результат в цикле агента. Предварительные требования, структура проекта и полный процесс сборки.
Источники: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator
В этом руководстве пошагово описывается процесс создания системы от сырьевых материалов до готового решения для: Agentic AI — инструмента, позволяющего Ollama писать и запускать код на Python, а также локального генератора кода. Основное внимание уделяется практическим шагам, четкой проверке результатов и коду, который можно просто добавить в репозиторий без необходимости догадываться о его назначении. На этапе обзора необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии системы. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Укажите названия элементов, определите критерии успеха и не допускайте безответственного частичного выполнения задачи.
Что мы создаем
На этапе «Что мы создаём» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Ведите журнал с информацией об идентификаторе запроса, идентификаторе модели и времени задержки при каждом вызове. Без такой записи периодические ошибки поставщика могут выглядеть как баги приложения.
Предварительные требования
При работе над этапом предварительных требований сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Фиксируйте ID запроса, ID модели и время задержки при каждом вызове. Без такой отчетности периодические ошибки поставщика могут выглядеть как баги приложения.
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 сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Фиксируйте ID запроса, ID модели и время задержки при каждом вызове. Без такой отчетности периодические ошибки поставщика будут выглядеть как баги приложения.
# 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
Очистка контекста
Этап обрезки контекста работает наилучшим образом, когда его рассматривают как измеримую область. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ.
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