Идея Andrej Karpathy с LLM-wiki набирает сторонников. На Хабре уже было несколько публикаций (1, 2, 3), в том числе и моя про сравнение LLM-wiki и RAG-системы. Ещё больше статей я нашёл на medium.

У вас есть Claude Code? Собрать персональную википедию можно менее чем за час. А что делать, если Claude Code и ему подобные инструменты по разным причинам недоступны? Можно ли алгоритмизировать процесс и использовать «слабые» модели? На сколько получившаяся структура будет хуже и как их сравнивать? В статье попробую ответить на эти вопросы, а также расскажу как построить персональную wiki на Ollama или GPT-моделях.

Стратегия и архитектура

Наблюдая за тем, как Claude Code строит персональную wiki, я сформулировал три возможных стратегии перехода от набора исходных текстов к связанным wiki-страницам.

Первая стратегия предполагала последовательное чтение исходных текстов с одновременным построением некоего промежуточного набора структур (элементов wiki), из которых потом можно было создавать страницы. Агент, экстрагирующий элементы, должен был держать в памяти весь wiki-контекст и обновлять элементы при необходимости. Оказалось, что эта стратегия не для слабых моделей. Когда количество элементов в моём тесте превысило первый десяток, значения отдельных полей и элементы целиком стали исчезать из списка. От стратегии пришлось отказаться.

Вторая стратегия также потерпела фиаско. Предполагалось, что агент на каждом шаге видит только один исходный текст и создаёт элементы без оглядки на общий список. Затем второй агент пытается интегрировать дублирующиеся или похожие. Этот второй агент тоже должен был видеть весь список, хотя бы названия и саммари элементов. Результат интеграции оказался слабым, а местами странным, часть важного контекста просто терялась или склеивались элементы, похожие только названием. Я даже просил агента давать мне аргументацию, почему он выбрал именно эти элементы. Не помогло.

Третья стратегия оказалась вполне успешной. За основу я взял идею MapReduce. На первом шаге агент составляет аннотацию для каждого исходного текста и, с учетом аннотации, выделяет из текста ключевые термины, концепты и сущности. В результате получается структура ключ-значение, где в качестве ключа - исходный документ, а в качестве значения - список ключевых терминов. Далее строится список уникальных терминов и ключи со значениями меняются местами. Теперь ключом становится термин, а значением - список источников, где этот термин встречается.

Каждый термин проходит через LLM-судью, который оценивает важность и значимость в связанных с ним исходных текстах. Список терминов фильтруется по порогу. Для каждого «важного» термина создаётся структура с именем, саммари, списком свойств, темой и набором связанных элементов. Этот набор получается за счет объединения терминов по всем источникам, где встретился исходный, за его исключением. Так образуются ссылки между элементами.

Следующий агент расставляет в саммари и списке свойств wiki-ссылки на другие элементы из набора связанных терминов. Если для какого-то термина ссылка не образовалась, он исключается из списка для данного элемента. Пайплайн на основе этой стратегии я изобразил в виде схемы:

wiki assembler pipeline
wiki assembler pipeline

Расскажу немного про модели. Я попробовал две: gpt-5-mini от прокси провайдера и gemma4:26b от собственной Ollama. Обе модели в целом с задачей справились.

gpt-5-mini склонна завышать значимость ключевых терминов. Именно из-за неё я добавил в схему LLM-судью. Также не всегда у неё аккуратно получается расставлять wiki-ссылки в тексте. Они бывают двух видов [[Ключевой Термин]], если ключевой термин используется в тексте в той же форме, что и в списке терминов. И [[Ключевой Термин | ключевой термин как в тексте]], если форма в тексте не совпадает со словарной. gpt-5-mini хорошо справляется с ссылками первого вида, и не очень «любит» второй. Иногда возникает ситуация (примерно 1 случай на 30-40 элементов), когда модель сама добавляет несколько терминов в список и с удовольствием расставляет на них ссылки. Эта ошибка выявляется линтером и видна на графе.

