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

AI-агент с долгосрочной памятью: строю личного аналитика с Claude Code

2026/03/10 10:59
14м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]
Изображение сгенерировано в Recraft
Изображение сгенерировано в Recraft

Разрабатываю AI-агента персональной аналитики для себя. Любопытной инженерной задачей стала архитектура памяти: как сделать, чтобы агент помнил не только последний разговор, но и паттерны, накопленные за месяцы. В этой статье описана архитектура, рабочие решения и грабли, на которые я наступила.

Зачем он мне нужен

Я уже давно не техред и даже не техпис, а последний текст на Хабре был написан мной больше трёх лет назад. За это время я стала дипломированным системным аналитиком, работаю в финтехе, и три года потихоньку расту в AI и ML.

Невзирая на профессиональные перемены, в моей жизни есть что-то неизменное — много лет я трекаю данные о движении и здоровье, работаю над улучшением своих HRV и VO2, слежу за трендами longevity и пью БАДы по результатам анализов, а не по рилсам.

Пару лет носила Oura, но базовый носитель – это Apple Watch, данные о тренировках и маркерах с которых копятся в Apple Health с 2017 года. Это 9 лет и около 9 миллионов записей.

Как и многие продвинутые любители wearables, я храню анализы в PDF, пользуюсь умными весами, считаю КБЖУ через приложение, делаю замеры, обожаю дашборды, сравнивать данные, видеть прогресс, фиксить регресс и анализировать свой образ жизни через разнообразные показатели.

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

Неудивительно, что в какой-то момент я просто устала кормить этим всем нейронки, путаться в архивах и захотела одну большую аналитическую кнопку.

Зачерпнула из своего изобильного data lake живой воды — получила внятный срез, где всё разложено по полочкам: отчёты и выборки, подсвеченные проблемные места, скорректированный план тренировок и питания, понимание как улучшить сон. А обратная связь — живой разбор с когнитивными искажениями, схемами реагирования и конкретными действиями. КПТ и схема-терапия под капотом. Ну просто карманные Брайан Джонсон с любимым психоаналитиком, тренером и семейным доктором строчат тебе сообщения — удобно же.

Так и родилась идея агента с точкой входа через ТГ, которому я буду всё это рассказывать и показывать, а он сам будет всё мэтчить и выдавать отчёты по запросу. И вот – Sketching… Gusting… Boondoggling… Sublimating… Flummoxing… Flowing… Perambulating… — кайфинг от взаимодействия с СС начался.

Что получилось: обзор системы

11aaf23546ca390904d1fd05996c0b6f.png

Стек: Python 3.12, FastAPI, PostgreSQL 16 + pgvector, aiogram 3, n8n (только для триггеров и вебхуков, не логика), Docker Compose.

Интерфейс — telegram-бот, голосовые сообщения, мультимодальность. Голос транскрибируется через Groq Whisper (бесплатно 2 часа в день, качество отличное).

LLM routing

Два провайдера с разными ролями:

  • Claude Haiku — агентика: /ask, /report, /scope, /mind, Memory Synthesizer. Следует системному промпту, не добавляет RLHF safety-дисклеймеров (например, «проконсультируйтесь с врачом» в каждый ответ про здоровье) и таблицы сырых данных вопреки инструкциям, выбран в качестве аналитика для отчётов.

  • GPT-4o-mini — парсинг: еда, фазы сна по фото, замеры тела, даты, медицинские документы. Дешевле, достаточно точно для структурированного извлечения, отвратительно по tone-of-voice и слабенько для аналитики. Сикофансия ChatGPT раздражает полмира, а назойливые присказки в начале и в конце сообщений не лечатся самыми жёсткими промптами.

Выбор пал на Haiku, а не на великолепные Sonnet или Opus, потому что пока играю в свою маленькую игрушку и практикую на мелких масштабах. Anthropic Tier 1 ограничивает 30k токенов/мин. Sonnet потребляет 35–60k за два шага агента — rate limit на каждом запросе. Haiku вписывается в лимит. Все вызовы идут через единую функцию ask_model() с автофоллбэком по провайдерам и логированием токенов, стоимости и времени.

