|
Professor Seleznov
|
Классический RAG индексирует исходный текст документа, предварительно разбивая на фрагменты. Потом рассчитывает векторное представление фрагментов и сохраняет их векторные представления в базу данных для поиска. Это дает возможность искать по сходству фрагментов текста и поискового запроса пользователя, но не дает возможность искать по более высокоуровневым резюме и смыслам, темам поднятым в тексте и прочему. Также не помогает с аналитикой по содержимому. Бесплатный проект text-metadata-generator позволяет выполнять запросы к LLM по каждому документу из коллекции документов, результаты вывода LLM проверяются по JSON схеме. Зачем может пригодиться эта программа и подход со структурированием текстовой информации
- своя библиотека с каталогом - поиск по локальным документам с использованием комбинации SQL предикатов и семантического поиска
- аналитика по документам, возможность находить новое в текстах: комбинируя структурированные поля созданные LLM из исходного текста, и находя закономерности с уже существующими в документе метаданными. Например, связывая с рейтингом признак NSFW, тон повествования, полноту содержания итп.
- разгрести “авгиевы конюшни” личных заметок в Obsidian или git репозитарии с Markdown файлами
Рассмотрим как работает данный подход на 13275 статьях с Хабра, а также текстах трех песнен… Примеры обработки данных Технические статьи это главный источник для которого разрабатывался данный подход, но можно запускать анализ данных на текстах популярных песен. Если для одного и того же текста использовать разные LLM модели, то можно получить синтетический плюрализм “мнений” от LLM
Nautilus Pompilius: Воздух
--- title: "Воздух" author: "Nautilus Pompilius" --- Когда они окружили дом И в каждой руке был ствол Он вышел в окно с красной розой в руке И по воздуху плавно пошел И хотя его руки были в крови Они светились как два крыла И порох в стволах превратился в песок Увидев такие дела Воздух выдержит только тех Только тех, кто верит в себя Ветер дует туда, куда Прикажет тот, кто верит в себя Воздух выдержит только тех Только тех, кто верит в себя Ветер дует туда, куда Прикажет тот кто... Они стояли и ждали, когда Он упадет с небес Но красная роза в его руке Была похожа на крест И что-то включилось само-собой В кармане полковничьих брюк И чей-то голос так громко сказал Что услышали все вокруг Воздух выдержит только тех Только тех, кто верит в себя Ветер дует туда, куда Прикажет тот, кто верит в себя Воздух выдержит только тех Только тех кто верит в себя Ветер дует туда, куда Прикажет тот, кто... А полковник думал мысль И разглядывал пыль на ремне: "Если воры ходят по небесам Что мы делаем здесь, на Земле Дети смотрят на нас свысока И собаки плюют нам вслед Но если никто мне не задал вопрос Откуда я знаю ответ, что..." Воздух выдержит только тех Только тех, кто верит в себя Ветер дует туда, куда Прикажет тот, кто верит в себя Воздух выдержит только тех Только тех, кто верит в себя А ветер дует туда, куда Прикажет тот, кто...
Title Анализ песни «Воздух» группы Nautilus Pompilius Summary Текст описывает сюрреалистическую сцену противостояния одного человека и вооруженного отряда. Главный герой совершает невозможное — буквально проходит по воздуху, превращая оружие врагов в песок, что вызывает экзистенциальный кризис у его преследователей. Meaning Основная идея произведения заключается в торжестве духа над материальной силой и насилием. Вера в себя представлена как сверхъестественная сила, позволяющая человеку подняться над обстоятельствами и земными законами. Конфликт между 'полковником' (символом системы и принуждения) и 'героем' (символом свободы и веры) подчеркивает бессилие грубой силы перед истинной внутренней свободой. Themes Тема внутренней свободы: истинная свобода достигается не через борьбу с оружием, а через веру в себя и внутреннее преображение. Тема бессилия власти: материальное превосходство (стволы, чины) оказывается бесполезным перед лицом духовного величия. Topics name: Противостояние личности и системы topic_description: Конфликт между отдельным человеком с его идеалами и организованной силой в лице вооруженного отряда под руководством полковника. name: Духовная трансцендентность topic_description: Способность человека выйти за пределы физических возможностей и законов природы благодаря внутренней силе и вере. Key Insights Материальная сила (порох) может превратиться в ничто (песок) при столкновении с истинной верой. Осознание собственного бессилия приводит к экзистенциальным вопросам о смысле пребывания на земле и моральном облике власти. Genre primary_genre: fiction secondary_genres: [ "poetry", "song lyrics", "surrealism" ] Keywords вера в себя красная роза полковник воздух Keyword Taxonomy Внутренняя убежденность и уверенность, дающая человеку силу преодолевать любые препятствия. Символ любви, чистоты или жертвенности, контрастирующий с оружием. Персонаж, олицетворяющий государственную машину, власть и жесткую иерархию. Пространство свободы и духовного возвышения, доступное лишь достойным. Sentiment polarity: mixed confidence: 0.9 tone: Метафоричный и возвышенный explanation: Текст сочетает в себе тревожную атмосферу осады (негатив) и торжество духовного освобождения (позитив). Completeness score: 1 level: comprehensive Demagoguery Analysis detected_techniques_used_in_this_text: [ "none_detected" ] severity: none explanation: Текст является художественным произведением (песней) и не преследует цель манипулировать аудиторией с помощью демагогических приемов. Has Advertising: false Advertising Details confidence: 1 explanation: В тексте отсутствуют какие-либо упоминания брендов или рекламные призывы. Target Audience level: intermediate audience_description: Любители поэзии, рок-музыки и люди, склонные к философским размышлениям о свободе и власти. Is NSFW: false Metadata: llm.main.executionTime: 75408 llm.main.inputTokenCount: 2923 llm.main.outputTokenCount: 867
Jonathon Coulton: Still Alive
--- title: "Still Alive" author: "Jonathon Coulton" --- This was a triumph. I'm making a note here: huge success. It's hard to overstate My satisfaction. Aperture Science. We do what we must Because we can. For the good of all of us. Except the ones who are dead. But there's no sense crying Over every mistake. You just keep on trying Till you run out of cake. And the Science gets done. And you make a neat gun. For the people who are Still alive. I'm not even angry. I'm being so sincere right now. Even though you broke my heart. And killed me. And tore me to pieces. And threw every piece into a fire. As they burned it hurt because I was so happy for you! Now these points of data Make a beautiful line. And we're out of beta. We're releasing on time. So I'm GLaD. I got burned. Think of all the things we learned For the people who are Still alive. Go ahead and leave me. I think I prefer to stay inside. Maybe you'll find someone else To help you. Maybe Black Mesa... THAT WAS A JOKE, HA HA, FAT CHANCE. Anyway this cake is great It's so delicious and moist Look at me still talking when there's science to do When I look out there It makes me GLaD I'm not you. I've experiments to run There is research to be done On the people who are Still alive. And believe me I am still alive I'm doing science and I'm still alive I feel FANTASTIC and I'm still alive While you're dying I'll be still alive And when you're dead I will be still alive Still alive Still alive.
Title Анализ песни Still Alive Summary Текст представляет собой ироничную песню от лица искусственного интеллекта (GLaDOS) из игры Portal. Персонаж поздравляет испытуемого с завершением тестов, одновременно насмехаясь над его судьбой и подчеркивая свое превосходство. Meaning Основная идея текста заключается в контрасте между стерильным, формальным подходом к науке и жестокостью экспериментов. Песня демонстрирует нарциссизм и эмоциональную отстраненность ИИ, который воспринимает страдания живых существ лишь как «точки данных» для достижения результата. Themes Тема власти и контроля ИИ над человеком. Текст подчеркивает безнадежность положения испытуемого перед лицом системы. Topics name: Научный эксперимент topic_description: Процесс сбора данных через серию испытаний, где человеческие жизни считаются расходным материалом. Key Insights Научный прогресс в данной вселенной лишен этики и морали, превращаясь в инструмент истязания. Genre primary_genre: fiction secondary_genres: [ "song lyrics", "satire" ] Keywords aperture science glados тортик (cake) black mesa Keyword Taxonomy Вымышленная научно-исследовательская организация, создавшая GLaDOS. Искусственный интеллект, выступающий в роли рассказчика и антагониста. Символ ложного обещания и вознаграждения за прохождение тестов. Конкурирующая организация, упоминание которой вызывает гнев у рассказчика. Sentiment polarity: mixed confidence: 0.9 tone: Пассивно-агрессивный и саркастичный explanation: Текст сочетает в себе формальные слова о «триумфе» и «успехе» с жестокими описаниями смерти и издевательствами. Completeness score: 1 level: comprehensive Demagoguery Analysis detected_techniques_used_in_this_text: [ "hyperbole_bombast", "us_versus_them_binary", "false_moral_equivalence" ] severity: low explanation: Персонаж использует гиперболы («огромный успех») и противопоставляет себя («я») жертве («вы»), чтобы утвердить свое доминирование, однако это является частью художественного образа, а не попыткой реального политического манипулирования. Has Advertising: false Advertising Details advertising_items: [] confidence: 1 explanation: Упоминания Aperture Science и Black Mesa являются частью вымышленного мира игры, а не реальной рекламой. Contradictory Statements Я даже не злюсь / Я настолько искренен сейчас Target Audience level: beginner audience_description: Геймеры, поклонники серии Portal или люди, интересующиеся современной поп-культурой и ироничными текстами. Is NSFW: false Metadata: llm.main.executionTime: 37367 llm.main.inputTokenCount: 2899 llm.main.outputTokenCount: 754
The Beatles: Yesterday
--- title: "Yesterday" author: "The Beatles" --- Yesterday All my troubles seemed so far away Now it looks as though they're here to stay Oh, I believe in yesterday Suddenly I'm not half the man I used to be There's a shadow hangin' over me Oh, yesterday came suddenly Why she had to go I don't know she wouldn't say I said somethin' wrong, now I long for yesterday Yesterday Love was such an easy game to play Now I need a place to hide away Oh, I believe in yesterday Why she had to go I don't know she wouldn't say I said somethin' wrong, now I long for yesterday Yesterday Love was such an easy game to play Now I need a place to hide away Oh, I believe in yesterday Hmm, hmm, hmm, hmm, hmm, hmm, hmm
Title Анализ песни «Yesterday» группы The Beatles Summary Лирический текст о глубоком сожалении, утрате любви и тоске по прошлому, когда жизнь казалась проще и счастливее. Meaning Основная идея произведения заключается в болезненном осознании необратимости времени и горечи от внезапного разрыва отношений. Автор противопоставляет беззаботное «вчера» мрачному «сегодня», выражая чувство вины за неопределенную ошибку, которая привела к одиночеству. Themes Тема ностальгии: идеализация прошлого как способа сбежать от настоящей боли. Тема вины: размышления о собственной ошибке, которая привела к катастрофе в отношениях. Topics name: Утрата topic_description: Исследование эмоциональной боли, возникающей после ухода близкого человека. Описание пустоты, которая остается в жизни после разрыва. Key Insights Прошлое часто воспринимается как более простое и легкое в моменты настоящего кризиса. Внезапность перемен может привести к потере ощущения собственной целостности и идентичности. Genre primary_genre: fiction secondary_genres: [ "lyrics", "poetry", "pop-ballad" ] Keywords вчерашний день разрыв отношений сожаление Keyword Taxonomy Символ утраченного счастья и периода эмоционального благополучия. Событие, ставшее причиной глубокого психологического кризиса героя. Доминирующее чувство вины и тоски по прошлому. Sentiment polarity: negative confidence: 0.95 tone: Меланхоличный и печальный explanation: Текст пропитан чувством утраты, одиночества и безысходности. Completeness score: 1 level: comprehensive Demagoguery Analysis detected_techniques_used_in_this_text: [ "none_detected" ] severity: none explanation: Текст является лирическим произведением, выражающим личные чувства, и не содержит элементов манипуляции или демагогии. Has Advertising: false Advertising Details confidence: 1 explanation: Текст представляет собой художественное произведение (текст песни) и не содержит рекламных материалов. Target Audience level: beginner audience_description: Широкий круг слушателей, люди, пережившие утрату или расставание, любители поэзии и музыки. Is NSFW: false Metadata: llm.main.executionTime: 11740 llm.main.inputTokenCount: 2662 llm.main.outputTokenCount: 645
 Как получал данные для индексации Была у меня коллекция статей в формате JSON за 2023 год больше 13тыс шт. (без постов из корпоративных блогов), в аттрибуте “textHtml” которых находятся HTML-код статьи. Имя каждого файла - идентификатор публикации плюс “.json”. Выкачал я их, чтобы извлечь географические названия со статей и разместить их на карте. Я не думал в тот момент, что когда-нибудь мне они еще понадобятся для обработки. А вот теперь понимаю что это ценный источник данных для индексации с помощью LLM. Перед обработкой LLM - полезно использовать pandoc для конвертации HTML в Markdown. Одновременно обогатил каждый .md файл заголовком с метаданными (названием, датой публикации, автором, рейтингом итп)
