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

Feature: manage ai endpoint #68

Merged
merged 10 commits into from
Aug 28, 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: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
- name: Build application
run: docker build -t leto-modelizer-api .
- name: Run application
run: docker compose up -d
run: docker compose -f docker-compose-e2e.yml up -d
env:
GITHUB_CLIENT_ID: test
GITHUB_CLIENT_SECRET: test
Expand Down
11 changes: 0 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,6 @@ To run all the application tests (unit and integration), use this command:
./gradlew test -i
```

To run only the application unit tests, use this command:

```shell
./gradlew unitTest -i
```

To run all the application integration tests, use this command:

```shell
./gradlew integrationTest -i
```
## Checkstyle

Before pushing your branch and open/synchronize a pull-request, you have to verify the checkstyle of your application. Here is the command to do so:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ enabling secure and streamlined user authentication.
| CSRF_TOKEN_TIMEOUT | No, default: `3600` | A configuration parameter that specifies the duration (in seconds) for which a Cross-Site Request Forgery (CSRF) token remains valid. This setting is used to prevent CSRF attacks by ensuring that the token used in a client session expires after a certain period, requiring a new token for future requests. |
| USER_SESSION_TIMEOUT | No, default: `3600` | A configuration parameter that defines the time (in seconds) a user's session remains active without any activity. After this period, the user is automatically logged out to help protect against unauthorized access and to manage server resource utilization efficiently. |
| SUPER_ADMINISTRATOR_LOGIN | No | A configuration parameter that defines the username on Github of the SUPER_ADMINISTRATOR. It will create user if it doesn't exist and associate it to the `SUPER_ADMINISTRATOR` role. |
| AI_HOST | No | A configuration parameter that defines the host of the ia server, example: http://localhost:8001/api/. If it's not set, users will not be approve to use ia in application. |

> Notes: `GITHUB_ENTERPRISE_*` variables are only required on self-hosted GitHub.

Expand Down Expand Up @@ -274,6 +275,7 @@ LETO_ADMIN_URL=http://localhost:9000/
LIBRARY_HOST_WHITELIST=https://github.com/ditrit/
CSRF_TOKEN_TIMEOUT=3600
USER_SESSION_TIMEOUT=3600
AI_HOST=http://locahost:8001/api/
```