Tool-calling агент: 14 инструментов

2a6c09fc81a836fccd3396b4e281dc18.png

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

14 инструментов в четырёх группах:

  • данные тела: get_health_metrics, get_sleep, get_body_metrics, get_nutrition, get_workouts

  • память и профиль: get_memory_insights, get_user_profile, search_personal_data, search_knowledge_base, get_mind_entries

  • медицина: get_lab_results, get_doctor_reports

  • аналитика: compute_correlation (коэффициент Спирмена с p-value), get_trend (помесячная агрегация).

Все инструменты поддерживают days (последние N дней) или from_date/to_date. При периоде больше 90 дней происходит автоматическая агрегация по месяцам: 24 строки вместо 730. Это ключевой момент: без агрегации один вызов get_health_metrics за 2 года возвращал 730 строк — 234k токенов, модель захлёбывалась.

Динамическая выборка инструментов

Для точечных /ask-запросов — показать динамику веса или состава тела за полгода — запускать все 14 инструментов избыточно. Навайбкодила select_tools() — keyword-matching по 8 категориям возвращает 3–4 инструмента вместо 14. Всегда включает get_user_profile и get_memory_insights. Fallback на все 14, если запрос слишком размытый или охватывает 4+ категории одновременно.

def select_tools(query: str) -> list[dict]: q = query.lower() selected = set(_ALWAYS_TOOL_NAMES) # user_profile + memory_insights matched = 0 for config in _TOOL_CATEGORIES.values(): if any(kw in q for kw in config["keywords"]): selected.update(config["tools"]) matched += 1 if matched == 0 or matched >= _FALLBACK_THRESHOLD: return ANALYST_TOOLS # всё return [_TOOL_BY_NAME[name] for name in selected]

Результат: /ask точечный запрос — 9,7k токенов вместо 101k. Основной выигрыш не в данных, а в схемах инструментов: каждая схема весит ~1k токенов, и они передаются на каждой итерации agent loop.

Компактный формат ответов инструментов

Вместо json.dumps() строк из БД — pre-aggregation в Python: средние за период, отдельно аномалии. 9 из 14 инструментов переведены на этот формат. Вот как выглядит вывод get_health_metrics за короткий период:

Метрики 2026-02-20 — 2026-03-06 (средние): HRV 41.2мс, ЧСС 72.4, ЧССп 58.1, шаги 9840/д, 512ккал, 7.8км Аномалии: 2026-02-23: HRV 28мс (низкий) 2026-03-01: шаги 312 (нет активности)

Агент получает готовую картину и тратит токены на анализ, а не на разбор сырых строк.

Долгосрочная память: 5 слоёв

dc6ca93e6a4927f443cfaa9915bdb480.png

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

Реализовано 5 слоёв:

Слой 1 — сессионная память. Последние 6 сообщений из БД, SQL-запрос, только для plain text. Дёшево, быстро, покрывает follow-up вопросы в рамках одного разговора.

Слой 2 — структурированная эпизодическая. PostgreSQL: health_metrics, sleep_sessions, lab_results, body_metrics, nutrition_logs, messages. Весь исторический массив агент достаёт через SQL-инструменты.

Слой 3 — семантическая личная. Pgvector, таблица message_embeddings. Эмбеддинги всех сообщений (OpenAI text-embedding-3-small, 1536 dims). Поиск по смыслу «когда я писала про усталость» найдёт релевантное, даже если слово не встречалось буквально.

Слой 4 — база знаний. Pgvector, таблица knowledge_chunks. Загруженные документы: PDF, TXT, MD, URL. Статьи и исследования по витаминам, метаболизму, женскому здоровью, протоколы тренировок, медицинские материалы — многое из этого собирается в NotebookLM и пока руками приносится боту. search_knowledge_base.

Слой 5 — синтезированные паттерны. Memory_insights, верифицированные пользователем инсайты агент читает при каждом запросе.

Для верификации придуман Memory Synthesizer — синтезатор памяти. Запускается механизм по еженедельному фоновому job (n8n cron). Один LLM-вызов анализирует данные за 28 дней по пяти источникам: health_metrics, messages, nutrition_logs, body_metrics, user_profile. Возвращает JSON с новыми паттернами и подтверждёнными старыми.

