Skip to content

feat(ai): added simple service with AI integration#57

Open
RaffSStein wants to merge 2 commits into
mainfrom
feat/ai/chat-bot
Open

feat(ai): added simple service with AI integration#57
RaffSStein wants to merge 2 commits into
mainfrom
feat/ai/chat-bot

Conversation

@RaffSStein

Copy link
Copy Markdown
Owner
  • added new module "ai-simple-service" (spring v3.5.8) with Spring AI integration
  • implemented simple endpoints with Ollama model integration

Copilot AI review requested due to automatic review settings December 8, 2025 12:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new AI service module (ai-simple-service) to the Wealth Management Platform, integrating Spring AI with Ollama for AI chat capabilities. However, the implementation deviates significantly from the project's established conventions and has several critical issues that need to be addressed.

Key Changes:

  • Added Spring AI dependency management (version 1.1.1) to the root POM
  • Created new ai-simple-service module with API data and core submodules
  • Implemented two REST controllers demonstrating Ollama chat model integration
  • Updated main README with enhanced tech stack documentation and restructured sections
  • Simplified user-service and document-service README files

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 24 comments.

Show a summary per file
File Description
pom.xml Added Spring AI BOM dependency management and registered new ai-simple-service modules
business-modules/ai-simple-service/ai-core/pom.xml New module POM using Spring Boot 3.5.8 (deviates from project's Spring Boot 4.x standard) with Spring AI dependencies
business-modules/ai-simple-service/ai-core/src/main/resources/application.yml Configuration for Ollama integration with hardcoded localhost endpoint and non-compliant application name
business-modules/ai-simple-service/ai-core/src/main/java/raff/stein/ai/controller/AiChatModelController.java REST controller with direct ChatModel usage; doesn't implement OpenAPI specification, uses Italian prompts, lacks security and error handling
business-modules/ai-simple-service/ai-core/src/main/java/raff/stein/ai/controller/AiChatClientController.java REST controller with ChatClient; doesn't implement OpenAPI specification, uses Italian prompts, duplicates configuration logic
business-modules/ai-simple-service/ai-core/src/main/java/raff/stein/ai/config/AiClientConfiguration.java ChatClient bean configuration with hardcoded options
business-modules/ai-simple-service/ai-core/src/main/java/raff/stein/ai/AiChatModelApplication.java Standard Spring Boot application entry point
business-modules/ai-simple-service/ai-api-data/pom.xml API data module POM with OpenAPI generator configuration
business-modules/ai-simple-service/ai-api-data/ai-api-data.yaml OpenAPI 3.1.0 specification defining /chat endpoint with security requirements, but missing securitySchemes definition
README.md Enhanced with warning banner, comprehensive tech stack section, and improved architecture documentation
CONTRIBUTING.md Added DB data model guidelines with a typo ("uaw" instead of "use")
business-modules/user-service/README.md Simplified by removing detailed technical sections on build, dependencies, and local development
business-modules/document-service/README.md Simplified by removing data model, API details, and process workflow documentation

Comment on lines +3 to +54
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;


@RestController
@RequestMapping("/model")
public class AiChatModelController {

private static final String DEFAULT_PROMPT = "Ciao, presentati in modo creativo.";

private final ChatModel chatModel;


public AiChatModelController(ChatModel chatModel) {
this.chatModel = chatModel;
}


@GetMapping("/simple/chat")
public String simpleChat() {

return chatModel.call(new Prompt(DEFAULT_PROMPT)).getResult().getOutput().getText();
}

@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {

response.setCharacterEncoding("UTF-8");

Flux<ChatResponse> stream = chatModel.stream(new Prompt(DEFAULT_PROMPT));
return stream.map(resp -> resp.getResult().getOutput().getText());
}


@GetMapping("/custom/chat")
public String customChat() {

OllamaChatOptions customOptions = OllamaChatOptions.builder()
.topP(0.7)
.model("llama3")
.temperature(0.8)
.build();

return chatModel.call(new Prompt(DEFAULT_PROMPT, customOptions)).getResult().getOutput().getText();
}

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The controllers directly expose implementation details (Ollama-specific endpoints) instead of following the OpenAPI specification defined in ai-api-data.yaml. According to the project guidelines, controllers should implement generated interfaces from the -api-data module. The OpenAPI spec defines a /chat endpoint with proper DTOs (ChatRequestDTO, ChatResponseDTO), but these controllers use different paths (/model/, /client/*) and don't implement the specified API contract.

Suggested change
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/model")
public class AiChatModelController {
private static final String DEFAULT_PROMPT = "Ciao, presentati in modo creativo.";
private final ChatModel chatModel;
public AiChatModelController(ChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/simple/chat")
public String simpleChat() {
return chatModel.call(new Prompt(DEFAULT_PROMPT)).getResult().getOutput().getText();
}
@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
Flux<ChatResponse> stream = chatModel.stream(new Prompt(DEFAULT_PROMPT));
return stream.map(resp -> resp.getResult().getOutput().getText());
}
@GetMapping("/custom/chat")
public String customChat() {
OllamaChatOptions customOptions = OllamaChatOptions.builder()
.topP(0.7)
.model("llama3")
.temperature(0.8)
.build();
return chatModel.call(new Prompt(DEFAULT_PROMPT, customOptions)).getResult().getOutput().getText();
}
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
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;
import raff.stein.ai.api.dto.ChatRequestDTO;
import raff.stein.ai.api.dto.ChatResponseDTO;
import raff.stein.ai.api.mapper.ChatMapper;
@RestController
@RequestMapping
public class AiChatModelController {
private final ChatModel chatModel;
private final ChatMapper chatMapper;
public AiChatModelController(ChatModel chatModel, ChatMapper chatMapper) {
this.chatModel = chatModel;
this.chatMapper = chatMapper;
}
/**
* Implements the /chat endpoint as defined in the OpenAPI spec.
* Accepts a ChatRequestDTO and returns a ChatResponseDTO.
*/
@PostMapping("/chat")
public ChatResponseDTO chat(@RequestBody ChatRequestDTO chatRequestDTO) {
// Map DTO to domain model (Prompt)
Prompt prompt = chatMapper.toPrompt(chatRequestDTO);
ChatResponse chatResponse = chatModel.call(prompt);
// Map domain model to DTO
return chatMapper.toChatResponseDTO(chatResponse);
}

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +49
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/client")
public class AiChatClientController {

private static final String DEFAULT_PROMPT = "Ciao, presentati in modo creativo.";

private final ChatClient chatClient;

public AiChatClientController(ChatModel chatModel) {

this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
.defaultOptions(
OllamaChatOptions.builder()
.topP(0.7)
.model("llama3")
.build()
)
.build();
}


@GetMapping("/simple/chat")
public String simpleChat() {

return chatClient.prompt(DEFAULT_PROMPT).call().content();
}


@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {

response.setCharacterEncoding("UTF-8");
return chatClient.prompt(DEFAULT_PROMPT).stream().content();
}

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The controllers directly expose implementation details (Ollama-specific endpoints) instead of following the OpenAPI specification defined in ai-api-data.yaml. According to the project guidelines, controllers should implement generated interfaces from the -api-data module. The OpenAPI spec defines a /chat endpoint with proper DTOs (ChatRequestDTO, ChatResponseDTO), but these controllers use different paths (/model/, /client/*) and don't implement the specified API contract.

Suggested change
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/client")
public class AiChatClientController {
private static final String DEFAULT_PROMPT = "Ciao, presentati in modo creativo.";
private final ChatClient chatClient;
public AiChatClientController(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
.defaultOptions(
OllamaChatOptions.builder()
.topP(0.7)
.model("llama3")
.build()
)
.build();
}
@GetMapping("/simple/chat")
public String simpleChat() {
return chatClient.prompt(DEFAULT_PROMPT).call().content();
}
@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
return chatClient.prompt(DEFAULT_PROMPT).stream().content();
}
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;
import raff.stein.ai.api.ChatApi; // Assuming this is the generated interface from ai-api-data
import raff.stein.ai.api.model.ChatRequestDTO;
import raff.stein.ai.api.model.ChatResponseDTO;
@RestController
@RequestMapping
public class AiChatClientController implements ChatApi {
private final ChatClient chatClient;
public AiChatClientController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* Implements the /chat endpoint as defined in OpenAPI spec.
* Accepts ChatRequestDTO, returns ChatResponseDTO.
*/
@Override
@PostMapping("/chat")
public ChatResponseDTO chat(@RequestBody ChatRequestDTO chatRequestDTO) {
// Map ChatRequestDTO to prompt string (domain model)
String prompt = chatRequestDTO.getPrompt();
// Call the chat client/service layer
String responseContent = chatClient.prompt(prompt).call().content();
// Map response to ChatResponseDTO
ChatResponseDTO responseDTO = new ChatResponseDTO();
responseDTO.setContent(responseContent);
return responseDTO;
}

Copilot uses AI. Check for mistakes.
return ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.defaultOptions(OllamaChatOptions.builder()
.topP(0.7)

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

Magic number 0.7 for topP is used without explanation. These configuration values should either be extracted to named constants with documentation explaining their purpose, or externalized to application.yml for easier tuning without code changes.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +55
public class AiChatModelController {

private static final String DEFAULT_PROMPT = "Ciao, presentati in modo creativo.";

private final ChatModel chatModel;


public AiChatModelController(ChatModel chatModel) {
this.chatModel = chatModel;
}


@GetMapping("/simple/chat")
public String simpleChat() {

return chatModel.call(new Prompt(DEFAULT_PROMPT)).getResult().getOutput().getText();
}

@GetMapping("/stream/chat")
public Flux<String> streamChat(HttpServletResponse response) {

response.setCharacterEncoding("UTF-8");

Flux<ChatResponse> stream = chatModel.stream(new Prompt(DEFAULT_PROMPT));
return stream.map(resp -> resp.getResult().getOutput().getText());
}


@GetMapping("/custom/chat")
public String customChat() {

OllamaChatOptions customOptions = OllamaChatOptions.builder()
.topP(0.7)
.model("llama3")
.temperature(0.8)
.build();

return chatModel.call(new Prompt(DEFAULT_PROMPT, customOptions)).getResult().getOutput().getText();
}
}

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The new AI service lacks test coverage. While other services in the repository (e.g., customer-service) include tests, this service has no test directory or test cases. According to the coding guidelines, when behavior changes or new code is added, unit tests (and integration tests where feasible) should be included. At minimum, add tests for the controller endpoints to verify correct request handling, response formatting, and error scenarios.

Copilot uses AI. Check for mistakes.
Comment thread CONTRIBUTING.md

- **Entity Keys**:
- For entities that are exposed externally (e.g., via events or APIs), use UUIDs as the primary keys. This ensures global uniqueness and facilitates safe event propagation across distributed systems.
- For entities whose lifecycle and visibility are strictly internal to a single microservice, uaw a numeric ID:

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

Typo: "uaw" should be "use".

Suggested change
- For entities whose lifecycle and visibility are strictly internal to a single microservice, uaw a numeric ID:
- For entities whose lifecycle and visibility are strictly internal to a single microservice, use a numeric ID:

Copilot uses AI. Check for mistakes.
)
.defaultOptions(
OllamaChatOptions.builder()
.topP(0.7)

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

Magic numbers 0.7 for topP are used without explanation. These configuration values should either be extracted to named constants with documentation explaining their purpose, or externalized to application.yml for easier tuning without code changes.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +15
@RestController
@RequestMapping("/client")
public class AiChatClientController {

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The OpenAPI specification defines security requirements (bearerAuth) for the /chat endpoint, but the implemented controllers (AiChatModelController, AiChatClientController) don't implement this specification and have no security annotations. According to the repository's security practices, JWT-based authentication should be implemented and security context should be enforced. Either implement the OpenAPI-defined endpoints with proper security, or update the OpenAPI spec to match the actual implementation.

Copilot uses AI. Check for mistakes.
<groupId>raff.stein</groupId>
<artifactId>ai-api-data</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The ai-core module doesn't depend on platform-core, which provides shared logging, security, and utilities used across all business modules. According to the project architecture, all business modules should depend on platform-core for centralized logging configuration (JSON format with correlation IDs), security (JWT authentication), and common utilities. This module should add a dependency on raff.stein:platform-core.

Suggested change
</dependency>
</dependency>
<dependency>
<groupId>raff.stein</groupId>
<artifactId>platform-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

Copilot uses AI. Check for mistakes.
@@ -69,33 +50,3 @@ The service is event-driven and interacts with the following Kafka topics:


Event payloads follow the schemas defined in `document-event-data`.

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

Significant technical documentation has been removed from the document-service README, including data model descriptions, API endpoint details, and key process workflows. This removal reduces documentation quality and makes it harder for developers to understand how the service works. While simplification is valuable, essential information about the service's capabilities, data model, and workflows should be preserved or clearly referenced.

Suggested change
Event payloads follow the schemas defined in `document-event-data`.
Event payloads follow the schemas defined in `document-event-data`.
## Data Model Overview
The main entities managed by the Document Service are:
- **Document**
- `id` (UUID): Unique identifier for the document.
- `name` (String): Original file name.
- `ownerId` (UUID): Reference to the customer or entity owning the document.
- `createdAt` (Timestamp): Creation time.
- `metadata` (Map<String, String>): Custom metadata fields.
- `versions` (List<DocumentVersion>): List of document versions.
- **DocumentVersion**
- `version` (Integer): Version number.
- `fileUrl` (String): Storage location or URL.
- `uploadedAt` (Timestamp): Upload time.
- `checksum` (String): File integrity hash.
- **AccessLog**
- `id` (UUID): Log entry ID.
- `documentId` (UUID): Document reference.
- `action` (String): Action performed (upload, download, etc.).
- `timestamp` (Timestamp): When the action occurred.
- `userId` (UUID): Who performed the action.
## API Endpoints
The main REST endpoints exposed by the Document Service are:
- `POST /api/documents`
- Upload a new document.
- Request: multipart/form-data (file + metadata)
- Response: Document metadata (JSON)
- `GET /api/documents/{id}`
- Retrieve document metadata by ID.
- Response: Document metadata (JSON)
- `GET /api/documents/{id}/download`
- Download the document file by ID.
- Response: File stream
- `GET /api/documents/{id}/versions`
- List all versions of a document.
- Response: List of DocumentVersion (JSON)
- `POST /api/documents/{id}/metadata`
- Update custom metadata for a document.
- Request: Metadata fields (JSON)
- Response: Updated Document metadata (JSON)
For full details, see the OpenAPI specification in `document-api-data/document-api.yaml`.
## Event Payloads
Event payloads are defined in the `document-event-data` module. Example schemas:
- **document-uploaded**
```yaml
DocumentUploadedEvent:
type: object
properties:
documentId:
type: string
format: uuid
ownerId:
type: string
format: uuid
version:
type: integer
uploadedAt:
type: string
format: date-time
metadata:
type: object
additionalProperties:
type: string
  • file-validated
    FileValidatedEvent:
      type: object
      properties:
        documentId:
          type: string
          format: uuid
        valid:
          type: boolean
        errors:
          type: array
          items:
            type: string

For full event schemas, see business-modules/document-service/document-event-data/document-event.yaml.

Copilot uses AI. Check for mistakes.
Comment thread pom.xml
Comment on lines +277 to +278
<module>business-modules/ai-simple-service/ai-api-data</module>
<module>business-modules/ai-simple-service/ai-core</module>

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The new ai-simple-service module is missing a README.md file. According to the project structure, each service should have its own README documenting the module's purpose, setup instructions, configuration options, and technical details. This is especially important for this module since it uses a different Spring Boot version and has unique dependencies (Ollama, Spring AI) that require specific setup.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants