Skip to content

Commit

Permalink
Integrate open ai to provide the ai summary and conversation health e…
Browse files Browse the repository at this point in the history
…valuation (#60)
  • Loading branch information
haiphucnguyen authored Jan 26, 2025
1 parent ceb8894 commit 9884cef
Show file tree
Hide file tree
Showing 34 changed files with 735 additions and 47 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ allprojects {
version = project.findProperty('version')
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}

subprojects {
Expand Down

This file was deleted.

6 changes: 6 additions & 0 deletions commons/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ group = 'io.flowinquiry'

repositories {
mavenCentral()

}

dependencyManagement {
Expand All @@ -18,8 +19,10 @@ dependencyManagement {

dependencies {
compileOnly(libs.lombok)
api(libs.bundles.caffeine.cache)
api(libs.bundles.json)
api(libs.bundles.shedlock)
api(libs.bundles.spring.ai)
api(libs.dot.env)
api(libs.j2html)
api(libs.jclouds) {
Expand All @@ -37,17 +40,20 @@ dependencies {
api("jakarta.annotation:jakarta.annotation-api")
api("org.apache.commons:commons-lang3")
api("org.hibernate.orm:hibernate-core")
api("org.hibernate.orm:hibernate-jcache")
api("org.hibernate.validator:hibernate-validator")
api("org.springframework.boot:spring-boot-starter-actuator")
api("org.springframework.boot:spring-boot-starter-aop")
api("org.springframework.boot:spring-boot-starter-data-jpa")
api("org.springframework.boot:spring-boot-loader-tools")
api("org.springframework.boot:spring-boot-starter-cache")
api("org.springframework.boot:spring-boot-starter-logging")
api("org.springframework.boot:spring-boot-starter-mail")
api("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
api("org.springframework.boot:spring-boot-starter-security")
api("org.springframework.boot:spring-boot-starter-thymeleaf")
api("org.springframework.boot:spring-boot-starter-undertow")

modules {
module("org.springframework.boot:spring-boot-starter-tomcat") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Tomcat")
Expand Down
26 changes: 26 additions & 0 deletions commons/src/main/java/io/flowinquiry/config/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.flowinquiry.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
Caffeine<Object, Object> caffeineBuilder =
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // Set cache expiration
.maximumSize(1000); // Set maximum cache size

CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineBuilder);
return cacheManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public class FlowInquiryProperties {

private final Http http = new Http();

private final Cache cache = new Cache();

private final Security security = new Security();

private final CorsConfiguration cors = new CorsConfiguration();
Expand All @@ -27,18 +25,6 @@ public static class Mail {
private String baseUrl = "";
}

@Getter
public static class Cache {
private final Ehcache ehcache = new Ehcache();

@Getter
@Setter
public static class Ehcache {
private int timeToLiveSeconds = 3600;
private long maxEntries = 100L;
}
}

@Getter
public static class Http {
private final Cache cache = new Cache();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static org.springframework.security.config.Customizer.withDefaults;

import io.flowinquiry.modules.usermanagement.AuthoritiesConstants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -28,10 +27,6 @@ public PasswordEncoder passwordEncoder() {
}

@Bean
@ConditionalOnProperty(
name = "flowinquiry.edition",
havingValue = "community",
matchIfMissing = true)
public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc)
throws Exception {
http.cors(withDefaults())
Expand All @@ -44,10 +39,10 @@ public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Buil
mvc.pattern(HttpMethod.POST, "/api/authenticate"),
mvc.pattern(HttpMethod.GET, "/api/authenticate"),
mvc.pattern("/api/register"),
mvc.pattern("/api/files/**"),
mvc.pattern("/api/activate"),
mvc.pattern("/api/account/reset-password/init"),
mvc.pattern("/api/account/reset-password/finish"),
mvc.pattern("/api/test/**"))
mvc.pattern("/api/account/reset-password/finish"))
.permitAll()
.requestMatchers(mvc.pattern("/api/admin/**"))
.hasAuthority(AuthoritiesConstants.ADMIN)
Expand All @@ -56,7 +51,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Buil
.requestMatchers(mvc.pattern("/management/**"))
.hasAuthority(
AuthoritiesConstants.ADMIN)) // Enforces ROLE_ADMIN
.httpBasic(withDefaults()) // Enable Basic Authentication
// .httpBasic(withDefaults()) // Enable Basic Authentication
.oauth2ResourceServer(
oauth2 -> oauth2.jwt(withDefaults())) // Enable OAuth2 Resource Server
.sessionManagement(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.flowinquiry.modules.ai.config;

import io.flowinquiry.modules.ai.service.ChatModelService;
import io.flowinquiry.modules.ai.service.OllamaChatModelService;
import io.flowinquiry.modules.ai.service.OpenAiChatModelService;
import java.util.Optional;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class ChatModelConfiguration {

@Bean
@Primary
@ConditionalOnBean({OllamaChatModelService.class, OpenAiChatModelService.class})
public ChatModelService chatModel(
Optional<OllamaChatModelService> ollamaChatModelService,
Optional<OpenAiChatModelService> openAiChatModelService) {
if (ollamaChatModelService.isPresent()) {
return ollamaChatModelService.get();
} else if (openAiChatModelService.isPresent()) {
return openAiChatModelService.get();
}

// If no chat models are present, this block won't execute due to @ConditionalOnBean
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.flowinquiry.modules.ai.service;

/**
* Interface representing a generic chat model service.
*
* <p>This interface defines the contract for interacting with different chat models, such as OpenAI
* or Ollama, by providing a method to process input and return a response. Implementations of this
* interface should encapsulate the logic specific to each chat model's API or processing.
*
* <h3>Usage Example</h3>
*
* <pre>{@code
* ChatModelService chatModelService = new OpenAiChatModelService();
* String response = chatModelService.call("Summarize this text.");
* System.out.println(response);
* }</pre>
*
* <h3>Implementations</h3>
*
* <ul>
* <li>{@link OpenAiChatModelService}
* <li>{@link OllamaChatModelService}
* </ul>
*
* <h3>Thread-Safety</h3>
*
* Implementations of this interface should ensure thread-safety if they are intended to be used in
* concurrent environments.
*
* @author Hai Nguyen
* @version 1.0
*/
public interface ChatModelService {

/**
* Processes the given input using the chat model and returns the model's response.
*
* <p>The input string can be any text to be processed by the chat model, such as a question, a
* request for summarization, or any prompt supported by the model.
*
* @param input the text input to be processed by the chat model
* @return the response generated by the chat model
* @throws IllegalArgumentException if the input is null or empty
*/
String call(String input);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.flowinquiry.modules.ai.service;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

@Service
@ConditionalOnProperty(
name = {"OLLAMA_CHAT_MODEL", "OLLAMA_API_KEY"},
matchIfMissing = false)
public class OllamaChatModelService implements ChatModelService {

@Override
public String call(String input) {
return "Response from Ollama: " + input;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.flowinquiry.modules.ai.service;

import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

@Service
@ConditionalOnProperty(
name = {"OPEN_AI_CHAT_MODEL", "OPEN_AI_API_KEY"},
matchIfMissing = false)
@ConditionalOnBean(OpenAiChatModel.class)
public class OpenAiChatModelService implements ChatModelService {

private final OpenAiChatModel openAiChatModel;

public OpenAiChatModelService(OpenAiChatModel openAiChatModel) {
this.openAiChatModel = openAiChatModel;
}

@Override
public String call(String input) {
return openAiChatModel.call(input);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,38 @@
import io.flowinquiry.modules.collab.repository.CommentRepository;
import io.flowinquiry.modules.collab.service.dto.CommentDTO;
import io.flowinquiry.modules.collab.service.mapper.CommentMapper;
import io.flowinquiry.modules.teams.service.event.TeamRequestNewCommentEvent;
import java.util.List;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class CommentService {

private final ApplicationEventPublisher eventPublisher;

private final CommentRepository commentRepository;

private final CommentMapper commentMapper;

public CommentService(CommentRepository commentRepository, CommentMapper commentMapper) {
public CommentService(
ApplicationEventPublisher eventPublisher,
CommentRepository commentRepository,
CommentMapper commentMapper) {
this.eventPublisher = eventPublisher;
this.commentRepository = commentRepository;
this.commentMapper = commentMapper;
}

public CommentDTO saveComment(CommentDTO comment) {
return commentMapper.toDTO(commentRepository.save(commentMapper.toEntity(comment)));
CommentDTO savedComment =
commentMapper.toDTO(commentRepository.save(commentMapper.toEntity(comment)));
if (savedComment.getEntityType() == EntityType.Team_Request) {
eventPublisher.publishEvent(new TeamRequestNewCommentEvent(this, savedComment));
}
return savedComment;
}

public Comment getCommentById(Long id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.flowinquiry.modules.teams.controller;

import io.flowinquiry.modules.teams.service.TeamRequestHealthEvalService;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/ai")
@ConditionalOnBean(OpenAiChatModel.class)
public class AiChatController {

private final TeamRequestHealthEvalService teamRequestHealthEvalService;

public AiChatController(TeamRequestHealthEvalService teamRequestHealthEvalService) {
this.teamRequestHealthEvalService = teamRequestHealthEvalService;
}

@PostMapping
public String createRequestSummary(@RequestBody String requestDescription) {
return teamRequestHealthEvalService.summarizeTeamRequest(requestDescription);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.flowinquiry.modules.audit.AbstractAuditingEntity;
import io.flowinquiry.modules.usermanagement.domain.User;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
Expand All @@ -13,6 +14,7 @@
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.util.Set;
Expand Down Expand Up @@ -98,4 +100,11 @@ public class TeamRequest extends AbstractAuditingEntity<Long> {
@Formula(
"(SELECT COUNT(a.id) FROM fw_entity_attachment a WHERE a.entity_type = 'Team_Request' AND a.entity_id = id)")
private int numberAttachments;

@OneToOne(
mappedBy = "teamRequest",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true)
private TeamRequestConversationHealth conversationHealth;
}
Loading

0 comments on commit 9884cef

Please sign in to comment.