Comments 13
Весьма наглядный пример и есть возможности для развития, благодарю!
Вопрос: не будет ли преимуществом объединить(перенести в единую модель) извлечение ключевых слов и формирование эбеддингов? Ведь когда Вы одной сеткой извлекаете ключевые слова и темы, то вы разрушаете между ними связь. Т.е. для второй сетки, которая фомирует эмбеддинги, это просто отдельные слова и фразы.
Т.е. для второй сетки, которая фомирует эмбеддинги, это просто отдельные слова и фразы.
Да, это так. Но верно если только генерировать эмбеддинги только для ключевых слов. В этом примере я подмешиваю ключевые слова к описанию темы.
Из плюсов отдельной LLM и эмбеддинг нейросети - высокая специализация каждой и производительность. А значит можно например убрать "подмешивание" ключевых слов и заменить на подмешивание краткого описания всей статьи в отдельную тему и быстро пересчитать.
Ваше предложение действительно рабочее для генерации эмбеддингов для исходных кодов программ, там также важно и большее контекстное окно, так как в отличии от обычного текста - без ущерба не разделить большой метод на независимые фрагменты.
Спасибо за несколько полезных идей (для меня они были, увы, неочевидны) - начну их сейчас пробовать. Как раз работаю над подобной задачей. Кроме gemma3 пробовали ли Вы использовать что-то другое - Spacy, например?
может не в тему, но есть уже семантический поиск по вебу: https://exa.ai/
cудя по размеру инвестиций это у них даже довольно дешево вышло
Статья оставила двоякое впечатление... С одной стороны - достаточно интересная тема... А с другой - осталось впечатление, что просто в рабочем проекте, "на коленках", поиграли с технологией, что-то получили, и решили по-быстрому оформить в виде статьи.
Отсюда странный выбор технологий - использовали просто то, что и так было в проекте и под рукой в целом (подозреваю, что изначально просто junit-тест был написан для пробы).
Зачем тут spring? Main-класса было бы достаточно. Инъекция зависимостей ради пары зависимостей?
Зачем lombok? Пару геттеров-сеттеров можно и руками написать.
Чем обусловлен выбор testcontainers для БД? Есть полноценные встраиваемые БД. А то и просто в RAM/файлах можно было хранить.
Как бы да, это все "лучшие практики" в Java, но в таком маленьком проекте они просто избыточны - руками все сделать не сильно сложнее, а то и проще было бы.
Ну и главное - а где собственно семантический поиск? Получили просто индекс близости статей к друг другу.
Для полноценного же поиска не хватило малости - просто строки ввода, которую надо было через embedding прогнать и вывести список статей по близости к запросу (опционально - перевести через LLM текст запроса).
P.S. ну или просто кто-то с ИИ для кодинга игрался - что тоже нельзя исключать. Ну или лабораторная работа какая...
Комментарий оставляет двоякое впечатление. С первого взгляда похож на конструктивный вопрос о выборе технологий, но по своему тону и форме выражение больше на попытку обесценить работу автора и завязать спор на эмоциях.
"Тут технология X, а почему не Y?". Краткий мой ответ на вопросы такого типа - это личные предпочтения, основанные на опыте разработки прототипов систем. Бывает что код со временем прототипы превращаются в систему, где важнее читаемость и простота расширения, а не отсутствие фреймворков и внешних зависимостей.
Эта статья иллюстрирует подход, который можно адаптировать под конкретные задачи. Почти любой проект можно реализовать на разных технологиях в зависимости от целей, а в данном случае баланс между образовательной ценностью и "написанием с минимумом фреймворков" казался приоритетным в первом. ИИ для кодинга это вообще отдельная тема про агенты и RAG и совсем не gemma3.
Для минимального примера можно обойтись без Spring и Lombok. Однако цель этой публикации — демонстрация практической реализации с легкой повторяемостью и фокусе на сути идеи, а не на написании кода без использования фреймворков. Spring Boot уменьшает усилия на подключение Ollama в Spring AI и в логику индексации данных. Lombok сокращает boilerplate с сеттерами и try/catch, что важно в статье, где фокус на логике, а не на многословном синтаксисе языка программирования. В образовательных целей такой подход позволяет читателю сосредоточиться на главном.
Выбор TestContainers здесь для быстрого старта примера, без описаний инструкций как запустить и настроить PostgreSQL. Опять же фокус на сути, а не командах развертывания СУБД. Мне был нужен именно PostgreSQL c pgvector для долгосрочного хранения данных и написаний произвольных по сложности запросов, а не Sqlite/H2database итп
Спасибо, про семантический поиск ваш комментарий справедлив хоть и забежал в мою будущую публикацию по этой теме. В этой статье показана базовая реализация индексации текстов, вычисление эмбеддингов, но без интерфейса для пользовательского поиска. Добавление интерфейса — логичный следующий этап, который можно реализовать на основе предложенной архитектуры. Например, можно было бы расширить приложение методом вроде searchByQuery() с использованием этих же эмбеддингов. Дополнил эту статью примером получения вектора SELECT get_embedding('SQL final state machine') для ее логической завершенности.
Проект действительно начался как эксперимент и превратился в прототип, демонстрирующий потенциал локальных LLM для структурирования данных. Я показал, как интегрировать PostgreSQL, Ollama и Java для решения задачи, которая обычно требует облачных сервисов или отдельных pipeline с DS/DevOPS экспертизой. Для меня это комплимент, что сложный функционал удалось реализовать по виду как лабораторную работу, что говорит об умении делать сложное проще для понимания читателями без ущерба для цели проекта.
Соглашусь с комментатором выше, что код вызывает двоякое чувство. Действительно, ощущение, что код сгенерирован не совсем хорошей моделью. Ведь, когда видишь примеры кода, даже для обзора технологий, хочется, чтобы они были качественные.
Если в проекте spring boot, почему приложение не построено как классическое boot приложение с разделением на сервисы / репозитории?
Для работы с БД стоило использовать spring-jdbc с его пулом коннекций и jdbcTemplate.
Для логирования ошибок стоило использовать spring-logging / ломбоковский @slf4j
.
boxedArray = new Float[vector.length];
connection.createArrayOf("float4", boxedArray);
зачем тут обертки? достаточно примитивов float[]
Работа с файлами через File устарела на лет 15, давно есть более удобный java.nio.file.Files
У меня возникает двоякое чувство, что оба критических комментария похожи на результаты сгенерированые не совсем хорошей моделью по стандартному промпту для критики кода на java. И это даже пока без анализа профилей коментаторов на экспертность в обсуждаемой теме, статистики комментариев по тону, наличию обесценивания и конструктивности.
Ваш комментарий рекомендует распространенную ошибку в разработке систем: слишком большое внимание к следованию предполагаемым «лучшим практикам» без учета конкретного контекста и целей проекта. Я и не претендовал здесь на абсолютную архитектурную чистоту примера и сделал все что считаю полезным написания этого кода.
Вашим советам тут не следует следовать по нескольким причинам...
Для работы с БД стоило использовать spring-jdbc с его пулом коннекций и jdbcTemplate.
Код здесь предназначен для демонстрации примера реализации семантического поиска, а не для готового к производству корпоративного приложения. Основное внимание уделяется иллюстрации концепции, а не соблюдению абсолютно всех практик разработки и модулей фреймворка. Поскольку я автор, то и выбор/баланс что мне использовать из Spring, а что не нужно в этом проекте за мной.
Если в проекте spring boot, почему приложение не построено как классическое boot приложение с разделением на сервисы / репозитории?
Предложение полной архитектуры Spring с сервисами/репозиториями добавит сюда избыточной сложности. Когда потребуется, то я добавлю все необходимые дополнительные абстракции и слои.
Работа с файлами через File устарела на лет 15, давно есть более удобный java.nio.file.Files
Утверждение, что "работа с файлами через File устарела на 15 лет", преувеличено. java.nio.file.Files предлагает определенные преимущества, но java.io.File по-прежнему широко используется и вполне подходит для тех операций с файлами, что используются в этом примере
Для логирования ошибок стоило использовать spring-logging / ломбоковский
@slf4j
Вполне возможно, так же как можно подключить сбор стуртурированных логов в OpenTelemetry, добавить трейсинг и сбор метрик для улучшения observability. Тогда это будет совсем другая статья и уровень сложности. И главный вопрос зачем это мне сейчас.
зачем тут обертки? достаточно примитивов float[]
Преобразование упакованного массива необходимо при работе с массивами JDBC, поскольку метод createArrayOf требует массивы объектов, а не примитивные массивы.
Я уже объяснил в предыдущем комментарии, какой я сделал выбор, чтобы сосредоточиться на реализации семантического поиска и тем подходам к расширяемости прототипа что я считаю рациональными, а не на применении здесь всех лучших практик разработки на Java.
Не хочется вступать в бессмысленную дискуссию, но все же.
Основной посыл моих замечаний был в том, что если вы пропагандируете какой-то подход / инструмент, даете людям примеры кода, то давайте приводить хорошие примеры кода!
Вашим советам тут не следует следовать по нескольким причинам
В вашем ответе увидел аргумент только по createArrayOf, признаю косяк.
Раз используете в примере spring-boot, то инъекция зависимостей и разделение на компоненты просятся сами собой. С этим глупо спорить.
Применять пулы коннекций и jdbc-template вместо ручного порождения коннекций и стейтментов - аналогично.
Просто пометить @slf4j и использовать стандартное логирование - тоже.
По поводу использования устаревшего File - да, в легаси проектах из начала 00-х еще встречается. Но использовать в современных проектах - это просто моветон.
Насчет createArrayOf который ждет Object[] - признаю, недосмотрел. Метод createArrayOf(String, Object) существует только в PGConnection. Собственно через него и работает createArrayOf(String, Object[]).
И это даже пока без анализа профилей коментаторов на экспертность в обсуждаемой теме
Нужно уметь принимать критику. Если мне джун укажет на явные косяки и отсутствие логики в коде, то я постараюсь его выслушать и сделать выводы.
Ваш комментарий, увы, продолжает прежнюю тенденцию — вы подменяете дискуссию по сути статьи на обсуждение второстепенных технических деталей, игнорируя её основную цель: демонстрацию практического подхода к семантической индексации текстов с использованием локальных LLM моделей и PostgreSQL / pgvector.
>>Примеры кода должны быть хорошими
Статья не претендует на роль руководства по идеальному стилю кодирования. Она показывает рабочую реализацию концепции, где фокус — на логике работы системы, а не на соблюдении всех best practices Java-сообщества. Если бы цель была в демонстрации идеального кода, статья превратилась бы в курс по Java-паттернам, а не в разбор ИИ-задачи.
Я принимаю конструктивную обратную связь, но ваш комментарий больше похож на демагогию со сменой контекста обсуждения. Вы сами выбрали свое "соломенное чучело - идеальный код" и пытаетесь дискредитировать содержание этой статьи. Делаете это с помощью указаний на несоответствие кода вашим представлениям о прекрасном.
В итоге в ваших комментариях оффтоп о стиле кода, а не на семантическом поиске и запуске локальных LLM моделей с помощь Spring AI. Статью я написал для тех, кто хочет разобраться, как применить LLM в Java проекте без использования облачных сервисов - API нейросетей.
Мне кажется стоит поменять sql запросы чтобы он выдавал только один уникальный ID статьи. А то такой ответ смотрится немного странно:
https://habr.com/ru/articles/757278
https://habr.com/ru/articles/757278
https://habr.com/ru/articles/757278
https://habr.com/ru/articles/757278
https://habr.com/ru/articles/757278
(5 rows)
Семантический поиск по статьям Хабра в PostgreSQL + индексация текстов LLM в Ollama