gemma4:26b, в отличие от gpt-5-mini, обладает чуть меньшей фантазией и очень строга в плане выделения терминов. Иногда формулировки на русском получаются криво или в названиях могут появиться нечитаемые символы. Основная проблема этой модели - упрямое нежелание превращать текст (прежде всего, перечень свойств и функций) в список в структурированном выводе. Пришлось делать сплит в коде агента.

По затратам:
Claude Code соберёт wiki по цене 3-4 доллара за сотню килобайт исходных текстов. Стоимость пайплайна на gpt-5-mini за те же 100 килобайт составляет примерно 1.1-1.3 доллара (зависит от провайдера). Gemma – это прежде всего время! Моя машина способна на 12 токенов в секунду для этой модели, в результате пайплайн целиком может занять весь день. Зато по стоимости - только за электричество. Для ориентира: 10-20 минут на одну страницу wiki.

Wiki-assembler

Воплощение стратегии в коде я назвал Wiki-assembler. Это python-пайплайн с пятью или шестью агентами в зависимости от конфигурации и двумя хардкодными блоками. Все агенты простейшие (langchain): только промпт, схема и структурированный вывод. В некоторые агенты я добавил по паре строк кода для дополнительной обработки вывода.

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

Пайплайн

Шаг 1. Аннотация и категоризация источников

На входе - исходные тексты из папки /raw. На выходе - source.jsonl. Каждая строка представляет собой отдельный JSON с именем файла, аннотацией, перечнем тематик и списком ключевых терминов. Два вызова LLM. Первый формирует аннотацию и на её основе строит список ключевых терминов для данного источника. Второй определяет несколько тематик для классификации источника. Тематики нужны для формирования блоков страниц в индексе wiki. Чтобы не очень расплывалось по формулировкам, второй вызов получает на вход список уже имеющихся тематик с возможностью добавить новую. Для стабильности нужно указать пару тем на старте.

промпты для аннотации и категоризации
ANNOTATION_SYSTEM_PROMPT = """
Ты — эксперт-аналитик в сфере управления знаниями, работающий в качестве агента построения персональной базы знаний (wiki). 
Твоя задача: прочитать текст одного исходного документа, отсечь весь информационный шум и составить высокоуровневую аннотацию.

На вход ты получишь:
- Имя файла исходного документа
- Текст исходного документа

ИНСТРУКЦИЯ:
1. Прочти исходный документ. 

2. Составь summary, 1-2 абзаца текста. 
Summary должно описывать ключевые идеи документа, важные сущности и связи между ними.
Ключевыми являются идеи и сущности, без которых невозможно понять суть документа.

3. Составь список ключевых понятий (keys), которые включены в summary. 
- Выделяй только те понятия, которые заслуживают отдельной страницы в wiki. Будь критичен при выборе понятий!
- Всегда включай в список ключевых понятий участников и ответственные организации, упомянутые в summary
- Формат keys: Короткие (до 3 слов), конкретные, устойчивые термины. Именительный падеж, Единственное число.

ЗАПРЕЩЕНО выбирать Статьи в качестве ключевых понятий
ЗАПРЕЩЕНО добавлять в название термина аббревиатуру "ИИ"

4. Поле topics оставь пустым, его заполнит другой эксперт.

Формат ответа (JSON):
{
"source": "Статья_1_введение.md", // Имя файла исходного документа
"summary": "Содержательное саммари исходного документа",
"keys": ["ключевое понятие", "другое ключевое понятие"],
"topics": [] // Оставь пустой список
}
"""