See Configuration section for more details.
Expand Down
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `GET /api/libraries/templates/[TEMPLATE_ID]/icon`, to get icon of template.
* `GET /api/libraries/templates/[TEMPLATE_ID]/schemas/[INDEX]`, to get schema of template.
* `GET /api/libraries/templates/[TEMPLATE_ID]/files/[INDEX]`, to get file of template.
* For AI:
* `GET /api/ia`, to create diagram with AI.
* `/api/login`, to login.
* `/api/redirect`, to redirect with token on leto-modelizer/leto-modelizer-admin.
54 changes: 54 additions & 0 deletions docker-compose-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
services:
libraries:
image: nginx:latest
ports:
- "8000:80"
volumes:
- ./src/test/resources/libraries:/usr/share/nginx/html:ro
- ./nginx_lib_dev.conf:/etc/nginx/conf.d/default.conf:ro
restart: always
ai:
build: ./src/test/resources/ai
ports:
- "8001:80"
volumes:
- ./src/test/resources/ai:/var/www/html
restart: always
db:
image: cockroachdb/cockroach:v23.1.15
command: start-single-node --certs-dir=/certs --advertise-addr=db
volumes:
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
- ./cockroach-certs:/certs
ports:
- "26257:26257"
- "8081:8080"
restart: always
api:
restart: always
image: leto-modelizer-api:latest
environment:
DATABASE_HOST: db:26257
AI_HOST: http://ai/api/
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
LIBRARY_HOST_WHITELIST: http://libraries/
SUPER_ADMINISTRATOR_LOGIN: ${SUPER_ADMINISTRATOR_LOGIN}
POSTGRES_DB: ${POSTGRES_DB:-leto_db}
POSTGRES_USER: ${POSTGRES_USER:-leto_admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
DATABASE_NAME: ${DATABASE_NAME:-leto_db}
DATABASE_USER: ${DATABASE_USER:-leto_admin}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:-password}
SSL_KEY_STORE: ${SSL_KEY_STORE:-classpath:keystore.jks}
SSL_KEY_STORE_PASSWORD: ${SSL_KEY_STORE_PASSWORD:-password}
SSL_KEY_PASSWORD: ${SSL_KEY_PASSWORD:-password}
LETO_MODELIZER_URL: ${LETO_MODELIZER_URL:-http://localhost:8080/}
LETO_ADMIN_URL: ${LETO_ADMIN_URL:-http://localhost:9000/}
CSRF_TOKEN_TIMEOUT: ${CSRF_TOKEN_TIMEOUT:-3600}
USER_SESSION_TIMEOUT: ${USER_SESSION_TIMEOUT:-3600}

ports:
- "8443:8443"
depends_on:
- db
26 changes: 9 additions & 17 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ services:
- "8000:80"
volumes:
- ./src/test/resources/libraries:/usr/share/nginx/html:ro
- ./nginx_dev.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx_lib_dev.conf:/etc/nginx/conf.d/default.conf:ro
restart: always
ai:
image: leto-modelizer-ai-proxy:latest
ports:
- "8585:8585"
network_mode: "host"
restart: always
db:
image: cockroachdb/cockroach:v23.1.15
Expand All @@ -21,26 +27,12 @@ services:
restart: always
image: leto-modelizer-api:latest
environment:
POSTGRES_DB: ${POSTGRES_DB:-leto_db}
POSTGRES_USER: ${POSTGRES_USER:-leto_admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
DATABASE_HOST: db:26257
DATABASE_NAME: ${DATABASE_NAME:-leto_db}
DATABASE_USER: ${DATABASE_USER:-leto_admin}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:-password}
SSL_KEY_STORE: ${SSL_KEY_STORE:-classpath:keystore.jks}
SSL_KEY_STORE_PASSWORD: ${SSL_KEY_STORE_PASSWORD:-password}
SSL_KEY_PASSWORD: ${SSL_KEY_PASSWORD:-password}
AI_HOST: http://ai/api/
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
LIBRARY_HOST_WHITELIST: http://libraries/
SUPER_ADMINISTRATOR_LOGIN: ${SUPER_ADMINISTRATOR_LOGIN}
LETO_MODELIZER_URL: ${LETO_MODELIZER_URL:-http://localhost:8080/}
LETO_ADMIN_URL: ${LETO_ADMIN_URL:-http://localhost:9000/}
LIBRARY_HOST_WHITELIST: ${LIBRARY_HOST_WHITELIST:-http://libraries/}
CSRF_TOKEN_TIMEOUT: ${CSRF_TOKEN_TIMEOUT:-3600}
USER_SESSION_TIMEOUT: ${USER_SESSION_TIMEOUT:-3600}


ports:
- "8443:8443"
depends_on:
Expand Down
1 change: 1 addition & 0 deletions nginx_dev.conf → nginx_lib_dev.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Nginx configuration to simulate storage server for libraries.
server {
listen 80;
server_name localhost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ditrit.letomodelizerapi.controller.CurrentUserController;
import com.ditrit.letomodelizerapi.controller.GroupController;
import com.ditrit.letomodelizerapi.controller.HomeController;
import com.ditrit.letomodelizerapi.controller.AIController;
import com.ditrit.letomodelizerapi.controller.LibraryController;
import com.ditrit.letomodelizerapi.controller.PermissionController;
import com.ditrit.letomodelizerapi.controller.RoleController;
Expand All @@ -13,7 +14,10 @@
import com.ditrit.letomodelizerapi.controller.handler.ConstraintViolationExceptionHandler;
import com.ditrit.letomodelizerapi.controller.handler.DataIntegrityViolationExceptionHandler;
import com.ditrit.letomodelizerapi.controller.handler.IllegalArgumentExceptionHandler;
import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
Expand All @@ -24,8 +28,10 @@ public class JerseyConfig extends ResourceConfig {

/**
* Default constructor to initialize registered endpoints and filters.
* @param aiHost the host value for ai request, injected from application properties.
*/
public JerseyConfig() {
@Autowired
public JerseyConfig(@Value("${ai.host}") final String aiHost) {
// Filter
// Controller
register(UserController.class);
Expand All @@ -38,6 +44,10 @@ public JerseyConfig() {
register(CsrfController.class);
register(PermissionController.class);

if (StringUtils.isNotBlank(aiHost)) {
register(AIController.class);
}

// Exception handler
register(ApiExceptionHandler.class);
register(ConstraintViolationExceptionHandler.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.ditrit.letomodelizerapi.controller;

import com.ditrit.letomodelizerapi.model.ai.AIRequestRecord;
import com.ditrit.letomodelizerapi.persistence.model.User;
import com.ditrit.letomodelizerapi.service.AIService;
import com.ditrit.letomodelizerapi.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;

/**
* Controller to manage ai endpoint.
*/
@Path("/ai")
@Produces(MediaType.APPLICATION_JSON)
@Controller
@Slf4j
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class AIController {

/**
* Service to manage user.
*/
private UserService userService;

/**
* Service to manage ai request.
*/
private AIService aiService;

/**
* Handles a POST request to initiate an interaction with an Artificial Intelligence (AI) based on the provided
* request details.
* This endpoint accepts an AI request record, which includes the parameters necessary for the AI interaction.
* The method retrieves the user from the session, logs the request details, and forwards the request to the AI
* service. It then constructs and returns a response containing the AI's output.
*
* <p>The method uses the AI service to process the request by the user, generating a JSON response that is returned
* to the client.
* This process allows for dynamic interactions with the AI, facilitating use cases such as querying for
* information, executing commands, or initiating workflows within the application.
*
* @param request the HttpServletRequest, used to access the user's session.
* @param aiRequestRecord the request details for the AI, validated to ensure it meets the expected format.
* @return a Response object containing the AI's response in JSON format, with a status of OK (200).
*/

@POST
public Response requestAI(final @Context HttpServletRequest request,
final @Valid AIRequestRecord aiRequestRecord) throws InterruptedException {
HttpSession session = request.getSession();
User user = userService.getFromSession(session);

log.info("[{}] Received POST request to request AI with {}", user.getLogin(), aiRequestRecord);

String json = aiService.sendRequest(aiRequestRecord);

return Response.status(HttpStatus.CREATED.value())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.entity(json)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ditrit.letomodelizerapi.model.ai;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

/**
* A record representing a request to an Artificial Intelligence (AI) within the application.
* This record includes details about the request such as the plugin involved, the type of request, and
* a description of the request. It is used as a data transfer object to encapsulate the information needed
* by the AI to process user requests.
*
* @param plugin the name of the plugin related to the AI request. Must not be blank.
* @param type the type of the AI request, constrained to specific values such as "diagram" by a pattern
* to ensure that only valid request types are considered.
* @param description a description of the AI request, providing context or additional information. Must not be blank.
*/
public record AIRequestRecord(
@NotBlank String plugin,
@Pattern(regexp = "diagram") String type,
@NotBlank String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Package that contains ai models.
*/
package com.ditrit.letomodelizerapi.model.ai;
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ public enum ErrorType {
/**
* Error to call when a file of library can't be downloaded.
*/
LIBRARY_FILE_DOWNLOAD_ERROR(211, "Library file can't be downloaded", HttpStatus.BAD_REQUEST);
LIBRARY_FILE_DOWNLOAD_ERROR(211, "Library file can't be downloaded", HttpStatus.BAD_REQUEST),

/**
* Error to use when the AI proxy fails to generate code.
*/
AI_GENERATION_ERROR(530, "The AI could not generate a proper response", HttpStatus.INTERNAL_SERVER_ERROR);

/**
* Error code.
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/ditrit/letomodelizerapi/service/AIService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ditrit.letomodelizerapi.service;

import com.ditrit.letomodelizerapi.model.ai.AIRequestRecord;

/**
* Service implementation for interacting with an Artificial Intelligence (AI).
* This class provides concrete implementation for sending requests to an AI, based on user input and specific
* request details encapsulated in an AIRequestRecord.
*/
public interface AIService {

/**
* Sends a request to the Artificial Intelligence (AI) based on the provided user and request record.
* This method constructs the request, sends it to the AI system, and returns the AI response as a String.
*
* @param aiRequest the AIRequestRecord containing details about the request to be sent to the AI.
* @return the response from the AI as a String.
*/
String sendRequest(AIRequestRecord aiRequest);
}
Loading
Loading