Skip to content

Commit

Permalink
Add 'Classification' example and update 'Question Answering (RAG)'
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasVitale committed Jun 4, 2024
1 parent 5fef7f6 commit b1b045b
Show file tree
Hide file tree
Showing 23 changed files with 332 additions and 23 deletions.
59 changes: 59 additions & 0 deletions 00-use-cases/classification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Classification

Classification with LLMs via Ollama.

## Running the application

The application relies on Ollama for providing LLMs. You can either run Ollama locally on your laptop, or rely on the Testcontainers support in Spring Boot to spin up an Ollama service automatically.

### Ollama as a native application

First, make sure you have [Ollama](https://ollama.ai) installed on your laptop.
Then, use Ollama to run the _mistral_ large language model.

```shell
ollama run mistral
```

Finally, run the Spring Boot application.

```shell
./gradlew bootRun
```

### Ollama as a dev service with Testcontainers

The application relies on the native Testcontainers support in Spring Boot to spin up an Ollama service with a _mistral_ model at startup time.

```shell
./gradlew bootTestRun
```

## Calling the application

You can now call the application that will use Ollama and _mistral_ to classify your text.
This example uses [httpie](https://httpie.io) to send HTTP requests.

Simple prompt:

```shell
http --raw "The piano is an instrument that evokes warmth and introspection, creating a sense of intimacy and drama." :8080/classify/simple
```

Structured prompt:

```shell
http --raw "The piano is an instrument that evokes warmth and introspection, creating a sense of intimacy and drama." :8080/classify/structured
```

Few shots simple prompt:

```shell
http --raw "The piano is an instrument that evokes warmth and introspection, creating a sense of intimacy and drama." :8080/classify/few-shots-prompt
```

Few shots with history prompt:

```shell
http --raw "The piano is an instrument that evokes warmth and introspection, creating a sense of intimacy and drama." :8080/classify/few-shots-history
```
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.thomasvitale.ai.spring;

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

@SpringBootApplication
public class Classification {

public static void main(String[] args) {
SpringApplication.run(Classification.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.thomasvitale.ai.spring;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
class ClassificationController {

private final ClassificationService classificationService;

ClassificationController(ClassificationService classificationService) {
this.classificationService = classificationService;
}

@PostMapping("/classify/simple")
String classifySimple(@RequestBody String input) {
return classificationService.classifySimple(input);
}

@PostMapping("/classify/structured")
ClassificationType classifyStructured(@RequestBody String input) {
return classificationService.classifyStructured(input);
}

@PostMapping("/classify/few-shots-prompt")
ClassificationType classifyFewShotsPrompt(@RequestBody String input) {
return classificationService.classifyFewShotsPrompt(input);
}

@PostMapping("/classify/few-shots-history")
ClassificationType classifyFewShotsHistory(@RequestBody String input) {
return classificationService.classifyFewShotsHistory(input);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.thomasvitale.ai.spring;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
class ClassificationService {

private final ChatClient chatClient;

ClassificationService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}

String classifySimple(String message) {
return chatClient
.prompt()
.user(userSpec -> userSpec.text("""
Classify the type of the provided text and return the classification type as "BOOK" OR "MUSIC".
{text}
""")
.param("text", message))
.call()
.content();
}

ClassificationType classifyStructured(String message) {
return chatClient
.prompt()
.user(userSpec -> userSpec.text("""
Classify the type of the provided text and return the classification type.
---------------------
TEXT:
{text}
---------------------
""")
.param("text", message))
.user(message)
.call()
.entity(ClassificationType.class);
}

ClassificationType classifyFewShotsPrompt(String message) {
return chatClient
.prompt()
.user(userSpec -> userSpec.text("""
Classify the type of the provided text and return the classification type.
For example:
Input: The celesta is a good instrument for fantasy or mystery compositions, creating a sense of mystical and supernatural.
Output: "MUSIC"
Input: In "The Lord of The Rings", Tolkien wrote the famous words: "There’s some good in this world, Mr. Frodo… and it’s worth fighting for.".
Output: "BOOK"
Input: They're taking the hobbits to Isengard! To Isengard! To Isengard!
Output: "UNKNOWN"
---------------------
TEXT:
{text}
---------------------
""")
.param("text", message))
.call()
.entity(ClassificationType.class);
}

ClassificationType classifyFewShotsHistory(String message) {
return chatClient
.prompt()
.messages(getPromptWithFewShotsHistory(message))
.call()
.entity(ClassificationType.class);
}

private List<Message> getPromptWithFewShotsHistory(String message) {
return List.of(
new SystemMessage("""
Classify the type of the provided text and return the classification type.
"""),
new UserMessage("""
The celesta is a good instrument for fantasy or mystery compositions, creating a sense of mystical and supernatural.
"""),
new AssistantMessage("MUSIC"),
new UserMessage("""
In "The Lord of The Rings", Tolkien wrote the famous words: "There’s some good in this world, Mr. Frodo… and it’s worth fighting for.".
"""),
new AssistantMessage("BOOK"),
new UserMessage("""
They're taking the hobbits to Isengard! To Isengard! To Isengard!
"""),
new AssistantMessage("UNKNOWN"),
new UserMessage(message)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.thomasvitale.ai.spring;

public enum ClassificationType {
BOOK, MUSIC, UNKNOWN;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
spring:
ai:
ollama:
chat:
options:
model: mistral
threads:
virtual:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.thomasvitale.ai.spring;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ClassificationTests {

@Test
void contextLoads() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
public class TestQuestionAnsweringWithDocuments {
public class TestClassification {

@Bean
@RestartScope
@ServiceConnection
OllamaContainer ollama() {
return new OllamaContainer(DockerImageName.parse("ghcr.io/thomasvitale/ollama-llama3")
return new OllamaContainer(DockerImageName.parse("ghcr.io/thomasvitale/ollama-mistral")
.asCompatibleSubstituteFor("ollama/ollama"));
}

public static void main(String[] args) {
SpringApplication.from(QuestionAnsweringWithDocuments::main).with(TestQuestionAnsweringWithDocuments.class).run(args);
SpringApplication.from(Classification::main).with(TestClassification.class).run(args);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Question Answering With Documents
# Question Answering (RAG)

Ask questions about documents with LLMs via Ollama.

Expand All @@ -9,10 +9,10 @@ The application relies on Ollama for providing LLMs. You can either run Ollama l
### Ollama as a native application

First, make sure you have [Ollama](https://ollama.ai) installed on your laptop.
Then, use Ollama to run the _llama3_ large language model.
Then, use Ollama to run the _mistral_ large language model.

```shell
ollama run llama3
ollama run mistral
```

Finally, run the Spring Boot application.
Expand All @@ -23,15 +23,15 @@ Finally, run the Spring Boot application.

### Ollama as a dev service with Testcontainers

The application relies on the native Testcontainers support in Spring Boot to spin up an Ollama service with a _llama3_ model at startup time.
The application relies on the native Testcontainers support in Spring Boot to spin up an Ollama service with a _mistral_ model at startup time.

```shell
./gradlew bootTestRun
```

## Calling the application

You can now call the application that will use Ollama and llama3 to load text documents as embeddings and generate an answer to your questions based on those documents (RAG pattern).
You can now call the application that will use Ollama and _mistral_ to load text documents as embeddings and generate an answer to your questions based on those documents (RAG pattern).
This example uses [httpie](https://httpie.io) to send HTTP requests.

```shell
Expand Down
40 changes: 40 additions & 0 deletions 00-use-cases/question-answering/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id 'java'
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

group = 'com.thomasvitale'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
implementation platform("org.springframework.ai:spring-ai-bom:${springAiVersion}")

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.ai:spring-ai-ollama-spring-boot-starter'

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

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.springframework.ai:spring-ai-spring-boot-testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:ollama'
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ChatService {

String chatWithDocument(String message) {
return chatClient.prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults().withTopK(3)))
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults().withTopK(5)))
.user(message)
.call()
.content();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import java.util.List;

@Component
public class DocumentEtlPipeline {
public class IngestionPipeline {

private static final Logger logger = LoggerFactory.getLogger(DocumentEtlPipeline.class);
private static final Logger logger = LoggerFactory.getLogger(IngestionPipeline.class);
private final VectorStore vectorStore;

@Value("classpath:documents/story1.md")
Expand All @@ -27,7 +27,7 @@ public class DocumentEtlPipeline {
@Value("classpath:documents/story2.txt")
Resource textFile2;

public DocumentEtlPipeline(VectorStore vectorStore) {
public IngestionPipeline(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}

Expand All @@ -48,7 +48,7 @@ public void run() {
documents.addAll(textReader2.get());

logger.info("Creating and storing Embeddings from Documents");
vectorStore.add(new TokenTextSplitter().split(documents));
vectorStore.add(new TokenTextSplitter(300, 300, 5, 1000, true).split(documents));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class QuestionAnsweringWithDocuments {
public class QuestionAnswering {

public static void main(String[] args) {
SpringApplication.run(QuestionAnsweringWithDocuments.class, args);
SpringApplication.run(QuestionAnswering.class, args);
}

@Bean
Expand Down
Loading

0 comments on commit b1b045b

Please sign in to comment.