From 8815e97b021548a21c61018f7ee6f3fed790ef60 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Wed, 25 Dec 2024 00:20:31 +0530 Subject: [PATCH] upgrade to SB 3.4 and polish observability (#130) * upgrade to SB 3.4 and polish observability * upgrade docker * fix : tests * polish --- .../docker/docker-compose.yml | 79 ++++++++++++++----- .../docker/docker_pgadmin_servers.json | 14 ++++ rag/rag-springai-ollama-llm/pom.xml | 48 +++++++---- .../config/RestClientBuilderConfig.java | 20 ----- .../service/AIChatService.java | 43 +++++----- .../service/DataIndexerService.java | 5 +- .../resources/application-local.properties | 11 +++ .../src/main/resources/application.properties | 14 +++- .../src/main/resources/logback-spring.xml | 16 ++++ .../config/TestcontainersConfiguration.java | 30 +++---- 10 files changed, 177 insertions(+), 103 deletions(-) create mode 100644 rag/rag-springai-ollama-llm/docker/docker_pgadmin_servers.json delete mode 100644 rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/config/RestClientBuilderConfig.java create mode 100644 rag/rag-springai-ollama-llm/src/main/resources/application-local.properties create mode 100644 rag/rag-springai-ollama-llm/src/main/resources/logback-spring.xml diff --git a/rag/rag-springai-ollama-llm/docker/docker-compose.yml b/rag/rag-springai-ollama-llm/docker/docker-compose.yml index 928f97c..e9d1046 100644 --- a/rag/rag-springai-ollama-llm/docker/docker-compose.yml +++ b/rag/rag-springai-ollama-llm/docker/docker-compose.yml @@ -1,19 +1,62 @@ services: - ollama: - image: langchain4j/ollama-llama3:latest - ports: - - '11434:11434' - redis-stack: - image: redis/redis-stack-server - ports: - - '6379:6379' - lgtm-stack: - image: grafana/otel-lgtm:0.8.0 - extra_hosts: ['host.docker.internal:host-gateway'] - container_name: lgtm-stack - environment: - - OTEL_METRIC_EXPORT_INTERVAL=500 - ports: - - "3000:3000" - - "4317:4317" - - "4318:4318" \ No newline at end of file + + ollama: + container_name: ollama + image: ollama/ollama:latest + ports: + - '11434:11434' + + postgresqldb: + container_name: postgresqldb + image: pgvector/pgvector:pg17 + extra_hosts: [ 'host.docker.internal:host-gateway' ] + restart: always + environment: + - POSTGRES_USER=appuser + - POSTGRES_PASSWORD=secret + - POSTGRES_DB=appdb + - PGPASSWORD=secret + logging: + options: + max-size: 10m + max-file: "3" + ports: + - '5432:5432' + healthcheck: + test: "pg_isready -U appuser -d appdb" + interval: 2s + timeout: 20s + retries: 10 + + pgadmin: + container_name: pgadmin_container + image: dpage/pgadmin4 + extra_hosts: [ 'host.docker.internal:host-gateway' ] + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} + PGADMIN_CONFIG_SERVER_MODE: "False" + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" + ports: + - "${PGADMIN_PORT:-5050}:80" + depends_on: + postgresqldb: + condition: service_healthy + volumes: + - ./docker_pgadmin_servers.json:/pgadmin4/servers.json + entrypoint: + - "/bin/sh" + - "-c" + - "/bin/echo 'postgresqldb:5432:*:appuser:secret' > /tmp/pgpassfile && chmod 600 /tmp/pgpassfile && /entrypoint.sh" + + lgtm-stack: + image: grafana/otel-lgtm:0.8.1 + extra_hosts: [ 'host.docker.internal:host-gateway' ] + container_name: lgtm-stack + environment: + - OTEL_METRIC_EXPORT_INTERVAL=500 + ports: + - "3000:3000" + - "4317:4317" + - "4318:4318" + - "9090:9090" diff --git a/rag/rag-springai-ollama-llm/docker/docker_pgadmin_servers.json b/rag/rag-springai-ollama-llm/docker/docker_pgadmin_servers.json new file mode 100644 index 0000000..7e97769 --- /dev/null +++ b/rag/rag-springai-ollama-llm/docker/docker_pgadmin_servers.json @@ -0,0 +1,14 @@ +{ + "Servers": { + "1": { + "Name": "Docker Compose DB", + "Group": "Servers", + "Port": 5432, + "Username": "appuser", + "Host": "postgresqldb", + "SSLMode": "prefer", + "MaintenanceDB": "appdb", + "PassFile": "/tmp/pgpassfile" + } + } +} \ No newline at end of file diff --git a/rag/rag-springai-ollama-llm/pom.xml b/rag/rag-springai-ollama-llm/pom.xml index 3fd60de..1730d20 100644 --- a/rag/rag-springai-ollama-llm/pom.xml +++ b/rag/rag-springai-ollama-llm/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.6 + 3.4.1 com.learning.ai @@ -16,7 +16,8 @@ 21 - 1.0.0-M4 + 1.0.0-M5 + 2.10.0-alpha 2.43.0 @@ -39,11 +40,7 @@ org.springframework.ai - spring-ai-redis-store-spring-boot-starter - - - org.apache.commons - commons-pool2 + spring-ai-pgvector-store-spring-boot-starter org.springframework.ai @@ -56,7 +53,14 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.6.0 + 2.7.0 + + + + org.springframework.boot + spring-boot-devtools + runtime + true @@ -76,6 +80,17 @@ io.micrometer micrometer-registry-otlp + + net.ttddyy.observation + datasource-micrometer-spring-boot + 1.0.6 + + + io.opentelemetry.instrumentation + opentelemetry-logback-appender-1.0 + runtime + + org.springframework.boot spring-boot-starter-test @@ -97,16 +112,14 @@ test - com.redis.testcontainers - testcontainers-redis + org.testcontainers + grafana test - 1.6.4 org.testcontainers - grafana + postgresql test - 1.20.4 io.rest-assured @@ -124,6 +137,13 @@ pom import + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otelInstrumentation.version} + pom + import + @@ -144,7 +164,7 @@ - 2.47.0 + 2.50.0 diff --git a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/config/RestClientBuilderConfig.java b/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/config/RestClientBuilderConfig.java deleted file mode 100644 index 8adbc8d..0000000 --- a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/config/RestClientBuilderConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.learning.ai.llmragwithspringai.config; - -import java.time.Duration; -import org.springframework.boot.web.client.ClientHttpRequestFactories; -import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; -import org.springframework.boot.web.client.RestClientCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration(proxyBeanMethods = false) -public class RestClientBuilderConfig { - - @Bean - RestClientCustomizer restClientCustomizer() { - return restClientBuilder -> restClientBuilder.requestFactory( - ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS - .withConnectTimeout(Duration.ofSeconds(60)) - .withReadTimeout(Duration.ofMinutes(5)))); - } -} diff --git a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/AIChatService.java b/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/AIChatService.java index 481e7b0..44e8f18 100644 --- a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/AIChatService.java +++ b/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/AIChatService.java @@ -3,10 +3,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.chat.client.advisor.RetrievalAugmentationAdvisor; +import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter; +import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.stereotype.Service; @@ -15,33 +14,29 @@ public class AIChatService { private static final Logger LOGGER = LoggerFactory.getLogger(AIChatService.class); - private static final String template = - """ - You are a helpful assistant, conversing with a user about the subjects contained in a set of documents. - Use the information from the DOCUMENTS section to provide accurate answers. If unsure or if the answer - isn't found in the DOCUMENTS section, simply state that you don't know the answer. + private final ChatClient aiClient; - DOCUMENTS: - {question_answer_context} + public AIChatService(ChatClient.Builder builder, VectorStore vectorStore) { - """; + var documentRetriever = VectorStoreDocumentRetriever.builder() + .vectorStore(vectorStore) + .similarityThreshold(0.50) + .build(); - private final ChatClient aiClient; - private final VectorStore vectorStore; + var queryAugmenter = + ContextualQueryAugmenter.builder().allowEmptyContext(true).build(); - public AIChatService(ChatClient.Builder builder, VectorStore vectorStore) { - this.aiClient = builder.build(); - this.vectorStore = vectorStore; + RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder() + .documentRetriever(documentRetriever) + .queryAugmenter(queryAugmenter) + .build(); + this.aiClient = + builder.clone().defaultAdvisors(retrievalAugmentationAdvisor).build(); } public String chat(String query) { - ChatResponse aiResponse = aiClient.prompt() - .advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.query(query), template)) - .user(query) - .call() - .chatResponse(); + String aiResponse = aiClient.prompt().user(query).call().content(); LOGGER.info("Response received from call :{}", aiResponse); - Generation generation = aiResponse.getResult(); - return (generation != null) ? generation.getOutput().getContent() : ""; + return aiResponse; } } diff --git a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/DataIndexerService.java b/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/DataIndexerService.java index aed87d0..8abe342 100644 --- a/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/DataIndexerService.java +++ b/rag/rag-springai-ollama-llm/src/main/java/com/learning/ai/llmragwithspringai/service/DataIndexerService.java @@ -1,6 +1,7 @@ package com.learning.ai.llmragwithspringai.service; import java.util.Map; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.document.DocumentReader; @@ -58,11 +59,11 @@ public void loadData(Resource documentResource) { return documents; }; vectorStore.accept(metadataEnricher.apply(tokenTextSplitter.apply(documentReader.get()))); - LOGGER.info("Loaded document to redis vector database."); + LOGGER.info("Loaded document to vector database."); } } public long count() { - return this.vectorStore.similaritySearch("*").size(); + return Objects.requireNonNull(this.vectorStore.similaritySearch("*")).size(); } } diff --git a/rag/rag-springai-ollama-llm/src/main/resources/application-local.properties b/rag/rag-springai-ollama-llm/src/main/resources/application-local.properties new file mode 100644 index 0000000..d500714 --- /dev/null +++ b/rag/rag-springai-ollama-llm/src/main/resources/application-local.properties @@ -0,0 +1,11 @@ +## WARNING: Development-only configuration +## The following setting will DELETE existing vector store data on startup +spring.ai.vectorstore.pgvector.removeExistingVectorStoreTable=true +spring.ai.ollama.baseUrl=http://localhost:11434 + + +logging.level.org.springframework.ai.rag=debug + +spring.datasource.url=jdbc:postgresql://localhost/appdb +spring.datasource.username=appuser +spring.datasource.password=secret diff --git a/rag/rag-springai-ollama-llm/src/main/resources/application.properties b/rag/rag-springai-ollama-llm/src/main/resources/application.properties index 6a0724c..4d7301f 100644 --- a/rag/rag-springai-ollama-llm/src/main/resources/application.properties +++ b/rag/rag-springai-ollama-llm/src/main/resources/application.properties @@ -3,6 +3,7 @@ spring.application.name=rag-springai-ollama-llm spring.threads.virtual.enabled=true spring.mvc.problemdetails.enabled=true +spring.ai.ollama.init.pull-model-strategy=WHEN_MISSING spring.ai.ollama.chat.options.model=mistral spring.ai.ollama.chat.options.temperature=0.3 spring.ai.ollama.chat.options.top-k=2 @@ -10,19 +11,24 @@ spring.ai.ollama.chat.options.top-p=0.2 spring.ai.ollama.embedding.options.model=nomic-embed-text -spring.ai.vectorstore.redis.index=vector_store -spring.ai.vectorstore.redis.prefix=ai -spring.ai.vectorstore.redis.initialize-schema=true +#PgVector +spring.ai.vectorstore.observations.include-query-response=true +spring.ai.vectorstore.pgvector.initialize-schema=true -spring.ai.ollama.baseUrl=http://localhost:11434 +spring.http.client.connect-timeout=PT1M +spring.http.client.read-timeout=PT5M spring.testcontainers.beans.startup=parallel ##Observability spring.ai.chat.observations.include-completion=true spring.ai.chat.observations.include-prompt=true +spring.ai.chat.client.observations.include-input=true management.endpoints.web.exposure.include=* management.metrics.tags.service.name=${spring.application.name} management.tracing.sampling.probability=1.0 management.otlp.tracing.endpoint=http://localhost:4318/v1/traces +management.otlp.logging.endpoint=http://localhost:4318/v1/logs + +logging.level.org.springframework.ai.rag=info diff --git a/rag/rag-springai-ollama-llm/src/main/resources/logback-spring.xml b/rag/rag-springai-ollama-llm/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1e2ef31 --- /dev/null +++ b/rag/rag-springai-ollama-llm/src/main/resources/logback-spring.xml @@ -0,0 +1,16 @@ + + + + + + true + true + true + + + + + + + diff --git a/rag/rag-springai-ollama-llm/src/test/java/com/learning/ai/llmragwithspringai/config/TestcontainersConfiguration.java b/rag/rag-springai-ollama-llm/src/test/java/com/learning/ai/llmragwithspringai/config/TestcontainersConfiguration.java index 4c7deda..b40831c 100644 --- a/rag/rag-springai-ollama-llm/src/test/java/com/learning/ai/llmragwithspringai/config/TestcontainersConfiguration.java +++ b/rag/rag-springai-ollama-llm/src/test/java/com/learning/ai/llmragwithspringai/config/TestcontainersConfiguration.java @@ -1,13 +1,10 @@ package com.learning.ai.llmragwithspringai.config; -import com.redis.testcontainers.RedisStackContainer; -import java.io.IOException; import java.time.Duration; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Scope; -import org.springframework.test.context.DynamicPropertyRegistry; +import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.grafana.LgtmStackContainer; import org.testcontainers.ollama.OllamaContainer; import org.testcontainers.utility.DockerImageName; @@ -17,30 +14,21 @@ public class TestcontainersConfiguration { @Bean @ServiceConnection - OllamaContainer ollama() throws IOException, InterruptedException { - // The model name to use (e.g., "orca-mini", "mistral", "llama2", "codellama", "phi", or - // "tinyllama") - OllamaContainer ollamaContainer = new OllamaContainer( - DockerImageName.parse("langchain4j/ollama-mistral:latest").asCompatibleSubstituteFor("ollama/ollama")); - ollamaContainer.start(); - ollamaContainer.execInContainer("ollama", "pull", "nomic-embed-text"); - return ollamaContainer; + OllamaContainer ollama() { + return new OllamaContainer(DockerImageName.parse("ollama/ollama")); } @Bean - RedisStackContainer redisContainer(DynamicPropertyRegistry properties) { - RedisStackContainer redis = new RedisStackContainer( - RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); - properties.add("spring.ai.vectorstore.redis.uri", () -> "redis://%s:%d" - .formatted(redis.getHost(), redis.getMappedPort(6379))); - return redis; + @ServiceConnection + PostgreSQLContainer postgreSQLContainer() { + return new PostgreSQLContainer<>(DockerImageName.parse("pgvector/pgvector:pg17")) + .withStartupTimeout(Duration.ofMinutes(2)); } @Bean - @Scope("singleton") - @ServiceConnection("otel/opentelemetry-collector-contrib") + @ServiceConnection LgtmStackContainer lgtmStackContainer() { - return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm").withTag("0.7.1")) + return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm").withTag("0.8.1")) .withStartupTimeout(Duration.ofMinutes(2)); } }