DanLevy.net

Хватит создавать ненадёжных агентов: используйте Workflows и Memory

Детерминированные паттерны для недетерминированных моделей.

У LLM есть странная особенность: они блестяще понимают нюансы, но ужасно следуют рецептам. Дайте GPT-4 расплывчатую задачу — и он рассмотрит варианты. Дайте ему точную последовательность шагов — и он может пропустить третий, потому что пятый «казался более уместным».

Это не баг модели. Это фундаментальная характеристика вероятностных систем, пытающихся решать детерминированные задачи.

Я наблюдал, как команды с этим борются. Они создают агента для обработки возвратов, дают ему десяток инструментов и ожидают, что он будет надёжно выполнять бизнес-процесс. Иногда всё работает идеально. Иногда он галлюцинирует несуществующие согласования. Иногда застревает, переспрашивая одно и то же три раза.

Решение — не лучшие промпты. Это понимание, когда пора перестать просить LLM «думать» и начать заставлять его «подчиняться».


Когда детерминизм побеждает креативность

Подумайте, что происходит при обработке тикета поддержки. Реальная бизнес-логика выглядит примерно так:

  1. Получить данные тикета из базы
  2. Проверить, подходит ли пользователь для возврата (правила политики)
  3. Убедиться, что транзакция существует и ещё не была возвращена
  4. Рассчитать сумму возврата
  5. Обработать обратный платёж
  6. Обновить статус тикета
  7. Отправить письмо с подтверждением

Можно поручить это LLM как упражнение на вызов инструментов. По моему опыту, это напрашивается на проблемы. Модель может решить, что шаги 2 и 3 «по сути одно и то же», и пропустить один. Или обработать возврат до проверки права на него, потому что пользователь выглядел расстроенным.

Workflows существуют именно для такого сценария. Они не впечатляющие, но в этом и суть.

Планировщик активностей по погоде

Вот практический пример, демонстрирующий паттерн. Нам нужны точные, фактические данные о погоде в паре с креативными предложениями активностей. Получение погоды никогда не должно быть креативным, а вот предложения — должны.

src/mastra/workflows/activity-planner.ts
import { createWorkflow, createStep } from '@mastra/core/workflows';
import { Agent } from '@mastra/core/agent';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Step 1: Fetch weather data (Deterministic)
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&current=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],
};
},
});
// Step 2: Agent suggests activities (Creative)
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 };
},
});
// Pipeline
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:


Проблема контекстного окна, о которой никто не говорит

Я постоянно вижу один и тот же паттерн. Кто-то создает чат-бота. Он отлично работает во время тестов. Затем в продакшене пользователи ведут более длинные диалоги, и бот внезапно «теряется».

Разработчик смотрит логи и понимает, что с каждым запросом отправляется вся история переписки. Все 47 сообщений. Они сжигают токены и место в контексте ради информации, которая в основном не важна.

Хуже того, существует феномен, который исследователи называют «потеря в середине» (lost in the middle): модели работают хуже, когда релевантная информация зарыта в длинном контексте. Модель буквально не видит леса за деревьями.

Отправка полной истории кажется безопасной. Вы даете модели «всю информацию». Но на самом деле вы мешаете ей сосредоточиться на главном.

Рабочая память против долгосрочного хранилища

Система памяти Mastra дает вам и то, и другое. Рабочая память удерживает последние сообщения в окне контекста. Семантический поиск (Semantic recall) ищет в истории сообщений, когда текущий запрос кажется связанным с прошлым.

src/mastra/agents/memory-agent.ts
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. Она исчезла. Агент говорит: «У меня нет этой информации».

С семантическим поиском:

  1. Запрос превращается в эмбеддинг: [0.234, -0.567, 0.891, ...]
  2. Эмбеддинг сравнивается с историей сообщений
  3. Сообщение №487 («Я бы рекомендовал Trattoria Bella — их карбонара великолепна») получает оценку сходства 0.89
  4. Это сообщение впрыскивается в текущий контекст
  5. Агент отвечает: «Я рекомендовал Trattoria Bella. Их карбонара — это то, что меня зацепило».

У агента как будто идеальная память, хотя он использует лишь малую часть контекстного окна. Это не просто изящная инженерия — это функциональная необходимость, как только диалоги выходят за пределы нескольких десятков сообщений.


Координация через сети агентов

Иногда вам нужны и структура, и гибкость. Чистые Workflows слишком жесткие. Чистые агенты слишком непредсказуемы.

Сети агентов (Agent networks) дают вам координатора, который решает, какого специализированного агента или workflow вызвать в зависимости от задачи. Думайте об этом как об умном балансировщике нагрузки для возможностей ИИ.

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' }),
}),
});

Когда вы обращаетесь к этой сети, координатор анализирует запрос и направляет его соответствующим образом:

Этот паттерн масштабируется лучше, чем попытки впихнуть всё в одного мега-агента. Специализированные агенты развивают глубокую экспертизу. Координатор берет на себя маршрутизацию. Каждая часть делает то, что умеет лучше всего.


Подведем итоги

Реальные производственные ИИ-системы нуждаются в архитектуре, а не только в промптах. Вы строите распределенные системы, где некоторые узлы оказываются LLM.

Workflows дают гарантии, когда вам нужно, чтобы всё прошло строго по плану. Memory обеспечивает контекст, не сжигая ваш бюджет на токены. Сети агентов позволяют собирать сложное из простых частей.

Ни в чем из этого нет гламура. Но наглядевшись на то, как «полностью автономные агенты» проваливаются в продакшене, я начал ценить скучную надежность выше эффектной непредсказуемости.

Ваши результаты могут отличаться, но по моему опыту, системы, которые действительно выходят в релиз и продолжают работать, — это те, что рассматривают LLM как компоненты в более крупной архитектуре, а не как магические коробки, решающие всё подряд.

Ресурсы

Читать серию

  1. Маршрутизация LLM
  2. Безопасность и Guardrails
  3. MCP и интеграция инструментов
  4. Workflows и Memory (Этот пост)