setup cluster session

This commit is contained in:
Ralph Soika 2025-06-01 11:19:57 +02:00
parent a6099e8004
commit 9e1e8dcf74
8 changed files with 121 additions and 73 deletions

4
.vscode/launch.json vendored
View file

@ -3,11 +3,11 @@
"configurations": [ "configurations": [
{ {
"type": "java", "type": "java",
"name": "Debug hai", "name": "Debug Dynamixs-AI",
"request": "attach", "request": "attach",
"hostName": "localhost", "hostName": "localhost",
"port": "8787", "port": "8787",
"projectName": "hai-office-demo" "projectName": "dynamixs-ai"
} }
] ]
} }

View file

@ -26,7 +26,7 @@ services:
LANG: "en_US.UTF-8" LANG: "en_US.UTF-8"
MAILGATEWAY: "localhost" MAILGATEWAY: "localhost"
ASYNCEVENT_PROCESSOR_ENABLED: "true" ASYNCEVENT_PROCESSOR_ENABLED: "true"
ASYNCEVENT_PROCESSOR_INITIALDELAY: "10000" ASYNCEVENT_PROCESSOR_INITIALDELAY: "30000"
OCR_SERVICE_ENDPOINT: "http://tika:9998/tika" OCR_SERVICE_ENDPOINT: "http://tika:9998/tika"
OCR_SERVICE_MODE: "MODEL" OCR_SERVICE_MODE: "MODEL"
#OCR_STRATEGY: "NO_OCR, OCR_ONLY, OCR_AND_TEXT_EXTRACTION, AUTO (default)" #OCR_STRATEGY: "NO_OCR, OCR_ONLY, OCR_AND_TEXT_EXTRACTION, AUTO (default)"

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
bean-discovery-mode="all" version="3.0">
</beans>

View file

@ -33,25 +33,25 @@ import java.util.logging.Logger;
import org.imixs.workflow.engine.SetupEvent; import org.imixs.workflow.engine.SetupEvent;
import org.imixs.workflow.office.config.AccessRoleController; import org.imixs.workflow.office.config.AccessRoleController;
import org.imixs.workflow.office.config.PropertiesConfigSource; import org.imixs.workflow.office.config.PropertiesConfigSource;
import org.imixs.workflow.services.rest.RestAPIException;
import ai.dynamixs.rag.ClusterException;
import ai.dynamixs.rag.ClusterService; import ai.dynamixs.rag.ClusterService;
import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource;
import jakarta.annotation.security.DeclareRoles; import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.ejb.Stateless; import jakarta.ejb.Stateless;
import jakarta.ejb.Timeout;
import jakarta.ejb.Timer;
import jakarta.ejb.TimerConfig;
import jakarta.ejb.TimerService;
import jakarta.enterprise.event.Observes; import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject; import jakarta.inject.Inject;
/** /**
* This SetupController extends the ConfigController and holds the data from the * This DynamixsSetupService reacts on the
* configuration entity 'BASIC'. This is the general configuration entity used * 'org.imixs.workflow.engine.SetupEvent' and initializes a Cassandra Session.
* by Imixs-Office-Workflow. * If the connection can not be established immediately the method retries a
* <p> * connection every 10 seconds.
* The item 'properties' managed by the 'BASIC' configuration entity is also
* used by the MartyConfigSource to provide optional config parameters.
* <p>
* Since version 4.5.1 the SetupController provides a list of fixed UserGroups.
* *
* @see AccessRoleController * @see AccessRoleController
* *
@ -69,32 +69,42 @@ import jakarta.inject.Inject;
public class DynamixsSetupService { public class DynamixsSetupService {
private static Logger logger = Logger.getLogger(DynamixsSetupService.class.getName()); private static Logger logger = Logger.getLogger(DynamixsSetupService.class.getName());
private static final long serialVersionUID = 1L;
@Inject @Inject
ClusterService clusterService; ClusterService clusterService;
/** @Resource
* Create a rest client instance private TimerService timerService;
*/
@PostConstruct
public void init() {
}
/** /**
* This method verifies the schema of the cassandra database * Try to connect to Cassandra
* <p>
*
* *
* @param setupEvent * @param setupEvent
* @throws RestAPIException
*/ */
public void setup(@Observes SetupEvent setupEvent) throws RestAPIException { public void setup(@Observes SetupEvent setupEvent) {
scheduleClusterCheck();
logger.log(Level.INFO, "├── verify cassandra database..."); }
/**
* Versucht, die Session herzustellen. Bei Fehler wird ein neuer Timer geplant.
*/
private void scheduleClusterCheck() {
try {
logger.log(Level.INFO, "├── Connecting Cassandra cluster...");
clusterService.getSession(); clusterService.getSession();
logger.log(Level.INFO, "├── ✅ Successfully connected to Cassandra!");
} catch (ClusterException e) {
logger.log(Level.WARNING, "├── " + e.getMessage());
logger.log(Level.WARNING, "├── ⚠️ Cluster not ready yet. Retrying in 10 seconds...");
// retry in 10 sec
timerService.createSingleActionTimer(10000, new TimerConfig());
}
} }
/**
* called by timer. Tries to connect cassandra clauster
*/
@Timeout
public void retryClusterCheck(Timer timer) {
scheduleClusterCheck(); // retry Check
}
} }

