This commit is contained in:
Florinda
2025-02-25 11:06:14 +01:00
21 changed files with 781 additions and 71 deletions

View File

@@ -29,7 +29,7 @@
<properties>
<java.version>21</java.version>
<!--<spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>-->
<spring-ai.version>1.0.0-M2</spring-ai.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
<spring-cloud.version>2023.0.3</spring-cloud.version>
</properties>
<dependencies>
@@ -48,6 +48,13 @@
<version>1.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20250107</version>
</dependency>
<!--<dependency>
<groupId>org.springframework.ai</groupId>

View File

@@ -0,0 +1,94 @@
package com.olympus.hermione.client;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AzureOpenApiChatClient implements OlympusChatClient {
private String apiUrl;
private String apiKey;
private int maxOutputTokens;
Logger logger = LoggerFactory.getLogger(AzureOpenApiChatClient.class);
@Override
public void init(String apiUrl, String apiKey, int maxOutputTokens) {
this.apiUrl = apiUrl;
this.apiKey = apiKey;
this.maxOutputTokens = maxOutputTokens;
}
@Override
public OlympusChatClientResponse getChatCompletion(String userInput) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("api-key", apiKey);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("messages", new Object[]{
Map.of("role", "user", "content", userInput)
});
requestBody.put("max_completion_tokens", maxOutputTokens);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.POST, request, String.class);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objectMapper.readTree(response.getBody());
} catch (JsonProcessingException e) {
logger.error(e.getMessage());
return null;
}
OlympusChatClientResponse olympusChatClientResponse = new OlympusChatClientResponse();
olympusChatClientResponse.setContent(extractMessageContent(response.getBody()));
olympusChatClientResponse.setInputTokens(rootNode.path("usage").path("prompt_tokens").asInt());
olympusChatClientResponse.setOutputTokens(rootNode.path("usage").path("completion_tokens").asInt());
olympusChatClientResponse.setTotalTokens(rootNode.path("usage").path("total_tokens").asInt());
return olympusChatClientResponse;
}
public String extractMessageContent(String jsonResponse) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonResponse);
JsonNode choicesNode = rootNode.path("choices");
if (choicesNode.isArray() && choicesNode.size() > 0) {
JsonNode messageNode = choicesNode.get(0).path("message");
JsonNode contentNode = messageNode.path("content");
return contentNode.asText();
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
package com.olympus.hermione.client;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GoogleGeminiChatClient implements OlympusChatClient {
private String apiUrl;
private String apiKey;
private int maxOutputTokens;
Logger logger = LoggerFactory.getLogger(AzureOpenApiChatClient.class);
@Override
public void init(String apiUrl, String apiKey, int maxOutputTokens) {
this.apiUrl = apiUrl;
this.apiKey = apiKey;
this.maxOutputTokens = maxOutputTokens;
}
@Override
public OlympusChatClientResponse getChatCompletion(String userInput) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("parts", new Object[]{Map.of("text", userInput)});
Map<String, Object> conversation = new HashMap<>();
conversation.put("contents", new Object[]{userMessage});
Map<String, Object> generationConfig = new HashMap<>();
/* generationConfig.put("temperature", 1);
generationConfig.put("topK", 40);
generationConfig.put("topP", 0.95);*/
generationConfig.put("maxOutputTokens", maxOutputTokens);
generationConfig.put("responseMimeType", "text/plain");
conversation.put("generationConfig", generationConfig);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(conversation, headers);
RestTemplate restTemplate = new RestTemplate();
String fullUrl = apiUrl + "?key=" + apiKey;
ResponseEntity<String> response = restTemplate.exchange(fullUrl, HttpMethod.POST, request, String.class);
OlympusChatClientResponse olympusChatClientResponse = new OlympusChatClientResponse();
try{
Map<String, Object> responseBody = new ObjectMapper().readValue(response.getBody(), Map.class);
List<Map<String, Object>> candidates = (List<Map<String, Object>>) responseBody.get("candidates");
Map<String, Object> firstCandidate = candidates.get(0);
Map<String, Object> content = (Map<String, Object>) firstCandidate.get("content");
List<Map<String, Object>> parts = (List<Map<String, Object>>) content.get("parts");
String text = (String) parts.get(0).get("text");
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode;
try {
rootNode = objectMapper.readTree(response.getBody());
} catch (JsonProcessingException e) {
logger.error(e.getMessage());
return null;
}
olympusChatClientResponse.setContent(text);
olympusChatClientResponse.setInputTokens(rootNode.path("usageMetadata").path("promptTokenCount").asInt());
olympusChatClientResponse.setOutputTokens(rootNode.path("usageMetadata").path("candidatesTokenCount").asInt());
olympusChatClientResponse.setTotalTokens(rootNode.path("usageMetadata").path("totalTokenCount").asInt());
}catch(Exception e){
logger.error("Error parsing response", e);
return null;
}
return olympusChatClientResponse;
}
}

