Галоўная / Артыкулы

Агентны AI: Ollama піша і запускае ваш Python лакальна

Лакальны агентны генератар кода: Ollama піша Python, запускае яго і чытае вынік у цыкле агента. Што патрэбна і як усё наладзіць.

Выдавец: REACTAPP.TOP11 хв чытання2446 слоў

Крыніцы: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator

У гэтым карыце парадоксальная дорага ад сыр'ёў да рабочай сістэмы для: Agentic AI — калі Ollama можа пісаць і запускаць ваш Python, а таксама для локальнага генератора коду. Акцэнт ставіцца на практычныя крокі, чыстае перакананне і код, які можна проста падставіць у рэпозытарый без неабяснення меты. У стадыўцы агульнага відзору неабходна практычна апісацыя вхідных дадзеных, адпаведальнага за крок і крэатарыяў выходу прычынамі перад зменыма коду. Аператары должны магчымае перапрацаваць крок пачаткуючы з вядомага контрольнага пункту, не спрабоўваючы з'ясаваць схованы стан. Спрыйміце гэтую стадыю як кантракт межа вхіднымі дадзенымі і перакананымі выходамі. Дайце назву рэзультатам, апісацыю крэатарыяў успеху і адмовіцеся ад беззвучнага частковага завершэння.

Што мы ствараем

Калі працюеце над этапам «Што мы ствараем», спачатку запісайце умовы кантракту: неабяжлівыя даны, сігнал успеху і тое, што выходзіць пад частковыя адказы. Такі спіс контролю дапамагае заставіць пазнейшыя змены коду быць чыстымі. Запісвайце час выканання і кост токена або запыту празьмаўляйчы з рэзультатамі функцыйнасці. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі працэс пераходзіць з дэмаверсіі ў спяльныя асяроддзя. Запісвайце ID запыту, ID модэлю і час затрымкі праз кожны вызов. Без такога лёгасу перыядычныя адказы прадастаўця выглядаюць як багі ў прыемніку.

Прыямыя праблемы

Калі працюеце над стадзіяй прабэральных умов, спачатку запісайце контракт: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі спіс пераконтроўвае чыстасць пазнейшых змян у кодзе. Зберагаюце настройкі праза код аплікацыі. Файлы серавэра, хранальнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаць адбавіць аўдыт без неабяжлівага чытання всей структуры. Запісвайце 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 працюе найкрашча, калі яго розглядаць як параметр, які можна змерыць. Запісаце адны ідэальны прыклад працы, адну сітуацыю неудачы і прыметкі па поверненні да пачатковага стану пры расшырэнні масштаба. Запісвайце часы выканання і вартасць токеноў або запытаў разам з функцыйнальнымі рэзультатамі. Відразлівае паказанне вартасцей запобегае неспадзяваным рахункам, калі працэс пераходзіць з дэмавайнага рэжыма ў спяльныя сераўеры. Закрэпіце інтэрпрэтара і файлы з правіламі залежнасцяў пры навучэнні працы з цыкламі. Разніця ў рабоце на ноутбуку і у сераўерных сэрвісах — гэта найчастэйшая прычына тых, чыя дэманстраціі 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

Калі працуеце з лімітамі „мягкага“ шляху, спачатку запісайце умовы: неабяжлівыя даны, сігнал успеху і тое, што выканаецца у разе частковага нявыпалення. Такі спіс дапамагае заліцварыць будучыя змены коду. Запісуйце час выканення і вартась токена або запытку падле функцыйнальных рэзультаатаў. Відразлівая вартась з самага пачатку запобегае неспакоўным рахункам, калі шлях пераходзіць з дэмавайнту ў спяльныя сераўысы. Запісуйце ID запытку, ID модэлю і час затрымкі праз кожны вызов. Без такога лёгася ўскладненыя проблэмы прадаўцу выглядаюць як багі ў прыемніку.

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),
    })

Як выглядае фальшывы календар з вызваў

Этап «Як выглядае фальшывы календар з вызваў» працюе найэфектывней, калі яго розглядаць як мерыемую плошчу. Запісаўце адна ідэальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць масштаб. Запісвайце часы выканання і кост токенаў або запытаў разам з функцыйнальнымі рэзултатамі. Візуальная наявнасць костаў з самага пачатку запобегае неспакойным рахункам, калі працэс пераходзіць з дэмавайнога рэжыма ў спяльныя сераўы. Закрэпіце інтэрпрэтара і файл з правіламі залежнасцяў пры навучэнні цыклу. Разніця межы ноутбукам і сістэмай 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

Чэк-ліст для эксплуатацыі