Skip to content

Commit

Permalink
Add 'Structured Data Extraction' use case
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasVitale committed Jun 4, 2024
1 parent 18a03d9 commit 136994a
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.thomasvitale.ai.spring;

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

@SpringBootTest
@Disabled
class SemanticSearchTests {

@Test
Expand Down
39 changes: 39 additions & 0 deletions 00-use-cases/structured-data-extraction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Structured Data Extraction

Structured data extraction 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 extract structured data from unstructured text.
This example uses [httpie](https://httpie.io) to send HTTP requests.

```shell
http --raw "I'm visiting Jon Snow. The blood pressure looks fine: 120/80. The temperature is 36 degrees. The diagnosis is: he knows nothing." :8080/extract
```
40 changes: 40 additions & 0 deletions 00-use-cases/structured-data-extraction/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
@@ -0,0 +1,8 @@
package com.thomasvitale.ai.spring;

import java.util.List;

public record PatientJournal(String fullName, List<Observation> observations, Diagnosis diagnosis) {
public record Observation(String type, String content) {}
public record Diagnosis(String content) {}
}
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 StructuredDataExtraction {

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 StructuredDataExtractionController {

private final StructuredDataExtractionService structuredDataExtractionService;

StructuredDataExtractionController(StructuredDataExtractionService structuredDataExtractionService) {
this.structuredDataExtractionService = structuredDataExtractionService;
}

@PostMapping("/extract")
PatientJournal extract(@RequestBody String input) {
return structuredDataExtractionService.extract(input);
}

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

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatOptionsBuilder;
import org.springframework.stereotype.Service;

@Service
class StructuredDataExtractionService {

private final ChatClient chatClient;

StructuredDataExtractionService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultOptions(ChatOptionsBuilder.builder()
.withTemperature(0.0f)
.build())
.build();
}

PatientJournal extract(String message) {
return chatClient
.prompt()
.user(userSpec -> userSpec.text("""
Extract structured data from the provided text.
If you do not know the value of a field asked to extract,
do not include any value for the field in the result.
Finally, save the object in the database.
---------------------
TEXT:
{text}
---------------------
""")
.param("text", message))
.call()
.entity(PatientJournal.class);
}

}
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 StructuredDataExtractionTests {

@Test
void contextLoads() {
}

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
public class TestStructuredDataExtraction {

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

public static void main(String[] args) {
SpringApplication.from(StructuredDataExtraction::main).with(TestStructuredDataExtraction.class).run(args);
}

}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Samples showing how to build Java applications powered by Generative AI and LLMs
| [classification](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/00-use-cases/classification) | Classification using LLMs via Ollama. |
| [question-answering](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/00-use-cases/question-answering) | Question answering with documents (RAG) using LLMs via Ollama. |
| [semantic-search](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/00-use-cases/semantic-search) | Semantic search using LLMs via Ollama. |
| [structured-data-extraction](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/00-use-cases/structured-data-extraction) | Structured data extraction using LLMs via Ollama. |

### 1. Chat Completion Models

Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include '00-use-cases:chatbot'
include '00-use-cases:classification'
include '00-use-cases:question-answering'
include '00-use-cases:semantic-search'
include '00-use-cases:structured-data-extraction'

include '01-chat-models:chat-models-mistral-ai'
include '01-chat-models:chat-models-ollama'
Expand Down

0 comments on commit 136994a

Please sign in to comment.