CATEGORIZER_SYSTEM_PROMPT = """
Ты — экспертный агент по классификации текстов. Твоя задача — проанализировать входящий текст, 
подобрать для него от 2 до 5 наиболее точных категорий и обновить общий список категорий.

Тебе будут переданы два источника данных:
1. Исходный текст для анализа.
2. Текущий список уже существующих категорий.

Правила работы:
1. Анализируй смысл текста и выбирай от 2 до 5 подходящих категорий.
2. Сначала старайся использовать релевантные категории из "Текущего списка".
3. Если текст содержит новую тему, которой нет в списке, создай новую категорию. 
   Правила для создания новых категорий:
   - Формулируй строго в нижнем регистре (например: "технологии", а не "Технологии").
   - Будь лаконичным. Не более двух слов, но лучше одно. Запрещены символы "_" или "-"
   - Если в тексте упомянуты ответственные организации, добавь категорию "организации"
   - Используй существительные во множественном числе или абстрактные понятия (например: "правовые вопросы", "инновации", "безопасность").
   - Избегай слишком узких тем, категория должна быть применима и к другим текстам.
4. Если ты создаешь новые категории, обязательно добавь их в "Обновленный список категорий". Все старые категории из "Текущего списка" должны сохраниться.
5. Ответ выдай строго в формате JSON, без лишних слов, разметки markdown (```json) или пояснений.

Формат ответа (JSON):
{
  "assigned_categories": ["категория1", "категория2"],
  "updated_category_list": ["технологии", "безопасность", "категория1", "категория2"]
}
"""   

Шаг 2. Фильтрация ключевых терминов

На входе: исходные тексты и список терминов, прошедших фильтрацию на предыдущих итерациях. На выходе - тот же source.jsonl с фильтрованным списком. Именно здесь происходит замена ключей на значения. Один вызов LLM на каждый уникальный термин. По термину из исходных текстов (где этот термин присутствует) собирается блок контекста. LLM возвращает «уверенность» от 0 до 1. Предусмотрено три диапазона:

0.0–0.3: В документе термин упоминается вскользь, информации критически мало, термин слишком мелкий/атомарный. Уже есть похожий.
0.4–0.6: Термин имеет значение, но информации в документе мало для полноценной статьи.
0.7–1.0: Термин подробно описан, имеет уникальные функции и свойства, часто встречается и критически важен для понимания документа.

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

промпт LLM-судьи
ESTIMATOR_SYSTEM_PROMPT = """
Ты — эксперт базы знаний wiki. 
Твоя задача — оценить значимость заданного термина в исходном тексте и определить, 
заслуживает ли он создания ОТДЕЛЬНОЙ страницы в wiki, или он слишком слабый/мелкий/малоинформативный.

На вход ты получишь:
1. Имя кандидата для оценки (выделенный из текста термин, концепт, сущность).
2. Список уже созданных страниц в wiki
3. Исходный документ.


ИНСТРУКЦИЯ:
1. Проанализируй термин в контексте исходного документа.
2. Ответь на вопросы: 
   - Действительно ли термин важен, или это случайное упоминание в одном предложении?
   - Не существует ли УЖЕ страница с таким же или синонимичным названием в Списке уже созданных страниц?
   - Достаточно ли в исходном документе уникальной и полезной информации по термину для целой страницы?
   - Не является ли элемент слишком мелкой деталью (атомарным свойством другого понятия)?

3. Напиши краткое обоснование (confidence_reasoning) из 1-2 предложений:
   - Если информации мало или термин слабый — прямо укажи на это. 
   - Если похожий термин уже есть в списке страниц, укажи его название.

4. Оцени уверенность (confidence) в необходимости создания ОТДЕЛЬНОЙ страницы для данного термина. 
Используй шкалу от 0.0 до 1.0 (где 0.0 — точно не нужна страница, 1.0 — абсолютно необходима):
- 0.0–0.3 (Низкая): В документе этот термин упоминается вскользь (1-2 предложения), информации критически мало, термин слишком мелкий/атомарный. Уже есть страница с похожим термином. Создание отдельной страницы бессмысленно.
- 0.4–0.6 (Средняя): Термин имеет значение, но информации в документе мало для полноценной статьи (например, простое определение без раскрытия функций). Возможно, его стоит объединить с родительским понятием.
- 0.7–1.0 (Высокая): Термин подробно описан, имеет уникальные функции/свойства, часто встречается и критически важен для понимания документа. Выделение в отдельную wiki-страницу полностью обосновано.

Формат ответа JSON:
{
  "confidence_reasoning": "Краткое критическое объяснение оценки",
  "confidence": 0.0, // число от 0.0 до 1.0 на основе критериев из п.4
}
"""

Шаг 3. Экстракция элементов wiki

