Зачем это нужно
До этого момента мы говорили о контексте, правилах, specs и skills. Все эти элементы помогают агенту думать и действовать предсказуемо. Но сами по себе они не дают агенту возможности выполнить внешнее действие.
Агент может рассуждать:
Нужно найти файл.
Нужно запустить тест.
Нужно прочитать схему базы.
Нужно создать issue.
Нужно получить статус CI.
Но LLM не выполняет эти действия напрямую. Модель выбирает действие, формирует аргументы, а выполнение делает agent runtime через tool.
Tool - это граница между рассуждением модели и реальным миром. Именно поэтому tools требуют инженерного дизайна: schema, permissions, observability, errors, retries, audit и human approval для опасных действий.
Главная цель урока - понять tool как функцию действия, которую модель может вызвать, и научиться проектировать простой tool на примере run_tests(path).
Основная идея
Tool - это функция, доступная agent runtime, которую модель может выбрать для выполнения конкретного действия.
Простая формула:
Tool = name + description + input schema + execution + output + permission boundary
Skill отвечает:
Как выполнять повторяемую процедуру?
Tool отвечает:
Какое действие можно выполнить прямо сейчас?
Пример:
| Задача агента | Skill или tool? |
|---|---|
| "Как добавить endpoint с тестами" | Skill |
| "Запустить тесты для файла" | Tool |
| "Как проводить code review" | Skill |
| "Получить diff текущей ветки" | Tool |
| "Как безопасно делать migration" | Skill |
| "Прочитать schema database" | Tool или Resource |
Важно:
LLM выбирает tool call.
Runtime выполняет tool call.
Модель получает result и продолжает работу.
Почему tool - это граница ответственности
Tool легко представить как обычную функцию: есть имя, аргументы и результат. Для agent development это слишком слабое представление. Tool - это место, где намерение модели превращается в действие системы.
До tool call модель находится в мире текста: она рассуждает, сравнивает варианты, строит план, формулирует аргументы. После tool call появляется внешний эффект: прочитан файл, запущен тест, отправлен комментарий, создан ticket, изменен код или затронута внешняя система. Поэтому tool всегда находится на границе ответственности.
На этой границе участвуют три стороны:
- модель решает, что действие нужно;
- runtime проверяет, можно ли выполнить действие и как именно;
- project policy определяет, какие действия допустимы без approval.
Если все это смешать, tool становится опасным. Например, если модель сама формирует shell-команду, сама решает, безопасна ли она, и runtime выполняет ее без проверки, то у нас нет настоящей границы. Есть только доверие к следующему token'у модели.
Хороший tool design делает границу явной:
Model:
I need to run tests for this file.
Runtime:
The tool accepts only repository-relative paths and known modes.
Policy:
Targeted tests are allowed. Full integration suite may require confirmation.
Tool result:
The command ran, these tests passed or failed, here is evidence.
Такой подход особенно важен для coding agents. Агент может быть полезен только тогда, когда его действия проверяемы. Tool дает проверяемость, если он узкий, типизированный, observable и честно сообщает failures. Tool разрушает проверяемость, если он превращается в универсальную черную коробку.
Практический критерий:
Если вы не можете объяснить, что tool может сделать,
какие у него side effects и как выглядит failure,
его рано давать агенту.
Ключевые термины
| Термин | Значение |
|---|---|
| Tool | Исполняемая функция, доступная агенту. |
| Tool Call | Запрос модели на выполнение tool с аргументами. |
| Runtime | Слой приложения, который валидирует, разрешает и выполняет tool. |
| Input Schema | Формальное описание аргументов tool. |
| Output Schema | Формальное или ожидаемое описание результата tool. |
| Structured Result | Результат в машинно-читаемом формате, например JSON. |
| Permission Boundary | Граница прав: что tool может делать и когда нужен approval. |
| Read Tool | Tool, который только читает информацию. |
| Write Tool | Tool, который изменяет состояние. |
| Destructive Tool | Tool, который может удалить данные, деплоить, платить, отзывать доступ или ломать систему. |
| Idempotency | Свойство повторного вызова без дополнительного изменения состояния. |
| Audit Log | Запись о том, кто, когда и с какими аргументами вызвал tool. |
Примеры tools
Для coding agent типичные tools:
read_file(path)
search_repository(query)
list_files(glob)
apply_patch(patch)
run_tests(command)
get_git_diff()
Для product/project workflow:
create_github_issue(title, body, labels)
comment_on_pull_request(pr_id, body)
get_ci_status(branch)
create_linear_ticket(title, description)
Для data/system workflow:
query_database(sql)
get_database_schema(database)
read_logs(service, time_range)
deploy_preview(branch)
rollback_deployment(deployment_id)
Не все эти tools одинаково безопасны. read_file и rollback_deployment нельзя проектировать одинаково.
Три Категории Tools
Для agent design удобно делить tools на три категории.
| Категория | Что делает | Примеры | Главный риск |
|---|---|---|---|
| Data tools | Дают агенту context. | read_file, search_docs, query_metrics, get_ci_status. |
Агент может получить лишние или sensitive данные. |
| Action tools | Меняют состояние системы. | apply_patch, create_ticket, send_email, deploy_preview. |
Side effects, которые трудно отменить. |
| Orchestration tools | Запускают другой agent/workflow или передают задачу. | run_reviewer_agent, handoff_to_support, start_eval_suite. |
Потеря контроля над context, cost и ownership. |
В coding-agent репозитории чаще всего сначала появляются data tools и локальные action tools:
read/search -> edit scoped files -> run tests -> inspect diff
Orchestration tools стоит добавлять позже, когда уже есть:
- task DAG;
- role definitions;
- run logs;
- output contracts;
- cost limits;
- failure handling;
- human approval gates.
Иначе "agent as a tool" превращается в черный ящик: один агент вызывает другого, тот делает diff, третий пишет summary, а в конце непонятно, кто за что отвечает.
Практический вывод:
Проектируйте data tools широко, action tools узко,
а orchestration tools только после появления process contracts.
Tool Call Lifecycle
Tool call не должен быть невидимой магией. Хороший runtime делает действия observable:
- какой tool выбран;
- какие аргументы переданы;
- был ли approval;
- какой результат получен;
- был ли error;
- что модель сделала после результата.
Tool как contract
Tool - это contract между моделью и внешним действием.
У хорошего tool есть:
- понятное имя;
- короткое description;
- строгая input schema;
- предсказуемый output;
- понятные errors;
- permission boundary;
- документация side effects;
- ограничения на scope;
- auditability.
Пример плохого tool:
do_stuff(input: string) -> string
Проблемы:
- непонятно, что делает;
- input невалидируемый;
- output неструктурированный;
- side effects неизвестны;
- permissions невозможно настроить.
Пример лучше:
run_tests(path: string, mode: "unit" | "integration" | "all") -> {
exit_code: number,
command: string,
stdout_excerpt: string,
stderr_excerpt: string,
passed: boolean
}
Даже без реальной реализации видно, как agent runtime может проверять и объяснять действие.
Input Schema
Input schema ограничивает то, что модель может передать в tool.
Например, для run_tests(path):
{
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Repository-relative path to a test file, test directory, or module."
},
"mode": {
"type": "string",
"enum": ["targeted", "module", "full"],
"description": "How broad the test run should be."
}
},
"required": ["path", "mode"],
"additionalProperties": false
}
Почему schema важна:
- не дает модели передавать случайные поля;
- упрощает UI approvals;
- делает logs понятнее;
- помогает runtime валидировать аргументы до выполнения;
- снижает вероятность dangerous command injection;
- улучшает recoverability после errors.
Слабый вариант:
{
"type": "object",
"properties": {
"command": { "type": "string" }
}
}
Если tool принимает произвольную команду, это уже почти shell access. Такой tool требует гораздо более строгих permissions, чем run_tests(path).
Output Schema
Tool output должен быть полезен модели.
Плохо:
Done.
Лучше:
{
"command": "npm test -- tests/projects.test.ts",
"exit_code": 0,
"passed": true,
"duration_ms": 1840,
"stdout_excerpt": "3 tests passed",
"stderr_excerpt": "",
"artifacts": []
}
Structured result помогает агенту:
- понять, прошла ли проверка;
- процитировать evidence пользователю;
- решить, продолжать ли debugging;
- отличить tool failure от failed test;
- сохранять воспроизводимость.
Tool errors
Нужно различать два типа ошибок.
| Тип ошибки | Пример | Что делать |
|---|---|---|
| Protocol/runtime error | Tool не найден, schema invalid, permission denied | Runtime возвращает ошибку, модель выбирает fallback или спрашивает. |
| Execution error | Тесты упали, API вернул 400, файл не найден | Tool возвращает actionable result, модель может исправлять задачу. |
Для run_tests падение тестов не является "tool сломался". Tool сработал успешно и сообщил, что тесты failed.
Плохой дизайн:
Если tests failed, tool падает как runtime error.
Лучше:
{
"passed": false,
"exit_code": 1,
"failure_summary": "Expected 201, got 400 in projects.test.ts:42"
}
Модель должна видеть failure, чтобы продолжить работу.
Read, Write, Destructive
Tools нужно классифицировать по уровню риска.
| Категория | Примеры | Риск | Policy |
|---|---|---|---|
| READ | read_file, search_repository, get_schema |
Утечка данных, prompt injection | Обычно можно auto-allow в доверенном scope. |
| WRITE | apply_patch, create_issue, comment_pr |
Изменение состояния | Нужны scope, logs, иногда approval. |
| DESTRUCTIVE | delete_table, deploy_prod, charge_card, revoke_access |
Потеря данных, деньги, production impact | Human approval по умолчанию. |
Эта классификация появится снова в уроке 7.6. Пока важно запомнить: tool design начинается с risk model.
Tool и agent loop
В хорошем execution loop tool calls не случайны. Они встроены в workflow:
Read spec
-> search_repository()
-> read_file()
-> plan
-> apply_patch()
-> run_tests()
-> observe result
-> fix if needed
-> report verification
Tool не заменяет reasoning. Tool дает проверяемое действие или наблюдение.
Плохой agent loop:
Модель сразу пишет код.
Не читает файлы.
Не запускает tests.
В конце говорит "готово".
Хороший loop:
Модель читает контекст.
Выбирает минимальный action.
Выполняет tool.
Использует результат.
Доказывает outcome.
Как агент должен выбирать tool
Наличие tool не означает, что агент должен сразу его вызвать. Tool call - это часть reasoning loop, а не замена reasoning. Перед вызовом tool агент должен понимать, какую неопределенность он снимает или какой проверяемый шаг выполняет.
Примеры правильной мотивации:
Я не знаю, где реализован endpoint.
-> search_repository("password reset")
Я нашел файл, но не видел его содержимое.
-> read_file("app/api/auth/reset.ts")
Я изменил код и должен проверить behavior.
-> run_tests("tests/auth/reset.test.ts", mode="targeted")
Я хочу оставить comment во внешнем PR.
-> сначала подготовить preview, затем request approval, затем comment_on_pr()
Плохой pattern:
У меня есть tool, значит я попробую его вызвать.
Так агент начинает "тыкать" в среду, вместо того чтобы двигаться по задаче. Это приводит к лишним tool calls, noisy logs, случайным side effects и росту стоимости. Особенно опасно, если tools имеют write access.
Хорошая последовательность выбора:
- Сформулировать, какой факт или действие нужно.
- Проверить, нельзя ли получить ответ из уже доступного context.
- Выбрать самый узкий tool.
- Передать минимальные аргументы.
- Прочитать result как evidence, а не как повод игнорировать spec.
- Если result failed, отличить failed task от broken tool.
Например, если агенту нужно понять, почему тест упал, он не должен сразу запускать full suite. Сначала лучше прочитать failure summary, открыть relevant file, запустить targeted test и только потом расширять scope проверки.
Tool discipline - это часть управляемой разработки. Чем мощнее tools, тем важнее, чтобы агент вызывал их с понятной целью.
Design example: run_tests(path)
Практика урока - спроектировать tool run_tests(path).
Purpose
Run project tests for a repository-relative path or selected scope and return structured results for the agent.
Inputs
{
"path": "tests/api/projects.test.ts",
"mode": "targeted"
}
Output
{
"command": "npm test -- tests/api/projects.test.ts",
"exit_code": 0,
"passed": true,
"duration_ms": 2140,
"summary": "12 tests passed",
"failure_summary": null
}
Constraints
- Принимать только repository-relative paths.
- Не принимать произвольные shell commands от модели.
- Не запускать full suite без явного выбора
mode: "full". - Ограничивать timeout.
- Возвращать stdout/stderr excerpt, а не бесконечный log.
- Не скрывать failed tests.
Permission
run_tests обычно безопаснее, чем write tool, но все равно может:
- тратить CPU;
- менять cache;
- создавать test artifacts;
- запускать flaky integration tests;
- обращаться к локальным services.
Поэтому policy может быть такой:
| Mode | Approval |
|---|---|
targeted |
Auto-allow |
module |
Auto-allow или soft confirmation |
full |
Ask if expensive |
| integration with external services | Ask |
Bad tool design examples
1. Too much power
run_any_command(command: string)
Проблема: это не test tool, а общий shell. Его нельзя защищать так же, как run_tests(path).
2. No structured output
run_tests(path) -> "ok"
Проблема: агент не может отличить passed, failed, skipped, timeout.
3. Hidden side effects
get_ci_status(branch)
А внутри tool еще перезапускает pipeline.
Проблема: name обещает read, implementation делает write.
4. No permission boundary
deploy(environment)
Без distinctions между preview, staging, production.
Лучше:
deploy_preview(branch)
request_production_deploy(release_id)
Разные риски должны быть разными tools или разными policy paths.
Tool design checklist
Перед тем как дать tool агенту, ответьте:
- Какое конкретное действие выполняет tool?
- Можно ли сузить input schema?
- Есть ли side effects?
- Tool read-only, write или destructive?
- Нужен ли human approval?
- Что вернется при success?
- Что вернется при expected failure?
- Какой timeout?
- Что логируется?
- Может ли output содержать sensitive data?
- Может ли input быть prompt injection carrier?
- Есть ли safer alternative как resource?
- Нужно ли это вообще делать tool, а не локальной командой?
Практика
Спроектируйте run_tests(path).
Заполните:
Name:
Description:
Inputs:
Output:
Permission policy:
Timeout:
Failure modes:
Audit fields:
When not to use:
Пример ответа:
Name:
run_tests
Description:
Run targeted project tests for a repository-relative path and return structured pass/fail output.
Inputs:
- path: repository-relative test path
- mode: targeted | module | full
Output:
- command
- exit_code
- passed
- duration_ms
- summary
- failure_summary
Permission policy:
- targeted auto-allowed
- full suite requires confirmation if expensive
- external integration tests require confirmation
When not to use:
- arbitrary shell commands
- deployment verification
- production smoke tests without explicit approval
Типичные ошибки
| Ошибка | Почему плохо | Исправление |
|---|---|---|
Tool принимает command: string |
Слишком широкая власть | Делать domain-specific inputs. |
| Tool возвращает только plain text | Модель плохо понимает outcome | Вернуть structured result. |
| Нет distinction read/write/delete | Permissions невозможно настроить | Классифицировать risk level. |
| Tool скрывает side effects | Пользователь теряет контроль | Описать side effects и approvals. |
| Tool делает слишком много | Непредсказуемость | Разделить на маленькие tools. |
| Нет failure semantics | Агент не знает, retry или stop | Развести runtime error и execution failure. |
| Tool output слишком длинный | Забивает context | Возвращать summary + excerpts + artifact links. |
Критерии готовности
После урока студент должен уметь:
- объяснить tool как функцию действия;
- отличить tool от skill;
- описать lifecycle tool call;
- спроектировать input/output schema;
- классифицировать tool как READ, WRITE или DESTRUCTIVE;
- объяснить, почему structured output важен;
- спроектировать простой
run_tests(path); - назвать основные риски плохого tool design.
Краткое резюме
Tool - это исполняемая capability агента. Модель выбирает tool и аргументы, runtime валидирует и выполняет действие, результат возвращается модели как наблюдение.
Хороший tool:
- узкий;
- typed;
- observable;
- проверяемый;
- ограниченный по правам;
- честно возвращает failures;
- не скрывает side effects.
Tools делают агента полезным в реальном engineering workflow, но каждый tool расширяет поверхность риска. Поэтому следующий шаг - понять MCP как стандартный способ предоставлять tools, resources и prompts разным AI applications.
Вопросы для самопроверки
- Почему LLM не "выполняет" действие напрямую?
- Чем
run_tests(path)безопаснее, чемrun_command(command)? - Почему failed tests должны быть execution result, а не runtime error?
- Какие поля вы включили бы в output tool
get_ci_status(branch)? - Какие tools в вашем проекте должны требовать human approval?
Что дальше
В следующем уроке мы разберем MCP - Model Context Protocol. Tool можно встроить в один agent runtime напрямую, но если capability должна быть переносимой между Codex, Claude Code, Cursor, внутренним agent host и другими клиентами, нужен стандартный способ подключения. MCP как раз решает эту задачу.