View file

@ -46,6 +46,7 @@ public class ClusterException extends Exception {
public static final String INVALID_DOCUMENT_OBJECT = "INVALID_DOCUMENT_OBJECT"; public static final String INVALID_DOCUMENT_OBJECT = "INVALID_DOCUMENT_OBJECT";
public static final String INVALID_KEYSPACE = "INVALID_KEYSPACE"; public static final String INVALID_KEYSPACE = "INVALID_KEYSPACE";
public static final String CLUSTER_ERROR = "CLUSTER_ERROR";
public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; public static final String INVALID_WORKITEM = "INVALID_WORKITEM";
public static final String MD5_ERROR = "MD5_ERROR"; public static final String MD5_ERROR = "MD5_ERROR";

View file

@ -15,7 +15,6 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import jakarta.ejb.Singleton; import jakarta.ejb.Singleton;
import jakarta.inject.Inject; import jakarta.inject.Inject;
@ -108,19 +107,25 @@ public class ClusterService {
@ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPASSWORD) @ConfigProperty(name = ENV_EMBEDDINGS_CLUSTER_SSL_KEYSTOREPASSWORD)
Optional<String> keystorePwd; Optional<String> keystorePwd;
// private Cluster cluster;
private CqlSession session; private CqlSession session;
@PostConstruct /**
private void init() { * This method tries to initalize the cassandara session object.
try { *
// cluster = initCluster(); * Note: the initialization is only successful if the cassandra cluster is fully
* started.
*
* @throws ClusterException
*/
private void init() throws ClusterException {
logger.info("├── initializing cluster...");
session = initSession(); session = initSession();
if (session != null) {
logger.info("│ ├── keySpace connection status = OK");
initKeySpace(); initKeySpace();
} catch (ClusterException e) { logger.info("├── ✅ initializing cluster successful.");
logger.severe("Failed to init cassandra session!");
e.printStackTrace();
} }
} }
@PreDestroy @PreDestroy
@ -129,12 +134,23 @@ public class ClusterService {
if (session != null) { if (session != null) {
session.close(); session.close();
} }
} }
public CqlSession getSession() { /**
* Returns the Cassandra Session object. If the session is not yet created, the
* method tries to init the session the first time. If initialization failed the
* method throws a ClusterException
*
* @return
*/
public CqlSession getSession() throws ClusterException {
if (session == null) { if (session == null) {
try {
init(); init();
} catch (Exception e) {
throw new ClusterException(ClusterException.CLUSTER_ERROR,
"Failed to init cassandra session: " + e.getMessage(), e);
}
} }
return session; return session;
} }
@ -156,20 +172,16 @@ public class ClusterService {
throw new ClusterException(ClusterException.INVALID_KEYSPACE, throw new ClusterException(ClusterException.INVALID_KEYSPACE,
"keyspace '" + keySpacename + "' name invalid."); "keyspace '" + keySpacename + "' name invalid.");
} }
logger.info("├── initializing cluster...");
logger.info("│ ├── connecting keyspace='" + keySpacename + "'..."); logger.info("│ ├── connecting keyspace='" + keySpacename + "'...");
// try to open keySpace // try to open keySpace
try { try {
// session = cluster.connect(keySpace.get()); // session = cluster.connect(keySpace.get());
} catch (InvalidQueryException e) { } catch (InvalidQueryException e) {
logger.warning("......connecting keySpace '" + keySpacename + "' failed: " + e.getMessage()); logger.warning("......connecting keySpace '" + keySpacename + "' failed: " + e.getMessage());
createKeySpace(keySpacename); createKeySpace(keySpacename);
} }
if (session != null) {
logger.info("│ ├── keySpace connection status = OK");
}
// return session;
} }
/** /**
@ -190,7 +202,7 @@ public class ClusterService {
"missing cluster contact points - verify configuration!"); "missing cluster contact points - verify configuration!");
} }
logger.info("│ ├── cluster conecting: " + contactPoint.get()); logger.info("│ ├── cluster connecting: " + contactPoint.get());
CqlSessionBuilder builder = CqlSession.builder(); CqlSessionBuilder builder = CqlSession.builder();
String[] hosts = contactPoint.get().split(","); String[] hosts = contactPoint.get().split(",");
@ -333,7 +345,7 @@ public class ClusterService {
session.execute(statement); session.execute(statement);
logger.info("│ ├── keyspace created..."); logger.info("│ ├── keyspace created...");
if (session != null) { if (session != null) {
logger.info("│ ├── create scheam..."); logger.info("│ ├── create schema...");
// now create table schemas // now create table schemas
createArchiveTableSchema(); createArchiveTableSchema();
} }

View file

@ -34,6 +34,8 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.ai.workflow.ImixsAIPromptEvent;
import org.imixs.ai.workflow.OpenAIAPIAdapter;
import org.imixs.ai.workflow.OpenAIAPIService; import org.imixs.ai.workflow.OpenAIAPIService;
import org.imixs.workflow.ItemCollection; import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.SignalAdapter; import org.imixs.workflow.SignalAdapter;
@ -41,6 +43,7 @@ import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AdapterException; import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.PluginException;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject; import jakarta.inject.Inject;
/** /**
@ -117,6 +120,9 @@ public class RAGAdapter implements SignalAdapter {
@Inject @Inject
private ClusterService clusterService; private ClusterService clusterService;
@Inject
private Event<ImixsAIPromptEvent> llmPromptEventObservers = null;
/** /**
* Default Constructor * Default Constructor
*/ */
@ -167,9 +173,37 @@ public class RAGAdapter implements SignalAdapter {
if (llmIndexDefinitions != null && llmIndexDefinitions.size() > 0) { if (llmIndexDefinitions != null && llmIndexDefinitions.size() > 0) {
for (ItemCollection indexDefinition : llmIndexDefinitions) { for (ItemCollection indexDefinition : llmIndexDefinitions) {
String llmText = indexDefinition.getItemValueString("text"); llmAPIEndpoint = parseLLMEndpointByBPMN(indexDefinition);
logger.info("POST index text: " + llmText); // do we have a valid endpoint?
float[] indexResult = llmService.processRAGIndex(llmText, llmAPIEndpoint); if (llmAPIEndpoint == null || llmAPIEndpoint.isEmpty()) {
throw new PluginException(OpenAIAPIAdapter.class.getSimpleName(), API_ERROR,
"imixs-ai configuration error: no llm service endpoint defined!");
}
if ("true".equalsIgnoreCase(indexDefinition.getItemValueString("debug"))) {
llmAPIDebug = true;
}
logger.info("post Llama-cpp request: " + llmAPIEndpoint);
String promptTemplate = llmService.loadPromptTemplate(event);
logger.info("===> PromptTemplate: ");
logger.info(promptTemplate);
String llmPrompt = llmService.buildPrompt(promptTemplate, workitem);
if (llmPrompt.isBlank()) {
throw new PluginException(
RAGAdapter.class.getSimpleName(), API_ERROR,
"Unable to parse prompt for RAG indexing");
}
if (llmAPIDebug) {
logger.info("===> Total Prompt Length = " + llmPrompt.length());
logger.info("===> Prompt: ");
logger.info(llmPrompt);
}
float[] indexResult = llmService.postEmbedding(llmPrompt, llmAPIEndpoint);
// clusterService.insertVector // clusterService.insertVector
logger.info("POST result: " + indexResult); logger.info("POST result: " + indexResult);

View file

@ -2062,7 +2062,7 @@ result.isValid=true;
<bpmn2:sequenceFlow id="sequenceFlow_ZsCUdg" sourceRef="IntermediateCatchEvent_3" targetRef="Task_2"> <bpmn2:sequenceFlow id="sequenceFlow_ZsCUdg" sourceRef="IntermediateCatchEvent_3" targetRef="Task_2">
<bpmn2:documentation id="documentation_hziJNw"/> <bpmn2:documentation id="documentation_hziJNw"/>
</bpmn2:sequenceFlow> </bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_qTIbqA" imixs:activityid="50" name="RAG Test"> <bpmn2:intermediateCatchEvent id="event_qTIbqA" imixs:activityid="50" name="RAG Test2">
<bpmn2:extensionElements> <bpmn2:extensionElements>
<imixs:item name="keylogtimeformat" type="xs:string"> <imixs:item name="keylogtimeformat" type="xs:string">
<imixs:value><![CDATA[2]]></imixs:value> <imixs:value><![CDATA[2]]></imixs:value>
@ -2133,26 +2133,26 @@ result.isValid=true;
<imixs:value>false</imixs:value> <imixs:value>false</imixs:value>
</imixs:item> </imixs:item>
</bpmn2:extensionElements> </bpmn2:extensionElements>
<bpmn2:documentation id="documentation_405Wvg"><![CDATA[Zwischenspeichern]]></bpmn2:documentation> <bpmn2:documentation id="documentation_405Wvg"><![CDATA[RAG Test]]></bpmn2:documentation>
<bpmn2:outgoing>sequenceFlow_08JtcA</bpmn2:outgoing> <bpmn2:outgoing>sequenceFlow_08JtcA</bpmn2:outgoing>
<bpmn2:signalEventDefinition id="signalEventDefinition_OmSXlg" signalRef="signal_4"/> <bpmn2:signalEventDefinition id="signalEventDefinition_OmSXlg" signalRef="signal_4"/>
</bpmn2:intermediateCatchEvent> </bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_08JtcA" sourceRef="event_qTIbqA" targetRef="Task_2"> <bpmn2:sequenceFlow id="sequenceFlow_08JtcA" sourceRef="event_qTIbqA" targetRef="Task_2">
<bpmn2:documentation id="documentation_mnWPUQ"/> <bpmn2:documentation id="documentation_mnWPUQ"/>
</bpmn2:sequenceFlow> </bpmn2:sequenceFlow>
<bpmn2:dataObject id="dataObject_axJA7Q" name="Data Object-4"> <bpmn2:dataObject id="dataObject_axJA7Q" imixs:datatype="prompt-data" name="Data Object-4">
<bpmn2:documentation id="documentation_3VwL8w"><![CDATA[<?xml version="1.0" encoding="UTF-8"?> <bpmn2:documentation id="documentation_3VwL8w"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<IndexDefinition> <PromptDefinition>
<index_options>{"n_predict": 4096, "temperature": 0}</index_options> <prompt_options>{}</prompt_options>
<![CDATA[<text><itemvalue>$workflowgroup</itemvalue>: <itemvalue>$workflowstatus</itemvalue> <prompt><![CDATA[<itemvalue>$workflowgroup</itemvalue>: <itemvalue>$workflowstatus</itemvalue>
# Summary # Summary
<itemvalue>$workflowsummary</itemvalue> <itemvalue>$workflowsummary</itemvalue>
<itemvalue>offer.summary</itemvalue> <itemvalue>offer.summary</itemvalue>
]]]]><![CDATA[>
]]]]><![CDATA[></text> </prompt>
</IndexDefinition> ]]></bpmn2:documentation> </PromptDefinition> ]]></bpmn2:documentation>
</bpmn2:dataObject> </bpmn2:dataObject>
<bpmn2:association id="association_JGXYyg" sourceRef="event_qTIbqA" targetRef="dataObject_axJA7Q"> <bpmn2:association id="association_JGXYyg" sourceRef="event_qTIbqA" targetRef="dataObject_axJA7Q">
<bpmn2:documentation id="documentation_mvyBDQ"/> <bpmn2:documentation id="documentation_mvyBDQ"/>
@ -2710,8 +2710,6 @@ result.isValid=true;
<bpmndi:BPMNEdge bpmnElement="Association_4" id="BPMNEdge_Association_4" sourceElement="BPMNShape_DataObject_2" targetElement="BPMNShape_Task_2"> <bpmndi:BPMNEdge bpmnElement="Association_4" id="BPMNEdge_Association_4" sourceElement="BPMNShape_DataObject_2" targetElement="BPMNShape_Task_2">
<bpmndi:BPMNLabel id="BPMNLabel_95"/> <bpmndi:BPMNLabel id="BPMNLabel_95"/>
<di:waypoint x="248.0" y="855.0"/> <di:waypoint x="248.0" y="855.0"/>
<di:waypoint x="248.0" y="855.0"/>
<di:waypoint x="248.0" y="855.0"/>
<di:waypoint x="248.0" y="636.0"/> <di:waypoint x="248.0" y="636.0"/>
<di:waypoint x="310.0" y="636.0"/> <di:waypoint x="310.0" y="636.0"/>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
@ -2808,7 +2806,6 @@ result.isValid=true;
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="association_JGXYyg" id="BPMNEdge_bHPaEg" sourceElement="BPMNShape_0OS0TQ" targetElement="BPMNShape_3PbNFQ"> <bpmndi:BPMNEdge bpmnElement="association_JGXYyg" id="BPMNEdge_bHPaEg" sourceElement="BPMNShape_0OS0TQ" targetElement="BPMNShape_3PbNFQ">
<di:waypoint x="513.0" y="175.0"/> <di:waypoint x="513.0" y="175.0"/>
<di:waypoint x="559.0" y="175.0"/>
<di:waypoint x="570.0" y="175.0"/> <di:waypoint x="570.0" y="175.0"/>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>