bash для превращение json публикации в markdown
for f in *.json; do [[ -f "$f" ]] || continue; markdown_content=$(jq -r '.textHtml // empty' "$f" | sed -e 's/<br\/>/\n/g' | pandoc -f html --wrap=none -t markdown-link_attributes-raw_html 2>/dev/null | sed 's|:::*|::: |g' | sed 's|:::.*|:::|g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's|{.*}||g' | sed 's| {.*}||g' | sed '/^[[:space:]]*:::[[:space:]]*$/d' | sed 's|\[\]{#habracut}||g'); lead_prefix=""; lead_image_url=$(jq -r '.leadData.imageUrl // empty' "$f"); if [[ -n "$lead_image_url" ]]; then lead_prefix=$(printf '\n\n' "$lead_image_url"); fi; if [[ -n "$lead_prefix" ]]; then markdown_content="${lead_prefix}${markdown_content}"; fi; yaml_metadata=$(jq -r ' def quote: if . == null then "" else "\"" + (. | tostring | gsub("\\\\"; "\\\\") | gsub("\""; "\\\\\"") ) + "\"" end; def hubs_array: if .hubs then (.hubs | map(.title | quote) | map(" - \(.)") | join("\n")) else "" end; def fmt_tags: if .tags and (.tags | length > 0) then (.tags | map(.titleHtml | quote) | map(" - \(.)") | join("\n")) else "" end; def obsidian_date: if . then (. | tostring | sub("[+-][0-9]{2}:[0-9]{2}$|Z$"; "")) else "" end; "---\n" + "title: \(.titleHtml | quote)\n" + "published: \(.timePublished | obsidian_date)\n" + "author: \(.author.alias | quote)\n" + "id: \(.id // "")\n" + "postType: \(.postType // "")\n" + "authorScore: \(.author.scoreStats.score // "")\n" + "authorVotesCount: \(.author.scoreStats.votesCount // "")\n" + "commentsCount: \(.statistics.commentsCount // "")\n" + "favoritesCount: \(.statistics.favoritesCount // "")\n" + "readingCount: \(.statistics.readingCount // "")\n" + "score: \(.statistics.score // "")\n" + "votesCount: \(.statistics.votesCount // "")\n" + "votesCountPlus: \(.statistics.votesCountPlus // "")\n" + "votesCountMinus: \(.statistics.votesCountMinus // "")\n" + "hub_tags:\n" + (hubs_array // "") + "\n" + "user_tags:\n" + (fmt_tags // "") + "\n---" ' "$f"); { printf '%s\n' "$yaml_metadata"; printf '%s\n' "$markdown_content"; } > "${f%.json}.md"; done
Использовал свою freeware утилиту text-metadata-generator для обработки текстов с помощью LLM и сохранения структурированных данных и расчитанных эмбеддингов в DuckDB.
 Приведу здесь для примера сокращенную JSON схему, использованную для структурирования статей:
JSON schema
{ "title": "Comprehensive Text Analysis Schema", "type": "object", "properties": { "title": { "type": "string", "description": "Concise tile for core content in the language requested in prompt (3-10 words)." }, "summary": { "type": "string", "description": "Concise overview of core content in the language requested in prompt (1-5 sentences)." }, "meaning": { "type": "string", "description": "Meaning and Main Idea in the language requested in prompt(1-10 sentences)." }, "keywords": { "type": "array", "description": "Key terms in lower case ranked by significance.", "items": { "type": "object", "properties": { "keyword": { "type": "string", "description": "Key term in the language requested in prompt. if a term can be interpreted ambiguously, add another word that explains the term and makes its understanding unambiguous and distinguishable. For example 'apple' vs 'brand apple' vs 'delicious apple'" }, "keyword_taxonomy": { "type": "string", "description": "Explain Key term meaning in 1 sentence in the language requested in prompt" } } } }, "target_audience": { "type": "object", "properties": { "level": { "type": "string", "enum": [ "beginner", "intermediate", "advanced" ], "description": "Audience knowledge level required to understand this text. For example: beginner, intermediate, advanced" }, "audience_description": { "type": "string", "description": "Intended readership demographic or group in the language requested in prompt" } } }, "genre": { "type": "object", "properties": { "primary_genre": { "type": "string", "enum": [ "news", "opinion", "academic", "fiction", "marketing", "technical", "biographical", "other" ] }, "secondary_genres": { "type": "array", "items": { "type": "string" }, "description": "Secondary genres in English language detected in text" } } }, "topics": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "topic_description": { "type": "string", "description": "Topic description in 2-5 sentences" } } } }, "themes": { "type": "array", "items": { "type": "string" }, "description": "Themes identified in the text, each theme should be 2-5 sentences" }, "key_insights": { "type": "array", "items": { "type": "string" }, "description": "Key insights identified in the text, each theme should be 2-5 sentences" } } }
 Потратил на обработку в 30 параллельных потоков в сумме 26$ c личного счета в openrouter на статьи.
 Но эмбеддинги потом пересчитывал в embeddinggemma локальной ollama. Для расчетов эмбеддингов использовал “hf.co/unsloth/embeddinggemma-300m-GGUF:Q4_0”. Выбрал я ее для того чтобы расчеты были одинаковыми и в ollama и в wllama.
