Ir al contenido

Creando su propio plugin de AI prompt

Puede crear su propio AI prompt.

Condiciones:

  • La nueva clase de AI prompt debe implementar la interfaz “AIPrompt”.
  • La nueva clase de AI prompt debe declararse bajo el paquete “com.openkm.plugin.ai”.
  • La nueva clase de AI prompt debe anotarse con@PluginImplementation”.
  • La nueva clase de AI prompt debe extender “BasePlugin”.

Interfaz AI prompt:

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();
}

La nueva clase debe cargarse en el paquete com.openkm.plugin.ai porque el sistema de plugins de la aplicación intentará cargarla desde ahí.

Método Tipo Descripción
AIResult executeNodePrompt(String uuid) AIResult El método ejecuta un AI prompt sobre un nodo concreto.
AIResult executeTextPrompt(String text) AIResult El método ejecuta un AI prompt sobre un fragmento de texto concreto.
getName() String Devuelve el nombre mostrado en la lista de selección de la interfaz de usuario.
getContentTypes() String[] Devuelve una lista de tipos MIME de documentos en los que se permite ejecutar el prompt. Si la lista está vacía, el prompt puede aplicarse a cualquier documento del que pueda extraerse contenido en formato texto.
Esta restricción solo se aplica a los AI prompts para nodos.
isManageNodePrompt() boolean Cuando el valor es true, indica que el plugin puede aplicarse a nodos.
isManageTextPrompt() boolean Cuando el valor es true, indica que el plugin puede aplicarse a texto.

Clase ImageToTextAIPrompt:

package com.openkm.plugin.ai;
import com.openkm.ai.AIProviderUtils;
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 lombok.extern.slf4j.Slf4j;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.InputStream;
@Slf4j
@PluginImplementation
public class ImageToTextAIPrompt extends BasePlugin implements AIPrompt {
@Autowired
private DbDocumentModule dbDocumentModule;
@Autowired
private AIProviderUtils aiProviderUtils;
@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 = aiProviderUtils.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;
}
}

Definición XML de metadatos de factura:

<?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>

Clase SampleMetadataAIPrompt:

package com.openkm.plugin.ai;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openkm.ai.AIProviderUtils;
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 lombok.extern.slf4j.Slf4j;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@PluginImplementation
public class SampleMetadataAIPrompt extends BasePlugin implements AIPrompt {
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 AIProviderUtils aiProviderUtils;
@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 = aiProviderUtils.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();
}
}
}

Clase SummarizeTextAIPrompt:

package com.openkm.plugin.ai;
import com.openkm.ai.AIProviderUtils;
import com.openkm.core.MimeTypeConfig;
import com.openkm.module.db.DbDocumentModule;
import com.openkm.plugin.BasePlugin;
import com.openkm.ws.common.util.AIResult;
import lombok.extern.slf4j.Slf4j;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
@PluginImplementation
public class SummarizeTextAIPrompt extends BasePlugin implements AIPrompt {
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 AIProviderUtils aiProviderUtils;
@Override
public String getName() {
return "Summarize 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 = aiProviderUtils.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;
}
}