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

MethodTypeDescription

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