{"id":462017,"date":"2025-06-03T21:01:43","date_gmt":"2025-06-03T21:01:43","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=462017"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=462017","title":{"rendered":"<span>\u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u044f\u043c \u0425\u0430\u0431\u0440\u0430 \u0432 PostgreSQL + \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432 LLM \u0432 Ollama<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 &#8212; \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u043e\u0432 \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430. \u0417\u0434\u0435\u0441\u044c \u044f \u0441\u043e\u0437\u0434\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441 \u0425\u0430\u0431\u0440\u0430, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438\u0437 \u043d\u0438\u0445 \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e  \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0445 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 LLM, \u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0442\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0441\u043c\u044b\u0441\u043b\u0443, \u0430 \u043d\u0435 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0430 \u0432\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/no\/dm\/k1\/nodmk1id882j3wrygndmlrcjsau.png\" alt=\"Habr data indexing\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/no\/dm\/k1\/nodmk1id882j3wrygndmlrcjsau.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/no\/dm\/k1\/nodmk1id882j3wrygndmlrcjsau.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Habr data indexing<\/figcaption><\/div>\n<\/figure>\n<p>\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 LLM \u044d\u0442\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438 \u043d\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0435\u0444\u0435\u0440\u0430\u0442\u043e\u0432 \u0434\u043b\u044f \u0448\u043a\u043e\u043b\u044c\u043d\u0438\u043a\u043e\u0432 \u0438\u043b\u0438 \u043f\u0438\u0441\u0435\u043c \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0431\u044b\u0441\u0442\u0440\u044b\u0445 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043d\u0430 \u0432\u0430\u0448\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b. \u042d\u0442\u043e \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u0434\u043b\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u043c\u0435\u0434\u0438\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0441\u043a\u043e\u0440\u043e \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0432\u043e\u043f\u043b\u043e\u0449\u0435\u043d\u0438\u0435 \u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u041f\u0430\u0443\u0442\u0438\u043d\u044b (\u0412\u0435\u0431\u0430) \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0438\u0437-\u0437\u0430 \u0442\u0440\u0443\u0434\u043e\u0435\u043c\u043a\u043e\u0441\u0442\u0438 \u0440\u0443\u0447\u043d\u043e\u0439 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u043b\u044e\u0434\u044c\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442.<\/p>\n<h3>\u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a<\/h3>\n<p>\u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u2014 \u044d\u0442\u043e \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0438 \u0441\u043c\u044b\u0441\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f &#8212; embeddings \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f, \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b.<\/p>\n<p>\u0422\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u043c \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u041d\u0435 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0441\u043c\u044b\u0441\u043b \u0441\u043b\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0427\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d \u043a \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0435\u0442 \u0441\u0438\u043d\u043e\u043d\u0438\u043c\u044b \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u044f \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u043c\u043d\u043e\u0433\u043e\u043c\u0435\u0440\u043d\u044b\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u044b, \u0433\u0434\u0435 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0431\u043b\u0438\u0437\u043a\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0442\u0441\u044f \u0440\u044f\u0434\u043e\u043c \u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435. \u0412\u0441\u0435 \u044d\u0442\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043c\u043e\u0434\u0435\u043b\u044f\u043c \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0431\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043a\u043e\u0440\u043f\u0443\u0441\u0430\u0445 \u0442\u0435\u043a\u0441\u0442\u043e\u0432. \u041a\u0430\u043a \u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c nomic-embed-text.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u0438\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c embedding \u043d\u0435 \u043f\u043e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c\u0443 \u0442\u0435\u043a\u0441\u0442\u0443<\/h3>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0438\u0434\u0435\u0439 \u043c\u043e\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u043d\u0435 \u0434\u043b\u044f \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0441\u0442\u0430\u0442\u0435\u0439, \u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u0437 \u043d\u0438\u0445 \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432. \u042d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432:<\/p>\n<ol>\n<li>\n<p><strong>\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e<\/strong> \u2014 \u0438\u043d\u043e\u0433\u0434\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0441\u0442\u0430\u0442\u0435\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0435\u0439\u0441\u044f \u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0442\u0435\u043c\u0435, \u0440\u0435\u043a\u043b\u0430\u043c\u0443 \u0438\u0442\u043f.<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u043c\u044b\u0441\u043b\u043e\u0432\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435<\/strong> \u2014 \u0430\u0432\u0442\u043e\u0440\u044b \u0441\u0442\u0430\u0442\u0435\u0439 \u0441\u0443\u0431\u044a\u0435\u043a\u0442\u0438\u0432\u043d\u044b \u0432 \u0432\u044b\u0431\u043e\u0440\u0435 \u0442\u0435\u0433\u043e\u0432, LLM \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043a\u043b\u0430\u0441\u0441\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0443\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u0430<\/strong> \u2014 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0430 \u0441\u0443\u0442\u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430.<\/p>\n<\/li>\n<\/ol>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h3>\n<p>\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432:<\/p>\n<ul>\n<li>\n<p><strong>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL<\/strong> \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>\u042f\u0437\u044b\u043a\u043e\u0432\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c (LLM)<\/strong> \u0432 Ollama \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0438\u0437 \u0441\u0442\u0430\u0442\u0435\u0439<\/p>\n<\/li>\n<li>\n<p><strong>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f embeddings<\/strong> \u0432 Ollama, \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0449\u0430\u044f \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>Java-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/strong> \u043d\u0430 \u0431\u0430\u0437\u0435 Spring Boot \u0438 Spring AI, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0438\u0445 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0421\u0423\u0411\u0414.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u043a\u043e\u0434\u0430 \u043d\u0443\u0436\u043d\u044b \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438: <strong>org.postgresql:postgresql:42.7.5, com.fasterxml.jackson.core:jackson-databind:2.19.0, org.springframework.ai:spring-ai-starter-model-ollama:1.0.0, org.projectlombok:lombok:1.18.34, org.testcontainers:postgresql:1.21.0<\/strong><\/p>\n<h3>\u0421\u0445\u0435\u043c\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/mm\/bw\/mr\/mmbwmrtpdrvw6qmgkv38i18m6-4.png\" alt=\"Database Schema\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/mm\/bw\/mr\/mmbwmrtpdrvw6qmgkv38i18m6-4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/mm\/bw\/mr\/mmbwmrtpdrvw6qmgkv38i18m6-4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Database Schema<\/figcaption><\/div>\n<\/figure>\n<h3>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h3>\n<h4>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h4>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043d\u0430 Java \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Spring Boot \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/p>\n<ul>\n<li>\n<p><code>HabrApplication<\/code> \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><code>DatabaseManager<\/code> \u2014 \u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445: <code>Article<\/code> &#8212; \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u043e\u043b\u044f\u043c JSON \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435, <code>Topics<\/code> &#8212; \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, <code>Chapter<\/code> \u0438 \u0434\u0440.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/e7\/fc\/n2\/e7fcn2t9p5shuh0vqtb_5tdayxa.png\" alt=\"Application Flow\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/e7\/fc\/n2\/e7fcn2t9p5shuh0vqtb_5tdayxa.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/e7\/fc\/n2\/e7fcn2t9p5shuh0vqtb_5tdayxa.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Application Flow<\/figcaption><\/div>\n<\/figure>\n<p>\u042d\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0441\u043a\u0430\u0447\u0430\u043d\u043d\u044b\u0435 \u0441 \u0445\u0430\u0431\u0440\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 -Darticles=\/home\/habr\/articles<\/p>\n<h4>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c PostgreSQL \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector. \u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b:<\/p>\n<pre><code class=\"sql\">CREATE TABLE habr (     id BIGINT PRIMARY KEY,     title TEXT,     text TEXT,     properties JSONB );  CREATE TABLE habr_vectors (     id BIGINT,     idx INTEGER,     notes TEXT,     search_vector vector(768),     PRIMARY KEY (id, idx),     FOREIGN KEY (id) REFERENCES habr(id) ); <\/code><\/pre>\n<p>\u0422\u0430\u0431\u043b\u0438\u0446\u0430 <code>habr<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u0430 <code>habr_vectors<\/code> \u2014 \u0442\u0435\u043c\u044b, \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u043c\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043d\u0438\u0445:<\/p>\n<ul>\n<li>\n<p><code>idx = 0<\/code> \u2014 \u043a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438<\/p>\n<\/li>\n<li>\n<p><code>idx = 1<\/code> \u2014 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430<\/p>\n<\/li>\n<li>\n<p><code>idx &gt; 1<\/code> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b, \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438<\/p>\n<\/li>\n<\/ul>\n<h4>\u041a\u043e\u0434 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h4>\n<p>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435, \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044e\u0449\u0435\u043c HabrApplication \u043e\u0442 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443 \u0421\u0423\u0411\u0414 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0445\u043e\u0441\u0442\u0430 \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0431\u043c\u0435\u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043c\u0438. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u0430\u043c\u043e\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043d\u0443\u0436\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e JVM, Docker, Ollama.<\/p>\n<pre><code class=\"java\">package com.github.isuhorukov;  import com.github.isuhorukov.model.Chapter; import lombok.Getter; import org.postgresql.ds.PGSimpleDataSource; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.PostgreSQLContainer;  import javax.sql.DataSource; import java.io.Closeable; import java.io.File; import java.sql.*; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set;  \/**  * \u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 PostgreSQL.  * \u041a\u043b\u0430\u0441\u0441 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 PostgreSQL \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector,  * \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438.  *\/ public class DatabaseManager implements Closeable {     private final PostgreSQLContainer&lt;?&gt; postgres;     @Getter     private DataSource dataSource;      \/**      * \u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 PostgreSQL.      * \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435.      *\/     public DatabaseManager() {         File dbDataDir = new File(\".\/postgres-data\");         if (!dbDataDir.exists()) {             dbDataDir.mkdirs();         }          postgres = new PostgreSQLContainer&lt;&gt;(\"pgvector\/pgvector:pg16\")                 .withDatabaseName(\"habr\")                 .withUsername(\"test\")                 .withPassword(\"test\")                 .withFileSystemBind(                         new File(\".\/data\").getAbsolutePath(),                         \"\/mnt\/data\",                         BindMode.READ_ONLY                 )                 .withFileSystemBind(                         dbDataDir.getAbsolutePath(),                         \"\/var\/lib\/postgresql\/data\",                         BindMode.READ_WRITE                 );                  postgres.start();         System.out.println(\"Database URL: \" + postgres.getJdbcUrl());         initializeDatabase();     }      \/**      * \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b.      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u0443\u0434\u0430\u043b\u0430\u0441\u044c      *\/     private void initializeDatabase() {         try {             PGSimpleDataSource pgDataSource = new PGSimpleDataSource();             pgDataSource.setUrl(postgres.getJdbcUrl());             pgDataSource.setUser(postgres.getUsername());             pgDataSource.setPassword(postgres.getPassword());                          this.dataSource = pgDataSource;                          try (Connection connection = dataSource.getConnection();                  Statement stmt = connection.createStatement()) {                 stmt.execute(\"CREATE EXTENSION vector\");                 stmt.execute(\"\"\"                         CREATE TABLE IF NOT EXISTS habr (                         id BIGINT PRIMARY KEY,                         title TEXT,                         text TEXT,                         properties JSONB                         )\"\"\");                 stmt.execute(\"\"\"                         CREATE TABLE IF NOT EXISTS habr_vectors(                         id BIGINT,                         idx INTEGER,                         notes TEXT,                         search_vector vector(768)                         )\"\"\");                 stmt.execute(\"ALTER TABLE habr_vectors \" +                         \"ADD CONSTRAINT habr_vectors_pkey PRIMARY KEY (id, idx);\");                  stmt.execute(\"ALTER TABLE habr_vectors \" +                         \"ADD CONSTRAINT fk_habr_vectors_habr \" +                         \"FOREIGN KEY (id) REFERENCES habr(id);\");                  stmt.execute(\"COMMENT ON TABLE habr IS '\u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0442\u0430\u0442\u0435\u0439 \u0441 \u0425\u0430\u0431\u0440\u0430'\");                 stmt.execute(\"COMMENT ON COLUMN habr.id IS '\u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr.title IS '\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr.text IS '\u041f\u043e\u043b\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 HTML \u0444\u043e\u0440\u043c\u0430\u0442\u0435'\");                 stmt.execute(\"COMMENT ON COLUMN habr.properties IS '\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 JSON \u0444\u043e\u0440\u043c\u0430\u0442\u0435'\");                  stmt.execute(\"COMMENT ON TABLE habr_vectors IS '\u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0441\u0442\u0430\u0442\u0435\u0439 \u0425\u0430\u0431\u0440\u0430'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.id IS '\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438,\" +                         \" \u043a\u0430\u043a \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0435 habr'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.idx IS '\u041f\u043e\u0440\u044f\u0434\u043a\u043e\u0432\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u0442\u0435\u043a\u0441\u0442\u0430 \" +                         \"\u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441 0 - \u043a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441 1 - \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0434\u043b\u044f \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441\u044b &gt;1 - \u0442\u0435\u043c\u044b \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.notes IS '\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 - \" +                         \"\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0442\u043b\u0438\u0447\u0438\u043c\u044b\u0439 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.search_vector IS '\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \" +                         \"\u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u0438\u0445 notes \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430'\");              }         } catch (SQLException e) {             throw new RuntimeException(\"Failed to initialize database\", e);         }     }      \/**      * \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e \u0425\u0430\u0431\u0440\u0430 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.      *       * @param id \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438      * @param title \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0441\u0442\u0430\u0442\u044c\u0438      * @param text \u0442\u0435\u043a\u0441\u0442 \u0441\u0442\u0430\u0442\u044c\u0438      * @param json \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSON      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435      *\/     public void saveHabrArticle(long id, String title, String text, String json) {         String sql = \"INSERT INTO habr (id, title, text, properties) VALUES (?,?,?,?::jsonb)\";          try (Connection connection = dataSource.getConnection();              PreparedStatement pstmt = connection.prepareStatement(sql)) {             pstmt.setLong(1, id);             pstmt.setString(2, title);             pstmt.setString(3, text);             pstmt.setString(4, json);             pstmt.executeUpdate();         } catch (SQLException e) {             throw new RuntimeException(\"Failed to save habr article\", e);         }     }      \/**      * \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u0425\u0430\u0431\u0440\u0430.      *       * @param id \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438      * @param idx \u0438\u043d\u0434\u0435\u043a\u0441 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u0434\u0430\u043d\u043d\u044b\u0445      * @param notes \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435      * @param vector \u043c\u0430\u0441\u0441\u0438\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0432\u0435\u043a\u0442\u043e\u0440\u0430      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435      *\/     public void saveHabrVector(long id, int idx, String notes, float[] vector) {         String sql = \"INSERT INTO habr_vectors (id, idx, notes, search_vector) VALUES (?, ?, ?, ?)\";          try (Connection connection = dataSource.getConnection();              PreparedStatement pstmt = connection.prepareStatement(sql)) {              pstmt.setLong(1, id);             pstmt.setInt(2, idx);             pstmt.setString(3, notes);              setVector(connection, pstmt, 4, vector);              pstmt.executeUpdate();         } catch (SQLException e) {             throw new RuntimeException(\"Failed to save vector data\", e);         }     }      \/**      * \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0432 PreparedStatement.      *       * @param connection \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445      * @param pstmt \u0437\u0430\u043f\u0440\u043e\u0441      * @param parameterIndex \u0438\u043d\u0434\u0435\u043a\u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430      * @param vector \u043c\u0430\u0441\u0441\u0438\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0432\u0435\u043a\u0442\u043e\u0440\u0430      * @throws SQLException \u0435\u0441\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u0443\u0434\u0430\u043b\u0430\u0441\u044c      *\/     private static void setVector(Connection connection, PreparedStatement pstmt, int parameterIndex,                                   float[] vector) throws SQLException {         if (vector != null) {             Float[] boxedArray = new Float[vector.length];             for (int i = 0; i &lt; vector.length; i++) {                 boxedArray[i] = vector[i];             }              Array vectorArray = connection.createArrayOf(\"float4\", boxedArray);             pstmt.setArray(parameterIndex, vectorArray);         } else {             pstmt.setNull(parameterIndex, Types.ARRAY);         }     }      \/**      * \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0430.      *       * @param id \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438      * @param idx \u0438\u043d\u0434\u0435\u043a\u0441 \u0432\u0435\u043a\u0442\u043e\u0440\u0430      * @param vector \u043d\u043e\u0432\u044b\u0439 \u043c\u0430\u0441\u0441\u0438\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0432\u0435\u043a\u0442\u043e\u0440\u0430      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c      *\/     public void updateSearchVector(long id, int idx, float[] vector) {         String sql = \"UPDATE habr_vectors SET search_vector = ? WHERE id = ? AND idx = ?\";          try (Connection connection = dataSource.getConnection();              PreparedStatement pstmt = connection.prepareStatement(sql)) {              setVector( connection, pstmt, 1, vector);              pstmt.setLong(2, id);             pstmt.setInt(3, idx);              int rowsUpdated = pstmt.executeUpdate();             if (rowsUpdated ==0) {                 throw new SQLException(\"Updating search_vector failed, no rows affected. ID: \" + id + \", IDX: \" + idx);             }         } catch (SQLException e) {             throw new RuntimeException(\"Failed to update search_vector for ID: \" + id + \", IDX: \" + idx, e);         }     }      \/**      * \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043d\u0430\u0431\u043e\u0440 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0441\u0442\u0430\u0442\u0435\u0439, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440\u044b.      *       * @return \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0445 \u0441\u0442\u0430\u0442\u0435\u0439      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c      *\/     public Set&lt;Long&gt; getProcessedForSummaryArticleIds() {         String sql = \"SELECT DISTINCT id FROM habr_vectors\";         Set&lt;Long&gt; ids = new HashSet&lt;&gt;();          try (Connection connection = dataSource.getConnection();              Statement stmt = connection.createStatement();              ResultSet rs = stmt.executeQuery(sql)) {              while (rs.next()) {                 ids.add(rs.getLong(\"id\"));             }         } catch (SQLException e) {             throw new RuntimeException(\"Failed to retrieve distinct vector IDs\", e);         }          return ids;     }      \/**      * \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0433\u043b\u0430\u0432, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f.      *       * @return \u0441\u043f\u0438\u0441\u043e\u043a \u0433\u043b\u0430\u0432 \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c      *\/     public List&lt;Chapter&gt; getChapterForEmbedding() {         String sql = \"SELECT hv.id, hv.idx, hv.notes, kw.notes as keywords \" +                 \"FROM habr_vectors hv \" +                 \"LEFT JOIN habr_vectors kw ON hv.id = kw.id AND kw.idx = 1 \" +                 \"WHERE hv.search_vector IS NULL AND hv.idx &lt;&gt; 1\";          List&lt;Chapter&gt; results = new ArrayList&lt;&gt;();          try (Connection connection = dataSource.getConnection();              Statement stmt = connection.createStatement();              ResultSet rs = stmt.executeQuery(sql)) {              while (rs.next()) {                 Chapter chapter = new Chapter();                 chapter.setId(rs.getLong(\"id\"));                 chapter.setIdx(rs.getInt(\"idx\"));                 chapter.setNotes(rs.getString(\"notes\"));                 chapter.setKeywords(rs.getString(\"keywords\"));                 results.add(chapter);             }         } catch (SQLException e) {             throw new RuntimeException(\"Failed to retrieve vectors\", e);         }          return results;     }      \/**      * \u041e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 PostgreSQL.      *\/     @Override     public void close() {         postgres.stop();     } } <\/code><\/pre>\n<h4>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u044e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u044d\u0442\u0430\u043f\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 <code>HabrApplication<\/code>:<\/p>\n<h3>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u0442\u0430\u0442\u0435\u0439<\/h3>\n<pre><code class=\"java\">private List&lt;Article&gt; loadArticles(String articlesDirPath, ObjectMapper objectMapper) {     File[] articlesPath = new File(articlesDirPath).listFiles();     return Arrays.stream(articlesPath)             .parallel()             .map(file -&gt; getArticle(file, objectMapper))             .toList(); } <\/code><\/pre>\n<p>\u0421\u0442\u0430\u0442\u044c\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0438\u0437 JSON-\u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u044b <code>Article<\/code>.<\/p>\n<h3>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0435\u0439 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<pre><code class=\"java\">private static void saveArticle(Article article, DatabaseManager databaseManager, ObjectMapper objectMapper) {     String textHtml = article.getTextHtml();     article.setTextHtml(null);     databaseManager.saveHabrArticle(article.getId(), article.getTitleHtml(), textHtml,              objectMapper.writeValueAsString(article));     article.setTextHtml(textHtml); } <\/code><\/pre>\n<p>\u0421\u0442\u0430\u0442\u044c\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0442\u0435\u043a\u0441\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u044b\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435, \u0430 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSON.<\/p>\n<h3>\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432<\/h3>\n<pre><code class=\"java\">private static @Nullable Topics getTopics(ChatModel chatModel, Article article) {     System.out.println(\"Getting topics for article. Text length: \" + article.getTextHtml().length());     try {         return ChatClient.create(chatModel).prompt()                 .user(u -&gt; u.text(\"Answer in english language only! Enumerate in details topics covered in html text: {html}\")                         .param(\"html\", article.getTextHtml()))                 .call()                 .entity(Topics.class);     } catch (Exception e) {         System.out.println(\"Exception in article id=\" + article.getId() + \": \" + e.getMessage());         return null;     } } <\/code><\/pre>\n<p>\u0414\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u044f\u0437\u044b\u043a\u043e\u0432\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430\u044f \u0447\u0435\u0440\u0435\u0437 Spring AI. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e \u0432 Ollama \u0442\u0435\u043a\u0441\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 <code>Topics<\/code>.<\/p>\n<h3>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439<\/h3>\n<pre><code class=\"java\">private float[] createEmbedding(EmbeddingModel embeddingModel, List&lt;String&gt; textForEmbeddings, int idx, String text) {     if (idx != 1) {         return embeddingModel.embed(textForEmbeddings.get(1) + \". \" + text);     } else {         return embeddingModel.embed(text);     } } <\/code><\/pre>\n<p>\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0442\u0435\u043c\u044b \u0438 \u0434\u043b\u044f \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u0447\u0442\u043e \u0434\u043b\u044f \u0442\u0435\u043c \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f (idx != 1) \u043c\u044b \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0442\u0435\u043c\u044b \u0441 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<pre><code class=\"java\">databaseManager.saveHabrVector(article.getId(), idx, text, vectors); <\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435.<\/p>\n<h4>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h4>\n<details class=\"spoiler\">\n<summary>HabrApplication.java<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"java\">package com.github.isuhorukov;  import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.isuhorukov.model.Article; import com.github.isuhorukov.model.Topics; import lombok.SneakyThrows; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean;  import java.io.File; import java.util.*;  \/**  * \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u0441 Habr.  * \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u0438, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445  * \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u043c\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430.  *\/ @SpringBootApplication public class HabrApplication {     public static void main(String[] args) {         SpringApplication.run(HabrApplication.class, args);     }      \/**      * CommandLineRunner \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.      *      * @param embeddingModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430      * @param chatModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430      * @param articlesDirPath \u043f\u0443\u0442\u044c \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0441\u043e \u0441\u0442\u0430\u0442\u044c\u044f\u043c\u0438      * @return \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 CommandLineRunner      *\/     @Bean     CommandLineRunner run(EmbeddingModel embeddingModel, ChatModel chatModel,                           @Value(\"${articles}\") String articlesDirPath) {         return args -&gt; {             ObjectMapper objectMapper = createObjectMapper();             List&lt;Article&gt; articleList = loadArticles(articlesDirPath, objectMapper);             processArticles(articleList, embeddingModel, chatModel, objectMapper);         };     }          \/**      * \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438.      *       * @param articlesDirPath \u043f\u0443\u0442\u044c \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0441\u043e \u0441\u0442\u0430\u0442\u044c\u044f\u043c\u0438      * @param objectMapper \u043c\u0430\u043f\u043f\u0435\u0440 \u0434\u043b\u044f \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 JSON      * @return \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0442\u0430\u0442\u0435\u0439      *\/     private List&lt;Article&gt; loadArticles(String articlesDirPath, ObjectMapper objectMapper) {         File[] articlesPath = new File(articlesDirPath).listFiles();         return Arrays.stream(articlesPath)                 .parallel()                 .map(file -&gt; getArticle(file, objectMapper))                 .toList();     }          \/**      * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0442\u0430\u0442\u0435\u0439: \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f      * \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0438\u0445 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.      *       * @param articleList \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0442\u0430\u0442\u0435\u0439 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438      * @param embeddingModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439      * @param chatModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u0435\u0439      * @param objectMapper \u043c\u0430\u043f\u043f\u0435\u0440 \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432      *\/     private void processArticles(List&lt;Article&gt; articleList, EmbeddingModel embeddingModel,                                  ChatModel chatModel, ObjectMapper objectMapper) {         try (DatabaseManager databaseManager = new DatabaseManager()) {             articleList.forEach(article -&gt; saveArticle(article, databaseManager, objectMapper));             articleList.forEach(article -&gt; processArticleEmbeddings(article, chatModel, embeddingModel, databaseManager));         }     }          \/**      * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e: \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0442\u0435\u043c\u044b \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f.      *       * @param article \u0441\u0442\u0430\u0442\u044c\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438      * @param chatModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c      * @param embeddingModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439      * @param databaseManager \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432      *\/     private void processArticleEmbeddings(Article article, ChatModel chatModel,                                           EmbeddingModel embeddingModel, DatabaseManager databaseManager) {         Topics topics;         List&lt;String&gt; textForEmbeddings;                  try {             topics = getTopics(chatModel, article);             if (topics == null) {                 return;             }             textForEmbeddings = getTextForEmbeddings(topics);         } catch (Exception e) {             System.out.println(\"Error processing article \" + article.getId() + \": \" + e.getMessage());             return;         }                  for (int idx = 0; idx &lt; textForEmbeddings.size(); idx++) {             String text = textForEmbeddings.get(idx);             float[] vectors = generateEmbedding(embeddingModel, textForEmbeddings, idx, text);             databaseManager.saveHabrVector(article.getId(), idx, text, vectors);         }     }          \/**      * \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430.      *       * @param embeddingModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439      * @param textForEmbeddings \u0441\u043f\u0438\u0441\u043e\u043a \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438      * @param idx \u0438\u043d\u0434\u0435\u043a\u0441 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430      * @param text \u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438      * @return \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430      *\/     private float[] generateEmbedding(EmbeddingModel embeddingModel, List&lt;String&gt; textForEmbeddings, int idx, String text) {         if (idx != 1) {             return embeddingModel.embed(textForEmbeddings.get(1) + \". \" + text);         } else {             return embeddingModel.embed(text);         }     }      \/**      * \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.      *       * @param article \u0441\u0442\u0430\u0442\u044c\u044f \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f      * @param databaseManager \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432      * @param objectMapper \u043c\u0430\u043f\u043f\u0435\u0440 \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432      *\/     @SneakyThrows     private static void saveArticle(Article article, DatabaseManager databaseManager, ObjectMapper objectMapper) {         String textHtml = article.getTextHtml();         article.setTextHtml(null);         databaseManager.saveHabrArticle(article.getId(), article.getTitleHtml(), textHtml,                  objectMapper.writeValueAsString(article));         article.setTextHtml(textHtml);     }      \/**      * \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0442\u0435\u043c\u044b \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e LLM \u043c\u043e\u0434\u0435\u043b\u0438.      *       * @param chatModel \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u044c\u0438      * @param article \u0441\u0442\u0430\u0442\u044c\u044f \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430      * @return \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u0442\u0435\u043c\u0430\u043c\u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438\u043b\u0438 null \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043a\u0438      *\/     private static @Nullable Topics getTopics(ChatModel chatModel, Article article) {         System.out.println(\"Getting topics for article. Text length: \" + article.getTextHtml().length());         try {             return ChatClient.create(chatModel).prompt()                     .user(u -&gt; u.text(\"Answer in english language only! Enumerate in details topics covered in html text: {html}\")                             .param(\"html\", article.getTextHtml()))                     .call()                     .entity(Topics.class);         } catch (Exception e) {             System.out.println(\"Exception in article id=\" + article.getId() + \": \" + e.getMessage());             return null;         }     }      \/**      * \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0442\u0435\u043c \u0441\u0442\u0430\u0442\u044c\u0438.      *       * @param topics \u0442\u0435\u043c\u044b \u0441\u0442\u0430\u0442\u044c\u0438      * @return \u0441\u043f\u0438\u0441\u043e\u043a \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438      *\/     private static @NotNull List&lt;String&gt; getTextForEmbeddings(Topics topics) {         List&lt;String&gt; topicsText = topics.getTopics().stream()                 .map(topic -&gt; topic.getName() + \". \" + topic.getDescription())                 .toList();                          List&lt;String&gt; topicsTextFull = new ArrayList&lt;&gt;(topicsText.size() + 2);         topicsTextFull.add(topics.getSummary());         topicsTextFull.add(topics.getKeywords());         topicsTextFull.addAll(topicsText);                  return topicsTextFull;     }      \/**      * \u0414\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e \u0438\u0437 \u0444\u0430\u0439\u043b\u0430.      *       * @param file \u0444\u0430\u0439\u043b \u0441\u043e \u0441\u0442\u0430\u0442\u044c\u0435\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSON      * @param objectMapper \u043c\u0430\u043f\u043f\u0435\u0440 \u0434\u043b\u044f \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438      * @return \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u0442\u0430\u0442\u044c\u0438      *\/     @SneakyThrows     private static Article getArticle(File file, ObjectMapper objectMapper) {         return objectMapper.readValue(file, Article.class);     }      \/**      * \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 ObjectMapper \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JSON.      *       * @return ObjectMapper      *\/     private ObjectMapper createObjectMapper() {         ObjectMapper objectMapper = new ObjectMapper();         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);         return objectMapper;     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439<\/h4>\n<p>\u0412 \u0444\u0430\u0439\u043b\u0435 <code>application.properties<\/code> \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u043c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0432 Spring AI:<\/p>\n<pre><code>spring.ai.ollama.embedding.options.model=nomic-embed-text:v1.5 spring.ai.ollama.chat.options.model=gemma3:4b spring.ai.ollama.chat.options.num-ctx=128000 spring.ai.ollama.embedding.options.num-ctx=2048 <\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e:<\/p>\n<ul>\n<li>\n<p><code>gemma3:4b<\/code> \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><code>nomic-embed-text:v1.5<\/code> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 (embeddings).<\/p>\n<\/li>\n<\/ul>\n<p>\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 Ollama \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 pull \u0438\u043b\u0438 run.<\/p>\n<h3>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 Spring AI<\/h3>\n<p>\u0424\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a Spring AI \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 ChatClient.create(chatModel).prompt() &#8230; call().entity(Topics.class) \u0434\u0435\u043b\u0430\u0435\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0432\u044b\u0432\u043e\u0434 \u0441\u0445\u0435\u043c\u044b \u0438\u0437 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412\u0435\u0434\u044c \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435 LLM \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434 JSON Schema, \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u0435.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043c\u043e\u0447\u044c \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438 \u0441 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u043e\u0439 \u043f\u043e\u043b\u0435\u0439 \u0438 \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e <a class=\"mention\" href=\"\/users\/JsonPropertyDescription\">@JsonPropertyDescription<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u043e\u043b\u0435 description \u0432 \u0441\u0445\u0435\u043c\u0435 JSON. \u041d\u0443 \u0438 \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043a\u0430\u043a \u0438 \u0441 \u043b\u044e\u0434\u044c\u043c\u0438, \u043d\u0430\u0437\u044b\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u043b\u044f \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u043e \u043d\u0435 a1, a2&#8230;<\/p>\n<pre><code class=\"java\">package com.github.isuhorukov.model;  import com.fasterxml.jackson.annotation.JsonPropertyDescription; import lombok.Data;  import java.util.List;  @Data public class Topics {     private List&lt;Topic&gt; topics;     @JsonPropertyDescription(\"Text summary in english language\")     private String summary;     @JsonPropertyDescription(\"Keywords for topic in english language. Format as string concatenated with,\")     private String keywords; } <\/code><\/pre>\n<pre><code class=\"java\">package com.github.isuhorukov.model;  import com.fasterxml.jackson.annotation.JsonPropertyDescription; import lombok.Data;  @Data public class Topic {     @JsonPropertyDescription(\"Short topic name in english language\")     private String name;     @JsonPropertyDescription(\"Detailed description for this topic in english language\")     private String description; } <\/code><\/pre>\n<p>\u0414\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u0432 Topics, Topic \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f JSON \u0441\u0445\u0435\u043c\u0430:<\/p>\n<pre><code class=\"json\">{   \"$schema\" : \"https:\/\/json-schema.org\/draft\/2020-12\/schema\",   \"type\" : \"object\",   \"properties\" : {     \"keywords\" : {       \"type\" : \"string\",       \"description\" : \"Keywords for topic in english language. Format as string concatenated with,\"     },     \"summary\" : {       \"type\" : \"string\",       \"description\" : \"Text summary in english language\"     },     \"topics\" : {       \"type\" : \"array\",       \"items\" : {         \"type\" : \"object\",         \"properties\" : {           \"description\" : {             \"type\" : \"string\",             \"description\" : \"Detailed description for this topic in english language\"           },           \"name\" : {             \"type\" : \"string\",             \"description\" : \"Short topic name in english language\"           }         },         \"additionalProperties\" : false       }     }   },   \"additionalProperties\" : false } <\/code><\/pre>\n<h3>\u0412 \u0447\u0435\u043c \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439<\/h3>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 Ollama API, \u0430 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442:<\/p>\n<ul>\n<li>\n<p><strong>\u041a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0445<\/strong> \u2014 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u043f\u043e\u043a\u0438\u0434\u0430\u044e\u0442 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443<\/p>\n<\/li>\n<li>\n<p><strong>\u042d\u043a\u043e\u043d\u043e\u043c\u0438\u044f<\/strong> \u2014 \u043d\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0437\u0430 API-\u0432\u044b\u0437\u043e\u0432\u044b \u043a \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c<\/p>\n<\/li>\n<li>\n<p><strong>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c<\/strong> \u2014 \u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u043d\u0430\u0434 \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 \u0438 \u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432<\/strong> \u2014 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443. \u0421\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e, \u0431\u0435\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u0440\u0430\u0437\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0441\u0443\u0442\u043e\u043a, \u043a\u0430\u043a \u0431\u044b\u0432\u0430\u0435\u0442 \u0441 Claude Sonet.  \u041d\u043e \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043c\u043e\u0449\u043d\u0430\u044f \u0432\u0438\u0434\u0435\u043e\u043a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0440\u0438\u043b\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u044a\u0435\u043c\u043e\u043c \u0432\u0438\u0434\u0435\u043e\u043f\u0430\u043c\u044f\u0442\u0438, \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0439 LLM \u043c\u043e\u0434\u0435\u043b\u0438.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0417\u0430\u043f\u0440\u043e\u0441\u044b \u0432 PostgreSQL \u043a \u0434\u0430\u043d\u043d\u044b\u043c \u0425\u0430\u0431\u0440\u0430<\/h3>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0432\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043e\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u043d\u043e\u0443\u0442\u0431\u0443\u043a\u0430 \u0438 &#171;\u0441\u0436\u0438\u0433\u0430\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u0438\u0445 \u043a\u0438\u043b\u043e\u0432\u0430\u0442\u0442&#187; \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u044f\u043c\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b \u0441\u043e\u0442\u043d\u044e \u0442\u044b\u0441\u044f\u0447 \u0441\u0442\u0430\u0442\u0435\u0439 \u0441 \u0425\u0430\u0431\u0440\u0430 \u0432 PostgreSQL, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0433\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0435 SQL \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0431\u043b\u0438\u0437\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432.<\/p>\n<p>\u0412\u0430\u0436\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435 \u043f\u0440\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u044e: \u0445\u043e\u0442\u044c \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 \u0441 \u0445\u0430\u0431\u0440\u0430 \u0438 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435, \u043d\u043e \u044f \u0437\u0430\u0434\u0430\u0432\u0430\u043b \u043f\u0440\u043e\u043c\u043f\u0442 \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438 \u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0443 \u043c\u0435\u043d\u044f \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u0447\u0438\u043d: \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043b\u0438\u0431\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u044f\u0437\u044b\u043a \u0434\u043b\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438 \u0443 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u043e \u0432 \u043e\u0431\u044a\u0435\u043c\u0430\u0445 \u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0434\u0430\u0442\u0430\u0441\u0435\u0442\u043e\u0432 \u043f\u0440\u0438 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0438 LLM, \u0432\u0442\u043e\u0440\u043e\u0435 &#8212; \u043c\u043e\u044f \u0443\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u044c, \u0447\u0442\u043e \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0431\u043b\u0438\u0437\u043e\u0441\u0442\u044c \u0443 nomic-embed-text \u0434\u043b\u044f \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0438\u043d\u043e\u043d\u0438\u043c\u043e\u0432 \u0441\u043b\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435, \u0432 \u0442\u0440\u0435\u0442\u044c\u0438\u0445 &#8212; \u043c\u0435\u043d\u044c\u0448\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0438 \u043f\u0440\u0438 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0411\u0430\u0437\u0430 \u043f\u043e\u0441\u043b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043d\u0443\u0436\u043d\u044b\u0435 \u043c\u043d\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u0435\u0439 <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/t1\/to\/zj\/t1tozjnjqdbgyrk8jrymxsdoqio.png\" alt=\"Habr data\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/t1\/to\/zj\/t1tozjnjqdbgyrk8jrymxsdoqio.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/t1\/to\/zj\/t1tozjnjqdbgyrk8jrymxsdoqio.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Habr data<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u043d\u0430\u0439\u0434\u0443 \u0441\u0432\u043e\u0438 \u0441\u0442\u0430\u0442\u044c\u0438, \u0447\u0442\u043e \u043f\u043e\u043f\u0430\u043b\u0438 \u0432 \u044d\u0442\u0443 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"sql\">select id, title from habr where properties-&gt;'author'-&gt;&gt;'alias' = 'igor_suhorukov' <\/code><\/pre>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/0e\/mj\/nx\/0emjnxxbnw2yefrggv0le9ixxqk.png\" alt=\"My articles\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/0e\/mj\/nx\/0emjnxxbnw2yefrggv0le9ixxqk.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/0e\/mj\/nx\/0emjnxxbnw2yefrggv0le9ixxqk.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>My articles<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u044f \u043f\u043e\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0441\u044c \u0447\u0442\u043e \u0436\u0435 \u043f\u0440\u043e\u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u043f\u043e \u043c\u043e\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0445 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u043e\u0432 \u043d\u0430 SQL \u0432 PostgreSQL:<\/p>\n<pre><code class=\"sql\">select id,idx, notes from habr_vectors where id=728196 <\/code><\/pre>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/oz\/8z\/hu\/oz8zhumxjcvitvnaw6_tq9diwzg.png\" alt=\"Article details\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/oz\/8z\/hu\/oz8zhumxjcvitvnaw6_tq9diwzg.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/oz\/8z\/hu\/oz8zhumxjcvitvnaw6_tq9diwzg.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Article details<\/figcaption><\/div>\n<\/figure>\n<p>\u0418 \u0442\u0435\u043f\u0435\u0440\u044c \u0445\u043e\u0447\u0443 \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0439\u0442\u0438 5 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u043b\u0438\u0437\u043a\u0438\u0445 \u043f\u043e \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0435 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u0438\u0437 \u0432\u0441\u0435\u0445 \u0441\u0442\u0430\u0442\u0435\u0439, \u043a\u0440\u043e\u043c\u0435 \u043c\u043e\u0435\u0439 \u0442\u0435\u043a\u0443\u0449\u0435\u0439: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/yz\/pl\/hh\/yzplhhfjiki9gmiifrv4xxvld8e.png\" alt=\"Article details\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/yz\/pl\/hh\/yzplhhfjiki9gmiifrv4xxvld8e.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/yz\/pl\/hh\/yzplhhfjiki9gmiifrv4xxvld8e.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Article details<\/figcaption><\/div>\n<\/figure>\n<pre><code class=\"sql\">select id, idx, notes from habr_vectors where id&lt;&gt;728196 and idx=0 order by search_vector &lt;=&gt; (select search_vector from habr_vectors where id=728196 and idx=0) limit 5 <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443 \u0434\u043b\u044f \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u0430\u043c\u0438.<\/p>\n<pre><code class=\"sql\">select 'https:\/\/habr.com\/ru\/articles\/' ||  id as link from habr_vectors where id&lt;&gt;728196  order by search_vector &lt;=&gt; (select search_vector from habr_vectors where id=728196 and idx=0) limit 5;               link                -------------------------------------  https:\/\/habr.com\/ru\/articles\/757278  https:\/\/habr.com\/ru\/articles\/757278  https:\/\/habr.com\/ru\/articles\/760720  https:\/\/habr.com\/ru\/articles\/713714  https:\/\/habr.com\/ru\/articles\/723202 (5 rows)  Time: 3.063 ms <\/code><\/pre>\n<p>\u0414\u043b\u044f inner product \u043c\u0435\u0436\u0434\u0443 \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u0430\u043c\u0438:<\/p>\n<pre><code class=\"sql\">select 'https:\/\/habr.com\/ru\/articles\/' ||  id as link from habr_vectors where id&lt;&gt;728196  order by search_vector &lt;#&gt; (select search_vector from habr_vectors where id=728196 and idx=0) limit 5;                 link                  -------------------------------------  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)  Time: 127.108 ms <\/code><\/pre>\n<p>\u0414\u043b\u044f \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f L2 \u043c\u0435\u0436\u0434\u0443 \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u0430\u043c\u0438:<\/p>\n<pre><code class=\"sql\">osmworld=# select 'https:\/\/habr.com\/ru\/articles\/' ||  id as link from habr_vectors where id&lt;&gt;728196  order by search_vector &lt;+&gt; (select search_vector from habr_vectors where id=728196 and idx=0) limit 5;                 link                  -------------------------------------  https:\/\/habr.com\/ru\/articles\/760720  https:\/\/habr.com\/ru\/articles\/713714  https:\/\/habr.com\/ru\/articles\/707650  https:\/\/habr.com\/ru\/articles\/721464  https:\/\/habr.com\/ru\/articles\/784412 (5 rows)  Time: 132.400 ms <\/code><\/pre>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u042f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0441\u0432\u043e\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0434\u043b\u044f \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u044f\u043c \u0425\u0430\u0431\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f:<\/p>\n<ul>\n<li>\n<p>\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043a\u0440\u0430\u0442\u043a\u0438\u0439 \u0440\u0435\u0444\u0435\u0440\u0430\u0442, \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044f\u0437\u044b\u043a\u043e\u0432\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430<\/p>\n<\/li>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 Ollama \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043c\u043d\u043e\u0433\u043e \u0434\u0435\u043d\u0435\u0433, \u043e\u043d\u0438 \u043d\u0435 \u0432\u0430\u0448\u0438 \u0438\u043b\u0438 \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043e\u0431\u044a\u0435\u043c \u0431\u0435\u0437 \u0437\u0430\u043a\u0443\u043f\u043a\u0438 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u0435\u0439, \u0442\u043e \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043a\u0430\u043a \u0441 OpenAI \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c, \u0442\u0430\u043a \u0438 \u043b\u044e\u0431\u043e\u0435 \u0438\u0437 \u0434\u043b\u0438\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432: Anthropic Claude, Azure OpenAI, DeepSeek, Google VertexAI Gemini, Groq, HuggingFace, Mistral AI, MiniMax, Moonshot AI, NVIDIA (OpenAI-proxy), OCI GenAI\/Cohere, Perplexity (OpenAI-proxy), QianFan, ZhiPu AI, Amazon Bedrock Converse<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0434\u043b\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u0442\u0435\u043c\u044b, \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0438 embeddings \u0432 PostgreSQL<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0432 Java-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430. \u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a LLM \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u043f\u0440\u0430\u0432\u0434\u0430\u043d \u043a\u0430\u043a \u0432 \u0445\u043e\u0431\u0431\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445, \u0442\u0430\u043a \u0438 \u0432 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0445 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439 \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f\/ \u0418\u0441\u043a\u0443\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0418\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0430.<\/p>\n<p>\u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0441\u043c\u044b\u0441\u043b\u0443, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e \u043c\u043d\u043e\u0433\u043e\u043c \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0447\u0435\u043c \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u043c. \u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0443\u0442 \u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u043f\u0440\u0438\u043d\u0446\u0438\u043f \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c. \u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u0434\u0430\u043d\u043d\u044b\u043c \u0425\u0430\u0431\u0440\u0430 \u0443\u0436\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c. \u0422\u0430\u043a \u043a\u0430\u043a \u0432\u044b\u0440\u0430\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 SQL \u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u043c\u043e\u0441\u0442\u044c PostgreSQL \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u0447\u0442\u0438 \u043b\u044e\u0431\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/915348\/\"> https:\/\/habr.com\/ru\/articles\/915348\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u043e\u043a\u0430\u0436\u0443 \u0432\u0430\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 &#8212; \u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u043e\u0432 \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430. \u0417\u0434\u0435\u0441\u044c \u044f \u0441\u043e\u0437\u0434\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441 \u0425\u0430\u0431\u0440\u0430, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438\u0437 \u043d\u0438\u0445 \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e  \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0445 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 LLM, \u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0442\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0441\u043c\u044b\u0441\u043b\u0443, \u0430 \u043d\u0435 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0430 \u0432\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430.<\/p>\n<figure class=\"\">\n<div><figcaption>Habr data indexing<\/figcaption><\/div>\n<\/figure>\n<p>\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 LLM \u044d\u0442\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438 \u043d\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0435\u0444\u0435\u0440\u0430\u0442\u043e\u0432 \u0434\u043b\u044f \u0448\u043a\u043e\u043b\u044c\u043d\u0438\u043a\u043e\u0432 \u0438\u043b\u0438 \u043f\u0438\u0441\u0435\u043c \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0431\u044b\u0441\u0442\u0440\u044b\u0445 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043d\u0430 \u0432\u0430\u0448\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b. \u042d\u0442\u043e \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u0434\u043b\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u043c\u0435\u0434\u0438\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0441\u043a\u043e\u0440\u043e \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0432\u043e\u043f\u043b\u043e\u0449\u0435\u043d\u0438\u0435 \u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u041f\u0430\u0443\u0442\u0438\u043d\u044b (\u0412\u0435\u0431\u0430) \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0438\u0437-\u0437\u0430 \u0442\u0440\u0443\u0434\u043e\u0435\u043c\u043a\u043e\u0441\u0442\u0438 \u0440\u0443\u0447\u043d\u043e\u0439 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u043b\u044e\u0434\u044c\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442.<\/p>\n<h3>\u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a<\/h3>\n<p>\u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u2014 \u044d\u0442\u043e \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0438 \u0441\u043c\u044b\u0441\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f &#8212; embeddings \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f, \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b.<\/p>\n<p>\u0422\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u043c \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u041d\u0435 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0441\u043c\u044b\u0441\u043b \u0441\u043b\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0427\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d \u043a \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0435\u0442 \u0441\u0438\u043d\u043e\u043d\u0438\u043c\u044b \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u044f \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u043c\u043d\u043e\u0433\u043e\u043c\u0435\u0440\u043d\u044b\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u044b, \u0433\u0434\u0435 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0431\u043b\u0438\u0437\u043a\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0442\u0441\u044f \u0440\u044f\u0434\u043e\u043c \u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435. \u0412\u0441\u0435 \u044d\u0442\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043c\u043e\u0434\u0435\u043b\u044f\u043c \u043c\u0430\u0448\u0438\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0431\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043a\u043e\u0440\u043f\u0443\u0441\u0430\u0445 \u0442\u0435\u043a\u0441\u0442\u043e\u0432. \u041a\u0430\u043a \u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c nomic-embed-text.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u043e\u0438\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c embedding \u043d\u0435 \u043f\u043e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c\u0443 \u0442\u0435\u043a\u0441\u0442\u0443<\/h3>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0438\u0434\u0435\u0439 \u043c\u043e\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u043d\u0435 \u0434\u043b\u044f \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0441\u0442\u0430\u0442\u0435\u0439, \u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u0437 \u043d\u0438\u0445 \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432. \u042d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432:<\/p>\n<ol>\n<li>\n<p><strong>\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e<\/strong> \u2014 \u0438\u043d\u043e\u0433\u0434\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0441\u0442\u0430\u0442\u0435\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0435\u0439\u0441\u044f \u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0442\u0435\u043c\u0435, \u0440\u0435\u043a\u043b\u0430\u043c\u0443 \u0438\u0442\u043f.<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u043c\u044b\u0441\u043b\u043e\u0432\u043e\u0435 \u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435<\/strong> \u2014 \u0430\u0432\u0442\u043e\u0440\u044b \u0441\u0442\u0430\u0442\u0435\u0439 \u0441\u0443\u0431\u044a\u0435\u043a\u0442\u0438\u0432\u043d\u044b \u0432 \u0432\u044b\u0431\u043e\u0440\u0435 \u0442\u0435\u0433\u043e\u0432, LLM \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043a\u043b\u0430\u0441\u0441\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0443\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u0430<\/strong> \u2014 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u043c\u044b \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0430 \u0441\u0443\u0442\u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430.<\/p>\n<\/li>\n<\/ol>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h3>\n<p>\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432:<\/p>\n<ul>\n<li>\n<p><strong>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL<\/strong> \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>\u042f\u0437\u044b\u043a\u043e\u0432\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c (LLM)<\/strong> \u0432 Ollama \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432 \u0438\u0437 \u0441\u0442\u0430\u0442\u0435\u0439<\/p>\n<\/li>\n<li>\n<p><strong>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f embeddings<\/strong> \u0432 Ollama, \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0449\u0430\u044f \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>Java-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/strong> \u043d\u0430 \u0431\u0430\u0437\u0435 Spring Boot \u0438 Spring AI, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0438\u0445 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0421\u0423\u0411\u0414.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u043a\u043e\u0434\u0430 \u043d\u0443\u0436\u043d\u044b \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438: <strong>org.postgresql:postgresql:42.7.5, com.fasterxml.jackson.core:jackson-databind:2.19.0, org.springframework.ai:spring-ai-starter-model-ollama:1.0.0, org.projectlombok:lombok:1.18.34, org.testcontainers:postgresql:1.21.0<\/strong><\/p>\n<h3>\u0421\u0445\u0435\u043c\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<figure class=\"\">\n<div><figcaption>Database Schema<\/figcaption><\/div>\n<\/figure>\n<h3>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/h3>\n<h4>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h4>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043d\u0430 Java \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Spring Boot \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/p>\n<ul>\n<li>\n<p><code>HabrApplication<\/code> \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><code>DatabaseManager<\/code> \u2014 \u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445: <code>Article<\/code> &#8212; \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u043e\u043b\u044f\u043c JSON \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435, <code>Topics<\/code> &#8212; \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, <code>Chapter<\/code> \u0438 \u0434\u0440.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435:<\/p>\n<figure class=\"\">\n<div><figcaption>Application Flow<\/figcaption><\/div>\n<\/figure>\n<p>\u042d\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0441\u043a\u0430\u0447\u0430\u043d\u043d\u044b\u0435 \u0441 \u0445\u0430\u0431\u0440\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 -Darticles=\/home\/habr\/articles<\/p>\n<h4>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c PostgreSQL \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector. \u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b:<\/p>\n<pre><code class=\"sql\">CREATE TABLE habr (     id BIGINT PRIMARY KEY,     title TEXT,     text TEXT,     properties JSONB );  CREATE TABLE habr_vectors (     id BIGINT,     idx INTEGER,     notes TEXT,     search_vector vector(768),     PRIMARY KEY (id, idx),     FOREIGN KEY (id) REFERENCES habr(id) ); <\/code><\/pre>\n<p>\u0422\u0430\u0431\u043b\u0438\u0446\u0430 <code>habr<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u0430 <code>habr_vectors<\/code> \u2014 \u0442\u0435\u043c\u044b, \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u043c\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043d\u0438\u0445:<\/p>\n<ul>\n<li>\n<p><code>idx = 0<\/code> \u2014 \u043a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438<\/p>\n<\/li>\n<li>\n<p><code>idx = 1<\/code> \u2014 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430<\/p>\n<\/li>\n<li>\n<p><code>idx &gt; 1<\/code> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b, \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438<\/p>\n<\/li>\n<\/ul>\n<h4>\u041a\u043e\u0434 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h4>\n<p>\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435, \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044e\u0449\u0435\u043c HabrApplication \u043e\u0442 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443 \u0421\u0423\u0411\u0414 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0445\u043e\u0441\u0442\u0430 \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0431\u043c\u0435\u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043c\u0438. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u0430\u043c\u043e\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043d\u0443\u0436\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e JVM, Docker, Ollama.<\/p>\n<pre><code class=\"java\">package com.github.isuhorukov;  import com.github.isuhorukov.model.Chapter; import lombok.Getter; import org.postgresql.ds.PGSimpleDataSource; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.PostgreSQLContainer;  import javax.sql.DataSource; import java.io.Closeable; import java.io.File; import java.sql.*; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set;  \/**  * \u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 PostgreSQL.  * \u041a\u043b\u0430\u0441\u0441 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 PostgreSQL \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c pgvector,  * \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438.  *\/ public class DatabaseManager implements Closeable {     private final PostgreSQLContainer&lt;?&gt; postgres;     @Getter     private DataSource dataSource;      \/**      * \u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 PostgreSQL.      * \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435.      *\/     public DatabaseManager() {         File dbDataDir = new File(\".\/postgres-data\");         if (!dbDataDir.exists()) {             dbDataDir.mkdirs();         }          postgres = new PostgreSQLContainer&lt;&gt;(\"pgvector\/pgvector:pg16\")                 .withDatabaseName(\"habr\")                 .withUsername(\"test\")                 .withPassword(\"test\")                 .withFileSystemBind(                         new File(\".\/data\").getAbsolutePath(),                         \"\/mnt\/data\",                         BindMode.READ_ONLY                 )                 .withFileSystemBind(                         dbDataDir.getAbsolutePath(),                         \"\/var\/lib\/postgresql\/data\",                         BindMode.READ_WRITE                 );                  postgres.start();         System.out.println(\"Database URL: \" + postgres.getJdbcUrl());         initializeDatabase();     }      \/**      * \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b.      * @throws RuntimeException \u0435\u0441\u043b\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u0443\u0434\u0430\u043b\u0430\u0441\u044c      *\/     private void initializeDatabase() {         try {             PGSimpleDataSource pgDataSource = new PGSimpleDataSource();             pgDataSource.setUrl(postgres.getJdbcUrl());             pgDataSource.setUser(postgres.getUsername());             pgDataSource.setPassword(postgres.getPassword());                          this.dataSource = pgDataSource;                          try (Connection connection = dataSource.getConnection();                  Statement stmt = connection.createStatement()) {                 stmt.execute(\"CREATE EXTENSION vector\");                 stmt.execute(\"\"\"                         CREATE TABLE IF NOT EXISTS habr (                         id BIGINT PRIMARY KEY,                         title TEXT,                         text TEXT,                         properties JSONB                         )\"\"\");                 stmt.execute(\"\"\"                         CREATE TABLE IF NOT EXISTS habr_vectors(                         id BIGINT,                         idx INTEGER,                         notes TEXT,                         search_vector vector(768)                         )\"\"\");                 stmt.execute(\"ALTER TABLE habr_vectors \" +                         \"ADD CONSTRAINT habr_vectors_pkey PRIMARY KEY (id, idx);\");                  stmt.execute(\"ALTER TABLE habr_vectors \" +                         \"ADD CONSTRAINT fk_habr_vectors_habr \" +                         \"FOREIGN KEY (id) REFERENCES habr(id);\");                  stmt.execute(\"COMMENT ON TABLE habr IS '\u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0442\u0430\u0442\u0435\u0439 \u0441 \u0425\u0430\u0431\u0440\u0430'\");                 stmt.execute(\"COMMENT ON COLUMN habr.id IS '\u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr.title IS '\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr.text IS '\u041f\u043e\u043b\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 HTML \u0444\u043e\u0440\u043c\u0430\u0442\u0435'\");                 stmt.execute(\"COMMENT ON COLUMN habr.properties IS '\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 JSON \u0444\u043e\u0440\u043c\u0430\u0442\u0435'\");                  stmt.execute(\"COMMENT ON TABLE habr_vectors IS '\u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0441\u0442\u0430\u0442\u0435\u0439 \u0425\u0430\u0431\u0440\u0430'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.id IS '\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0442\u0430\u0442\u044c\u0438,\" +                         \" \u043a\u0430\u043a \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0435 habr'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.idx IS '\u041f\u043e\u0440\u044f\u0434\u043a\u043e\u0432\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u0442\u0435\u043a\u0441\u0442\u0430 \" +                         \"\u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441 0 - \u043a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441 1 - \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0434\u043b\u044f \u0441\u0442\u0430\u0442\u044c\u0438. \" +                         \"\u0418\u043d\u0434\u0435\u043a\u0441\u044b &gt;1 - \u0442\u0435\u043c\u044b \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.notes IS '\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 - \" +                         \"\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0442\u043b\u0438\u0447\u0438\u043c\u044b\u0439 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442'\");                 stmt.execute(\"COMMENT ON COLUMN habr_vectors.search_vector IS '\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \" +                         \"\u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u0438\u0445 notes \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430'\");              }         } catch (SQLException e) {             throw new RuntimeException(\"Failed to initialize database\", e);         }     }      \/**      * \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e \u0425\u0430\u0431\u0440\u0430 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.      *       * @param id<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-462017","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/462017","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=462017"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/462017\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=462017"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=462017"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=462017"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}