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

Домашка

Петля devbuddy не знает слова «хватит»: наблюдения копятся до упора в окно. Сегодня вы это чините — history.py с функцией budget_history, которая держит свежие наблюдения и схлопывает старые в сводку, укладываясь в заданный бюджет токенов. Петля получит дешёвую память, а вы — ещё один переиспользуемый модуль.

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

Файл history.py.

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

budget_history(history: list[str], max_tokens: int, estimate=estimate_tokens) -> list[str]:

  • если вся история укладывается в max_tokens — вернуть её без изменений (копию);

  • иначе оставить максимальный суффикс (самые свежие наблюдения), который вместе со сводкой влезает в бюджет; сводка — первой строкой: (ранее: K наблюдений опущено), где K — число отброшенных;

  • если не влезает даже одно наблюдение со сводкой — вернуть только сводку по всей истории.

Подсчёт токенов — через estimate_tokens из tokens.py (передаётся параметром — в тестах можно подменить). Считаем "\n".join(...) от списка строк вместе со сводкой.

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

  • Только стандартная библиотека + ваш tokens.py.

  • Функция чистая: исходный список не менять.

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

test_history.py, запуск python -m pytest test_history.py -v:

python
from history import budget_history
from tokens import estimate_tokens

LINES = [f"наблюдение номер {i}: " + "данные " * 20 for i in range(10)]


def test_short_history_untouched():
    history = ["одно", "два"]
    assert budget_history(history, 10_000) == ["одно", "два"]
    assert history == ["одно", "два"]  # исходник не тронут


def test_result_fits_budget():
    out = budget_history(LINES, 120)
    assert estimate_tokens("\n".join(out)) <= 120


def test_tail_preserved():
    out = budget_history(LINES, 400)
    kept = out[1:] if out[0].startswith("(ранее:") else out
    assert kept == LINES[-len(kept):]  # хвост сохраняется подряд, без дыр


def test_summary_when_dropped():
    out = budget_history(LINES, 120)
    assert len(out) < len(LINES)
    assert out[0].startswith("(ранее:")
    assert "8" in out[0] or "9" in out[0]  # отброшено почти всё


def test_tiny_budget_returns_summary_only():
    out = budget_history(["очень длинное наблюдение " * 50, "второе"], 1)
    assert len(out) == 1
    assert out[0].startswith("(ранее:")

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

python
>>> from history import budget_history
>>> history = ["шаг 1: нашли файл", "шаг 2: прочитали", "шаг 3: сверили", "шаг 4: готово"]
>>> budget_history(history, 12)
['(ранее: 2 наблюдений опущено)', 'шаг 3: сверили', 'шаг 4: готово']

(точные границы зависят от estimate_tokens — в вашем запуске чисел в сводке может быть другое, тесты проверяют свойства, а не точную строку.)

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

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

  • Результат всегда в бюджете, хвост подряд, отброшенное — со сводкой.

  • Коммит: history.py, test_history.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 мин

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