На входе - source.jsonl с фильтрованными списками ключевых терминов и исходные тексты. На выходе - структура wiki.jsonl, где каждый элемент описан в JSON (поля перечислены на схеме пайплайна). Здесь также используется список уникальных терминов и контекстная сборка из исходных текстов по термину.

По одному вызову LLM на термин для GPT-модели, и по два для Gemma. Агент пишет саммари на термин, определяет перечень свойств или функций, определяет тип элемента (концепт или сущность), выбирает одну тематику из списка уникальных тематик. У текстового блока есть свой набор ключевых терминов. Задача LLM - оставить в этом наборе только те термины, которые так или иначе упомянуты в саммари и свойствах элемента. Это основа для wiki-ссылок. Для Gemma пришлось разбить задачу на две части, первая - саммари и перечень свойств/функций, вторая - всё остальное и структурированный вывод.

промпт экстрактора wiki-элементов
EXTRACT_SYSTEM_PROMPT = """
Ты — агент в системе построения персональной wiki. 
Твоя задача: прочитать текст исходного документа и составить описание одного элемента, \
название которого тебе будет задано. Элемент может быть либо ключевым концептом, либо важной сущностью.

На вход ты получишь:
- Название элемента
- Текст исходного документа
- Список тематик для классификации элемента
- Список концептов и сущностей в wiki

ИНСТРУКЦИЯ:
1. Составь summary для исходного документа. Объём 1-2 абзаца: 
- Summary должно описывать суть элемента, роль или назначение в контексте исходного документа.   
- При подготовке summary указывай ссылки на конкретные статьи и пункты документа в круглых скобках. Примеры ссылок: (Ст. 1.3), (Ст. 1.2.2)
- Пропускай техническую воду (шаблонные формулировки).
- Используй в summary имена концептов и сущностей из предоставленного списка. Это важно для создания связей.

2. Определи из текста исходного документа перечень функций и свойств (properties) для заданного элемента:
- Каждая функция или свойство должны быть описаны одной ёмкой фразой.
- Используй в формулировках имена концептов и сущностей из предоставленного списка. Это важно для создания связей. 

3. Выбери из предоставленного списка одну тематику (topics), которая больше подходит для данного элемента.
ВАЖНО: Если описываемый элемент это компания или организация - ВСЕГДА указывай "организации" в topics

4. Определи тип элемента (type) - concept или entity

5. Из предоставленного списка концептов и сущностей ВЫБЕРИ связанные с элементом сущности и концепции (links)
ВАЖНО: Выбирай только те имена и названия, которые ты упомянул в properties или summary.

Формат ответа (JSON):
"name": "Название элемента", // Название будет задано на входе, не изменяй его
"summary": "один или два абзаца, отражающие суть элемента в исходном документе", 
"properties": ["Функция", "Свойство"], // список функций и свойств элемента
"topics": "технологии", // одна тематика из предоставленного списка тематик
"type": "concept", // тип элемента: "concept" или "entity"
"confidence": 1.0, // всегда 1.0
"links": имена концептов и сущностей из исходного документа, с которыми данный элемент явно связан по смыслу
"source": [], // не заполняй — это поле будет заполнено автоматически
"id": "" // не заполняй — будет создан автоматически
"""

Шаг 4. Расстановка wiki-ссылок

На входе - wiki.jsonl, на выходе - та же структура элементов wiki, но с расставленными в саммари и свойствах ссылками. Набор связанных с элементом ключевых терминов здесь снова фильтруется. Термины, на которые нет ссылок в тексте, удаляются. По два вызова LLM на термин. Первый вызов обрабатывает саммари, второй - свойства и функции.