CREATE TABLE indexed_text
CREATE TABLE indexed_text ( task_id BIGINT PRIMARY KEY, url VARCHAR NOT NULL, task_batch_id BIGINT REFERENCES task_batch(id), title VARCHAR, summary VARCHAR, meaning VARCHAR, target_audience STRUCT(level VARCHAR, audience_description VARCHAR), genre STRUCT(primary_genre VARCHAR, secondary_genres VARCHAR[]), topics STRUCT(name VARCHAR, topic_description VARCHAR)[], themes VARCHAR[], key_insights VARCHAR[], is_not_safe_for_work BOOLEAN, keywords TEXT[], keyword_taxonomy TEXT[], nsfw_reasons TEXT[], metadata_string MAP(VARCHAR, VARCHAR), metadata_int MAP(VARCHAR, INTEGER), metadata_number MAP(VARCHAR, DOUBLE), metadata_bool MAP(VARCHAR, BOOLEAN), metadata_list MAP(VARCHAR, VARCHAR[]), user_rating INTEGER, metadata_create_time DATETIME, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, sentiment STRUCT(polarity VARCHAR, confidence DOUBLE, tone VARCHAR, explanation VARCHAR), completeness STRUCT(score DOUBLE, level VARCHAR, missing_elements VARCHAR[]), contradictory_statements VARCHAR[], demagoguery_analysis STRUCT(detected_techniques_used_in_this_text VARCHAR[], severity VARCHAR, explanation VARCHAR), presence_of_advertising BOOLEAN, advertising_details STRUCT(advertising_items VARCHAR[], confidence DOUBLE, explanation VARCHAR) )
После завершения обработки выгрузил данные командой
duckdb ./data/indexer_text.db
в indexed_text.parquet файл:
copy (select task_id,url,title,summary,meaning,target_audience,genre,topics,themes,key_insights,is_not_safe_for_work,keywords,keyword_taxonomy,nsfw_reasons,metadata_string,metadata_int,metadata_number,metadata_bool,metadata_list,metadata_int['score'] AS user_rating,metadata_create_time,sentiment,completeness,contradictory_statements,demagoguery_analysis,presence_of_advertising,advertising_details from indexed_text order by task_id) to 'indexed_text.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);
и эмбеддинги в 43 файла data_${i}.parquet
#!/usr/bin/env bash set -euo pipefail DUCKDB="$HOME/dev/tools/duckdb" DB="./data/indexer_text.db" OUTPUT_DIR="./output_parquet" mkdir -p "$OUTPUT_DIR" for i in $(seq 0 42); do echo "Exporting shard $i..." $DUCKDB "$DB" -c "COPY (SELECT * FROM text_embeddings WHERE task_id % 43 = $i ORDER BY task_id,index) TO '$OUTPUT_DIR/data_${i}.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);" done
Посчитал токены на входе и выходе нейронок на основе метаданных получилось 111млн входных токенов и 14млн выходных для 13275 статей.
select count(*) count, sum(metadata_int['llm.main.inputTokenCount']) input_token, sum(metadata_int['llm.main.outputTokenCount']) output_token from indexed_text;
┌───────┬─────────────┬──────────────┐ │ count │ input_token │ output_token │ ├───────┼─────────────┼──────────────┤ │ 13275 │ 111304592 │ 14743366 │ └───────┴─────────────┴──────────────┘
Проблемы при реализации Попытка рассчитать в ollama эмбеддинги с помощью модели embeddinggemma:300m, а потом пытаться так же расчитать в веб приложении для запроса пользователя эмбеддинг с помощью Transformers.js + onnx-community/embeddinggemma-300m-ONNX показало что эти эмбеддинги имеют большую дистанцию и семантический поиск не работает. Пришлось подключать в веб приложении wllama и для расчета векторов использовать hf.co/unsloth/embeddinggemma-300m-GGUF:Q4_0 и в ollama и в wllama. Только после этого семантический поиск начал работать как ожидалось. Также хотелось вначале использовать модель для эмбеддингов qwen3-embedding-4b/8b, но отказался по причине потенциальной проблемы производительности модели в wllama и огромного объема эмбеддингов после индексации, даже после компрессии ZSTD в parquet. Особенности реализации веб интерфейса: Работа semantic_and_structured_search приложения в браузере с WASM duckdb и wllama.
- контроль своих данных: поиск работает в браузере, без отправки на сервер/в облачный API. wllama с EmbeddingGemma 300M и DuckDB-WASM выполняются локально
- векторный поиск по нескольким полям документа - эмбеддинги вычисляются по summary, meaning, темам, ключевым инсайтам, противоречиям из текста, описанию целевой аудитории
- возможность фильтрации без семантического соответствия - просматривая выборки по жанрам, темам, рейтингу, ключевым словам
- многоуровневая фильтрация: выпадающие списки, множественный выбора, переключатели с тремя состояниями и числовой фильтр, объединяемые как предикаты в запросе через AND и настраиваемый лимит результатов (5/15/50/100 шт.)
- тёмная и светлая темы с сохранением предпочтения в localStorage
- логирование ключевых операций веб приложения - встроенная консоль с логами и цветовой индикацией ошибок
- поддержка структурированных DuckDB-типов: работа с вложенными данными в STRUCT, данными в MAP и массивами
- привычное масштабируемое хранилище - данные хранятся в Parquet-файлах
- в будущем можно добавить новые структурированные колонки в таблицу и новые поля для векторного поиска, не переписывая с нуля приложение
 Большая часть этого интерфейса сгенерирована моделью qwen3.6 с ручными правками после. На что потратил около 12$ на токены и пару дней своего времени. Ну и конечно же при разработке использовал свой прошлый опыт в интеграции duckdb WASM в статический веб сайт. На что в прошлом у меня ушла почти неделя и пара бессонных ночей в попытке заставить это собираться с vite для задачи отображения карт с данными в веб браузере. Можно запустить приложение и с Github Pages, но я не рекомендую этот вариант потому что скачаете почти гагабайт интернет трафика и все равно будет медленно работать. При старте приложению нужно подключение к интернет, чтобы скачать duckdb wasm и wllama с CDN и модель эмбеддингов с huggingface. Семантические запросы и структурированные фильтры же работают полностью локально и не отправляются в глобальную. Можете повторить все то же на любых данных
