Skip to content

Xalok Assistant Interface

Introduction

XAI (Xalok Assistant Interface) is a generic interface that enables the addition of custom ad hoc functionalities to the news editing layer from any third party. These functionalities can connect with external APIs and utilize any information from the content to modify its data. Customizations can be dynamically added on-the-fly directly within the instance. (See XALOK-777 document XAI.pdf)

Overview

Ultimately it is about being able to transform a certain edit element into a certain edit element (including empty elements; for example we could generate a certain result regardless of the initial edit element or we could generate an action with no output given a certain edit element).

So, a transformation is to take an element and get a certain result. Examples of transformations could be "translate this paragraph to English", "generate the tags of the news", "import a content from Wikipedia", "count the number of words in the news", etc... architecture

We then differentiate three elements: the interaction types (on the right) , the transformation adapters with the backend (in the middle) and the transformation adapters with our Admin editing interface (on the left).

Interaction types

Interaction types are fixed, as they define what data are expected, what results are expected and how those data are expected to be used. So for example an interaction type could be to generate a list of options from a list of options, then it is clear that the interface client must be able to generate a list of options, to consume a list of options and to display it appropriately. They therefore require ad hoc implementations (except for further generalization) to introduce new types of transformation (for example, in the case of being able to display a map to the user, the possibility of presenting a map in the interface would have to be implemented).

Currently, the given types are defined:

  • CHOICES_TYPE, it is expected that, from an input, a set of options will be presented from which a single option will be selected.
  • TEXT_REPORT_TYPE, you expect to get a generic (plain text) report, encoded as text, about some input information.
  • DIFF_TYPE, is expected to get a modified version of the input, so the user will want to see the differences between the input and output.
  • ACTION_TYPE, no specific output is expected to be generated, but a spillover effect is expected somewhere.

Backend adapters

Backend adapters abstract a real transformation of an input into an output. Ultimately the form of the input is self-defined (i.e., the input can follow any structure), as well as the output.

The reason is that ultimately, a backend is nothing more than an on-demand (in the instance) configurable controller with the following form:

text
wfadmin_xai_run    POST    ANY    ANY    /xai/run/{backend_alias}

Where the input and output are a json.

Currently, a backend consists of:

  • An alias (slug) to invoke it.
  • A title describing it.
  • The type of operation for which it is designed.
  • A brief description (for internal annotations).
  • A PHP transformation that you should assume is wrapped in the signature function transform(array $input): array { with the result to be set in the variable $output.
  • A JSON example ready to test the backend.

For example, suppose we have a certain backend capable of suggesting several titles given an input text (specifically a call to the OpenAI text completion API). In such a case, we could define the backend as:

php
/*
  input: {full_text: "..."}
  output: {success: true|false, error: "...", choices: ["...", "...", ...]}
*/
// function transform(array $input): array {

$token = "sk-XXXXXXXXXXXXX";
$model = "text-davinci-003";
$query = "Dado el siguiente texto, escribe cinco posibles titulares:\n" . $input['full_text'];

$data = array("model" => $model, "prompt" => $query, "temperature" => 0.5, "max_tokens" => 128);

$ch = curl_init("https://api.openai.com/v1/completions");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json", "Authorization: Bearer " . $token));
curl_setopt($ch, CURLOPT_TIMEOUT, 60);

$result = curl_exec($ch);
if ($result === false) {
    $error = curl_error($ch);
    $output['error'] = $error;
} else {
    $response = json_decode($result, true);
    $output['success'] = true;
    unset($output['error']);
    $output['_openai'] = $response;
    preg_match_all('/"(.*?)"/', $response['choices'][0]['text'], $matches);
    $output['choices'] = $matches[1];
}

curl_close($ch);

Interface adapters

The interface adapters are primarily intended for use in the Admin UI, specifically, under the wf-modules framework. However, it could accommodate other types of interfaces.

Currently and specifically, interface adapters connect the content data model. Each interface adapter decides on which modules (wf-modules) it is able to act.

An interface adapter is then composed of:

  • A title describing it.
  • The backend that you will use to perform the transformation.
  • A definition that will filter, determine, for which elements it applies (for example, for which wf-module it is prepared).
  • The input value adapter, which gets the data from the model to deliver it to the backend.
  • The output value adapter, which uses the response data from the backend to propagate it to the model.

Continuing with the previous example, an interface adapter for title suggestion could be defined as follows:

Title wf-module filter, simply check de role:

javascript
return !module.page && module?.model?.getMasterRole() === 'title';

The input data will be the whole content text:

javascript
const tryParseJSONObject = function (jsonString){
    try {
        var o = JSON.parse(jsonString);
        if (o && typeof o === "object") return o;
    } catch (e) { }
    return false;
};

const stripHtml = function (html){
    let tmp = document.createElement("DIV");
    tmp.innerHTML = html;
    return tmp.textContent || tmp.innerText || "";
};

const flatContent = function (m) {
    var r = [];
    if (undefined !== m && null !== m)
        for(const [k,v] of Object.entries(m))
            if(k === 'content' && !tryParseJSONObject(v)) return [stripHtml(v)];
            else if(typeof v === 'object')                r.push(flatContent(v));
    return r;
};

const page = module.model.options.page;
const title = page.attributes.modules.title.content;
const epigraph = (page.attributes.modules.epigraph || {}).content || '';
const fullText = flatContent(page.attributes.modules).flat(9999).join(' ');

return {full_text: fullText};

The output data will be used to populate the title module:

javascript
return module?.model?.set('content', value)

Enabling

XAI is disabled by default, to enable along de project you must set to 1 the following value:

yaml
parameters:
    wf_cms.xai.enabled: 1

IMPORTANT: that value is used on config.js generation (so make wf_assets should be executed sometime)

Configuring

New backends and interface adapters could be created from the /dashboard

Additionally, you will need to assign the necessary roles mentioned above to your user.

To configure the API keys, go to __APP_URL__/settings/ under the xai-key group. The default implementation is for ChatGPT, but you will also see a data-key field for integration with Hiberus' data team.

Examples