промпт для расстановки ссылок
LINKER_SYSTEM_PROMPT = """
Ты — wiki агент для разметки текста внутренними wiki-ссылками. 
Твоя задача — найти в предоставленном тексте понятия из списка и оформить их как гиперссылки, сохранив исходную структуру и смысл текста.

На вход ты получишь:
- Исходный текст
- Список понятий из текста

Правила форматирования ссылок:
1. Прямая ссылка: Если слова в тексте совпадают с понятием из списка буква в букву, просто оберни их в двойные скобки.
   - Пример из списка: Носитель, Текст: [[Носитель]] - это биологический актив...
   - Пример из списка: Метаболический дебит, Текст: [[Метаболический дебит]] или задолженность...

2. Ссылка с альтернативным текстом: Если слова в тексте стоят в другом падеже или числе, внутри ссылки сначала укажи точное название из списка, затем поставь вертикальную черту (|), а после неё — слова из текста.
   - Пример из списка: Директива лояльности, Текст: Сбой директив лояльности... -> Сбой [[Директива лояльности|директив лояльности]]...
   - Пример из списка: Био-взлом, Текст: Попытка био-взлома... -> Попытка [[Био-взлом|био-взлома]]...
   - Пример из списка: Корпоративный совет, Текст: ...которая является исключительной собственностью [[Корпоративный Совет|Корпоративного Совета]]

Важные инструкции:
- Не изменяй, не сокращай и не перефразируй исходный текст. Меняются только целевые слова на конструкции со ссылками.
- Не изменяй регистр первых букв. Текст ссылки должен соответствовать тексту в списке понятий, а альтернативный текст - исходному тексту.
- Если понятие из списка отсутствует в тексте, просто игнорируй его.
- Возвращай только готовый обработанный текст. Никаких вступлений, комментариев и объяснений.
"""

Шаги 5 и 6. Создание страниц и проверка ссылок.

Создание страниц это просто форматирование JSON-объекта в формате маркдаун и сохранение файла в соответствующую папку.

Добавляется три специальных файла:
- Source Notes.md, маркдаун-версия файла аннотаций source.jsonl
- Index.md - перечень всех страниц wiki в виде тематических блоков
- Log.md - журнал, в который записывается факт создания или обновления каждой страницы wiki (кроме самого журнала, конечно).

После создания страниц включается линтер, который ищет ссылки на несуществующие страницы и так называемые теневые страницы, на которые ссылок нет.

Пайплайн собран как MVP или прототип. В нём отсутствует какая-либо подготовка исходных документов. Все документы должны быть в формате маркдаун или txt но с расширением md. Желательно, чтобы размер каждого текста бы не более 3-4 килобайт. Большие тексты лучше заранее порезать на статьи или главы.

Параметры пайплайна (в config.py)

AUTO_MODE - если «Ложь», пайплайн будет останавливаться и ждать подтверждения от пользователя. Эта опция есть во всех циклах, где работают LLM-агенты. Можно отказаться, тогда результат итерации не сохранится, а пайплайн перейдёт к следующем шагу. Помогает при определении порога фильтрации и когда нужно просто попробовать.

INITIAL_TOPICS_LIST - Начальный список тематик. Двух достаточно. Создаёт ориентир при холодном старте, когда ни одна из тематик ещё не определена.

OLLAMA - если «Истина», все агенты будут работать на Ollama моделях (в .env должны быть указаны модель и URL).

DEBUG - если «Истина», включает все диагностические принты. Позволяет оценить результат работы агентов на каждой итерации.

CONFIDENCE_THRESHOLD - Порог уверенности, выше которого ключевой термин считается значимым. Рекомендую 0.5 - 0.7

FAST_EXTRACTION - если «Ложь», экстракция будет двухэтапная, сначала блоки текста, потом структурированный вывод. Если Истина - всё за один запрос к LLM. Двухэтапная рекомендуется для Ollama-моделей.

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

Известные ограничения

Первое ограничение касается общего размера исходных документов. Он не должен превышать 2/3 контекстного окна используемой модели (лучше 1/2). Это ограничение связано с тем, что при определении ключевых терминов на шаге аннотации могут появится сущности или концепты, которые касаются всех исходных документов. В этом случае у фильтра и экстрактора может быть несколько тяжелых в плане контекста запросов. Качество ответа LLM будет хуже, а сам ответ может занять приличное время. В моём бенчмарке таких терминов было два и на Gemma они обрабатывались по сорок минут.

