Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ jobs:
run: mvn -B package --file pom.xml

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
#- name: Update dependency graph
# uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
33 changes: 33 additions & 0 deletions springboot-modules/spring-ai/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@
<artifactId>spring-ai</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>ollama</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
Expand Down Expand Up @@ -48,6 +73,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.21.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kodesastra.ai.ollama;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiOllamaApplication {

public static void main(String[] args) {
SpringApplication.run(SpringAiOllamaApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spring.application.name=spring-ai-ollama
spring.autoconfigure.exclude=org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration,org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration,org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration,org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration,org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration,org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

@startuml
'https://plantuml.com/class-diagram
set namespaceSeparator none
scale 1
skinparam padding 0
skinparam ranksep 50
hide empty attributes
skinparam Handwritten false
skinparam ClassBorderColor black
skinparam BackgroundColor #F0EDDE
skinparam ClassAttributeFontColor #222222
skinparam ClassFontStyle bold

skinparam class {
ArrowColor #3C88A3
ArrowFontColor #3C88A3
hide empty attributes
skinparam Handwritten false
skinparam ClassBorderColor black
BackgroundColor #FFFFFF
}
'important Spring AI Ollama classes
class "OllamaChatModel" as ocm {
+call(Prompt prompt): ChatResponse
}

class "ChatResponse" as cr {
+getResult(): Generation
}

class "OllamaOptions" as oo {
+builder(): OllamaOptions.Builder
+getTemperature(): Double
+getModel(): OllamaModel
..
<i>Other methods to set and get Ollama options
}

class "Prompt" as p {
+Prompt(String prompt, OllamaOptions.Builder builder)
+builder(): Prompt.Builder
}

class "OllamaOptions.Builder" as ob {
+temperature(Double temperature): Prompt.Builder
+model(OllamaModel model)
+build(): OllamaOptions
..
<i>Other methods to build Ollama options
}

ocm .down.> p : uses
ocm -up-> cr : call(Prompt):ChatResponse
oo +-down- ob : static nested
p .up.> ob : uses
@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.kodesastra.ai.ollama;

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.Profile;
import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
@Profile("ollama")
class OllamaTestContainersDefaultConfig {
@Bean
@ServiceConnection
OllamaContainer ollamaContainer() {
return new OllamaContainer(
DockerImageName.parse("ollama/ollama:latest")
).withCreateContainerCmdModifier(cmd ->
cmd.getHostConfig().withDeviceRequests(null)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.kodesastra.ai.ollama;

import java.io.IOException;


import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.ollama.OllamaContainer;

@SpringBootTest
@Import(OllamaTestContainersDefaultConfig.class)
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles("ollama")
class SpringAiOllamaDefaultConfigLiveTest {
private final Logger logger = LoggerFactory.getLogger(SpringAiOllamaDefaultConfigLiveTest.class);

@Autowired
private OllamaContainer ollamaContainer;

@Autowired
private OllamaChatModel ollamaChatModel;

@BeforeAll
public void setup() throws IOException, InterruptedException {
//print the Ollama URL and port
logger.info("Ollama URL: {}, port: {}", ollamaContainer.getEndpoint(), ollamaContainer.getPort());
Container.ExecResult execResult = ollamaContainer.execInContainer("ollama", "pull",
OllamaModel.LLAMA3_2.getName());
if (execResult.getExitCode() != 0) {
logger.error("Failed to pull model: {}", execResult.getStderr());
throw new IOException("Failed to pull model: " + execResult.getStderr());
}
}

@Test
void givenDefaultOllamaConnection_whenInvokedWithPrompt_thenResponds() {
logger.info("SpringAIOllamaLiveTest context loaded successfully.");
String prompt = """
Context:
The Amazon rainforest is the largest tropical rainforest in the world, spanning several countries in South America.
It is home to a vast diversity of plant and animal species, many of which are not found anywhere else on Earth.
The rainforest plays a crucial role in regulating the global climate by absorbing large amounts of carbon dioxide.
Question: Why is the Amazon rainforest important for the Earth's climate?
Instructions:
Please answer strictly from the context provided in the prompt and do not include any additional information.
Keep the answer short and concise.
""";

ChatResponse response = ollamaChatModel.call(new Prompt(prompt, OllamaOptions.builder()
.model(OllamaModel.LLAMA3_2)
.temperature(0.4)
.build()));
assertThat(response.getResult()
.getOutput()).isNotNull()
.extracting(output -> output.getText().toLowerCase())
.asString()
.contains("carbon dioxide");

logger.info("Response: {}", response.getResult()
.getOutput()
.getText());
}
}