Skip to content

Commit

Permalink
Generative AI support for Spring Petclinic Microservices (#281)
Browse files Browse the repository at this point in the history
* updated git ignore

* New microservice for generative ai chatbot based on Spring AI. Supports listing vets, listing owners, adding owners and adding pets to owners

* Update README.md

* Use webjar as webjar

* Externalise JavaScript for handling chatbox interaction

* Use @PostMapping for the /fallback endpoint

* Configure the /fallback uri for the POST verb only

* Move the spring-ai.png to the docs directory

* Switch by default to the openai demo account

* Use lambda expression

* Remove Lombok from the genai-service

* Remove creds.yaml

* Fix SonarQube issues

* Fix SonarQube issues (second attempt)

* Add the genai-service

* Use lambda expression

* Fix Docker warnings

* Setting the GenAI environment variables

---------

Co-authored-by: Antoine Rey <[email protected]>
  • Loading branch information
odedia and arey authored Dec 27, 2024
1 parent f2c1422 commit 79b527a
Show file tree
Hide file tree
Showing 40 changed files with 1,371 additions and 22 deletions.
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

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

0 comments on commit 79b527a

Please sign in to comment.