View File

@@ -0,0 +1,8 @@
package com.olympus.hermione.client;
public interface OlympusChatClient {
public OlympusChatClientResponse getChatCompletion(String userInput);
public void init(String apiUrl, String apiKey, int maxOutputTokens);
}

View File

@@ -0,0 +1,46 @@
package com.olympus.hermione.client;
public class OlympusChatClientResponse {
private String content;
private int inputTokens;
private int outputTokens;
private int totalTokens;
public OlympusChatClientResponse() {
}
public OlympusChatClientResponse(String content, int inputTokens, int outputTokens, int totalTokens) {
this.content = content;
this.inputTokens = inputTokens;
this.outputTokens = outputTokens;
this.totalTokens = totalTokens;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getInputTokens() {
return inputTokens;
}
public void setInputTokens(int inputTokens) {
this.inputTokens = inputTokens;
}
public int getOutputTokens() {
return outputTokens;
}
public void setOutputTokens(int outputTokens) {
this.outputTokens = outputTokens;
}
public int getTotalTokens() {
return totalTokens;
}
public void setTotalTokens(int totalTokens) {
this.totalTokens = totalTokens;
}
}

View File

@@ -0,0 +1,26 @@
package com.olympus.hermione.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAIClientBuilderCustomizer;
import com.azure.core.http.HttpClient;
import com.azure.core.util.HttpClientOptions;
@Configuration
public class AzureAIConfig {
@Bean
public AzureOpenAIClientBuilderCustomizer responseTimeoutCustomizer() {
return openAiClientBuilder -> {
HttpClientOptions clientOptions = new HttpClientOptions()
.setResponseTimeout(Duration.ofMinutes(5));
openAiClientBuilder.httpClient(HttpClient.createDefault(clientOptions));
};
}
}

View File

@@ -43,7 +43,8 @@ public class VectorStoreConfig {
fields.add(AzureVectorStore.MetadataField.text("KsFileSource"));
fields.add(AzureVectorStore.MetadataField.text("KsDocumentId"));
return new AzureVectorStore(searchIndexClient, embeddingModel,initSchema, fields);
//return new AzureVectorStore(searchIndexClient, embeddingModel,initSchema, fields);
return null;
}
}

View File

