Хватит создавать ненадёжных агентов: используйте Workflows и Memory
Детерминированные паттерны для нестохастических моделей.
У LLM есть странная особенность: они блестяще понимают нюансы, но ужасно следуют рецептам. Дайте GPT-4 расплывчатую задачу — и он рассмотрит варианты. Дайте ему точную последовательность шагов — и он может пропустить третий, потому что пятый «казался более уместным».
Это не баг модели. Это фундаментальная характеристика вероятностных систем, пытающихся решать детерминированные задачи.
Я наблюдал, как команды с этим борются. Они создают агента для обработки возвратов, дают ему десяток инструментов и ожидают, что он будет надёжно выполнять бизнес-процесс. Иногда всё работает идеально. Иногда он галлюцинирует несуществующие согласования. Иногда застревает, переспрашивая одно и то же три раза.
Решение — не лучшие промпты. Это понимание, когда пора перестать просить LLM «думать» и начать заставлять его «подчиняться».
Когда детерминизм побеждает креативность
Подумайте, что происходит при обработке тикета поддержки. Реальная бизнес-логика выглядит примерно так:
- Получить данные тикета из базы
- Проверить, подходит ли пользователь для возврата (правила политики)
- Убедиться, что транзакция существует и ещё не была возвращена
- Рассчитать сумму возврата
- Обработать обратный платёж
- Обновить статус тикета
- Отправить письмо с подтверждением
Можно поручить это LLM как упражнение на вызов инструментов. По моему опыту, это напрашивается на проблемы. Модель может решить, что шаги 2 и 3 «по сути одно и то же», и пропустить один. Или обработать возврат до проверки права на него, потому что пользователь выглядел расстроенным.
Workflows существуют именно для такого сценария. Они не впечатляющие, но в этом и суть.
Планировщик активностей по погоде
Вот практический пример, демонстрирующий паттерн. Нам нужны точные, фактические данные о погоде в паре с креативными предложениями активностей. Получение погоды никогда не должно быть креативным, а вот предложения — должны.
import { createWorkflow, createStep } from '@mastra/core/workflows';import { Agent } from '@mastra/core/agent';import { openai } from '@ai-sdk/openai';import { z } from 'zod';
// Шаг 1: Получение данных о погоде (Детерминированный)const fetchWeather = createStep({ id: 'fetch-weather', description: 'Fetches weather forecast for a given city', inputSchema: z.object({ city: z.string(), }), outputSchema: z.object({ location: z.string(), temperature: z.number(), conditions: z.string(), precipitationChance: z.number(), }), execute: async ({ inputData }) => { // ... (fetch logic) ... const weather = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code&daily=precipitation_probability_mean`).then(r => r.json());
return { location: inputData.city, temperature: weather.current.temperature_2m, conditions: getWeatherCondition(weather.current.weather_code), precipitationChance: weather.daily.precipitation_probability_mean[0], }; },});
// Шаг 2: Агент предлагает активности (Креативный)const activityPlanner = new Agent({ id: 'activity-planner-agent', name: 'Activity Planner', instructions: `You are a local activities expert. Based on weather conditions, suggest 3-5 appropriate activities. - For rain (>50% precipitation), prioritize indoor activities - For extreme temperatures, consider climate-appropriate options - Always include one adventurous and one relaxing option`, model: openai('gpt-5'),});
const planActivities = createStep({ id: 'plan-activities', description: 'Uses AI to suggest activities based on weather', inputSchema: z.object({ location: z.string(), temperature: z.number(), conditions: z.string(), precipitationChance: z.number(), }), outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData }) => { const prompt = `Weather in ${inputData.location}: ${inputData.temperature}°C...`; const response = await activityPlanner.generate(prompt); return { activities: response.text }; },});
// Конвейерexport const activityPlannerWorkflow = createWorkflow({ id: 'activity-planner', inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ activities: z.string() }),}) .then(fetchWeather) .then(planActivities);
activityPlannerWorkflow.commit();LLM никогда не обращается к API погоды напрямую. Он получает проверенные данные на входе, а затем делает то, в чём действительно хорош: предлагает контекстные рекомендации. Если перевернуть это и позволить агенту самому получать данные о погоде, рано или поздно вы получите солнечный прогноз, когда на самом деле идёт дождь.
Когда стоит задуматься о workflows:
- У вас есть известная последовательность шагов, которые должны выполняться в определённом порядке
- Вам нужна наблюдаемость на каждом этапе (логи, метрики, тайминги)
- Нужна логика повторных попыток для ненадёжных внешних API
- Бизнес-правила нельзя «интерпретировать» — их нужно соблюдать точно
Проблема контекстного окна, о которой никто не говорит
Есть паттерн, который я продолжаю встречать. Кто-то создаёт чат-бота. Он отлично работает при тестировании. Затем в продакшене пользователи ведут более длинные диалоги, и вдруг бот начинает теряться.
Разработчик смотрит в логи и понимает, что отправляет всю историю разговора с каждым запросом. Все 47 сообщений. Они сжигают токены и контекстное пространство на информацию, которая в основном нерелевантна.
Хуже того, есть феномен, который исследователи называют «потерянным в середине» — модели работают хуже, когда релевантная информация похоронена в длинном контексте. Модель буквально не видит леса за деревьями.
Отправка полной истории разговора кажется безопасной. Вы даёте модели «всю информацию». Но на самом деле вы усложняете модели фокусировку на важном.
Рабочая память против долговременного хранилища
Система памяти Mastra даёт вам и то, и другое. Рабочая память хранит последние сообщения в контекстном окне. Семантический поиск ищет в исторических сообщениях, когда текущий запрос кажется связанным.
import { Agent } from '@mastra/core/agent';import { Memory } from '@mastra/memory';import { LibSQLStore } from '@mastra/libsql';
export const memoryAgent = new Agent({ id: 'memory-agent', name: 'Memory Agent', instructions: 'You are a helpful assistant with perfect recall of our conversations.', model: openai('gpt-5'), memory: new Memory({ storage: new LibSQLStore({ id: 'memory-agent-store', url: 'file:../mastra.db', }), options: { lastMessages: 20, // Keep last 20 messages in context semanticRecall: { enabled: true, // Use embeddings to find old stuff topK: 5, threshold: 0.7, }, }, }),});Вот как это выглядит на практике. Пользователь спрашивает: «А какой итальянский ресторан ты рекомендовал в прошлом месяце?»
Без семантического поиска агент видит последние 20 сообщений. Рекомендация ресторана была сообщением 487 из 506. Её нет. Агент отвечает: «У меня нет этой информации.»
С семантическим поиском:
- Запрос преобразуется в эмбеддинг:
[0.234, -0.567, 0.891, ...] - Эмбеддинг сравнивается с историческими сообщениями
- Сообщение 487 («Рекомендую Trattoria Bella — их карбонара невероятна») набирает 0,89 сходства
- Это сообщение вставляется в текущий контекст
- Агент отвечает: «Я рекомендовал Trattoria Bella. Их карбонара — то, что привлекло моё внимание.»
Агент кажется обладающим идеальной памятью, используя лишь долю контекстного окна. Это не просто хитрая инженерия — это функциональная необходимость, когда разговоры выходят за пределы пары десятков сообщений.
Координация через Agent Networks
Иногда нужны и структура, и гибкость. Чистые workflows слишком жёсткие. Чистые агенты слишком непредсказуемы.
Agent Networks дают вам координатора, который решает, какого специализированного агента или workflow вызвать в зависимости от задачи. Думайте об этом как об умном балансировщике нагрузки для AI-возможностей.
export const coordinatorAgent = new Agent({ id: 'coordinator-agent', name: 'Research Coordinator', instructions: `You are a network of researchers and writers. - Use researchAgent for gathering facts - Use writingAgent for producing final content - Use weatherTool for current weather data - Use activityPlannerWorkflow for location-based planning
Always produce comprehensive, well-structured responses.`, model: openai('gpt-5'),
// Available primitives agents: { researchAgent, writingAgent }, workflows: { activityPlannerWorkflow }, tools: { weatherTool },
// Network requires memory memory: new Memory({ storage: new LibSQLStore({ id: 'network-store', url: 'file:../network.db' }), }),});Когда вы запрашиваете эту сеть, координатор анализирует запрос и маршрутизирует соответствующим образом:
- «Мне нужны факты о X» запускает исследовательского агента
- «Спланируй выходные в Сиэтле» запускает workflow планировщика активностей
- «Напиши отчёт о Y» подключает агента-писателя
Этот паттерн масштабируется лучше, чем попытка впихнуть всё в одного мега-агента. Специализированные агенты развивают сфокусированную экспертизу. Координатор занимается маршрутизацией. Каждая часть делает то, в чём хороша.
Собираем всё вместе
Реальные продакшен-AI-системы нуждаются в архитектуре, а не только в промптах. Вы строите распределённые системы, где некоторые узлы оказываются LLM.
Workflows дают вам гарантии, когда всё должно происходить точно правильно. Memory даёт вам контекст без сжигания бюджета токенов. Agent Networks позволяют компоновать сложность из более простых частей.
Ничего из этого не впечатляюще. Но после того, как я наблюдал, как достаточно «полностью автономных агентов» падают в продакшене, я начал ценить скучную надёжность выше захватывающей непредсказуемости.
Ваш опыт может отличаться, но по моим наблюдениям, системы, которые действительно запускаются и продолжают работать, — это те, что рассматривают LLM как компоненты более крупной архитектуры, а не как волшебные ящики, решающие всё.
Ресурсы
Читайте серию
- Маршрутизация LLM
- Безопасность и ограничители
- MCP и интеграция инструментов
- Workflows и Memory (Этот пост)