Механизм накопления доказательной базы — ключевой момент архитектуры. Паттерн не уходит пользователю сразу: он накапливает подтверждения при каждом прогоне. Порог — 3 подтверждения. Только потом — пуш в Telegram, пользователь верифицирует через /memory. «Да» → memory_insights, агент читает при каждом запросе. «нет» → rejected. TTL pending_insights — 30 дней: паттерн, который не набрал подтверждений, удаляется.

# synthesizer.py — фрагмент накопления паттернов for pid in confirmed_ids: row = await conn.fetchrow( """ UPDATE pending_insights SET confirmations = confirmations + 1, last_seen = $1 WHERE id = $2 AND user_id = $3 AND status = 'pending' RETURNING id, pattern_text, confirmations """, today, pid, user_id, ) if row and row["confirmations"] == CONFIRMATIONS_THRESHOLD: # 3 push_patterns.append((row["id"], row["pattern_text"]))

Пример паттерна, который система нашла: «Когда HRV < 40 мс, в следующие 2 дня калораж выше нормы на ~200 ккал». Это наблюдение из данных конкретного человека, подтверждённое трижды за три недели и верифицированное им самим.

User_profile. Это скорее постоянный контекст, чем слой памяти: подгружается в system prompt при каждом запросе. Медицинский фон, цели, личностный профиль. Инструкция агенту в формате: «используй такой-то факт для интерпретации данных».

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

RAG по документам

Два слоя RAG с одинаковым техническим пайплайном, но разной семантикой.

Message_embeddings — личная история: всё, что пользователь говорил боту. Search_personal_data находит релевантное по смыслу. Полезно для запросов типа «когда я в последний раз писала, что устала после тренировки» — точный SQL не поможет.

Knowledge_chunks — загруженные материалы. Пайплайн: документ → PyMuPDF (текст) или Vision fallback, если нет текстового слоя → чанкинг → OpenAI text-embedding-3-small → pgvector с ivfflat индексом. Cosine search. Добавить документ: переслать PDF/TXT/MD боту или вставить URL.

В одном ответе агент может совместить оба слоя: данные из БД + релевантный кусок из загруженного исследования. Агент сам решает, когда обращаться к каждому.

Источники данных

Apple Health

Программирование на шоткатах — для очень упорных и терпеливых
Программирование на шоткатах — для очень упорных и терпеливых

Изначально планировался полный пайплайн через iOS Shortcuts → n8n → PostgreSQL. По факту работает только один большой шоткат: активность (HRV, шаги, ЧСС, калории, VO2max и ещё пять показателей). Установлен автозапуск шотката на iPhone, но работает он через раз, и если телефон заблочен, то может не сработать. Поэтому запускаю шоткат перед сном сама.

Сон и тренировки на шоткатах убраны в бэклог — iOS 26.3 не принимает комбинацию Source filter + Value filter в одном шоткате, отдаёт пустой результат. Фундаментальный баг HealthKit, из-за которого 5 часов жизни и сотни токенов СС потрачено впустую.

Внезапной задачей стала дедупликация: iPhone и Apple Watch пишут одни метрики параллельно. Решение — source filter только для Apple Watch + нормализация на входе в Python: запятая → точка (русская локаль iPhone), вложенный объект {"": "2026-02-25"} → дата. Оба бага нашлись уже в проде.

# intake.py def normalize_decimal(value) -> Decimal | None: """Запятая → точка (русская локаль iPhone), пустая строка → None.""" if value is None: return None s = str(value).strip().replace(",", ".") if not s: return None try: return Decimal(s) except InvalidOperation: return None

Решение пока такое: еженедельный экспорт export.xml. Import-скрипты автоматически берут from_date по MAX(recorded_date) в БД, обрабатывают только новое. Таким образом качественная глубокая аналитика будет раз в неделю, между экспортами фазы сна и ЧСС тренировок только исторические.

Тренировки из XML

Import_workouts.py парсит <Workout> + <WorkoutStatistics> из export.xml. WorkoutStatistics содержит HKQuantityTypeIdentifierHeartRate — avg и max HR для каждой тренировки без JOIN к записям пульса. Залито 4019 тренировок за 2017–2026.

