Inhalt
Агенту нельзя просто дать 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.