Skip to content

Commit f5fac7a

Browse files
committed
feat: Enhance Spring Boot AI template
1 parent bdef16c commit f5fac7a

File tree

12 files changed

+94
-51
lines changed

12 files changed

+94
-51
lines changed

app-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ backend:
1313
# host: 127.0.0.1
1414
csp:
1515
connect-src: ["'self'", 'http:', 'https:']
16+
img-src: ["'self'", 'data:', 'https:', 'https://avatars.githubusercontent.com']
1617
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
1718
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
1819
cors:

demo-catalog/templates/spring-boot-ai-rag/base/.github/workflows/commit-stage.yml

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ jobs:
3535
security-events: write
3636
steps:
3737
- name: Check out source code
38-
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
38+
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
3939
with:
4040
fetch-depth: 0
4141

4242
- name: Set up Java
4343
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
4444
with:
45-
java-version: 22
45+
java-version: 23
4646
distribution: 'graalvm'
4747

4848
- name: Setup Gradle
@@ -53,20 +53,17 @@ jobs:
5353
env:
5454
SONAR_TOKEN: ${{ '${{ secrets.SONAR_TOKEN }}' }}
5555

56-
- name: SBOM vulnerability scanning
57-
uses: aquasecurity/trivy-action@97646fedde05bcd0961217c60b50e23f721e7ec7 # master
56+
- name: Publish SBOM
57+
uses: DependencyTrack/gh-upload-sbom@48feab3080ff9e8f51f4d21861d9fc914eb744f5 # v3.1.0
58+
if: ${{ '${{ !contains(github.ref_name, '/') }}' }}
5859
with:
59-
scan-type: 'sbom'
60-
scan-ref: 'build/reports/application.cdx.json'
61-
format: 'sarif'
62-
output: 'trivy-results-build.sarif'
63-
64-
- name: Upload vulnerability report
65-
uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
66-
if: success() || failure()
67-
with:
68-
sarif_file: 'trivy-results-build.sarif'
69-
category: build
60+
serverHostname: ${{ '${{ secrets.DEPENDENCY_TRACK_HOSTNAME }}' }}
61+
apiKey: ${{ '${{ secrets.DEPENDENCY_TRACK_API_KEY }}' }}
62+
projectName: ${{ '${{ github.event.repository.name }}' }}
63+
projectVersion: ${{ '${{ github.ref_name }}' }}
64+
projectTags: 'java,spring-boot'
65+
bomFilename: "build/reports/application.cdx.json"
66+
autoCreate: true
7067

7168
- name: Package as OCI image
7269
run: ./gradlew bootBuildImage --imageName ${{ '${{ env.REGISTRY }}' }}/${{ '${{ env.IMAGE_NAME }}' }}:${{ '${{ env.VERSION }}' }}

demo-catalog/templates/spring-boot-ai-rag/base/.sdkmanrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# See https://sdkman.io/usage#config
44
# A summary is to add the following to ~/.sdkman/etc/config
55
# sdkman_auto_env=true
6-
java=22-graalce
6+
java=23-graalce