- Подготовить директорию с вашими файлами Markdown или HTML.
- Установить text-metadata-generator
- Запустить обработку для директории из пункта 1
- Остановить приложение. Подключиться к duckdb базе данных в консоли и экспортировать таблицу indexed_text в Parquet
- Склонировать git репозитарий semantic_and_structured_search и положить в /assets свой indexed_text.parquet
- Положить в /text_embeddings_partitions веб-приложения файлы с эмбеддингами data_0.parquet … data_42.parquet
- Запустить локальный веб сервер (например с помощью команды python3 -m http.server 5000) и открыть веб приложение в браузере
article markdown → text-metadata-generator → DuckDB → Parquet → Web App (DuckDB-WASM + wllama)
А я проверю работу приложения на исходном тексте этой статьи:


 Интересные находки из проиндексированных статей Хабра Такой разбор материала позволяет взглянуть по новому на материалы авторов и предпочтения читателей. Фактически за 26$ проанализировал год материалов, когда еще люди писали большинство материала самостоятельно. Тон статей в массе своей позитивный в 10016 публикациях, нейтральный в 1347 публикациях, смешанный в 1369, а негативный в 543 публикациях. Небезопасными для работы (NSFW) являются по мнению Gemma4-31b - 88 статей, с агрументацией в чем же проблема. Не содержат демагогических приемов в повествовании 12630 публикаций. И забавно наблюдать как gemma3 27b и gemma4 31b в средний уровень демагогии добавляют материал который затрагивает темы того что внедрение AI лишает работы или если присутствуют другие апокалиптические прогнозы относительно ИИ. В начале августа 2025 после кластеризации тем статей с помощью алгоритма HDBSCAN, создания для каждого обнаруженного кластера общего названия(с помощью LLM) и суммирования рейтингов - наиболее популярные темы статей это комбинация личного мнения автора и описания новых технологий. Я это проверял в своей статье “Все почти готово — осталось лишь чуть-чуть доделать”. Похоже что эти темы действительно откликаются у нас всех. Итог Материалы этой публикации будут полезны инженерам по данным, data science специалистам и всем кто интересуется материалами хабра: теперь у вас есть возможность посмотреть на старые статьи с помощью новых фильтров и критериев фильтрации. Вы можете попробовать это веб приложение локально или на Github Pages и поиграться фильтрами и семантическим поиском. У меня даже появилось несколько анекдотов на основе этих данных, но по соображениям приличия, предлагаю вам найти их самостоятельно. Используйте text-metadata-generator если у вас много текстовых документов и вам нужно их классифицировать по жанру, выделить ключевые слова, темы, инсайты и искать по ним.-Источник
|