План · Фаза 1

Фаза 1: Локальный семантический граф

Доказать, что личный граф знаний с LLM работает лучше, чем просто заметки. Без федерации, без протокола — только локальная магия.

ФАЗА 1
Локальный семантический граф
3-6 месяцев
Цель: продемонстрировать, что извлечение триплетов из Markdown-заметок через локальный LLM + запросы по графу дают заметно лучшие ответы, чем полнотекстовый поиск. Метрика: 80% запросов отвечены точнее на тестовом корпусе из 1000 заметок.

Deliverables

  • Python-прототип (reference-имплементация локального узла)
  • Плагин для Obsidian (минимальный: извлечение триплетов + чат)
  • Тестовый корпус 1000 заметок с размеченными правильными ответами
  • Бенчмарк: граф vs полнотекстовый поиск vs чистый LLM
  • Публичный репозиторий + документация по установке

Критерий перехода к фазе 2

Метрика 80% точности достигнута на тестовом корпусе, прототип стабильно работает у 5+ beta-тестеров, есть как минимум один контрибьютор извне core-команды. Если метрика не достигнута — продлеваем фазу или пересматриваем подход (возможно, чистый LLM-подход лучше, и FSS не нужен).

Компоненты прототипа

1. Хранилище Markdown + frontmatter

Основа — обычная папка с Markdown-файлами. Каждый файл имеет frontmatter YAML с типизированными свойствами (через Obsidian Properties или вручную). Это позволяет сразу работать с существующими Obsidian-хранилищами без миграции.

---
title: Преступление и наказание
type: book
author: Достоевский Ф.М.
year: 1866
rating: 9
read_at: 2024-03-15
tags: [русская-литература, экзистенциализм]
---

# Преступление и наказание

Прочитал в марте 2024. Главный конфликт — между идеей Раскольникова
и человечностью, которая в нём остаётся...

Frontmatter — первый источник триплетов: модель читает его и сразу получает типизированные свойства без извлечения из текста. Это даёт надёжный baseline для случая, когда пользователь уже структурировал часть данных.

2. Извлекатель триплетов (LLM)

Python-модуль, который для каждой заметки запускает локальный LLM с задачей извлечь триплеты. Используется structured output (Outlines или instructor), что гарантирует JSON строго определённой схемы. Модель: Qwen 2.5 14B через Ollama. На современном ноутбуке — ~3 секунды на заметку.

extracted_triples = [
  Triple(
    subject="note:prestuplenie",
    predicate="written_by",
    object="person:dostoevsky",
    confidence=0.95
  ),
  Triple(
    subject="note:prestuplenie",
    predicate="influenced_by",
    object="concept:existentialism",
    confidence=0.78
  ),
  Triple(
    subject="note:prestuplenie",
    predicate="main_conflict",
    object="идея vs человечность",
    confidence=0.82
  )
]

Каждый триплет получает confidence — оценку модели, насколько она уверена в извлечении. Это важно для последующих запросов: триплеты с confidence < 0.5 помечаются как «предположительные» и не используются в строгих ответах.

3. Локальный граф (SQLite)

Извлечённые триплеты сохраняются в SQLite с индексами по subject, predicate, object. Это даёт O(log n) поиск по любому полю, чего достаточно для личного графа (10-100k триплетов на 1000 заметок). Граф пересобирается при изменении заметки, с инкрементальным обновлением.

CREATE TABLE triples (
  id INTEGER PRIMARY KEY,
  subject TEXT NOT NULL,
  predicate TEXT NOT NULL,
  object TEXT NOT NULL,
  object_type TEXT,
  source_note TEXT NOT NULL,
  confidence REAL,
  extracted_at TIMESTAMP,
  extracted_by TEXT
);
CREATE INDEX idx_subject ON triples(subject);
CREATE INDEX idx_predicate ON triples(predicate);
CREATE INDEX idx_object ON triples(object);

SQLite выбран осознанно: embedded, без сервера, файл хранится рядом с заметками, при необходимости удаляется и перестраивается за минуты. Для 100k триплетов SQLite справляется за миллисекунды. Если граф вырастет до миллионов — миграция на DuckDB или embedded Postgres, но это не актуально для фазы 1.

4. Векторный индекс (LanceDB)

Параллельно с триплетами строится векторный индекс заметок через bge-m3 (мультиязычные эмбеддинги). Это даёт семантический поиск: «найди заметки про влияние Достоевского на экзистенциализм, даже если слово не используется». LanceDB embedded, хранит векторы в файле рядом с SQLite.

import lancedb
db = lancedb.connect("~/.fss/vectors")
table = db.create_table("notes", data=[
  {"id": "note:prestuplenie", "vector": embed(text), "text": text}
])