demo-catalog/templates/spring-boot-ai-rag/base/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ LLM-powered application with RAG capabilities.
1919
This example uses [httpie](https://httpie.io) to send HTTP requests.
2020

2121
```shell
22-
http --raw "Who is the lead singer?" :8080/chat
22+
http --raw "Who is the lead singer?" :8080/chat -b --pretty none
2323
```
2424

2525
```shell
26-
http --raw "What instrument does Clara play" :8080/chat
26+
http --raw "What instrument does Clara play" :8080/chat -b --pretty none
2727
```
2828

2929
## 🖊️  License

demo-catalog/templates/spring-boot-ai-rag/base/build.gradle

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ plugins {
33
id 'org.springframework.boot' version '3.4.0-M3'
44
id 'io.spring.dependency-management' version '1.1.6'
55
id 'org.cyclonedx.bom' version '1.10.0'
6-
id 'org.sonarqube' version '4.4.1.3373'
6+
id 'org.sonarqube' version '5.1.0.4882'
77
}
88

99
group = '${{ values.groupId }}'
1010
version = '0.0.1-SNAPSHOT'
1111

1212
java {
1313
toolchain {
14-
languageVersion = JavaLanguageVersion.of(22)
14+
languageVersion = JavaLanguageVersion.of(23)
1515
}
1616
}
1717

@@ -26,7 +26,8 @@ repositories {
2626
}
2727

2828
ext {
29-
set('springAiVersion', "1.0.0-M2")
29+
set('otelInstrumentationVersion', "2.9.0-alpha")
30+
set('springAiVersion', "1.0.0-SNAPSHOT")
3031
}
3132

3233
dependencies {
@@ -36,43 +37,45 @@ dependencies {
3637
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
3738
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
3839
implementation 'io.micrometer:micrometer-registry-otlp'
40+
implementation 'io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0'
3941

4042
{% if values.llmProvider == "mistral-ai" %}
4143
implementation 'org.springframework.ai:spring-ai-mistral-ai-spring-boot-starter'
42-
{% endif %}
44+
{%- endif %}
4345
{% if values.llmProvider == "ollama" %}
4446
implementation 'org.springframework.ai:spring-ai-ollama-spring-boot-starter'
4547
testImplementation 'org.testcontainers:ollama'
46-
{% endif %}
48+
{%- endif %}
4749
{% if values.llmProvider == "openai" %}
4850
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
49-
{% endif %}
51+
{%- endif %}
5052
{% if values.vectorStore == "chroma" %}
5153
implementation 'org.springframework.ai:spring-ai-chroma-store-spring-boot-starter'
5254
testImplementation 'org.testcontainers:chromadb'
53-
{% endif %}
55+
{%- endif %}
5456
{% if values.vectorStore == "postgresql" %}
5557
implementation 'org.springframework.ai:spring-ai-pgvector-store-spring-boot-starter'
5658
testImplementation 'org.testcontainers:postgresql'
57-
{% endif %}
59+
{%- endif %}
5860
{% if values.vectorStore == "weaviate" %}
5961
implementation 'org.springframework.ai:spring-ai-weaviate-store-spring-boot-starter'
6062
testImplementation 'org.testcontainers:weaviate'
61-
{% endif %}
63+
{%- endif %}
6264
implementation "org.springframework.ai:spring-ai-spring-cloud-bindings"
6365

6466
testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools'
6567

6668
testImplementation 'org.springframework.boot:spring-boot-starter-test'
6769
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
6870
testImplementation 'org.springframework.ai:spring-ai-spring-boot-testcontainers'
69-
testImplementation 'org.testcontainers:junit-jupiter'
71+
testImplementation 'org.springframework:spring-webflux'
7072
testImplementation 'org.testcontainers:grafana'
7173
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
7274
}
7375

7476
dependencyManagement {
7577
imports {
78+
mavenBom "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}"
7679
mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
7780
}
7881
}
@@ -82,8 +85,7 @@ tasks.named('test') {
8285
}
8386

8487
tasks.named('bootBuildImage') {
85-
builder = "paketobuildpacks/builder-jammy-buildpackless-tiny"
86-
buildpacks = [ "gcr.io/paketo-buildpacks/java" ]
88+
builder = "paketobuildpacks/builder-noble-java-tiny"
8789
}
8890

8991
sonar {

demo-catalog/templates/spring-boot-ai-rag/base/src/main/java/${{values.basePackageDir}}/Application.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ${{ values.basePackage }};
22

3+
import io.opentelemetry.api.OpenTelemetry;
4+
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
35
import jakarta.annotation.PostConstruct;
46
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
@@ -11,9 +13,11 @@
1113
import org.springframework.beans.factory.annotation.Value;
1214
import org.springframework.boot.SpringApplication;
1315
import org.springframework.boot.autoconfigure.SpringBootApplication;
16+
import org.springframework.boot.context.event.ApplicationReadyEvent;
1417
import org.springframework.boot.web.client.ClientHttpRequestFactories;
1518
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
1619
import org.springframework.boot.web.client.RestClientCustomizer;
20+
import org.springframework.context.ApplicationListener;
1721
import org.springframework.context.annotation.Bean;
1822
import org.springframework.context.annotation.Configuration;
1923
import org.springframework.core.io.Resource;
@@ -32,11 +36,18 @@ public static void main(String[] args) {
3236
SpringApplication.run(Application.class, args);
3337
}
3438

39+
@Bean
40+
ApplicationListener<ApplicationReadyEvent> logbackOtelAppenderInitializer(OpenTelemetry openTelemetry) {
41+
return _ -> OpenTelemetryAppender.install(openTelemetry);
42+
}
43+
3544
}
3645

3746
@RestController
3847
class ChatController {
3948

49+
private static final Logger logger = LoggerFactory.getLogger(ChatController.class);
50+
4051
private final ChatClient chatClient;
4152
private final VectorStore vectorStore;
4253

@@ -47,6 +58,7 @@ class ChatController {
4758

4859
@PostMapping("/chat")
4960
String chatWithDocument(@RequestBody String message) {
61+
logger.info("Received user message: {}", message);
5062
return chatClient.prompt()
5163
.advisors(new QuestionAnswerAdvisor(vectorStore))
5264
.user(message)

demo-catalog/templates/spring-boot-ai-rag/base/src/main/resources/application.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ spring:
2121
{%- endif %}
2222
{%- if values.llmProvider == "ollama" %}
2323
ollama:
24+
init:
25+
pull-model-strategy: when_missing
2426
chat:
2527
options:
26-
model: mistral
28+
model: llama3.2
2729
num-ctx: 4096
2830
temperature: 0.7
2931
embedding:
@@ -64,3 +66,8 @@ management:
6466
tracing:
6567
sampling:
6668
probability: 1.0
69+
otlp:
70+
logging:
71+
endpoint: "http://localhost:4318/v1/logs"
72+
tracing:
73+
endpoint: "http://localhost:4318/v1/traces"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<include resource="org/springframework/boot/logging/logback/base.xml" />
4+
5+
<appender name="OpenTelemetry"
6+
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
7+
<captureExperimentalAttributes>true</captureExperimentalAttributes>
8+
<captureCodeAttributes>true</captureCodeAttributes>
9+
<captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
10+
</appender>
11+
12+
<root level="INFO">
13+
<appender-ref ref="OpenTelemetry"/>
14+
</root>
15+
16+
</configuration>

demo-catalog/templates/spring-boot-ai-rag/base/src/test/java/${{values.basePackageDir}}/ApplicationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package ${{ values.basePackage }};
22

3-
import org.junit.jupiter.api.Disabled;
43
import org.junit.jupiter.api.Test;
54
import org.springframework.boot.test.context.SpringBootTest;
5+
import org.springframework.context.annotation.Import;
66

77
@SpringBootTest
8-
@Disabled
8+
@Import({IntegrationTestSetup.class, TestcontainersConfiguration.class})
99
class ApplicationTests {
1010

1111
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ${{ values.basePackage }};
2+
3+
import org.springframework.boot.devtools.restart.RestartScope;
4+
import org.springframework.boot.test.context.TestConfiguration;
5+
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
6+
import org.springframework.context.annotation.Bean;
7+
{%- if values.llmProvider == "ollama" %}
8+
import org.testcontainers.ollama.OllamaContainer;
9+
{%- endif %}
10+
11+
@TestConfiguration(proxyBeanMethods = false)
12+
public class IntegrationTestSetup {
13+
14+
{% if values.llmProvider == "ollama" %}
15+
@Bean
16+
@RestartScope
17+
@ServiceConnection
18+
OllamaContainer ollama() {
19+
return new OllamaContainer("ollama/ollama").withReuse(true);
20+
}
21+
{%- endif %}
22+
23+
}
24+

demo-catalog/templates/spring-boot-ai-rag/base/src/test/java/${{values.basePackageDir}}/TestcontainersConfiguration.java

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,30 @@
44
import org.springframework.boot.test.context.TestConfiguration;
55
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
66
import org.springframework.context.annotation.Bean;
7-
import org.springframework.context.annotation.Profile;
87
{%- if values.vectorStore == "postgresql" %}
98
import org.testcontainers.containers.PostgreSQLContainer;
109
{%- endif %}
1110
import org.testcontainers.grafana.LgtmStackContainer;
12-
{% if values.llmProvider == "ollama" %}
13-
import org.testcontainers.ollama.OllamaContainer;
14-
{%- endif %}
15-
import org.testcontainers.utility.DockerImageName;
1611

1712
import java.time.Duration;
1813

1914
@TestConfiguration(proxyBeanMethods = false)
2015
class TestcontainersConfiguration {
2116

22-
{% if values.llmProvider == "ollama" %}
23-
@Bean
24-
@RestartScope
25-
@ServiceConnection
26-
@Profile("ollama-image")
27-
OllamaContainer ollama() {
28-
return new OllamaContainer(DockerImageName.parse("ghcr.io/thomasvitale/ollama-mistral")
29-
.asCompatibleSubstituteFor("ollama/ollama"));
30-
}
31-
{%- endif %}
32-
3317
{% if values.vectorStore == "postgresql" %}
3418
@Bean
3519
@RestartScope
3620
@ServiceConnection
3721
PostgreSQLContainer<?> postgresContainer() {
38-
return new PostgreSQLContainer<>(DockerImageName.parse("pgvector/pgvector:pg16"));
22+
return new PostgreSQLContainer<>("pgvector/pgvector:pg17");
3923
}
4024
{%- endif %}
4125

4226
@Bean
4327
@RestartScope
4428
@ServiceConnection
4529
LgtmStackContainer lgtmContainer() {
46-
return new LgtmStackContainer("grafana/otel-lgtm:0.7.3")
30+
return new LgtmStackContainer("grafana/otel-lgtm:0.7.6")
4731
.withStartupTimeout(Duration.ofMinutes(2))
4832
.withReuse(true);
4933
}

0 commit comments

Comments
 (0)