From 8c16275c01926c7b4f4d65882129428f78eca729 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Thu, 6 Mar 2025 17:53:16 +0900 Subject: [PATCH] .... --- build.gradle.kts | 3 +- .../lunaticbum/back/lun/configs/AppConfig.kt | 8 +- .../back/lun/controllers/Telegram.kt | 45 ++-- .../lunaticbum/back/lun/model/QCollection.kt | 115 +++++++++ .../kr/lunaticbum/back/lun/model/SearXng.kt | 2 + .../kr/lunaticbum/back/lun/service/Lama.kt | 237 +++++++++++++++--- src/main/resources/application.properties | 23 +- 7 files changed, 374 insertions(+), 59 deletions(-) create mode 100644 src/main/kotlin/kr/lunaticbum/back/lun/model/QCollection.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9ac771a..076b69f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,7 +64,8 @@ dependencies { // implementation("org.springframework.ai:spring-ai-ollama-spring-boot-starter:1.0.0-SNAPSHOT") implementation(platform("org.springframework.ai:spring-ai-bom:1.0.0-M6")) implementation("org.springframework.ai:spring-ai-ollama-spring-boot-starter:1.0.0-M6") - implementation("org.springframework.ai:spring-ai-redis-store") + implementation ("org.springframework.ai:spring-ai-qdrant-store-spring-boot-starter") +// implementation ("io.qdrant:client:1.13.0") implementation ("org.slf4j:slf4j-simple:1.7.25") diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/configs/AppConfig.kt b/src/main/kotlin/kr/lunaticbum/back/lun/configs/AppConfig.kt index 0e9b03e..8357b66 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/configs/AppConfig.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/configs/AppConfig.kt @@ -1,7 +1,7 @@ package kr.lunaticbum.back.lun.configs +import io.qdrant.client.QdrantClient import org.springframework.ai.ollama.api.OllamaApi -import org.springframework.ai.ollama.api.OllamaOptions import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -30,6 +30,12 @@ class AppConfig : WebMvcConfigurer { super.addInterceptors(registry) } + +// @Bean +// fun qdrantClient(): QdrantClient { +// return QdrantClient("https://ollama.lunaticbum.kr:6334") +// } + @Bean fun chatClient(): OllamaApi { return OllamaApi("https://lama.lunaticbum.kr") diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt index 69059e0..96d6d0f 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/controllers/Telegram.kt @@ -12,6 +12,7 @@ import com.google.maps.model.RankBy import jakarta.servlet.http.HttpServletRequest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.launch import kr.lunaticbum.back.lun.configs.GlobalEnvironment import kr.lunaticbum.back.lun.model.* @@ -437,26 +438,40 @@ class Telegram { @Autowired lateinit var lama : Lama + @ResponseBody @GetMapping("query/{path}") fun googleQueryTest(@PathVariable path: String): String { var originalQuery = path - val gSearch = "https://psn.lunaticbum.kr/search?q=${originalQuery?.replace("오늘", SimpleDateFormat("yyyMMdd").format(Date()))}&language=auto&time_range=month&safesearch=0&categories=general&format=json" - println("gSearch >>> ${gSearch}") - var additionalInfo = StringBuffer() - additionalInfo.append("참고자료") - var idx = 0 - WebClient.create().get() - .uri(gSearch) - .retrieve() - .bodyToMono(SearXng::class.java).timeout(Duration.ofMinutes(20L)).block()?.let { gsResult -> - gsResult.results?.filter { it.score > 0.5}?.forEach { - additionalInfo.append(idx).append(":").append(Gson().toJson(it)) - idx += 1 - } - } +// POST /collections +// +// Content-Type: application/json +// +// { +// "name": "movies", +// "vector_size": 3072, +// "distance": "Cosine" +// } - lama.generateResponse(query = originalQuery) +// println(lama.makeCollection()) + +// val gSearch = "https://psn.lunaticbum.kr/search?q=${originalQuery?.replace("오늘", SimpleDateFormat("yyyMMdd").format(Date()))}&language=auto&time_range=month&safesearch=0&categories=general&format=json" +// println("gSearch >>> ${gSearch}") +// var additionalInfo = StringBuffer() +// additionalInfo.append("참고자료") +// var idx = 0 +// WebClient.create().get() +// .uri(gSearch) +// .retrieve() +// .bodyToMono(SearXng::class.java).timeout(Duration.ofMinutes(20L)).block()?.let { gsResult -> +// gsResult.results?.filter { it.score > 0.5}?.forEach { +// additionalInfo.append(idx).append(":").append(Gson().toJson(it)) +// idx += 1 +// } +// } + CoroutineScope(Dispatchers.IO).async { + lama.generateResponse(query = originalQuery) + } return "TEST" } diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/QCollection.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/QCollection.kt new file mode 100644 index 0000000..3b260b0 --- /dev/null +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/QCollection.kt @@ -0,0 +1,115 @@ +package kr.lunaticbum.back.lun.model + + +class QCollection { + var result: QResult? = null + var status: String? = null + var time: Double = 0.0 +} +class QConfig { + var params: QParams? = null + var hnsw_config: QHnswConfig? = null + var optimizer_config: QOptimizerConfig? = null + var wal_config: QWalConfig? = null + var quantization_config: Any? = null + var strict_mode_config: QStrictModeConfig? = null +} + +class QHnswConfig { + var m: Int = 0 + var ef_construct: Int = 0 + var full_scan_threshold: Int = 0 + var max_indexing_threads: Int = 0 + var on_disk: Boolean = false +} + +class QOptimizerConfig { + var deleted_threshold: Double = 0.0 + var vacuum_min_vector_number: Int = 0 + var default_segment_number: Int = 0 + var max_segment_size: Any? = null + var memmap_threshold: Any? = null + var indexing_threshold: Int = 0 + var flush_interval_sec: Int = 0 + var max_optimization_threads: Any? = null +} + +class QParams { + var vectors: QVectors? = null + var shard_number: Int = 0 + var replication_factor: Int = 0 + var write_consistency_factor: Int = 0 + var on_disk_payload: Boolean = false +} + +class QPayloadSchema + +class QResult { + var status: String? = null + var optimizer_status: String? = null + var indexed_vectors_count: Int = 0 + var points_count: Long = 0 + var segments_count: Int = 0 + var config: QConfig? = null + var payload_schema: QPayloadSchema? = null +} + + + +class QStrictModeConfig { + var enabled: Boolean = false +} + +class QVectors { + var size: Int = 0 + var distance: String? = null +} + +class QWalConfig { + var wal_capacity_mb: Int = 0 + var wal_segments_ahead: Int = 0 +} + +class QSearchResult { + var id: Int = 0 + var version: Int = 0 + var score: Double = 0.0 +} + +class QSearch { + var result: ArrayList? = null + var status: String? = null + var time: Double = 0.0 +} + + +// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1 +// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1 +/* ObjectMapper om = new ObjectMapper(); +Root root = om.readValue(myJsonString, Root.class); */ +class QContentsPayload { + var url: String? = null + var title: String? = null + var content: String? = null + var engine: String? = null + var template: String? = null + var parsed_url: ArrayList? = null + var engines: ArrayList? = null + var positions: ArrayList? = null + var score: Double = 0.0 + var category: String? = null + var originQuery: String? = null + var pageData: String? = null +} + +class QContentsResult { + var id: Int = 0 + var payload: QContentsPayload? = null +} + +class QContents { + var result: ArrayList? = null + var status: String? = null + var time: Double = 0.0 +} + diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/model/SearXng.kt b/src/main/kotlin/kr/lunaticbum/back/lun/model/SearXng.kt index d5edf82..c83814b 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/model/SearXng.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/model/SearXng.kt @@ -12,6 +12,7 @@ class SearXng { var unresponsive_engines: ArrayList>? = null } class SearXngResult { + var originQuery : String? = null var url: String? = null var title: String? = null var content: String? = null @@ -23,4 +24,5 @@ class SearXngResult { var positions: ArrayList? = null var score: Double = 0.0 var category: String? = null + var pageData : String? = null } diff --git a/src/main/kotlin/kr/lunaticbum/back/lun/service/Lama.kt b/src/main/kotlin/kr/lunaticbum/back/lun/service/Lama.kt index 8dc8ec2..f37294c 100644 --- a/src/main/kotlin/kr/lunaticbum/back/lun/service/Lama.kt +++ b/src/main/kotlin/kr/lunaticbum/back/lun/service/Lama.kt @@ -1,61 +1,222 @@ package kr.lunaticbum.back.lun.service -import org.springframework.ai.chat.messages.UserMessage -import org.springframework.ai.chat.prompt.Prompt -import org.springframework.ai.document.Document -import org.springframework.ai.ollama.OllamaChatModel + +import com.google.gson.Gson +import com.knuddels.jtokkit.api.IntArrayList +import io.micrometer.observation.ObservationRegistry +import kr.lunaticbum.back.lun.configs.GlobalEnvironment +import kr.lunaticbum.back.lun.model.* +import org.jsoup.Jsoup +import org.springframework.ai.embedding.EmbeddingRequest +import org.springframework.ai.ollama.OllamaEmbeddingModel import org.springframework.ai.ollama.api.OllamaApi -import org.springframework.ai.vectorstore.SearchRequest -import org.springframework.ai.vectorstore.VectorStore +import org.springframework.ai.ollama.api.OllamaOptions +import org.springframework.ai.ollama.management.ModelManagementOptions import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service -import java.util.stream.Collectors +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.WebClient +import java.net.URLEncoder +import java.text.SimpleDateFormat +import java.time.Duration +import java.util.* +import kotlin.collections.ArrayList @Service class Lama { + + @Qualifier("chatClient") @Autowired private lateinit var chatClient: OllamaApi - @Autowired - private lateinit var vectorStore: VectorStore + //, val date : String = SimpleDateFormat("yyyyMMddHHmmss").format(Date()) +// data class QSearchData(val query : FloatArray,val limit : Int) - fun generateResponse(query: String?): String { - println("On generateResponse :: find something") - // 1. 유사 문서 검색 + data class QSearchData(val vector : FloatArray,val limit : Int) + data class QPut(val points : ArrayList) + data class QData(val id : Long, val vector : FloatArray, val payload : SearXngResult) - val relevantDocs = vectorStore.similaritySearch( - SearchRequest.builder().query(query!!).topK(3).build() + data class QContentsList(var ids : ArrayList = ArrayList(), var with_payload : Boolean = true, var with_vector : Boolean = false) + // fun makeCollection() : String{ +// +// class CollectionPut { +// val name = "blama_vectors" +// val vector_size = 3072 +// val distance = "Cosine" +// } +// val qUrl = "https://ollama.lunaticbum.kr/collections" +// val client = WebClient.create() +// return client.post() +// .uri(qUrl) +// .header("api-key","blama-admin-key-gb") +// .body(BodyInserters.fromValue(Gson().toJson(CollectionPut()))) +// .retrieve() +// .bodyToMono(String::class.java).timeout(Duration.ofMinutes(20L)).block() ?: "" +// } + var qPointsCount : Long = 0 + private fun checkCollection() : Long { + val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors" + val client = WebClient.create() + return client.get() + .uri(qUrl) + .header("api-key", "blama-admin-key-gb") + .retrieve() + .bodyToMono(QCollection::class.java).timeout(Duration.ofMinutes(20L)).block()?.result?.points_count ?: 0L + } + private fun addDocuments(query : String) { + + val embeddingModel = OllamaEmbeddingModel( + chatClient, + OllamaOptions.builder().build(), + ObservationRegistry.create(), + ModelManagementOptions.defaults() ) + val gSearch = "https://psn.lunaticbum.kr/search?q=${query?.replace("오늘", SimpleDateFormat("yyyMMdd").format(Date()))}&language=auto&time_range=month&safesearch=0&categories=general&format=json" + println("gSearch >>> ${gSearch}") + val sdss = QPut(arrayListOf()) + WebClient.create().get() + .uri(gSearch) + .retrieve() + .bodyToMono(SearXng::class.java).timeout(Duration.ofMinutes(20L)).block()?.let { gsResult -> + gsResult.results?.filter { it.score > 0.5}?.forEach { + qPointsCount += 1 + println("in filter") + it.originQuery = query + val data = Gson().toJson(it) + println(it.title) + Jsoup.connect(it.url).get().body().text()?.let { text -> + try { + println("text >>>>> $text") + it.pageData = chatClient.chat(OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages( + listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content("'${text}' 웹 페이지 모든 내욜을 복사 한건데 본문 내용만 정리해줘").build()) + ).build()).message.content + println("summary result >>>>> ${it.pageData}") + val embeddingResponse = embeddingModel.call( + EmbeddingRequest( + listOf(data), + OllamaOptions.builder() + .model("nomic-embed-text") + .truncate(false) + .build() + ) + ) + sdss.points.add(QData(id = qPointsCount,embeddingResponse.result.output,it)) + }catch (e : Exception) { - // 2. 프롬프트 구성 - val context = relevantDocs?.map { it.text }?.joinToString(separator = "\n") - val prompt = """ - Context information is below. - ---- - $context - ---- - Given the context information and not prior knowledge, answer the query: $query - \n한국어로 대답해줘 - """.trimIndent() + } + } + } + } + println("out filter") + if (sdss.points.size > 0) { + println("sdss.points.size ${sdss.points.size} ${Gson().toJson(sdss)}") + val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points") + val client = WebClient.create() + client.put() + .uri(qUrl) + .header("api-key", "blama-admin-key-gb") + .body(BodyInserters.fromValue(Gson().toJson(sdss))) + .retrieve() + .bodyToMono(String::class.java).timeout(Duration.ofMinutes(20L)).block() ?: "" - // 3. Ollama를 사용하여 응답 생성 - val response: OllamaApi.ChatResponse = chatClient.chat(OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages( - listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content(prompt).build()) - ).build()) - println(response.message.content) - println("On generateResponse :: END OF Answer") - return response.message.content - } - - // 문서 추가 메소드 - fun addDocuments(documents: List?) { - documents?.let { - vectorStore.add(it) } -// vectorStore!!.add(documents) + println("end of search") } + + private fun embedQuery(embedFlots : FloatArray) : QContents?{ + val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points/search") + val client = WebClient.create() + var lists = client.post() + .uri(qUrl) + .header("api-key","blama-admin-key-gb") + .body(BodyInserters.fromValue(Gson().toJson(QSearchData(embedFlots,5)))) + .retrieve() + .bodyToMono(QSearch::class.java).timeout(Duration.ofMinutes(20L)).block() + return if (lists?.result?.size ?: 0 > 0) { + val qContents = QContentsList() + + lists?.result?.forEach { + qContents.ids.add(it.id) + } + val qCUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points") + val client2 = WebClient.create() + client.post() + .uri(qCUrl) + .header("api-key", "blama-admin-key-gb") + .body(BodyInserters.fromValue(Gson().toJson(qContents))) + .retrieve() + .bodyToMono(QContents::class.java).timeout(Duration.ofMinutes(20L)).block() + } else { + null + } + } + + @Autowired + lateinit var globalEvv : GlobalEnvironment + + suspend fun generateResponse(query: String?, targetId: String? = globalEvv.telegramMyId) { + val embeddingModel = OllamaEmbeddingModel( + chatClient, + OllamaOptions.builder().build(), + ObservationRegistry.create(), + ModelManagementOptions.defaults() + ) + println("On generateResponse :: find something ${query}") + query?.let { + var embeddingResponse = embeddingModel.call( + EmbeddingRequest( + listOf(query), + OllamaOptions.builder() + .model("nomic-embed-text") + .truncate(false) + .build() + ) + ) + println("points size ${embeddingResponse.result.output.size}") + qPointsCount = checkCollection() + addDocuments(it) + embeddingResponse = embeddingModel.call( + EmbeddingRequest( + listOf(query), + OllamaOptions.builder() + .model("nomic-embed-text") + .truncate(false) + .build() + ) + ) + println(embeddingResponse.result.output) + var context : String? = "" + try { + embedQuery(embeddingResponse.result.output)?.result?.forEach { result -> + context += "참고자료:".plus(if (result.payload?.pageData?.length ?: 0 > 10) { + result.payload?.pageData + } else { + result.payload?.content + }) + context +="\n" + } + + }catch (e:Exception){ + e.printStackTrace() + } + val response: OllamaApi.ChatResponse = if (context?.length ?: 0 > 10) { + val prompt = "Context information is below.\n$context\nGiven the context information and not prior knowledge, answer the query: $query\n한국어로 대답해줘".trimIndent() + chatClient.chat(OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages( + listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content(prompt).build()) + ).build()) + } else { + chatClient.chat(OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages( + listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content(query).build()) + ).build()) + } + println(response.message.content) + println("On generateResponse :: END OF Answer") + } + } + + } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9f89ff2..9fe312c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -65,10 +65,25 @@ spring.data.mongodb.option.local-threshold=15 spring.ai.ollama.base-url=https://lama.lunaticbum.kr spring.ai.ollama.chat.options.model=phi4:14b -spring.data.redis.host=ollama.lunaticbum.kr -spring.ai.vectorstore.redis.initialize-schema=true -spring.ai.vectorstore.redis.index=spring-ai-redis-index -spring.ai.vectorstore.redis.prefix=spring-ai-redis-embedding +##spring.data.redis.url=ollama.lunaticbum.kr +#spring.data.redis.host=lunaticbum.kr +#spring.data.redis.port=6379 +# +##spring.ai.vectorstore.redis.uri="redis://lunaticbum.kr:6379" +# +# +#spring.ai.vectorstore.redis.initialize-schema=true +#spring.ai.vectorstore.redis.index=spring-ai-redis-index +#spring.ai.vectorstore.redis.prefix=spring-ai-redis-embedding +#https://ollama.lunaticbum.kr/collections/blama_vectors +spring.ai.vectorstore.qdrant.host=ollama.lunaticbum.kr +spring.ai.vectorstore.qdrant.port=443 +#spring.ai.vectorstore.qdrant.initialize-schema=true +spring.ai.vectorstore.qdrant.api-key=blama-admin-key-gb +spring.ai.vectorstore.qdrant.collection-name=blama_vectors +spring.ai.ollama.embedding.model=nomic-embed-text + +spring.ai.ollama.embedding.enabled=true #>>>>>>> ab915d0a416c69708f1df1ad76d7a14c779c1f59