Memory Agent Google: SQLite + LLM вместо векторных баз для Obsidian | Гайд 2026 | AiManual
AiManual Logo Ai / Manual.
03 Апр 2026 Гайд

Memory Agent от Google: как заменить векторные базы данных на SQLite и LLM-рассуждение для Obsidian

Пошаговый гайд по замене векторных БД на SQLite и LLM-рассуждение для Obsidian с использованием Memory Agent от Google. Актуально на 2026 год.

Векторные базы убили? Не совсем, но Memory Agent показывает альтернативу

Каждый второй RAG-пайплайн сегодня — это векторная база. Pinecone, Weaviate, Qdrant. Вы тратите недели на настройку эмбеддингов, индексы съедают гигабайты RAM, а поиск по смыслу все равно иногда выдает ерунду. Звучит знакомо?

В начале 2025 года Google Research тихо выкатила концепцию Memory Agent — архитектурный паттерн, который предлагает забыть про вектора для небольших и средних знаний. Вместо эмбеддингов — SQLite. Вместо косинусной похожести — LLM-рассуждение. Идея проста до гениальности: если контекстные окна моделей уже перевалили за 1 млн токенов (как у Gemini Ultra 2.0 или GPT-5), зачем разбивать текст на куски и искать похожие? Проще спросить у модели: «Вот все мои заметки, найди то, что относится к вопросу X».

Важно: Memory Agent — не готовый продукт, а архитектурный шаблон. Вы не найдете его в npm или PyPI. Это способ думать о памяти для AI-ассистентов, особенно для локальных сред вроде Obsidian.

Почему SQLite и LLM бьют вектора в их же игре

Представьте: у вас 5000 заметок в Obsidian. Векторный подход требует:

  • Разбить каждую заметку на чанки (по 500 токенов, условно)
  • Сгенерировать эмбеддинги для каждого чанка (с помощью модели, которая тоже ест ресурсы)
  • Сохранить эмбеддинги в отдельную БД, построить индекс HNSW или IVF
  • При запросе — искать похожие чанки, потом агрегировать ответ

Memory Agent делает иначе:

  1. Все заметки хранятся в SQLite в сыром виде (метаданные + текст)
  2. При запросе — LLM анализирует вопрос и решает, какие таблицы или строки могут быть релевантны
  3. SQLite выполняет быстрый поиск по ключевым словам или метаданным
  4. LLM получает кандидатов и «рассуждает», какие из них действительно отвечают на вопрос
  5. Итоговый контекст подается в основную модель для генерации ответа

Разница — как между поиском в Google (вектора) и вопросом к эксперту, который помнит все ваши файлы (LLM-рассуждение). Для личных знаний, где данные не миллионы документов, а тысячи, второй подход часто точнее и намного экономнее. Подробнее о конфликте подходов я писал в статье про архитектуру локального RAG-пайплайна.

Собираем Memory Agent для Obsidian: пошаговый разбор

Теория — это здорово, но давайте руками. Вот как заменить векторную базу на SQLite + LLM для ваших заметок. Предполагаю, что у вас уже стоит Obsidian и Python 3.11+.

1 Готовим базу: выгружаем заметки в SQLite

Первым делом — спарсим все .md файлы из вашего хранилища Obsidian. Не нужно сложных инструментов — обычный скрипт на Python.

import sqlite3
import os
import frontmatter
from pathlib import Path

# Создаем базу
conn = sqlite3.connect('obsidian_memory.db')
cursor = conn.cursor()