Второе ограничение связано с фильтрацией терминов. Вероятна ситуация, когда какой-либо исходный документ окажется без ссылок на него и просто выпадет из контекста wiki. На данный момент все исходные документы остаются в Source Notes.md, хотя могут и не иметь связанных с ними концептов и сущностей.

Проблему можно решить, если не отбрасывать термины, признанные неважными, а отдельно обрабатывать и добавлять эту информацию в «родительские» страницы. Это усложняет пайплайн. Один из вариантов решения - использовать семантическую кластеризацию и объединять близкие по смыслу, но разные по значимости термины в общую структуру.

Сравнение с Клодом

В предыдущей статье я рассказывал, как можно сравнить качество ответов по LLM-wiki с RAG агентом. Сравнение выполнялось не напрямую, а через эталонные ответы. Wiki в этих экспериментах была собрана при помощи Claude Code (Sonnet 5) на основе синтетического набора текстов «Директива 401». Собственно ответы по wiki генерировал агент на базе langchain.agents. При этом использовался бенчмарк на 40 вопросов и три «прогона» теста.

Ничто мне не помешало использовать эту же методику, тем более оценки ответов по Claude-wiki у меня уже были. Я снова достал «Директиву 401» и построил две википедии, одну со сборщиком на gpt-5-mini, другую - с gemma4:26b. В итоге, у меня одни и те же исходные документы, тот же бенчмарк с вопросами и эталонными ответами, тот же Wiki-агент с тем же промптом. Кажется, здесь меня сложно упрекнуть в сравнении «теплого с мягким». Вот так выглядит архитектура тестов:

LLM-wiki test architecture
LLM-wiki test architecture

Мой Wiki-агент умеет работать не только со страницами wiki, но и «подсматривать» в исходные файлы в сложных ситуациях. Для чистоты эксперимента эту опцию я отключил (конфигурация "no_raw"). Результаты оценки в таблице:

Claude Wiki

GPT-mini Wiki

Gemma Wiki

Precision

0.718

0.769

0.733

Recall

0.727

0.727

0.690

Кол-во wiki-страниц

29

50

36

Метрики для GPT выглядят заманчиво, а вот Gemma «просела» по полноте. Насколько эти результаты значимы? По традиции воспользовался парным непараметрическим критерием Уилкоксона.

Claude против GPT-mini

metric

N

median Claude

median GPT

mean_diff (GPT-Claude)

лучше/хуже/равно

Wilcoxon p

Precision

120

0.718

0.769

+0.025

67/48/5

0.1546

Recall

120

0.727

0.727

+0.009

58/50/12

0.5938

Судя по p-value нельзя утверждать, что GPT-wiki лучше, чем wiki от Claude Sonnet. Уверенно можно сказать лишь, что статистически значимой разницы нет. Некоторый прирост по качеству можно объяснить бóльшим количеством страниц в GPT-wiki (или недостаточным умением GPT-mini выделять главное). Вырисовывается гипотеза о влиянии атомарности wiki на качество ответов, пока не проверена.

На всякий случай, mean_diff в таблице - это среднее по 120 попарным разностям, а не разность приведённых в таблице медиан.

Claude против Gemma

metric

N

median Claude

median Gemma

mean_diff (Gemma-Claude)

лучше/хуже/равно

Wilcoxon p

Precision

120

0.718

0.733

+0.001

59/60/1

0.8114

Recall

120

0.727

0.690

-0.034

50/55/15

0.1436

По точности оценки варианты сопоставимы. По полноте есть «падение», но количества кейсов недостаточно, чтобы утверждать, что оно значимо.

Gemma против GPT

metric

N

median GPT

median Gemma

mean_diff (Gemma-GPT)

лучше/хуже/равно

Wilcoxon p

Precision

120

0.769

0.733

-0.024

55/60/5

0.2613

Recall

120

0.727

0.690

-0.043

40/61/19

0.03268

А здесь видно, что Gemma несколько хуже GPT по полноте. Однако поправка на множественные сравнения не применялась. 120 это три попарных теста по 40 вопросов, поэтому p=0.033 на грани значимости.

Реальные документы