Первая версия на iterparse накапливала DOM-дерево в памяти: 6+ ГБ на файле 3.9 ГБ. Ubuntu, которую я накатила на свой старенький MacBookPro, не справлялась. Попросила СС что-то с этим сделать, и он переписал на xml.sax.ContentHandler — событийная модель, 26 МБ:

class WorkoutHandler(xml.sax.ContentHandler): def startElement(self, name, attrs): if name == 'Workout': self.current = { 'type': attrs.get('workoutActivityType', ''), 'start': attrs.get('startDate'), 'duration': attrs.get('duration'), } elif name == 'WorkoutStatistics' and self.current: qty = attrs.get('type', '') if 'HeartRate' in qty: self.current['avg_hr'] = attrs.get('average') self.current['max_hr'] = attrs.get('maximum')

Маппинг типов HKWorkoutActivityType условный → четыре категории: strength, cardio, low_intensity, other. Например, FunctionalStrengthTraining, TraditionalStrengthTraining, CrossTraining, CoreTraining → strength. Walking, Hiking, Yoga, Pilates, Barre → low_intensity. Агент фильтрует не по сырому типу Apple, а по категории.

Сон через скриншоты

iOS не отдаёт фазы сна через Shortcuts, с этим тоже было несколько часов мучений. Решение: пользователь присылает скриншот Apple Health → Vision (GPT-4o-mini) извлекает Core/Deep/REM/Awake в минутах → sleep_sessions. Принимает как сводный экран со всеми фазами, так и отдельные экраны интервалов — мёрджит в один буфер, через /done сохраняет.

Идея передавать данные через шоткаты была в том, чтобы получать аналитику по запросу в любое время дня и ночи. И в том, чтобы не покупать стороннее приложение или не делать своё для парсинга в реальном времени. Но из-за того, пайплайн данных через шоткаты не удалось внедрить, качественную аналитику смогу проводить раз в неделю, когда буду передавать обновлённый export, а на неделе аналитика будет либо глубокой на исторических данных, либо без фаз сна и ЧСС во время тренировок на текущей неделе.

Лабораторные анализы: EAV-паттерн

8332950fdb2e8811b2def2cfe701a36e.png

Разные лаборатории называют одни и те же показатели по-разному: «ТТГ», «TSH», «тиреотропин» — одно и то же, но три разные строки, если хранить как есть. И количество показателей не фиксировано: один анализ — 5 строк, другой — 48.

Решение: EAV-паттерн (Entity-Attribute-Value). Каждый числовой показатель = одна строка в lab_results. Два ключа: parameter_name (как в документе) и parameter_key (стандартный, для агента):

CREATE TABLE lab_results ( id SERIAL PRIMARY KEY, session_id INTEGER REFERENCES lab_sessions(id), user_id TEXT NOT NULL, parameter_name TEXT, -- «ТТГ» / «TSH» / «тиреотропин» parameter_key TEXT, -- стандартный: «tsh» category TEXT, -- «thyroid», «hormones», «lipids» value_numeric NUMERIC, unit TEXT, ref_min NUMERIC, ref_max NUMERIC, is_abnormal BOOLEAN, test_date DATE );

Пайплайн: PDF → PyMuPDF (текст) → LLM парсит, стандартизирует parameter_key. Если PDF без текстового слоя (скан) → рендерим первую страницу в PNG → Vision LLM. Агент запрашивает динамику по parameter_key и получает все замеры независимо от источника и лаборатории.

Сompute_correlation работает с lab_results через единый METRIC_MAP: insulin, glucose, tsh, estradiol, ferritin и 10+ других показателей на равных с метриками Apple Health. Можно спросить: «есть ли связь между инсулином и весом за три года» — агент посчитает коэффициент Спирмена и расскажет всю базу на Haiku c причинами и следствиями.

Что работает, как работаем