# Таблица для заметок
cursor.execute('''
CREATE TABLE IF NOT EXISTS notes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    file_path TEXT UNIQUE,
    title TEXT,
    content TEXT,
    tags TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')

# Индексируем для быстрого поиска по тексту (не вектора, а полнотекстовый!)
cursor.execute("CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(title, content, tags)")

# Проходим по папке Obsidian
vault_path = Path.home() / 'Obsidian Vault'
for md_file in vault_path.rglob('*.md'):
    with open(md_file, 'r', encoding='utf-8') as f:
        post = frontmatter.load(f)
        title = post.get('title', md_file.stem)
        tags = ','.join(post.get('tags', []))
        content = post.content

        cursor.execute('''
        INSERT OR REPLACE INTO notes (file_path, title, content, tags)
        VALUES (?, ?, ?, ?)
        ''', (str(md_file), title, content, tags))

        # Заполняем FTS
        cursor.execute('INSERT INTO notes_fts (title, content, tags) VALUES (?, ?, ?)',
                       (title, content, tags))

conn.commit()
conn.close()
print(f"Индексировано заметок: {cursor.rowcount}")
💡
Используем FTS5 — встроенный в SQLite полнотекстовый поиск. Он не семантический, но быстро находит по ключевым словам. Это будет первым этапом фильтрации перед LLM.

2 Пишем Memory Agent: SQLite + LLM-рассуждение

Сердце системы — агент, который решает, что искать. Берем локальную LLM (например, Gemma 3 8B через Ollama) или облачную (Gemini Ultra 2.0). Я покажу вариант с Gemini, так как он бесплатен до определенного лимита.

import google.generativeai as genai
import sqlite3
from datetime import datetime, timedelta

# Настройка Gemini (актуально на 03.04.2026)
genai.configure(api_key='YOUR_API_KEY')
model = genai.GenerativeModel('gemini-2.0-ultra')

class MemoryAgent:
    def __init__(self, db_path='obsidian_memory.db'):
        self.conn = sqlite3.connect(db_path)
        self.cursor = self.conn.cursor()

    def _reason_about_query(self, user_query):
        """LLM анализирует запрос и решает, как искать."""
        prompt = f"""
        Ты — Memory Agent для Obsidian. У тебя есть доступ к базе заметок.
        Запрос пользователя: "{user_query}"
        
        Определи:
        1. Ключевые слова для полнотекстового поиска в SQLite FTS.
        2. Возможные теги (tags), которые могут быть в заметках.
        3. Ограничение по времени (если запрос про недавние события).
        
        Верни ответ в формате:
        KEYWORDS: список слов через пробел
        TAGS: список тегов через запятую или None
        TIME_FILTER: last_week, last_month, None
        """
        response = model.generate_content(prompt)
        result_text = response.text
        # Парсим ответ (упрощенно)
        lines = result_text.split('\n')
        keywords = ''
        tags = None
        time_filter = None
        for line in lines:
            if line.startswith('KEYWORDS:'):
                keywords = line.replace('KEYWORDS:', '').strip()
            elif line.startswith('TAGS:'):
                tags = line.replace('TAGS:', '').strip()
                if tags.lower() == 'none':
                    tags = None
            elif line.startswith('TIME_FILTER:'):
                time_filter = line.replace('TIME_FILTER:', '').strip()
                if time_filter.lower() == 'none':
                    time_filter = None
        return keywords, tags, time_filter

    def search(self, user_query, limit=10):
        """Основной метод поиска."""
        # Шаг 1: LLM решает, как искать
        keywords, tags, time_filter = self._reason_about_query(user_query)
        
        # Шаг 2: SQLite ищет кандидатов
        sql_query = '''
        SELECT n.id, n.title, n.content, n.tags, n.created_at
        FROM notes n
        JOIN notes_fts fts ON n.id = fts.rowid
        WHERE notes_fts MATCH ?
        '''
        params = [keywords]
        
        if tags:
            sql_query += ' AND n.tags LIKE ?'
            params.append(f'%{tags}%')
        
        if time_filter == 'last_week':
            week_ago = (datetime.now() - timedelta(days=7)).isoformat()
            sql_query += ' AND n.created_at > ?'
            params.append(week_ago)
        
        sql_query += ' LIMIT ?'
        params.append(limit * 3)  # Берем в 3 раза больше для последующей фильтрации LLM
        
        self.cursor.execute(sql_query, params)
        candidates = self.cursor.fetchall()
        
        # Шаг 3: LLM выбирает лучшие кандидаты
        candidate_texts = []
        for cand in candidates:
            cand_str = f"ID: {cand[0]}, Title: {cand[1]}, Tags: {cand[3]}, Snippet: {cand[2][:200]}"
            candidate_texts.append(cand_str)
        
        prompt = f"""
        Запрос: {user_query}
        
        Кандидаты из базы:
        {'\n'.join(candidate_texts)}
        
        Выбери TOP-{limit} самых релевантных кандидатов (укажи только ID через запятую).
        Объясни кратко, почему они подходят.
        """
        response = model.generate_content(prompt)
        # Парсим ID (реализуйте согласно ответу модели)
        # ... (опущено для краткости)
        
        # Возвращаем финальные заметки
        final_ids = [1, 3, 5]  # Пример
        final_notes = [c for c in candidates if c[0] in final_ids]
        return final_notes[:limit]

    def close(self):
        self.conn.close()

Внимание: Код упрощен. В реальности нужно обрабатывать ошибки API, кэшировать запросы, и аккуратно парсить вывод LLM. Но скелет рабочий.

3 Интеграция с Obsidian: плагин или внешний скрипт

Тут два пути. Первый — написать свой плагин на TypeScript (долго, но красиво). Второй — использовать внешний скрипт, который запускается через Obsidian Command Palette и показывает результаты в модальном окне. Я предпочитаю второй вариант, потому что он проще.

Установите плагин Hotkey Helper (партнерская ссылка) для удобства. Затем создайте Python-скрипт, который будет вызываться через системную команду.

# obsidian_memory_cli.py
import sys
from memory_agent import MemoryAgent

query = sys.argv[1] if len(sys.argv) > 1 else ""
agent = MemoryAgent()
results = agent.search(query, limit=5)

# Выводим в формате, который поймет Obsidian
for r in results:
    print(f"## {r[1]}\n")
    print(f"{r[2][:500]}...\n")
    print(f"**Tags:** {r[3]}\n")
    print("---\n")
agent.close()

В Obsidian настройте хоткей, который запускает этот скрипт через терминал и выводит результат. Подробнее о такой интеграции я писал в статье про замену Gemini CLI для Obsidian.

Подводные камни: где Memory Agent проигрывает векторам

Не обольщайтесь. Этот подход — не серебряная пуля. Вот где он даст сбой:

  • Большие объемы: Если у вас 100k+ документов, LLM будет долго рассуждать над каждым запросом. Векторный поиск масштабируется лучше.
  • Сложные семантические связи: «Найди заметки про квантовые вычисления, но не про физику» — с таким вектора справятся через вычитание эмбеддингов, а LLM может запутаться.
  • Стоимость: Если используете облачную LLM (Gemini, GPT), каждый запрос платный. Локальные модели медленнее, но бесплатны.

Прямо сейчас Memory Agent идеален для личных знаний (до 10k заметок) и корпоративных вики среднего размера. Для всего остального — смотрите в сторону гибридных систем, как в статье про графовую когнитивную память.

Ответы на частые вопросы

Вопрос Ответ
Какие LLM лучше всего подходят? Для локальности — Gemma 3 8B или Qwen 2.5 14B. Для точности — Gemini Ultra 2.0 или GPT-5. Берите модель с контекстом хотя бы 128k токенов.
Как часто обновлять базу? Поставьте cron-задачу на ежедневную индексацию новых файлов. Или используйте файловые вотчеры.
А если заметки содержат картинки? Memory Agent работает только с текстом. Для мультимодальности потребуется отдельный пайплайн, как в гайде по Android-ассистенту.

Что дальше? Прогноз от автора

К 2027 году векторные базы не умрут, но станут нишевым инструментом для поиска по гигабайтам неструктурированных данных. Для всего остального — SQLite + LLM-рассуждение. Google уже тестирует Memory Agent в своих ассистентах, и скоро появятся готовые библиотеки.

Мой совет: не бросайтесь переписывать все RAG-системы. Начните с малого — внедрите Memory Agent для своего Obsidian. Если понравится — переносите на корпоративные знания. И не забывайте про полный каталог инструментов для локального ИИ, там есть все для экспериментов.

А если столкнетесь с проблемой, когда свежие SQL-данные конфликтуют со старыми векторами — читайте мой разбор про конфликт источников в RAG. Удачи в коде!

Подписаться на канал