Skip to content
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

Generative AI support for Spring Petclinic Microservices #281

Merged
merged 18 commits into from
Dec 27, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ target/

# Branch switching
generated/

**/.DS_Store
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Distributed version of the Spring PetClinic Sample Application built with Spring Cloud
# Distributed version of the Spring PetClinic Sample Application built with Spring Cloud and Spring AI

[![Build Status](https://github.com/spring-petclinic/spring-petclinic-microservices/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-microservices/actions/workflows/maven-build.yml)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Expand All @@ -17,7 +17,7 @@ If everything goes well, you can access the following services at given location
* Discovery Server - http://localhost:8761
* Config Server - http://localhost:8888
* AngularJS frontend (API Gateway) - http://localhost:8080
* Customers, Vets and Visits Services - random port, check Eureka Dashboard
* Customers, Vets, Visits and GenAI Services - random port, check Eureka Dashboard
* Tracing Server (Zipkin) - http://localhost:9411/zipkin/ (we use [openzipkin](https://github.com/openzipkin/zipkin/tree/main/zipkin-server))
* Admin Server (Spring Boot Admin) - http://localhost:9090
* Grafana Dashboards - http://localhost:3000
Expand Down Expand Up @@ -46,7 +46,7 @@ For instance, if you target container images for an Apple M2, you could use the
```

Once images are ready, you can start them with a single command
`docker-compose up` or `podman-compose up`.
`docker compose up` or `podman-compose up`.

Containers startup order is coordinated with the `service_healthy` condition of the Docker Compose [depends-on](https://github.com/compose-spec/compose-spec/blob/main/spec.md#depends_on) expression
and the [healthcheck](https://github.com/compose-spec/compose-spec/blob/main/spec.md#healthcheck) of the service containers.
Expand Down Expand Up @@ -79,6 +79,7 @@ This project consists of several microservices:
- **Customers Service**: Manages customer data.
- **Vets Service**: Handles information about veterinarians.
- **Visits Service**: Manages pet visit records.
- **GenAI Service**: Provides a chatbot interface to the application.
- **API Gateway**: Routes client requests to the appropriate services.
- **Config Server**: Centralized configuration management for all services.
- **Discovery Server**: Eureka-based service registry.
Expand All @@ -93,6 +94,41 @@ Each service has its own specific role and communicates via REST APIs.

![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram.jpg)

## Integrating the Spring AI Chatbot

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: we need to offer a solution to developers who don't have an Azure or OpenAI account and don't want to pay for it: either enable fallback by default, or disable chat, or have a local solution with ollama. What do you think @odedia ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I began with using Llama 3.1, but the chat client became incredibly verbose and not as useful as OpenAI, that's why I switched to it.
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@odedia I've switched the Maven configuration from Azure OpenAI to OpenAI with the free demo account that OpenAI suggests. To use it, we have to use the gpt-4o-mini model.

Spring Petclinic integrates a Chatbot that allows you to interact with the application in a natural language. Here are some examples of what you could ask:

1. Please list the owners that come to the clinic.
2. Are there any vets that specialize in surgery?
3. Is there an owner named Betty?
4. Which owners have dogs?
5. Add a dog for Betty. Its name is Moopsie.
6. Create a new owner.

![Screenshot of the chat dialog](docs/spring-ai.png)

This `spring-petlinic-genai-service` microservice currently supports **OpenAI** (default) or **Azure's OpenAI** as the LLM provider.
In order to start the microservice, perform the following steps:

1. Decide which provider you want to use. By default, the `spring-ai-openai-spring-boot-starter` dependency is enabled.
You can change it to `spring-ai-azure-openai-spring-boot-starter`in the `pom.xml`.
2. Create an OpenAI API key or a Azure OpenAI resource in your Azure Portal.
Refer to the [OpenAI's quickstart](https://platform.openai.com/docs/quickstart) or [Azure's documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/) for further information on how to obtain these.
You only need to populate the provider you're using - either openai, or azure-openai.
If you don't have your own OpenAI API key, don't worry!
You can temporarily use the `demo` key, which OpenAI provides free of charge for demonstration purposes.
This `demo` key has a quota, is limited to the `gpt-4o-mini` model, and is intended solely for demonstration use.
With your own OpenAI account, you can test the `gpt-4o` model by modifying the `deployment-name` property of the `application.yml` file.
3. Export your API keys and endpoint as environment variables:
* either OpenAI:
```bash
export OPENAI_API_KEY="your_api_key_here"
```
* or Azure OpenAI:
```bash
export AZURE_OPENAI_ENDPOINT="https://your_resource.openai.azure.com"
export AZURE_OPENAI_KEY="your_api_key_here"
```

## In case you find a bug/suggested improvement for Spring Petclinic Microservices

Expand Down
25 changes: 22 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3'

services:
config-server:
image: springcommunity/spring-petclinic-config-server
Expand Down Expand Up @@ -79,6 +77,27 @@ services:
ports:
- 8083:8083


genai-service:
image: springcommunity/spring-petclinic-genai-service
container_name: genai-service
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- AZURE_OPENAI_KEY=${AZURE_OPENAI_KEY}
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
deploy:
resources:
limits:
memory: 512M
depends_on:
config-server:
condition: service_healthy
discovery-server:
condition: service_healthy
ports:
- 8084:8084


api-gateway:
image: springcommunity/spring-petclinic-api-gateway
container_name: api-gateway
Expand Down Expand Up @@ -131,7 +150,7 @@ services:
limits:
memory: 256M
ports:
- 3000:3000
- 3030:3030

prometheus-server:
build: ./docker/prometheus
Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17 as builder
FROM eclipse-temurin:17 AS builder
WORKDIR application
ARG ARTIFACT_NAME
COPY ${ARTIFACT_NAME}.jar application.jar
Expand All @@ -11,7 +11,7 @@ WORKDIR application
ARG EXPOSED_PORT
EXPOSE ${EXPOSED_PORT}

ENV SPRING_PROFILES_ACTIVE docker
ENV SPRING_PROFILES_ACTIVE=docker

COPY --from=builder application/dependencies/ ./

Expand Down
Binary file modified docs/microservices-architecture-diagram.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/spring-ai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.7</version>
<version>3.3.4</version>
</parent>

<groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic-microservices</artifactId>
<version>3.2.7</version>
<version>3.3.4</version>
<name>${project.artifactId}</name>
<packaging>pom</packaging>

Expand All @@ -20,6 +20,7 @@
<module>spring-petclinic-customers-service</module>
<module>spring-petclinic-vets-service</module>
<module>spring-petclinic-visits-service</module>
<module>spring-petclinic-genai-service</module>
<module>spring-petclinic-config-server</module>
<module>spring-petclinic-discovery-server</module>
<module>spring-petclinic-api-gateway</module>
Expand Down
5 changes: 3 additions & 2 deletions scripts/run_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ set -o pipefail

pkill -9 -f spring-petclinic || echo "Failed to kill any apps"

docker-compose kill || echo "No docker containers are running"
docker compose kill || echo "No docker containers are running"

echo "Running infra"
docker-compose up -d grafana-server prometheus-server tracing-server
docker compose up -d grafana-server prometheus-server tracing-server

echo "Running apps"
mkdir -p target
Expand All @@ -23,6 +23,7 @@ sleep 20
nohup java -jar spring-petclinic-customers-service/target/*.jar --server.port=8081 --spring.profiles.active=chaos-monkey > target/customers-service.log 2>&1 &
nohup java -jar spring-petclinic-visits-service/target/*.jar --server.port=8082 --spring.profiles.active=chaos-monkey > target/visits-service.log 2>&1 &
nohup java -jar spring-petclinic-vets-service/target/*.jar --server.port=8083 --spring.profiles.active=chaos-monkey > target/vets-service.log 2>&1 &
nohup java -jar spring-petclinic-genai-service/target/*.jar --server.port=8084 --spring.profiles.active=chaos-monkey > target/genai-service.log 2>&1 &
nohup java -jar spring-petclinic-api-gateway/target/*.jar --server.port=8080 --spring.profiles.active=chaos-monkey > target/gateway-service.log 2>&1 &
nohup java -jar spring-petclinic-admin-server/target/*.jar --server.port=9090 --spring.profiles.active=chaos-monkey > target/admin-server.log 2>&1 &
echo "Waiting for apps to start"
Expand Down
2 changes: 1 addition & 1 deletion spring-petclinic-admin-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic-microservices</artifactId>
<version>3.2.7</version>
<version>3.3.4</version>
</parent>

<properties>
Expand Down
9 changes: 8 additions & 1 deletion spring-petclinic-api-gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
<parent>
<groupId>org.springframework.samples</groupId>
<artifactId>spring-petclinic-microservices</artifactId>
<version>3.2.7</version>
<version>3.3.4</version>
</parent>

<properties>
<webjars-bootstrap.version>5.3.3</webjars-bootstrap.version>
<webjars-font-awesome.version>4.7.0</webjars-font-awesome.version>
<webjars-angular.version>1.8.3</webjars-angular.version>
<webjars-angular-ui-router.version>1.0.30</webjars-angular-ui-router.version>
<webjars-marked.version>14.1.2</webjars-marked.version>

<libsass-maven-plugin.version>0.2.29</libsass-maven-plugin.version>
<docker.image.exposed.port>8081</docker.image.exposed.port>
<docker.image.dockerfile.dir>${basedir}/../docker</docker.image.dockerfile.dir>
Expand Down Expand Up @@ -124,6 +126,11 @@
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>marked</artifactId>
<version>${webjars-marked.version}</version>
</dependency>

<!-- Testing -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ RouterFunction<?> routerFunction() {
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4)).build())
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build())
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.springframework.samples.petclinic.api.boundary.web;

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

@RestController
public class FallbackController {

@PostMapping("/fallback")
public ResponseEntity<String> fallback() {
return ResponseEntity.status(HttpStatus.SC_SERVICE_UNAVAILABLE)
.body("Chat is currently unavailable. Please try again later.");
}
}
19 changes: 17 additions & 2 deletions spring-petclinic-api-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ spring:
import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/}
cloud:
gateway:
default-filters:
- name: CircuitBreaker
args:
name: defaultCircuitBreaker
fallbackUri: forward:/fallback
- name: Retry
args:
retries: 1
statuses: SERVICE_UNAVAILABLE
methods: POST
routes:
- id: vets-service
uri: lb://vets-service
Expand All @@ -24,8 +34,13 @@ spring:
- Path=/api/customer/**
filters:
- StripPrefix=2


- id: genai-service
uri: lb://genai-service
predicates:
- Path=/api/genai/**
filters:
- StripPrefix=2
- CircuitBreaker=name=genaiCircuitBreaker,fallbackUri=/fallback

---
spring:
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading