Skip to content

BAEL-9368: Google Cloud and Spring AI #18744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
16 changes: 15 additions & 1 deletion spring-ai-4/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-vertex-ai-gemini</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-vertex-ai-embedding</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-model-chat-memory-repository-jdbc</artifactId>
Expand Down Expand Up @@ -89,6 +97,12 @@
<spring.boot.mainclass>com.baeldung.springai.memory.Application</spring.boot.mainclass>
</properties>
</profile>
<profile>
<id>vertexai</id>
<properties>
<spring.boot.mainclass>com.baeldung.springai.vertexai.Application</spring.boot.mainclass>
</properties>
</profile>
</profiles>

<build>
Expand All @@ -113,7 +127,7 @@
<properties>
<junit-jupiter.version>5.9.0</junit-jupiter.version>
<spring-boot.version>3.5.0</spring-boot.version>
<spring-ai.version>1.0.0</spring-ai.version>
<spring-ai.version>1.0.1</spring-ai.version>
</properties>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@SpringBootApplication(exclude = {
org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiMultiModalEmbeddingAutoConfiguration.class,
org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiTextEmbeddingAutoConfiguration.class,
org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration.class,
})
public class Application {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.baeldung.springai.vertexai;

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

@SpringBootApplication(exclude = {
org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration.class,
org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration.class,
org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration.class,
org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration.class,
org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration.class,
org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration.class
})
public class Application {

public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setAdditionalProfiles("vertexai");
app.run(args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.baeldung.springai.vertexai;

import javax.validation.constraints.NotNull;

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

@RestController
public class ChatController {

private final ChatService chatService;

public ChatController(ChatService chatService) {
this.chatService = chatService;
}

@PostMapping("/chat")
public ResponseEntity<String> chat(@RequestBody @NotNull String prompt) {
String response = chatService.chat(prompt);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.baeldung.springai.vertexai;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope
public class ChatService {

private final ChatClient chatClient;

public ChatService(ChatModel chatModel, ChatMemory chatMemory) {
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}

public String chat(String prompt) {
return chatClient.prompt()
.user(userMessage -> userMessage.text(prompt))
.call()
.content();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.baeldung.springai.vertexai;

import javax.validation.constraints.NotNull;

import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MimeType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class MultiModalEmbeddingController {

private final MultiModalEmbeddingService embeddingService;

public MultiModalEmbeddingController(MultiModalEmbeddingService embeddingService) {
this.embeddingService = embeddingService;
}

@PostMapping("/embedding/image")
public ResponseEntity<EmbeddingResponse> getEmbedding(@RequestParam("image") @NotNull MultipartFile imageFile) {
EmbeddingResponse response = embeddingService.getEmbedding(
MimeType.valueOf(imageFile.getContentType()),
imageFile.getResource());
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.baeldung.springai.vertexai;

import java.util.List;
import java.util.Map;

import org.springframework.ai.content.Media;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.DocumentEmbeddingModel;
import org.springframework.ai.embedding.DocumentEmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;

@Service
public class MultiModalEmbeddingService {

private final DocumentEmbeddingModel documentEmbeddingModel;

public MultiModalEmbeddingService(DocumentEmbeddingModel documentEmbeddingModel) {
this.documentEmbeddingModel = documentEmbeddingModel;
}

public EmbeddingResponse getEmbedding(MimeType mimeType, Resource resource) {
Document document = new Document(new Media(mimeType, resource), Map.of());
DocumentEmbeddingRequest request = new DocumentEmbeddingRequest(List.of(document));
return documentEmbeddingModel.call(request);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.baeldung.springai.vertexai;

import javax.validation.constraints.NotNull;

import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TextEmbeddingController {

private final TextEmbeddingService textEmbeddingService;

public TextEmbeddingController(TextEmbeddingService textEmbeddingService) {
this.textEmbeddingService = textEmbeddingService;
}

@PostMapping("/embedding/text")
public ResponseEntity<EmbeddingResponse> getEmbedding(@RequestBody @NotNull String text) {
EmbeddingResponse response = textEmbeddingService.getEmbedding(text);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.baeldung.springai.vertexai;

import java.util.Arrays;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.stereotype.Service;

@Service
public class TextEmbeddingService {

private final EmbeddingModel embeddingModel;

public TextEmbeddingService(EmbeddingModel embeddingModel) {
this.embeddingModel = embeddingModel;
}

public EmbeddingResponse getEmbedding(String... texts) {
EmbeddingRequest request = new EmbeddingRequest(Arrays.asList(texts), null);
return embeddingModel.call(request);
}

}
14 changes: 14 additions & 0 deletions spring-ai-4/src/main/resources/application-vertexai.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
spring:
ai:
vertex:
ai:
gemini:
project-id: "c1-lumion"
location: "europe-west1"
model: "gemini-2.0-flash-lite"
embedding:
project-id: "c1-lumion"
location: "europe-west1"
text:
options:
model: "gemini-embedding-001"
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.baeldung.springai.vertexai;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("vertexai")
class ChatServiceLiveTest {

private static final String PROMPT = "Tell me who you are?";

@Autowired
private ChatService chatService;

@Test
void whenChatServiceIsCalled_thenServiceReturnsNonEmptyResponse() {
String response = chatService.chat(PROMPT);
assertThat(response).isNotEmpty();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.baeldung.springai.vertexai;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.MimeTypeUtils;

@SpringBootTest
@ActiveProfiles("vertexai")
class MultiModalEmbeddingServiceLiveTest {

private static final String IMAGE_PATH = "image/chiikawa.png";

@Autowired
private MultiModalEmbeddingService embeddingService;

@Test
void whenGetEmbeddings_thenReturnEmbeddingResponse() {
Resource imageResource = new ClassPathResource(IMAGE_PATH);
EmbeddingResponse response = embeddingService.getEmbedding(MimeTypeUtils.IMAGE_PNG, imageResource);
assertThat(response).isNotNull();
assertThat(response.getResults()).isNotNull();
assertThat(response.getResults().isEmpty()).isFalse();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.baeldung.springai.vertexai;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("vertexai")
class TextEmbeddingServiceLiveTest {

@Autowired
private TextEmbeddingService embeddingService;

@Test
void whenGetEmbeddings_thenReturnEmbeddingResponse() {
String text = "This is a test string for embedding.";
EmbeddingResponse response = embeddingService.getEmbedding(text);
assertThat(response).isNotNull();
assertThat(response.getResults()).isNotNull();
assertThat(response.getResults().isEmpty()).isFalse();
}

}