Creating your own AI prompt plugin
You can create your own AI prompt.
Conditions:
- The new AI prompt class must implement the "AIPrompt" interface.
- The new Automation Action class must be declared under the package "com.openkm.plugin.ai".
- The new Automation Action class must be annotated with "@PluginImplementation".
- The new Automation Action class must extend of "BasePlugin".
Automation Action interface:
package com.openkm.plugin.ai;
import com.openkm.ws.common.util.AIResult;
import net.xeoh.plugins.base.Plugin;
public interface AIPrompt extends Plugin {
String getName();
AIResult executeNodePrompt(String uuid) throws AIPromptException;
AIResult executeTextPrompt(String text) throws AIPromptException;
String[] getContentTypes();
boolean isManageNodePrompt();
boolean isManageTextPrompt();
}
The new class must be loaded into the package com.openkm.plugin.ai because the application plugins system will try to load from there.
Do not miss the tag @PluginImplementation otherwise the application plugin system will not be able to retrieve the new class.
More information about Register a new plugin.
Methods description
Method | Type | Description |
---|---|---|
AIResult executeNodePrompt(String uuid) |
void |
The method executes an AI prompt on a specific node. |
AIResult executeTextPrompt(String text) |
void |
The method executes an AI prompt on a specific text. |
getName() |
String |
Sets the name shown in the user interface selector list. |
getContentTypes() |
String[] |
Returns a list of MIME types for documents in which prompt execution is allowed. If the list is empty, the prompt can be applied to any document from which content can be extracted in text format. This restriction applies only to AI prompts for nodes. |
isManageNodePrompt() |
boolean |
When the value is true, it indicates that the plugin can be applied to nodes. |
isManageTextPrompt() |
boolean |
When the value is true, it indicates that the plugin can be applied to text. |
In the following examples, we recommend paying attention to the calls made to the methods of the OpenAIUtils class. Currently, prompts can be executed with either text or images. These are the only two allowed formats.
Image to text sample
SampleImageToTextAIPrompt class:
package com.openkm.plugin.ai;
import com.openkm.ai.OpenAIUtils;
import com.openkm.bean.Document;
import com.openkm.core.MimeTypeConfig;
import com.openkm.module.db.DbDocumentModule;
import com.openkm.plugin.BasePlugin;
import com.openkm.ws.common.util.AIResult;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.InputStream;
@PluginImplementation
public class SampleImageToTextAIPrompt extends BasePlugin implements AIPrompt {
private static final Logger log = LoggerFactory.getLogger(SampleImageToTextAIPrompt.class);
@Autowired
private DbDocumentModule dbDocumentModule;
@Autowired
private OpenAIUtils openAIUtils;
@Override
public String getName() {
return "Image to Text";
}
@Override
public AIResult executeNodePrompt(String uuid) throws AIPromptException {
log.debug("executeNodePrompt({})", uuid);
try {
Document doc = dbDocumentModule.getProperties(null, uuid);
AIResult result = new AIResult();
String text;
try (InputStream is = dbDocumentModule.getContent(null, uuid, false)) {
text = openAIUtils.extractTextFromImage(doc.getMimeType(), IOUtils.toByteArray(is));
}
if (StringUtils.isNotEmpty(text)) {
result.setText(text);
// HTML response
MutableDataSet options = new MutableDataSet();
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
Node document = parser.parse(text);
result.setHtml(renderer.render(document));
} else {
result.setError("Not being able to extract text from image");
result.setErrorCode(AIResult.ErrorCodes.OTHER);
}
return result;
} catch (Exception e) {
throw new AIPromptException(e);
}
}
@Override
public AIResult executeTextPrompt(String text) throws AIPromptException {
throw new AIPromptException("Not implemented");
}
@Override
public String[] getContentTypes() {
return new String[]{MimeTypeConfig.MIME_JPEG, MimeTypeConfig.MIME_PNG, MimeTypeConfig.MIME_GIF};
}
@Override
public boolean isManageNodePrompt() {
return true;
}
@Override
public boolean isManageTextPrompt() {
return false;
}
}
Extract metadata sample
Invoice metadata XML definition:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE property-groups PUBLIC "-//OpenKM//DTD Property Groups 3.11//EN"
"https://www.openkm.com/dtd/property-groups-3.11.dtd">
<property-groups>
<property-group label="Invoice" name="okg:invoice">
<input label="Invoice Number" name="okp:invoice.number"/>
<input label="Invoice Date" type="date" name="okp:invoice.date"/>
<input label="Invoice Amount" name="okp:invoice.amount"/>
<input label="Customer Name" name="okp:invoice.customer_name"/>
<input label="Customer CIF" name="okp:invoice.customer_cif"/>
<textarea label="Customer Address" name="okp:invoice.customer_address"/>
</property-group>
</property-groups>
SampleMetadataAIPrompt class:
package com.openkm.plugin.ai;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openkm.ai.OpenAIUtils;
import com.openkm.core.MimeTypeConfig;
import com.openkm.module.db.DbDocumentModule;
import com.openkm.module.db.DbPropertyGroupModule;
import com.openkm.plugin.BasePlugin;
import com.openkm.util.ISO8601;
import com.openkm.ws.common.util.AIResult;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Map;
@PluginImplementation
public class SampleMetadataAIPrompt extends BasePlugin implements AIPrompt {
private static final Logger log = LoggerFactory.getLogger(SampleMetadataAIPrompt.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String METADATA_GROUP = "okg:invoice";
@Autowired
private DbPropertyGroupModule dbPropertyGroupModule;
@Autowired
private DbDocumentModule dbDocumentModule;
@Autowired
private OpenAIUtils openAIUtils;
@Override
public String getName() {
return "Sample Metadata AI";
}
@Override
public AIResult executeNodePrompt(String uuid) throws AIPromptException {
log.debug("executePrompt({})", uuid);
try {
AIResult result = new AIResult();
String text = dbDocumentModule.getExtractedText(null, uuid);
String json = "";
if (StringUtils.isNotEmpty(text)) {
json = openAIUtils.extractMetadataFromText(text, InvoiceMetadataSimple.class);
} else {
result.setError("The document has no content or it has not been extracted yet");
result.setErrorCode(AIResult.ErrorCodes.TEXT_EMPTY);
}
if (StringUtils.isNotEmpty(json)) {
InvoiceMetadataSimple metadata = objectMapper.readValue(json, InvoiceMetadataSimple.class);
log.info("JSON: {} => InvoiceMetadata: {}", json, metadata);
StringBuilder sb = new StringBuilder();
if (metadata.isValid()) {
Map<String, String> props = metadata.getProperties();
// Add or update metadata group
if (dbPropertyGroupModule.hasGroup(null, uuid, METADATA_GROUP)) {
dbPropertyGroupModule.setProperties(null, uuid, METADATA_GROUP, props);
sb.append("Metadata updated for document");
} else {
dbPropertyGroupModule.addGroup(null, uuid, METADATA_GROUP, props);
sb.append("Metadata added to document");
}
} else {
sb.append("Invoice not detected");
}
// HTML response text response
String html = "<p class=\"text-success\">" + sb + "</p>\n";
html += metadata.toHtml();
result.setHtml(html);
// Plain text response
sb.append("\n");
sb.append(metadata.toText());
result.setText(sb.toString());
}
return result;
} catch (Exception e) {
throw new AIPromptException(e);
}
}
@Override
public AIResult executeTextPrompt(String text) throws AIPromptException {
throw new AIPromptException("Not implemented");
}
@Override
public String[] getContentTypes() {
return new String[]{MimeTypeConfig.MIME_TEXT};
}
@Override
public boolean isManageNodePrompt() {
return true;
}
@Override
public boolean isManageTextPrompt() {
return false;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class InvoiceMetadataSimple {
public String invoice_number;
public String invoice_date;
public String invoice_amount;
public String customer_cif;
public String customer_name;
public String customer_address;
public Map<String, String> getProperties() {
Map<String, String> props = new HashMap<>();
props.put("okp:invoice.number", this.invoice_number);
props.put("okp:invoice.amount", this.invoice_amount);
props.put("okp:invoice.customer_cif", this.customer_cif);
props.put("okp:invoice.customer_name", this.customer_name);
props.put("okp:invoice.customer_address", this.customer_address);
if (ISO8601.parseBasic(this.invoice_date) != null) {
props.put("okp:invoice.date", this.invoice_date);
} else {
log.warn("Wrong invoice date format: {}", this.invoice_date);
}
return props;
}
public boolean isValid() {
return StringUtils.isNotEmpty(this.invoice_number)
&& StringUtils.isNotEmpty(this.invoice_date)
&& StringUtils.isNotEmpty(this.invoice_amount);
}
public String toHtml() {
StringBuilder builder = new StringBuilder();
builder.append("<ul>");
builder.append("<li><b>invoice_number</b>: ").append(invoice_number).append("</li>\n");
builder.append("<li><b>invoice_date</b>: ").append(invoice_date).append("</li>\n");
builder.append("<li><b>invoice_amount</b>: ").append(invoice_amount).append("</li>\n");
builder.append("<li><b>customer_cif</b>: ").append(customer_cif).append("</li>\n");
builder.append("<li><b>customer_name</b>: ").append(customer_name).append("</li>\n");
builder.append("<li><b>customer_address</b>: ").append(customer_address).append("</li>\n");
builder.append("</ul>");
return builder.toString();
}
public String toText() {
StringBuilder builder = new StringBuilder();
builder.append("invoice_number: ").append(invoice_number).append("\n");
builder.append("invoice_date: ").append(invoice_date).append("\n");
builder.append("invoice_amount: ").append(invoice_amount).append("\n");
builder.append("customer_cif: ").append(customer_cif).append("\n");
builder.append("customer_name: ").append(customer_name).append("\n");
builder.append("customer_address: ").append(customer_address).append("\n");
return builder.toString();
}
}
}
Basic summarization sample
SampleTextAIPrompt class:
package com.openkm.plugin.ai;
import com.openkm.ai.OpenAIUtils;
import com.openkm.core.MimeTypeConfig;
import com.openkm.module.db.DbDocumentModule;
import com.openkm.plugin.BasePlugin;
import com.openkm.ws.common.util.AIResult;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@PluginImplementation
public class SampleTextAIPrompt extends BasePlugin implements AIPrompt {
private static final Logger log = LoggerFactory.getLogger(SampleTextAIPrompt.class);
private static final String PROMPT = "Summarize the provided text responding in text language using the content. " +
"Returned response in plain text with a maximum of 1000 characters.";
@Autowired
private DbDocumentModule dbDocumentModule;
@Autowired
private OpenAIUtils openAIUtils;
@Override
public String getName() {
return "Sample Text AI";
}
@Override
public AIResult executeNodePrompt(String uuid) throws AIPromptException {
log.debug("executeNodePrompt({})", uuid);
try {
AIResult result = new AIResult();
String text = dbDocumentModule.getExtractedText(null, uuid);
if (StringUtils.isNotEmpty(text)) {
return generateSummary(text);
} else {
result.setError("The document has no content or it has not been extracted yet");
result.setErrorCode(AIResult.ErrorCodes.TEXT_EMPTY);
}
return result;
} catch (Exception e) {
throw new AIPromptException(e);
}
}
@Override
public AIResult executeTextPrompt(String text) throws AIPromptException {
log.debug("executeTextPrompt({})", text);
try {
return generateSummary(text);
} catch (Exception e) {
throw new AIPromptException(e);
}
}
/**
* Generate summary from text
*/
private AIResult generateSummary(String text) throws Exception {
AIResult result = new AIResult();
String summary = "";
if (StringUtils.isNotEmpty(text)) {
summary = openAIUtils.processText(PROMPT, text);
}
if (StringUtils.isNotEmpty(summary)) {
// HTML response text response
String html = "<p>" + summary + "</p>\n";
result.setHtml(html);
// Plain text response
result.setText(summary);
}
return result;
}
@Override
public String[] getContentTypes() {
return new String[]{MimeTypeConfig.MIME_TEXT};
}
@Override
public boolean isManageNodePrompt() {
return true;
}
@Override
public boolean isManageTextPrompt() {
return true;
}
}