@@ -21,6 +21,7 @@ public class Scenario {
private String id;
private String name;
private String description;
private String hint;
private String startWithStepId;
private List<ScenarioStep> steps;
private List<ScenarioInputs> inputs;

View File

@@ -1,15 +1,12 @@
package com.olympus.hermione.models;
import java.util.HashMap;
import java.util.Date;
import java.util.HashMap;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import com.olympus.hermione.dto.ScenarioExecutionInput;
import com.olympus.hermione.models.Scenario;
import com.olympus.hermione.security.entity.User;
import lombok.Getter;
import lombok.Setter;
@@ -38,6 +35,6 @@ public class ScenarioExecution {
private Date startDate;
private Date endDate;
private Long usedTokens;
private Integer usedTokens;
private String rating;
}

View File

@@ -41,7 +41,7 @@ public class CanvasExecutionService {
Prompt prompt = new Prompt(List.of(userMessage,systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
canvasOutput.setStringOutput(response.get(0).getOutput().getContent());
canvasOutput.setStringOutput(response.get(0).getOutput().getText());
return canvasOutput;
} else{
logger.error("Input is not correct");
@@ -61,7 +61,7 @@ public class CanvasExecutionService {
Prompt prompt = new Prompt(List.of(userMessage,systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
canvasOutput.setStringOutput(response.get(0).getOutput().getContent());
canvasOutput.setStringOutput(response.get(0).getOutput().getText());
return canvasOutput;
} else{
logger.error("Input is not correct");
@@ -79,7 +79,7 @@ public class CanvasExecutionService {
Prompt prompt = new Prompt(List.of(userMessage,systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
canvasOutput.setStringOutput(response.get(0).getOutput().getContent());
canvasOutput.setStringOutput(response.get(0).getOutput().getText());
return canvasOutput;
} else{
logger.error("Input is not correct");

View File

@@ -48,6 +48,8 @@ import com.olympus.hermione.stepSolvers.AdvancedAIPromptSolver;
import com.olympus.hermione.stepSolvers.SummarizeDocSolver;
import com.olympus.hermione.stepSolvers.BasicAIPromptSolver;
import com.olympus.hermione.stepSolvers.BasicQueryRagSolver;
import com.olympus.hermione.stepSolvers.DeleteDocTempSolver;
import com.olympus.hermione.stepSolvers.EmbeddingDocTempSolver;
import com.olympus.hermione.stepSolvers.QueryNeo4JSolver;
import com.olympus.hermione.stepSolvers.SourceCodeRagSolver;
import com.olympus.hermione.stepSolvers.StepSolver;
@@ -60,6 +62,7 @@ import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.olympus.hermione.stepSolvers.ExternalAgentSolver;
import com.olympus.hermione.stepSolvers.ExternalCodeGenieSolver;
import com.olympus.hermione.stepSolvers.OlynmpusChatClientSolver;
@@ -206,7 +209,7 @@ public class ScenarioExecutionService {
ScenarioStep startStep = steps.stream().filter(step -> step.getStepId().equals(startStepId)).findFirst().orElse(null);
executeScenarioStep(startStep, scenarioExecution);
Long usedTokens = (scenarioExecution.getUsedTokens() != null) ? scenarioExecution.getUsedTokens() : Long.valueOf(0);
Integer usedTokens = (scenarioExecution.getUsedTokens() != null) ? scenarioExecution.getUsedTokens() : 0;
while (scenarioExecution.getNextStepId()!=null) {
ScenarioStep step = steps.stream().filter(s -> s.getStepId().equals(scenarioExecution.getNextStepId())).findFirst().orElse(null);
@@ -215,7 +218,7 @@ public class ScenarioExecutionService {
if (scenarioExecution.getUsedTokens() != null && scenarioExecution.getUsedTokens() != 0) {
usedTokens += scenarioExecution.getUsedTokens();
scenarioExecution.setUsedTokens(Long.valueOf(0)); //resetting value for next step if is not an AI step
scenarioExecution.setUsedTokens(0); //resetting value for next step if is not an AI step
}
if(scenarioExecution.getLatestStepStatus() != null && scenarioExecution.getLatestStepStatus().equals("ERROR")){
@@ -262,6 +265,15 @@ public class ScenarioExecutionService {
case "SUMMARIZE_DOC":
solver = new SummarizeDocSolver();
break;
case "OLYMPUS_QUERY_AI":
solver = new OlynmpusChatClientSolver();
break;
case "EMBED_TEMPORARY_DOC":
solver = new EmbeddingDocTempSolver();
break;
case "DELETE_TEMPORARY_DOC":
solver = new DeleteDocTempSolver();
break;
default:
break;
}
@@ -413,17 +425,19 @@ public class ScenarioExecutionService {
private ChatModel createChatModel(AiModel aiModel){
switch(aiModel.getApiProvider()){
case "AzureOpenAI":
OpenAIClient openAIClient = new OpenAIClientBuilder()
OpenAIClientBuilder openAIClient = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(aiModel.getApiKey()))
.endpoint(aiModel.getEndpoint())
.buildClient();
.endpoint(aiModel.getEndpoint());
AzureOpenAiChatOptions openAIChatOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(aiModel.getModel())
.withTemperature(aiModel.getTemperature())
.withMaxTokens(aiModel.getMaxTokens())
.deploymentName(aiModel.getModel())
.maxTokens(aiModel.getMaxTokens())
.temperature(Double.valueOf(aiModel.getTemperature()))
.build();
AzureOpenAiChatModel azureOpenaichatModel = new AzureOpenAiChatModel(openAIClient, openAIChatOptions);
logger.info("AI model used: " + aiModel.getModel());
return azureOpenaichatModel;
@@ -432,15 +446,32 @@ public class ScenarioExecutionService {
case "OpenAI":
OpenAiApi openAiApi = new OpenAiApi(aiModel.getApiKey());
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.withModel(aiModel.getModel())
.withTemperature(aiModel.getTemperature())
.withMaxTokens(aiModel.getMaxTokens())
.model(aiModel.getModel())
.temperature(Double.valueOf(aiModel.getTemperature()))
.maxTokens(aiModel.getMaxTokens())
.build();
OpenAiChatModel openaichatModel = new OpenAiChatModel(openAiApi,openAiChatOptions);
logger.info("AI model used: " + aiModel.getModel());
return openaichatModel;
case "GoogleGemini":
OpenAIClientBuilder openAIClient2 = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(aiModel.getApiKey()))
.endpoint(aiModel.getEndpoint());
AzureOpenAiChatOptions openAIChatOptions2 = AzureOpenAiChatOptions.builder()
.deploymentName(aiModel.getModel())
.maxTokens(aiModel.getMaxTokens())
.temperature(Double.valueOf(aiModel.getTemperature()))
.build();
AzureOpenAiChatModel azureOpenaichatModel2 = new AzureOpenAiChatModel(openAIClient2, openAIChatOptions2);
logger.info("AI model used : " + aiModel.getModel());
return azureOpenaichatModel2;
default:
throw new IllegalArgumentException("Unsupported AI model: " + aiModel.getName());
}
@@ -476,7 +507,7 @@ public class ScenarioExecutionService {
return result;
}
}
public String updateRating(String id, String rating){
logger.info("updateRating function:");

View File

@@ -6,6 +6,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.olympus.hermione.dto.aientity.CiaOutputEntity;
import com.olympus.hermione.models.ScenarioExecution;
import com.olympus.hermione.utility.AttributeParser;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient.CallResponseSpec;
import org.springframework.ai.chat.messages.Message;
@@ -21,9 +25,9 @@ public class AdvancedAIPromptSolver extends StepSolver {
private boolean qai_load_graph_schema=false;
private String qai_output_entityType;
private String qai_custom_memory_id;
private String qai_available_tools;
Logger logger = (Logger) LoggerFactory.getLogger(BasicQueryRagSolver.class);
Logger logger = (Logger) LoggerFactory.getLogger(AdvancedAIPromptSolver.class);
private void loadParameters(){
logger.info("Loading parameters");
@@ -48,7 +52,8 @@ public class AdvancedAIPromptSolver extends StepSolver {
this.qai_custom_memory_id = (String) this.step.getAttributes().get("qai_custom_memory_id");
//TODO: Add memory ID attribute to have the possibility of multiple conversations
this.qai_available_tools = (String) this.step.getAttributes().get("qai_available_tools");
}
@Override
@@ -59,18 +64,39 @@ public class AdvancedAIPromptSolver extends StepSolver {
this.scenarioExecution.setCurrentStepId(this.step.getStepId());
loadParameters();
String userText = this.qai_user_input;
Message userMessage = new UserMessage(userText);
Message systemMessage = new SystemMessage(this.qai_system_prompt_template);
Object tools = null;
if(this.qai_available_tools!=null && !this.qai_available_tools.isEmpty()){
for(String tool: this.qai_available_tools.split(",")){
logger.info("Tool: " + tool);
if(tool.equals("SourceCodeTool")){
tools = new com.olympus.hermione.tools.SourceCodeTool(discoveryClient);
}
}
}
CallResponseSpec resp=null;
if(tools==null){
logger.info("No tools available");
resp = chatClient.prompt()
.user(this.qai_user_input)
.system(this.qai_system_prompt_template)
.advisors(advisor -> advisor
.param("chat_memory_conversation_id", this.scenarioExecution.getId()+this.qai_custom_memory_id)
.param("chat_memory_response_size", 100))
.call();
}else{
resp = chatClient.prompt()
.user(this.qai_user_input)
.system(this.qai_system_prompt_template)
.advisors(advisor -> advisor
.param("chat_memory_conversation_id", this.scenarioExecution.getId()+this.qai_custom_memory_id)
.param("chat_memory_response_size", 100))
.tools(tools)
.call();
}
CallResponseSpec resp = chatClient.prompt()
.messages(userMessage,systemMessage)
.advisors(advisor -> advisor
.param("chat_memory_conversation_id", this.scenarioExecution.getId()+this.qai_custom_memory_id)
.param("chat_memory_response_size", 100))
.call();
if(qai_output_entityType!=null && qai_output_entityType.equals("CiaOutputEntity")){
logger.info("Output is of type CiaOutputEntity");
@@ -92,7 +118,7 @@ public class AdvancedAIPromptSolver extends StepSolver {
Usage usage = resp.chatResponse().getMetadata().getUsage();
if (usage != null) {
Long usedTokens = usage.getTotalTokens();
Integer usedTokens = usage.getTotalTokens();
this.scenarioExecution.setUsedTokens(usedTokens);
} else {
logger.info("Token usage information is not available.");

View File

@@ -58,14 +58,17 @@ public class BasicAIPromptSolver extends StepSolver {
Message systemMessage = new SystemMessage(this.qai_system_prompt_template);
CallResponseSpec resp = chatClient.prompt()
.messages(userMessage,systemMessage)
.user(this.qai_user_input)
.system(this.qai_system_prompt_template)
//.messages(userMessage,systemMessage)
.call();
String output = resp.content();
Usage usage = resp.chatResponse().getMetadata().getUsage();
if (usage != null) {
Long usedTokens = usage.getTotalTokens();
Integer usedTokens = usage.getTotalTokens();
this.scenarioExecution.setUsedTokens(usedTokens);
} else {
logger.info("Token usage information is not available.");

View File

@@ -6,8 +6,7 @@ import java.util.ArrayList;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SearchRequest.Builder;
import com.olympus.hermione.models.ScenarioExecution;
import com.olympus.hermione.utility.AttributeParser;
@@ -72,22 +71,29 @@ public class BasicQueryRagSolver extends StepSolver {
loadParameters();
logParameters();
SearchRequest request = SearchRequest.defaults()
.withQuery(this.query)
.withTopK(this.topk)
.withSimilarityThreshold(this.threshold);
Builder request_builder = SearchRequest.builder()
.query(this.query)
.topK(this.topk)
.similarityThreshold(this.threshold);
if(this.rag_filter != null && !this.rag_filter.isEmpty()){
request.withFilterExpression(this.rag_filter);
request_builder.filterExpression(this.rag_filter);
logger.info("Using Filter expression: " + this.rag_filter);
}
SearchRequest request = request_builder.build();
List<Document> docs = this.vectorStore.similaritySearch(request);
logger.info("Number of VDB retrieved documents: " + docs.size());
List<String> result = new ArrayList<String>();
for (Document doc : docs) {
result.add(doc.getContent());
result.add(doc.getText());
}
//concatenate the content of the results into a single string

View File

@@ -0,0 +1,82 @@
package com.olympus.hermione.stepSolvers;
import ch.qos.logback.classic.Logger;
import com.olympus.hermione.models.ScenarioExecution;
import com.olympus.hermione.utility.AttributeParser;
import java.util.List;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.filter.Filter;
public class DeleteDocTempSolver extends StepSolver {
private String rag_filter;
private int topk;
private double threshold;
private String query;
Logger logger = (Logger) LoggerFactory.getLogger(BasicQueryRagSolver.class);
private void loadParameters(){
logger.info("Loading parameters");
this.scenarioExecution.getExecSharedMap().put("scenario_execution_id", this.scenarioExecution.getId());
AttributeParser attributeParser = new AttributeParser(this.scenarioExecution);
// this.scenario_execution_id = attributeParser.parse((String) this.scenarioExecution.getId());
this.rag_filter = attributeParser.parse((String) this.step.getAttributes().get("rag_filter"));
if(this.step.getAttributes().containsKey("rag_query")){
this.query = (String) this.step.getAttributes().get("rag_query");
}else{
this.query = "*";
}
if(this.step.getAttributes().containsKey("rag_topk")){
this.topk = (int) this.step.getAttributes().get("rag_topk");
}else{
this.topk = 1000;
}
if(this.step.getAttributes().containsKey("rag_threshold")){
this.threshold = (double) this.step.getAttributes().get("rag_threshold");
}else{
this.threshold = 0.0;
}
}
@Override
public ScenarioExecution solveStep(){
System.out.println("Solving step: " + this.step.getName());
this.scenarioExecution.setCurrentStepId(this.step.getStepId());
loadParameters();
vectorStore.delete(rag_filter);
/*SearchRequest searchRequest = SearchRequest.defaults()
.withQuery(this.query)
.withTopK(this.topk)
.withSimilarityThreshold(this.threshold)
.withFilterExpression(this.rag_filter);
List<Document> docs = vectorStore.similaritySearch(searchRequest);
List<String> ids = docs.stream().map(Document::getId).toList();
vectorStore.delete(ids);*/
this.scenarioExecution.setNextStepId(this.step.getNextStepId());
return this.scenarioExecution;
}
}

View File

@@ -0,0 +1,106 @@
package com.olympus.hermione.stepSolvers;
import ch.qos.logback.classic.Logger;
import com.olympus.hermione.models.ScenarioExecution;
import com.olympus.hermione.utility.AttributeParser;
import java.io.File;
import java.util.Collections;
import java.util.List;
import org.apache.tika.Tika;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
public class EmbeddingDocTempSolver extends StepSolver {
private String scenario_execution_id;
private String path_file;
private int default_chunk_size;
private int min_chunk_size;
private int min_chunk_length_to_embed;
private int max_num_chunks;
Logger logger = (Logger) LoggerFactory.getLogger(BasicQueryRagSolver.class);
private void loadParameters(){
logger.info("Loading parameters");
this.scenarioExecution.getExecSharedMap().put("scenario_execution_id", this.scenarioExecution.getId());
logger.info("Scenario Execution ID: "+this.scenarioExecution.getId());
AttributeParser attributeParser = new AttributeParser(this.scenarioExecution);
this.scenario_execution_id = attributeParser.parse((String) this.scenarioExecution.getId());
this.path_file = attributeParser.parse((String) this.step.getAttributes().get("path_file"));
if(this.step.getAttributes().containsKey("default_chunk_size")){
this.default_chunk_size = (int) this.step.getAttributes().get("default_chunk_size");
}else{
this.default_chunk_size = 8000;
}
if(this.step.getAttributes().containsKey("min_chunk_size")){
this.min_chunk_size = (int) this.step.getAttributes().get("min_chunk_size");
}else{
this.min_chunk_size = 50;
}
if(this.step.getAttributes().containsKey("min_chunk_length_to_embed")){
this.min_chunk_length_to_embed = (int) this.step.getAttributes().get("min_chunk_length_to_embed");
}else{
this.min_chunk_length_to_embed = 50;
}
if(this.step.getAttributes().containsKey("max_num_chunks")){
this.max_num_chunks = (int) this.step.getAttributes().get("max_num_chunks");
}else{
this.max_num_chunks = 1000;
}
}
@Override
public ScenarioExecution solveStep(){
try{
logger.info("Solving step: " + this.step.getName());
this.scenarioExecution.setCurrentStepId(this.step.getStepId());
loadParameters();
logger.info("Embedding documents");
File file = new File(this.path_file);
Tika tika = new Tika();
tika.setMaxStringLength(-1);
String text = tika.parseToString(file);
Document myDoc = new Document(text);
List<Document> docs = Collections.singletonList(myDoc);
TokenTextSplitter splitter = new TokenTextSplitter(this.default_chunk_size,
this.min_chunk_size,
this.min_chunk_length_to_embed,
this.max_num_chunks,
true);
docs.forEach(doc -> {
List<Document> splitDocs = splitter.split(doc);
logger.info("Number of documents: " + splitDocs.size());
splitDocs.forEach(splitDoc -> {
splitDoc.getMetadata().put("KsScenarioExecutionId", this.scenario_execution_id);
});
vectorStore.add(splitDocs);
});
}catch (Exception e){
logger.error("Error while solvingStep: "+e.getMessage());
e.printStackTrace();
}
this.scenarioExecution.setNextStepId(this.step.getNextStepId());
return this.scenarioExecution;
}
}

View File

@@ -0,0 +1,81 @@
package com.olympus.hermione.stepSolvers;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient.CallResponseSpec;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.metadata.Usage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.olympus.hermione.client.AzureOpenApiChatClient;
import com.olympus.hermione.client.GoogleGeminiChatClient;
import com.olympus.hermione.client.OlympusChatClient;
import com.olympus.hermione.client.OlympusChatClientResponse;
import com.olympus.hermione.dto.aientity.CiaOutputEntity;
import com.olympus.hermione.models.ScenarioExecution;
import com.olympus.hermione.utility.AttributeParser;
import ch.qos.logback.classic.Logger;
public class OlynmpusChatClientSolver extends StepSolver{
private String qai_system_prompt_template;
private String qai_user_input;
private String qai_output_variable;
Logger logger = (Logger) LoggerFactory.getLogger(BasicQueryRagSolver.class);
private void loadParameters(){
logger.info("Loading parameters");
AttributeParser attributeParser = new AttributeParser(this.scenarioExecution);
this.qai_system_prompt_template = attributeParser.parse((String) this.step.getAttributes().get("qai_system_prompt_template"));
this.qai_user_input = attributeParser.parse((String)this.step.getAttributes().get("qai_user_input"));
this.qai_output_variable = (String) this.step.getAttributes().get("qai_output_variable");
}
@Override
public ScenarioExecution solveStep(){
System.out.println("Solving step: " + this.step.getName());
this.scenarioExecution.setCurrentStepId(this.step.getStepId());
loadParameters();
OlympusChatClient chatClient = null;
if ( scenarioExecution.getScenario().getAiModel().getApiProvider().equals("AzureOpenAI")) {
chatClient = new AzureOpenApiChatClient();
}
if ( scenarioExecution.getScenario().getAiModel().getApiProvider().equals("GoogleGemini")) {
chatClient = new GoogleGeminiChatClient();
}
chatClient.init(scenarioExecution.getScenario().getAiModel().getEndpoint(),
scenarioExecution.getScenario().getAiModel().getApiKey(),
scenarioExecution.getScenario().getAiModel().getMaxTokens());
String userText = this.qai_user_input;
String systemText = this.qai_system_prompt_template;
OlympusChatClientResponse resp = chatClient.getChatCompletion(systemText +"\n"+ userText);
String output = resp.getContent();
this.scenarioExecution.getExecSharedMap().put(this.qai_output_variable, output);
this.scenarioExecution.setUsedTokens(resp.getTotalTokens());
this.scenarioExecution.setNextStepId(this.step.getNextStepId());
return this.scenarioExecution;
}
}

View File

@@ -163,7 +163,7 @@ public class SummarizeDocSolver extends StepSolver {
// Gestione del conteggio token usati
Usage usage = resp.chatResponse().getMetadata().getUsage();
if (usage != null) {
Long usedTokens = usage.getTotalTokens();
Integer usedTokens = usage.getTotalTokens();
this.scenarioExecution.setUsedTokens(usedTokens);
} else {
logger.info("Token usage information is not available.");

View File

@@ -0,0 +1,20 @@
package com.olympus.hermione.tools;
import java.time.LocalDateTime;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
public class SimpleDateTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
System.out.println("ChatGPT sta cxhiedendo Getting current date and time");
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}

View File

@@ -0,0 +1,69 @@
package com.olympus.hermione.tools;
import java.time.LocalDateTime;
import java.util.logging.Logger;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import com.olympus.dto.CodeRagResponse;
import com.olympus.dto.SimilaritySearchCodeInput;
public class SourceCodeTool {
private DiscoveryClient discoveryClient;
Logger logger = Logger.getLogger(SourceCodeTool.class.getName());
public SourceCodeTool(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Tool(description = "Get the source code of a class by fully qualified name")
public String getClassCodeByFullyQualifidClassName(String fullyQualifiedClassName) {
logger.info("[TOOL]LLM SourceCodeTool Getting source code for class: " + fullyQualifiedClassName);
ServiceInstance serviceInstance = discoveryClient.getInstances("source-code-module").get(0);
RestTemplate restTemplate = new RestTemplate();
String sourceCodeResponse =
restTemplate.getForEntity(serviceInstance.getUri() + "/getClassDetailedInfo?id="+fullyQualifiedClassName,String.class ).getBody();
return sourceCodeResponse;
}
@Tool(description = "Retrieve the source code that match concept with natural language query ")
public String getCodeBySimilarity(String query) {
logger.info("[TOOL]LLM SourceCodeTool Getting source code for class: " + query);
SimilaritySearchCodeInput similaritySearchCodeInput = new SimilaritySearchCodeInput();
similaritySearchCodeInput.setQuery(query);
similaritySearchCodeInput.setTopK("3");
similaritySearchCodeInput.setSimilarityThreshold("0.7");
similaritySearchCodeInput.setFilterExpression("");
ServiceInstance serviceInstance = discoveryClient.getInstances("source-code-module").get(0);
RestTemplate restTemplate = new RestTemplate();
CodeRagResponse[] ragresponse =
restTemplate.postForEntity(serviceInstance.getUri() + "/similarity-search-code",
similaritySearchCodeInput,CodeRagResponse[].class ).getBody();
String code="";
for (CodeRagResponse codeRagResponse : ragresponse) {
code += "SOURCE CODE OF "+codeRagResponse.getCodeType()+" : " + codeRagResponse.getFullyQualifiedName() +" \n";
code += codeRagResponse.getCode() + "\n";
code += "-----------------------------------\n";
}
return code;
}
}

View File

@@ -1,71 +1,77 @@
spring.application.name=hermione
server.port=8081
# spring.data.mongodb.uri=mongodb+srv://olympus_adm:26111979@olympus.l6qor4p.mongodb.net/?retryWrites=true&w=majority&appName=Olympus
# spring.data.mongodb.database=olympus
# spring.data.mongodb.username=olympus_adm
# spring.data.mongodb.password=26111979
spring.data.mongodb.uri=mongodb+srv://olympusadmin:Camilla123!@db-olympus.global.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000
spring.data.mongodb.database=olympus
spring.data.mongodb.username=olympusadmin
spring.data.mongodb.password=Camilla123!
spring.ai.vectorstore.mongodb.indexName=vector_index
spring.ai.vectorstore.mongodb.collection-name=vector_store
spring.ai.vectorstore.mongodb.initialize-schema=false
spring.ai.vectorstore.azure.api-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
spring.ai.vectorstore.azure.url=https://search-olympus.search.windows.net_old
spring.ai.vectorstore.azure.initialize-schema =false
spring.ai.openai.api-key=sk-proj-j3TFJ0h348DIzMrYYfyUT3BlbkFJjk4HMc8A2ux2Asg8Y7H1
spring.ai.openai.chat.options.model=gpt-4o-mini
spring.main.allow-circular-references=true
spring.ai.azure.openai.api-key=4eHwvw6h7vHxTmI2870cR3EpEBs5L9sXZabr9nz37y39TXtk0xY5JQQJ99AKAC5RqLJXJ3w3AAABACOGLdow
spring.ai.azure.openai.endpoint=https://ai-olympus-new.openai.azure.com/
#spring.ai.azure.openai.api-key=9fb33cc69d914d4c8225b974876510b5
#spring.ai.azure.openai.endpoint=https://ai-olympus.openai.azure.com/
spring.ai.azure.openai.chat.options.deployment-name=gpt-4o-mini
spring.ai.azure.openai.chat.options.temperature=0.7
# neo4j.uri=neo4j+s://e17e6f08.databases.neo4j.io:7687
# neo4j.username=neo4j
# neo4j.password=8SrSqQ3q6q9PQNWtN9ozqSQfGce4lfh_n6kKz2JIubQ
neo4j.uri=bolt://57.153.162.67:7687
neo4j.username=neo4j
neo4j.password=01J5h56IsTyyKt
# spring.neo4j.uri=neo4j+s://e17e6f08.databases.neo4j.io:7687
# spring.neo4j.authentication.username=neo4j
# spring.neo4j.authentication.password=8SrSqQ3q6q9PQNWtN9ozqSQfGce4lfh_n6kKz2JIubQ
spring.neo4j.uri=bolt://57.153.162.67:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=01J5h56IsTyyKt
spring.main.allow-bean-definition-overriding=true
logging.level.org.springframework.ai.chat.client.advisor=INFO
eureka.client.serviceUrl.defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
eureka.instance.preferIpAddress: true
hermione.fe.url = http://localhost:5173
java-parser-module.url: http://java-parser-module-service.olympus.svc.cluster.local:8080
java-re-module.url: http://java-re-module-service.olympus.svc.cluster.local:8080
jsp-parser-module.url: http://jsp-parser-module-service.olympus.svc.cluster.local:8080
spring.ai.vectorstore.chroma.client.host=http://108.142.74.161
spring.ai.vectorstore.chroma.client.port=8000
spring.ai.vectorstore.chroma.client.key-token=tKAJfN1Yv5lP7pKorJHGfHMQhNEcM9uu
spring.ai.vectorstore.chroma.initialize-schema=true
spring.ai.vectorstore.chroma.collection-name=olympus
file.upload-dir=/mnt/hermione_storage/documents/file_input_scenarios/
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
file.upload-dir=/mnt/hermione_storage/documents/file_input_scenarios/
generic-file-parser-module.url=http://generic-file-parser-module-service.olympus.svc.cluster.local:8080
java-parser-module.url: http://java-parser-module-service.olympus.svc.cluster.local:8080
java-re-module.url: http://java-re-module-service.olympus.svc.cluster.local:8080
jsp-parser-module.url: http://jsp-parser-module-service.olympus.svc.cluster.local:8080