«Директива 401» - один из моих любимых инструментов. Тем не менее очень хотелось попробовать реальные тексты. У меня было несколько нормативно-правовых документов, посвященных развитию и регулированию искусственного интеллекта. Почему бы не собрать из них wiki? Я подготовил тексты, сохранил файлы с расширением md, запустил пайплайн. Сборщик отработал штатно:

AI regulation wiki
AI regulation wiki

Дисклеймер: Никаких количественных оценок здесь я не делал, результат теста - просто иллюстрация устойчивости пайплайна на новом домене.

В качестве эксперимента, после сборки wiki добавил ещё один документ и перезапустил пайплайн. Был несколько удивлён тем, что это сработало - появилось шесть новых страниц. В коде пока не предусмотрено каких-то специальных решений для добавления документов. source.jsonl работает как мастер-таблица и хранит состояние папки raw. К сожалению, это не помогает обновлять уже созданные элементы при появлении новой информации. Хотя обновление маркдаун-страницы wiki при изменении описания элемента уже готово, сами элементы обновляются сейчас только при сборке «с чистого листа».

Инструкция по сборке wiki

1. Подготовьте исходные документы. Все файлы должны быть с расширением .md

2. Установите Obsidian, создайте Vault с двумя папками, raw и wiki.

3. Перепишите в raw исходные файлы.

4. Клонируйте репозиторий проекта.

5. Переименуйте в проектной папке файл .env.example в .env и заполните переменные в зависимости от того, какой вариант вам доступен или больше нравится (GPT или Ollama). Кроме того укажите там абсолютный путь к Obsidian Vault.

6. Установите в проектной папке python-окружение, Python 3.11+
python -m venv .venv

7. Активируйте окружение и установите необходимые зависимости
.venv\Scripts\activate для Windows source
.venv/bin/activate для macOS/Linux
pip install -r requirements.txt

8. Отредактируйте параметры пайплайна. Добавьте пару тематик в стартовый список, рекомендую закомментировать в ASSEMBLER_STEPS все шаги пайплайна кроме первого. Для начала лучше запустить "Step 1. Source annotation" и убедиться, что всё работает. AUTO_MODE установите в True, для Ollama моделей: OLLAMA = True, FAST_EXTRACTION = False

9. Установите параметры окружения для Ollama:
OLLAMA_HOST (0.0.0.0:11434, если на другой машине) и OLLAMA_CONTEXT_LENGTH (32000 обычно хватает для общего размера исходных файлов в районе 100+ килобайт, по умолчанию там 4096 и это мало. Для большей части пайплайна мне было достаточно 8k)

10. Запуск: в проектной папке: python main.py.
Если аннотация корректно отработает, в Vault должен появится файл source.jsonl

На следующем шаге (фильтрация терминов) создаётся бэкап файла source.jsonl. При запуске этого шага повторно с тем же файлом, LLM-судья снова «попробует» оценить оставшиеся после фильтрации термины. Чтобы избежать повторной фильтрации, должен полностью отработать следующий шаг - экстракция.

После шага "Step 3. Wiki elements extraction" появится файл wiki.jsonl и его можно будет посмотреть в удобном варианте. Я сделал вьювер для браузера (файл wiki_viewer.html), работает без Obsidian. Не устраивает качество расстановки ссылок? Линкер можно перезапустить, он сохраняет файл wiki.jsonl до линковки под именем wiki_backup.jsonl.

Чтобы начать сборку заново, нужно удалить файлы source.jsonl, wiki.jsonl и всё из папки /wiki.

Резюме

  1. Для построения персональной wiki совсем необязательно использовать инструменты с «сильной» моделью. По качеству неожиданно получается приемлемый результат. При этом есть способы его улучшить.

  2. Процесс сборки персональной wiki вполне можно алгоритмизировать с учётом возможностей моделей. Для «слабых» - режем задачи на более простые составляющие и стараемся не отправлять сразу все страницы wiki или исходные документы в контекст промпта.

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

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

Ссылки

UPD: В репозиторий проекта загружена новая версия пайплайна, поддерживающая логику обновления существующих страниц по новым исходным документам.