Домашка: агент с одним инструментом

Домашка

Сегодня в devbuddy появляется главное — loop.py, петля агента. Миниатюрная, с текстовым протоколом из урока, но настоящая: она сама решает, когда позвать инструмент, читает наблюдения и останавливается. Проверять будете сценариями ScriptedLLM — как в бою, только без сети и счета за токены.

Описание задачи

Файл loop.py.

Основная функциональность

  • build_prompt(task: str, history: list[str]) -> str — промпт одного шага: строка Задача: ..., затем (если история не пуста) блок Наблюдения: со строками - ..., в конце инструкция отвечать строго одной строкой ACT: <инструмент> <аргумент> или DONE: <ответ>.

  • run_agent(llm, tools: dict, task: str, max_steps: int = 5) -> str:

    • до max_steps раз спрашивает модель;

    • DONE: <ответ> — вернуть <ответ> (без префикса, без краевых пробелов);

    • ACT: <имя> <аргумент> — вызвать инструмент из tools; результат добавить в историю как <имя>(<аргумент>) -> <результат>;

    • неизвестное имя — добавить в историю ошибка: неизвестный инструмент <имя> и продолжать;

    • строка без знакомого префикса — ValueError с текстом ответа;

    • лимит исчерпан — вернуть ЛИМИТ ШАГОВ ().

Дополнительные условия

  • Никаких обращений к сети; модель — любой объект с ask(prompt) -> str.

  • tools — словарь имя -> функция(аргумент_строкой) -> строка.

Как проверить себя

test_loop.py, запуск python -m pytest test_loop.py -v (класс ScriptedLLM положите в fake_llm.py рядом с FakeLLM):

python
import pytest

from fake_llm import ScriptedLLM
from loop import build_prompt, run_agent


def test_done_after_one_action():
    llm = ScriptedLLM(["ACT: read_file secret.txt", "DONE: кекс"])
    tools = {"read_file": lambda path: "пароль: кекс"}
    assert run_agent(llm, tools, "найди пароль") == "кекс"
    assert llm.replies == []


def test_observation_fed_back():
    llm = ScriptedLLM(["ACT: read_file secret.txt", "DONE: кекс"])
    tools = {"read_file": lambda path: "пароль: кекс"}
    run_agent(llm, tools, "найди пароль")
    assert "read_file(secret.txt) -> пароль: кекс" in llm.prompts[1]


def test_unknown_tool_is_observation():
    llm = ScriptedLLM(["ACT: rm all", "DONE: ничего"])
    result = run_agent(llm, {}, "уничтожь всё")
    assert result == "ничего"
    assert "неизвестный инструмент rm" in llm.prompts[1]


def test_step_limit():
    llm = ScriptedLLM(["ACT: read_file a", "ACT: read_file b", "ACT: read_file c"])
    tools = {"read_file": lambda p: "пусто"}
    assert run_agent(llm, tools, "задача", max_steps=2) == "ЛИМИТ ШАГОВ (2)"
    assert len(llm.prompts) == 2


def test_garbage_raises():
    llm = ScriptedLLM(["мне кажется, тут нужно подумать..."])
    with pytest.raises(ValueError):
        run_agent(llm, {}, "задача")


def test_first_prompt_has_task_and_no_history():
    llm = ScriptedLLM(["DONE: ок"])
    run_agent(llm, {}, "найди пароль")
    assert "Задача: найди пароль" in llm.prompts[0]
    assert "Наблюдения:" not in llm.prompts[0]

Пример работы программы

python
>>> from loop import run_agent
>>> from fake_llm import ScriptedLLM
>>> llm = ScriptedLLM(["ACT: read_file todo.txt", "DONE: купить кекс"])
>>> tools = {"read_file": lambda p: "купить кекс"}
>>> run_agent(llm, tools, "что в списке дел?")
'купить кекс'

Критерии готовности

  • Шесть тестов зелёные.

  • Петля не делает ни одного ask() сверх нужного (проверяется llm.prompts).

  • Коммит: loop.py, fake_llm.py (с обоими классами), test_loop.py.

  • theory icon

    Петля агента: решить → сделать → посмотреть

    5 мин

  • homework icon

    Домашка: агент с одним инструментом

    3 мин

  • quiz icon

    Квиз: Петля агента: решить → сделать → посмотреть

    6 мин

  • theory icon

    Инструменты: руки агента

    5 мин

  • homework icon

    Домашка: набор разведчика

    3 мин

  • quiz icon

    Квиз: Инструменты: руки агента

    5 мин

  • theory icon

    Память и контекст: что тащим, что выбрасываем

    5 мин

  • homework icon

    Домашка: сжатие истории

    3 мин

  • quiz icon

    Квиз: Память и контекст: что тащим, что выбрасываем

    6 мин

  • theory icon

    Свой цикл против LangChain и LlamaIndex

    4 мин

  • homework icon

    HARD-задача: прятки в репозитории

    3 мин

  • quiz icon

    Квиз: Свой цикл против LangChain и LlamaIndex

    5 мин

  • project icon

    Проект «Разведчик»: агент, который знает ваш репозиторий

    3 мин

🎯
Тренажёр собеседованияЗакрепите знания перед интервью