Сегодня, спустя три недели плотной работы в перерывах между работой:

  • Агент отвечает на произвольные вопросы — роутинг через select_tools() по ключевым словам

  • 9 лет истории доступны для анализа: /ask динамика ТТГ за 5 лет — реальный результат из БД

  • Сompute_correlation: /ask есть ли связь между шагами и HRV — коэффициент Спирмена с p-value и интерпретацией на русском

  • Memory Synthesizer накапливает паттерны, верифицированные инсайты агент читает автоматически

  • Мультимодальность: голос → Whisper, скриншот весов → Vision → body_metrics, PDF анализов → EAV

  • Индикатор загрузки (send_chat_action(typing)) со статусными фразами, пока агент думает. Реализовала в духе клодклодовских абсурдных герундиев, которые захардкодил ему Anthropic в CLI. В бэклоге — сделать стриминг.

Слабые места

  • URL-индексация в базу знаний: Cloudflare блокирует большинство сайтов с качественным контентом, который агент может использовать.

  • Размерность ответов Haiku, формат ответов, глубина и полнота в процессе отладки. Нужно больше времени и опыта взаимодействия.

Документационный пайплайн

В процессе разработки выстроила документационный пайплайн для работы с Claude Code. Внедрила кастомный skill /updatedocs: СС читает код как источник правды и затем обновляет всю документацию.

Эта же документация — связка между сессиями. Файлы, которые загружаются в контекст автоматически при каждом старте:

CLAUDE.md ← архитектура, стек, соглашения, команды бота memory/MEMORY.md ← живая оперативная память: правила, баги, паттерны docs/CHANGELOG.md ← что сделано и когда docs/backlog.md ← что делать дальше docs/decisions/ ← feasibility-документы для сложных фич docs/diagrams.html ← визуальная архитектура docs/architecture.md ← архитектура агента docs/data-schema.md ← схемы данных агента

Claude Code по ходу сессии редактирует MEMORY.md как инструкцию для следующей версии себя. Семантически документируются паттерны и анти-паттерны в формате «docker compose down -v — ЗАПРЕЩЕНО», «никогда не светить пароль в bash-команде», «не дропать таблицы в БД без предварительного согласования», «iOS Shortcuts не принимает magic variables в date filter — это фундаментальный баг, не пробовать». Документируются провалы, которые уже случились, и следующий Клод их не повторяет.

После нескольких сессий пользуюсь командой /insights. Попросила СС писать инсайты в .md по-русски и складывать в папку с документами. Каждый файл, полученный по команде — по структуре как ретро (что получилось, где возникли проблемы, стиль разработки (!), что нового, что изменилось) и с обратной связью для меня как для мультиперсонажа во всём процессе. Инсайты СС помогают выстраивать совместную работу.

Пример того, как выглядит позитивный блок ретро-инсайта от СС
Пример того, как выглядит позитивный блок ретро-инсайта от СС

И о тайном страхе всех людей на земле: нет, AI не заменил мне меня. Я давно хотела личного ассистента, который помнит мои данные и может помочь находить взаимосвязи между активностью, питанием, сном и состоянием. Первые попытки были с Cursor, первый коммит в GitHub в прошлом году. Дело шло медленно.

Всё это время шла фоновая внутренняя работа. И вот, с Claude Code за 18 дней до и после основной работы — ранними утрами и поздними вечерами — персональный агент-аналитик из «когда-нибудь» превратился в крепкий MVP на проде.

121 час чистого времени разработки с СС (примерно в 5-7 раз быстрее обычной разработки, по замерам СС), 82 коммита на сегодняшний момент. Общее напряжение выше среднего, я стала хуже спать и поэтому больше есть. Ну хотя бы агент, из-за которого всё это произошло, сумел сопоставить данные и составил план на ближайшие пару недель.

Claude Сode не сделал всё за меня по кнопке: 17 раз он пошёл в неверном направлении, 11 раз не так понял задачу, один раз снёс production-данные DROP TABLE-ом.

Каждое решение об архитектуре, каждый дебаг, каждая итерация по поведению агента, каждое выстраивание процесса — это моя работа. AI ускорил рутину, но не заменил мышление. Разница в том, что раньше на эту рутину уходило 80% времени, теперь — 20%.

Что дальше? Другие модели, качество аналитики, туллинг, новые агенты. Заметки по ходу веду в TГ @analaiml.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.