Первые инструменты
С read агент умеет открыть файл, если знает имя.
«Найди все TODO в проекте» — и он начинает наугад открывать файлы.
Это не поиск. Добавим grep. Но появится новая задача:
модель должна сама понять, когда звать read, а когда
grep. Подсказка — в тексте описания инструмента.
grep с понятным описанием «когда вызывать».
На поиск модель идёт в grep, на «открой вот этот файл» —
в read. Переключение — за счёт docstring, без отдельного
роутера в вашем коде.
Слова, которые встретятся
- Docstring — строка в тройных кавычках у функции. Для человека это документация; для модели — инструкция «в каких ситуациях звать этот инструмент».
-
Regex (регулярное выражение) — шаблон поиска
вроде
TODOилиdef \w+(имя функции). -
Glob — маска имени файла:
*.py— «все Python-файлы». -
Маршрутизация / выбор инструмента — решение модели,
какую функцию вызвать. Вы не пишете
if«если TODO → grep»; вы пишете хорошие описания, и модель выбирает сама.
Быстрый путь
- Добавьте
grep: шаблон поиска, по желанию папка и маска файлов, не больше 50 совпадений. - Опишите его блоками WHEN TO USE / WHEN NOT TO USE / DO NOT USE FOR / EXAMPLES.
- Тем же форматом обновите описание у
read, чтобы он «отталкивал» поиск.
Упражнение 1.2
- Аргументы:
pattern, опциональноpathиglob. -
Внутри — системная команда
grepчерезsubprocess. Не лезьте в.venv,__pycache__,.git(там шум, не ваш код). - Если совпадений больше 50 — покажите первые 50 и напишите, сколько было всего.
- «Ничего не найдено» — нормальный успешный ответ, а не авария программы.
Плохое описание → плохой выбор
Сначала нарочно сделайте описание из двух слов и посмотрите, как модель ошибётся:
main.py
@agent.tool
def grep(ctx: RunContext[Path], pattern: str, glob: str | None = None) -> str:
"""Search files."""
...
Terminal
uv run python main.py . "Найди все TODO-комментарии в проекте"
Часто модель проигнорирует grep и полезет в
read (или, когда появится терминал, — в bash).
Двух слов мало, чтобы выбрать: она угадывает.
Описание — это подсказка модели, не «для документации»
Чините не обязательно код поиска. Сначала — текст, который видит модель:
фрагмент grep для main.py
@agent.tool
def grep(
ctx: RunContext[Path],
pattern: str,
path: str | None = None,
glob: str | None = None,
) -> str:
"""Поиск по содержимому файлов через regex. Строки с путями.
WHEN TO USE: паттерны по многим файлам, определения функций,
импорты, TODO, сообщения об ошибках.
WHEN NOT TO USE: чтение уже известного файла (используй read).
DO NOT USE FOR: запуск команд, листинг каталогов.
EXAMPLES:
- TODO: pattern "TODO" glob "*.py"
- определения функций: pattern "def \\w+" glob "*.py"
"""
import shlex
import subprocess
root = (ctx.deps / (path or ".")).resolve()
include = glob or "*.py"
cmd = (
f"grep -rn --exclude-dir=.venv --exclude-dir=__pycache__ "
f"--exclude-dir=.git --include={shlex.quote(include)} "
f"-E {shlex.quote(pattern)} {shlex.quote(str(root))}"
)
try:
proc = subprocess.run(
cmd, shell=True, capture_output=True, text=True, timeout=10
)
lines = [ln for ln in proc.stdout.strip().splitlines() if ln]
except subprocess.TimeoutExpired:
return "Таймаут: поиск занял слишком много времени."
if not lines:
return "Совпадений нет."
MAX_MATCHES = 50
truncated = len(lines) > MAX_MATCHES
shown = lines[:MAX_MATCHES]
body = "\n".join(shown)
if truncated:
body += f"\n... ({len(lines)} всего, показаны первые {MAX_MATCHES})"
return body
Что значат блоки в описании:
- WHEN TO USE — «зови меня, если…»
- WHEN NOT TO USE — «в этой ситуации лучше другой инструмент»
- DO NOT USE FOR — ещё раз жёстко: чем этим инструментом не решают задачу
- EXAMPLES — конкретные примеры аргументов
То же самое — для read: он должен явно отправлять поиск
к grep:
"""Прочитать файл проекта. Возвращает пронумерованные строки.
WHEN TO USE: просмотр содержимого, конфигов, исходников.
WHEN NOT TO USE: поиск по нескольким файлам (используй grep).
DO NOT USE FOR: запуск команд, листинг каталогов.
"""
import по большому проекту без лимита
вернёт сотни строк. Модели этого не нужно, чтобы ответить «где TODO».
50 — достаточно. 500 — уже мусор в контексте разговора.
Проверьте
Положите в маленький файл пару строк с # TODO:, затем:
uv run python main.py . "Найди все TODO-комментарии в проекте"
uv run python main.py . "Прочитай pyproject.toml"
Первый запрос должен уйти в grep, второй — в
read. Если оба раза один и тот же инструмент —
подкрутите описания, а не только код.
Готово, если:
- «Найди TODO» вызывает
grep, а неread - «Прочитай pyproject.toml» вызывает
read grepотдаёт не больше 50 строк и пишет, сколько было всего, если обрезал- У обоих инструментов в описании есть WHEN TO USE / WHEN NOT TO USE / DO NOT USE FOR
grep по кусочкам: сначала уберите EXAMPLES,
потом DO NOT USE FOR, потом WHEN NOT TO USE. На каком шаге модель
снова начинает угадывать через read (или терминал)?