# Семантический поиск
results = table.search(embed("влияние Достоевского на экзистенциализм"))
  .limit(10).to_list()

Векторный индекс не заменяет триплеты, а дополняет их. Триплеты отвечают на точные запросы («все книги Достоевского с рейтингом 8+»), векторы — на смысловые («что я читал про экзистенциализм, даже без этого слова»). Сочетание даёт мощь, недоступную ни одному из подходов по отдельности.

5. Слои запросов (LLM-агент)

Python-модуль, который принимает запрос на естественном языке и собирает ответ, используя все три уровня (текст, триплеты, векторы). Архитектура простая: LLM получает запрос + релевантный контекст (из векторов) + релевантные триплеты (из графа) + инструкции отвечать с указанием источников.

def answer(query: str) -> str:
    # 1. Семантический поиск по заметкам
    relevant_notes = vector_search(query, limit=5)

    # 2. Извлечение сущностей и запрос к графу
    entities = llm_extract_entities(query)
    triples = graph_query(entities)

    # 3. Сборка контекста
    context = build_context(relevant_notes, triples)

    # 4. Финальный ответ с provenance
    answer = llm_generate(query, context, require_sources=True)
    return answer

Ключевой момент — require_sources=True: модель обязана указать, из какой заметки или триплета взято каждое утверждение в ответе. Если модель не может указать источник, она обязана сказать «не знаю». Это критически отличается от чистого LLM-чата, где модель может галлюцинировать уверенно.

6. API-сервер (FastAPI)

Локальный HTTP-сервер на порту 8765, который даёт плагину Obsidian (и другим клиентам) доступ к графу. Три эндпоинта: POST /query (запрос на естественном языке), POST /reindex (переиндексация заметки), GET /graph (визуализация графа для отладки).

@app.post("/query")
async def query_endpoint(q: QueryRequest):
    answer = await answer_with_provenance(q.text)
    return {
      "answer": answer.text,
      "sources": answer.sources,
      "triples_used": answer.triples
    }

7. Плагин для Obsidian

Минимальный TypeScript-плагин, который добавляет в Obsidian чат-панель с доступом к локальному графу. Пользователь выделяет текст заметки → «Extract triples» в контекстном меню → плагин вызывает локальный API → триплеты сохраняются в frontmatter. Параллельно — чат для запросов к графу.

Это даёт первый реальный user-facing интерфейс, без необходимости перепрыгивать в отдельное приложение. Целевая аудитория фазы 1 — Obsidian-пользователи, поэтому плагин критичен для adoption.

Метрики и бенчмарк

Главная метрика фазы 1: 80% запросов отвечены точнее, чем полнотекстовый поиск. Измеряется на тестовом корпусе 1000 заметок с 100 размеченными запросами и правильными ответами. Для каждого запроса оцениваем три подхода: полнотекстовый поиск (baseline), чистый LLM (без графа), FSS (граф + LLM).

Оценка — слепая: 3 эксперта оценивают ответы каждого подхода по шкале 1-5, не зная, какой подход какой. FSS считается успешным, если в 80+ случаях из 100 получает оценку выше, чем полнотекстовый поиск. Это жёсткая метрика: если FSS не превосходит baseline заметно, его незачем строить.

Календарь фазы 1

МесяцДеливераблМетрика
1Python-прототип без UI: хранилище + LLM + граф10 заметок → 100 триплетов
2Векторный индекс + слои запросов10 тестовых запросов работают
3Плагин Obsidian + тестовый корпус 1000 заметокБенчмарк запущен
4Бенчмарк: FSS vs полнотекстовый vs LLMМетрика 80% достигнута
5-6Beta-тестеры (5+), полировка, публичный релиз5+ активных beta-тестеров

Что может пойти не так

Три главных риска фазы 1. Локальный LLM недостаточно точен: Qwen 2.5 14B может ошибаться в извлечении триплетов, особенно для мета-утверждений («эта заметка напоминает мне ту»). Смягчение: гибридный подход — для confidence < 0.5 используем облачную модель как second opinion. Производительность: 3 секунды на заметку × 1000 заметок = 50 минут на полную индексацию. Смягчение: инкрементальная индексация (только изменившиеся заметки) + фоновый режим. Метрика не достигнута: если FSS не превосходит полнотекстовый поиск, это сигнал, что проекту не нужно расти дальше в текущем виде. Это нормально — лучше узнать на фазе 1, чем на фазе 4.

Главный успех фазы 1

Не код, не плагин, не бенчмарк — а публичное доказательство, что личный семантический граф работает лучше, чем обычные заметки. Если это доказано, фаза 2 (протокол) имеет смысл. Если нет — пересматриваем концепцию целиком.

Назад к обзору плана · Вперёд к фазе 2 →

Документ: phase-1.html · Версия: v0.1 · Обновлён: 2026-08-03