Сессии — это простое решение для истории чата, но они исчезают при закрытии браузера или перезапуске сервера. Для всего, что должно сохранять память — бота поддержки, ассистента для заметок, общей беседы — вам нужно, чтобы история пережила процесс.
В NanoAgent это реализовано как функция высшего уровня. Вам больше не нужно вручную писать код вроде «загрузить массив, сохранить массив» — вы просто подключаете к агенту драйвер памяти. Он автоматически загружает беседу для сессии и сохраняет её после каждого завершенного вызова chat() / stream() .
Одна строка, которая меняет всё
$agent->setMemory(new FileMemory(__DIR__ . '/memory'), 'demo');
Вот и весь новый API. setMemory() принимает любой объект, реализующий NanoAgent\Contracts\Memory , и идентификатор сессии. С этого момента агент загружает сохраненную историю в самом начале и сохраняет её после каждого ответа. Никакой ручной хореографии с getHistory() / setHistory() / file_put_contents() .
Как это устроено в примере
Пример Memory Chat позволяет переключать драйверы с помощью выпадающего списка, чтобы вы могли почувствовать разницу:
use NanoAgent\Agent;
use NanoAgent\Memory\ArrayMemory;
use NanoAgent\Memory\FileMemory;
// One shared conversation for the demo; in a real app use a user or chat id.
$sessionId = 'demo';
$drivers = [
'file' => 'FileMemory (persistent)',
'array' => 'ArrayMemory (current request only)',
];
$driver = isset($_GET['driver'], $drivers[$_GET['driver']]) ? $_GET['driver'] : 'file';
$memory = match ($driver) {
'array' => new ArrayMemory(),
default => new FileMemory(__DIR__ . '/memory'),
};
$agent = new Agent(
llm: [
'provider' => $config['provider'] ?? 'groq',
'model' => $config['model'] ?? 'llama-3.3-70b-versatile',
'api_key' => $config['api_key'] ?? ''
],
systemPrompt: "You are a helpful assistant with a perfect long-term memory."
);
// Loads any stored history and saves after every chat() call.
$agent->setMemory($memory, $sessionId);
Всё остальное — это стандартное использование агента, а забота о памяти берётся на себя:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['message'])) {
// History is persisted automatically once the reply is complete.
$agent->chat(trim($_POST['message']));
}
Сброс истории также делается одним вызовом. clearHistory() очищает историю в памяти процесса и удаляет сохраненную строку/файл:
if (isset($_POST['reset'])) {
$agent->clearHistory();
header("Location: " . $selfUrl);
exit;
}
Три встроенных драйвера
| Драйвер | Хранилище | Требования | Используйте, когда |
|---|---|---|---|
ArrayMemory |
Только текущий процесс | - | Тесты, долгоживущие воркеры (очереди, ReactPHP, Swoole, CLI-циклы) |
FileMemory |
Один JSON-файл на сессию | - | Отсутствие инфраструктуры, один пользователь или небольшие утилиты |
PdoMemory |
SQL-таблица (SQLite, MySQL, PostgreSQL) |
ext-pdo + драйвер |
Многопользовательские приложения, продакшн, когда нужны запросы/очистка |
ArrayMemory — это базовая реализация («он всё забывает»), полезная для доказательства того, что именно драйвер отвечает за сохранение памяти. FileMemory именует файлы с помощью хэша от идентификатора сессии, поэтому произвольные идентификаторы (электронные почты, UUID, сырой ввод пользователя) не могут выйти за пределы директории; он записывает данные во временный файл, а затем переименовывает его, так что упавший запрос никогда не оставит наполовину записанный файл. PdoMemory сохраняет каждую сессию в виде одной строки с использованием нативных upsert-запросов в SQLite, MySQL/MariaDB и PostgreSQL:
use NanoAgent\Memory\PdoMemory;
// SQLite file, created on first use (keep it outside your public web root):
$agent->setMemory(PdoMemory::sqlite(__DIR__ . '/memory/chat.sqlite'), "user-$userId");
// Or reuse an existing PDO connection (MySQL, Postgres, Laravel's, ...):
$agent->setMemory(new PdoMemory($existingPdo), $sessionId);
Подробное руководство по работе с базами данных — строки подключения, схемы таблиц для каждой БД, безопасность идентификаторов сессий и очистка — находится в документации по PdoMemory.
Выбор идентификатора сессии
Идентификатор сессии определяет, какая беседа будет загружена, поэтому он должен браться из источника, которому доверяет ваш сервер, и никогда напрямую из пользовательского ввода — иначе один пользователь сможет подставить ID другого и прочитать его чат.
$agent->setMemory($memory, 'user-' . $user->id); // one per logged-in user
$agent->setMemory($memory, "user-{$user->id}-chat-{$chatId}"); // several per user
$agent->setMemory($memory, 'visitor-' . session_id()); // anonymous visitors
Почему это лучше ручного подхода с файлами
Вы по-прежнему можете делать это вручную — setHistory() при загрузке, file_put_contents(getHistory()) после каждого ответа (и старая версия этой статьи показывала именно такой подход). Драйвер памяти предлагает ту же идею, но с уже встроенным циклом сохранения:
-
Никакого шаблонного кода — один вызов
setMemory()вместо логики загрузки/сохранения/очистки в каждом запросе. -
Меняйте хранилище, а не агента — переходите от
FileMemoryкPdoMemory(или вашему собственному драйверу), и в коде агента не изменится ни единой строки. - Всё просто работает — драйвер сохраняет данные после каждого завершенного диалога, а вызов, вызвавший исключение посреди диалога, не сохраняется, поэтому в хранилище всегда остается последний успешно завершенный обмен репликами.
Нужен бэкенд, которого нет «из коробки» — Redis, кэш фреймворка, Eloquent? Реализуйте интерфейс NanoAgent\Contracts\Memory , состоящий из трех методов ( load() , save() , clear() ), и передайте его прямо в setMemory() .
Часть серии примеров NanoAgent. Лендинг + демо.
Комментарии (0)
Пока нет комментариев — будьте первым.