Агенту нельзя просто дать backend API и ждать магии
Делали простой сценарий:
— есть LLM-агент
— есть API сервиса поиска круизов
— есть клиент, который пишет: “хочу круиз из Казани в июле на 5 дней”
Задача агента — вызвать поиск и вернуть подходящие варианты.
Казалось бы, всё просто: берем метод API, заворачиваем в MCP tool, описываем параметры и даем агенту.
Формально работает. Но на практике быстро выяснилось, что tool получился не “LLM-friendly”, а “backend-friendly”.
То есть удобный для кода, но хрупкий для модели.
И в какой-то момент стало понятно: проблема не в агенте, а в интерфейсе, который мы ему дали.
Антипаттерн 1 — цепочка вызовов вместо одного
Проблема: инструмент поиска принимал не человеческие сущности, а внутренние id: города, теплоходы, направления, стоянки.
Поэтому агенту приходилось сначала вызывать tool-маппинг “название → id”, а потом уже search_cruises.
Что ломалось:
— модель забывала вызвать маппинг
— вызывала его не для всех сущностей
— путала полученные id между параметрами: cityStart, cityEnd, cities
Короткий вывод: если пользователь говорит названиями, а инструмент требует id, мы заставляем LLM быть не ассистентом, а glue-code между UI и базой данных.
Фикс: принимать в LLM-facing tool названия:
— город отправления: Москва
— теплоход: Беда
— направление: Волга
А id-резолвинг делать внутри инструмента.
Антипаттерн 2 — непопулярные типы аргументов
Проблема: некоторые параметры API были типизированы странно. Например, одиночное значение передавалось как массив: не durationFrom = 7, а durationFrom = [7].
Что ломалось:
— модель передавала скаляр вместо массива
— массив не там
— не тот shape
— параметр вообще пропускался
Короткий вывод: LLM лучше работает с boring-типами: string, number, boolean, простые объекты вроде min/max.
Необычная форма данных — это не нейтральная backend-деталь, а дополнительная когнитивная нагрузка.
Фикс:
Было:
— durationFrom: [7]
— durationTo: [10]
Стало:
— duration_days_min: 7
— duration_days_max: 10
Или один понятный объект: duration_days: от 7 до 10
Антипаттерн 3 — плохие имена параметров
Проблема: параметры назывались как поля backend API, а не как понятия из пользовательского запроса: cities, dateTo, costFrom, category_id, motorships.
Что ломалось: модель путала семантику.
Например, cities может значить город отправления, город прибытия, стоянки, города маршрута или просто направление.
dateTo тоже неочевиден: это дата отправления “до” или дата прибытия “до”?
Короткий вывод: имя параметра — это часть prompt’а.
Если имя требует знания внутренней модели данных, LLM начинает угадывать.
Фикс:
Было: cities, dateTo, costFrom
Стало: must_visit_cities_or_landmarks, departure_before, price_min_rub
Да, имена становятся длиннее. Но для LLM это плюс: название параметра одновременно работает как мини-инструкция.
Главный вывод
Хороший tool для агента — это не thin wrapper над backend API.
Backend API проектируется под код: id, внутренние типы, короткие поля, цепочки методов.
LLM-facing tool лучше проектировать под модель и пользовательскую задачу:
— одно пользовательское действие = один tool
— человеческие названия вместо внутренних id
— простые boring-типы вместо legacy-форматов
— говорящие имена параметров
— минимум промежуточного состояния
Иначе получается странная ситуация: модель вроде умная, MCP вроде подключили, API вроде рабочий — а агент всё равно хрупкий.
Потому что для LLM tool — это не endpoint.
Это UX.
Post #540
586
- 👍 19
- ❤ 9
- 🤩 5
- 🤷♂ 4