Commit 8a49ca81 authored by David Nichols's avatar David Nichols
Browse files

added a no-code chatbot excample plus supporting bb updates

parent cb3f1dbb
Pipeline #26892 passed with stage
in 19 minutes and 30 seconds
*~
qorusproject.json
.classpath
.project
from qore.__root__ import BBM_WebSocketServiceBase
from svc import QorusService
from qore.__root__ import BBM_QorusUiExtension
from qore.__root__ import BBM_WebSocketServiceEventSource
from qore.__root__.OMQ import Observer
class ChatbotWsDemo(BBM_WebSocketServiceBase):
class ChatbotWsDemo(QorusService):
def __init__(self):
####### GENERATED SECTION! DON'T EDIT! ########
self.class_connections = ClassConnections_ChatbotWsDemo()
......@@ -11,45 +11,26 @@ class ChatbotWsDemo(BBM_WebSocketServiceBase):
####### GENERATED SECTION! DON'T EDIT! ########
def init(self):
self.class_connections.UIExtension()
self.class_connections.Connection_1()
############ GENERATED SECTION END ############
####### GENERATED SECTION! DON'T EDIT! ########
class ClassConnections_ChatbotWsDemo(Observer): # has to inherit Observer because there is an event-based connector
class ClassConnections_ChatbotWsDemo:
def __init__(self):
UserApi.startCapturingObjectsFromPython()
# map of prefixed class names to class instances
self.class_map = {
'BBM_QorusUiExtension': BBM_QorusUiExtension(),
'BBM_WebSocketServiceEventSource': BBM_WebSocketServiceEventSource(),
'BBM_TextAnalysis': BBM_TextAnalysis(),
}
UserApi.stopCapturingObjectsFromPython()
# register observers
self.callClassWithPrefixMethod("BBM_WebSocketServiceEventSource", "registerObserver", self)
def callClassWithPrefixMethod(self, prefixed_class, method, *argv):
UserApi.logDebug("ClassConnections_ChatbotWsDemo: callClassWithPrefixMethod: method: %s, class: %y", method, prefixed_class)
return getattr(self.class_map[prefixed_class], method)(*argv)
# override Observer's update()
def update(id, params):
if (id == "BBM_WebSocketServiceEventSource.event (ignored)"):
WebSockets(params)
def UIExtension(self, params):
UserApi.logDebug("UIExtension called with data: %y", params)
def Connection_1(self, params):
UserApi.logDebug("Connection_1 called with data: %y", params)
UserApi.logDebug("calling init: %y", params)
return self.callClassWithPrefixMethod("BBM_QorusUiExtension", "init", params)
def WebSockets(self, params):
UserApi.logDebug("WebSockets called with data: %y", params)
mapper = UserApi.getMapper("chatbot-ws-input")
params = mapper.mapAuto(params)
UserApi.logDebug("calling processInputHash: %y", params)
return self.callClassWithPrefixMethod("BBM_TextAnalysis", "processInputHash", params)
############ GENERATED SECTION END ############
......@@ -5,7 +5,7 @@ desc: AI / NLP Chatbot Demo Server
lang: python
author:
- Qore Technologies, s.r.o.
base-class-name: BBM_WebSocketServiceBase
base-class-name: QorusService
class-name: ChatbotWsDemo
classes:
- BBM_WebSocketServiceBase
......@@ -25,6 +25,7 @@ fsm:
]
mappers:
- chatbot-ws-input:1.0
- chatbot-ws-output:1.0
remote: true
resource:
- intents.json
......@@ -43,17 +44,10 @@ version: '1.0'
servicetype: USER
code: chatbot-ws-demo-1.0.qsd.py
class-connections:
UIExtension:
Connection_1:
- class: BBM_QorusUiExtension
connector: init
trigger: init
WebSockets:
- class: BBM_WebSocketServiceEventSource
connector: webSocketReceiveEvent
trigger: null
- class: BBM_TextAnalysis
connector: processInputHash
mapper: chatbot-ws-input:1.0
config-items:
- name: http-listener-bind-address
parent:
......@@ -108,6 +102,8 @@ config-items:
interface-name: BBM_WebSocketServiceBase
interface-version: '1.0'
- name: websockets-auth-name
value:
"permissive"
parent:
interface-type: class
interface-name: BBM_WebSocketServiceBase
......@@ -169,32 +165,35 @@ config-items:
interface-type: class
interface-name: BBM_TextAnalysis
interface-version: '1.0'
- name: text-analysis-public-address
parent:
interface-type: class
interface-name: BBM_TextAnalysis
interface-version: '1.0'
- name: text-analysis-hash-event-key
parent:
interface-type: class
interface-name: BBM_TextAnalysis
interface-version: '1.0'
- name: ui-extension-group
value:
"Chatbot"
parent:
interface-type: class
interface-name: BBM_QorusUiExtension
interface-version: '1.0'
- name: ui-extension-menu
value:
"AI Chatbot Demo: Public Transport Services"
parent:
interface-type: class
interface-name: BBM_QorusUiExtension
interface-version: '1.0'
- name: ui-extension-desc
value:
"AI chatbot demo service"
parent:
interface-type: class
interface-name: BBM_QorusUiExtension
interface-version: '1.0'
- name: ui-extension-default-resource
value:
"views/index.html"
parent:
interface-type: class
interface-name: BBM_QorusUiExtension
......
# This is a generated file, don't edit!
type: mapper
name: chatbot-ws-output
desc: Maps the result of text analysis to a WebSocket message to the caller
mappertype: Mapper
version: '1.0'
author:
- Qore Technologies, s.r.o.
fields:
cid:
name: id
msg:
name: result
options:
mapper-input:
type: type
name: qoretechnologies
path: /building-blocks/text-analysis/output-event
can_manage_fields: false
custom-fields: {}
mapper-output:
type: type
name: qoretechnologies
path: /building-blocks/websockets/send-event
can_manage_fields: false
custom-fields: {}
'use strict';
// server-side template rendering
const ws_server = '{{regex_subst(UserApi::qorusGetLocalUrl(),
"^http(s)?://([^:]+)*",
"ws$1://" + UserApi::getConfigItemValue("chatbot-public-address"))
+ "/ai-chatbot"}}';
const ws_server = '{{"ws" + ($ctx.ssl ? "s" : "") + "://" + $ctx.hdr.host + "/" + UserApi::getConfigItemValue("websockets-root-uri")}}';
const outputYou = document.querySelector('.output-you');
//console.log("you: ", outputYou);
......
......@@ -43,16 +43,16 @@ config-items:
config_group: Text Analysis Main
description: >-
Text Analysis action hash; format: `tag`: `<action>`
Actions have the following format:
- `fsm:<name>` launch the given Finite State Machine (must be valid for the
service)
- `response`: return one of the valid responses
**NOTE**: the default action for any tag is to send a valid response
strictly_local: true
type: hash
......@@ -71,29 +71,21 @@ config-items:
The hard action hash is a hash of actions for specific literal imputs not
processed by the AI system; these inputs are checked first before submitting
for AI intent matching.
If the input matches exactly (case-insensitive), then the given action is
executed.
Hash keys are case-insensitive input strings, values are strings with the
following format:
- `fsm:<name>`: launch the given Finite State Machine (must be a valid FSM
for the current interface)
- any other string; return the given string as a response
strictly_local: true
type: "*hash"
- name: text-analysis-public-address
default_value:
"$2"
description: >-
The public-facing or user-facing hostname or IP address of the service; use
`$2` as the value to use the Qorus URL hostname
config_group: Text Analysis Web Config
strictly_local: true
- name: text-analysis-hash-event-key
default_value:
"msg"
......
......@@ -11,6 +11,13 @@
"url": "https://qorus-eks.qoretechnologies.com",
"custom_urls": []
}
],
"HQ": [
{
"name": "rippy",
"url": "https://dnichols:U2FsdGVkX1829F70XNMu7dkg43%2BEXpXZQlls1QqKSk0%3D@hq.qoretechnologies.com:8092",
"custom_urls": []
}
]
},
"source_directories": [
......
......@@ -35,7 +35,12 @@ class BBM_HttpServiceGenericBase inherits QorusService {
AbstractServiceHttpHandler handler;
}
init() {
#! Initializer connection method
/**
This is listed as an input/output provider to allow it to be chained to other actions, but no output is
returned and any input is ignored
*/
init(auto ignored) {
initImpl();
}
......@@ -69,7 +74,7 @@ class BBM_HttpServiceGenericBase inherits QorusService {
}
}
static private setupHandlerIntern(AbstractServiceHttpHandler handler) {
private setupHandlerIntern(AbstractServiceHttpHandler handler) {
*string bind = ServiceApi::getConfigItemValue("http-listener-bind-address");
if (!exists bind) {
ServiceApi::logInfo("no bind address; binding on global listeners");
......@@ -98,10 +103,14 @@ class BBM_HttpServiceGenericBase inherits QorusService {
info.get_remote_certs = True;
}
info.ssl_verify_flags = SSL_MAP{ServiceApi::getConfigItemValue("http-listener-ssl-mode")};
ServiceApi::logDebug("bind arg: %y", info);
ServiceApi::logInfo("setup %s handler with bind arg: %y", getHandlerProtocol(), info);
handler.addListener(info);
}
private string getHandlerProtocol() {
return "HTTP";
}
#! Returns the HTTP handler for the service
private abstract AbstractServiceHttpHandler getHandlerImpl();
}
# This is a generated file, don't edit!
type: class
name: BBM_HttpServiceGenericBase
class-name: BBM_HttpServiceGenericBase
base-class-name: QorusService
version: "1.0"
desc: base class for HTTP handler services
lang: qore
author:
- Qore Technologies, s.r.o.
base-class-name: QorusService
version: '1.0'
class-connectors:
- name: init
type: input-output
method: init
code: BBM_HttpServiceGenericBase-v1.0.qclass
config-items:
- name: http-listener-bind-address
default_value:
null
type: "*string"
default_value: null
description: optional bind address for a dedicated listener; if not set, then it will bind on all Qorus system listeners as a global handler
description: >-
optional bind address for a dedicated listener; if not set, then it will
bind on all Qorus system listeners as a global handler
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-cert-location
default_value:
null
type: "*string"
default_value: null
description: "optional X.509 certificate location name for a dedicated listener; location
resolved with `UserApi::getTextFileFromLocation()` or `UserApi::getBinaryFileFromLocation()`
depending on the value of `http-listener-cert-format` (ex: `file://$OMQ_DIR/user/certs/my-cert.pem`)"
description: >-
optional X.509 certificate location name for a dedicated listener; location
resolved with `UserApi::getTextFileFromLocation()` or
`UserApi::getBinaryFileFromLocation()` depending on the value of
`http-listener-cert-format` (ex: `file://$OMQ_DIR/user/certs/my-cert.pem`)
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-cert-format
type: "string"
default_value: "AUTO"
description: "possible values giving the format of the data defined by
`http-listener-cert-location`:\n- **`PEM`**: PEM format (text)\n- **`DER`**: DER format (binary)\n- **`AUTO`**: try to
determine the format automatically"
allowed_values: ["PEM", "DER", "AUTO"]
default_value:
"AUTO"
description: >-
possible values giving the format of the data defined by
`http-listener-cert-location`:
- **`PEM`**: PEM format (text)
- **`DER`**: DER format (binary)
- **`AUTO`**: try to determine the format automatically
allowed_values:
- "PEM"
- "DER"
- "AUTO"
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-key-location
default_value:
null
type: "*string"
default_value: null
description: "optional private key resource name for a dedicated listener; location resolved
with `UserApi::getTextFileFromLocation()` or `UserApi::getBinaryFileFromLocation()`
depending on the value of `http-listener-key-format` (ex: `file://$OMQ_DIR/user/certs/my-key.pem`)"
description: >-
optional private key resource name for a dedicated listener; location
resolved with `UserApi::getTextFileFromLocation()` or
`UserApi::getBinaryFileFromLocation()` depending on the value of
`http-listener-key-format` (ex: `file://$OMQ_DIR/user/certs/my-key.pem`)
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-key-format
type: "string"
default_value: "AUTO"
description: "possible values giving the format of the data defined by
`http-listener-key-location`:\n- **`PEM`**: PEM format (text)\n- **`DER`**: DER format (binary)\n- **`AUTO`**: try to
determine the format automatically"
allowed_values: ["PEM", "DER", "AUTO"]
default_value:
"AUTO"
description: >-
possible values giving the format of the data defined by
`http-listener-key-location`:
- **`PEM`**: PEM format (text)
- **`DER`**: DER format (binary)
- **`AUTO`**: try to determine the format automatically
allowed_values:
- "PEM"
- "DER"
- "AUTO"
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-key-password
default_value:
null
type: "*string"
default_value: null
description: optional password to PEM private key data for a dedicated listener
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-ssl-mode
type: string
default_value: NO-CLIENT-CERT-REQUIRED
default_value:
"NO-CLIENT-CERT-REQUIRED"
description: set the SSL mode
allowed_values:
- REQUIRE-CLIENT-CERT
- NO-CLIENT-CERT-REQUIRED
- REQUEST-CLIENT-CERT
- "REQUIRE-CLIENT-CERT"
- "NO-CLIENT-CERT-REQUIRED"
- "REQUEST-CLIENT-CERT"
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-accept-all-certs
default_value:
true
type: bool
default_value: true
description: flag to accept all client certificates
strictly_local: true
config_group: HTTP/S Listener
- name: http-listener-capture-client-certs
default_value:
true
type: bool
default_value: true
description: flag to capture client certificates
strictly_local: true
config_group: HTTP/S Listener
......@@ -25,4 +25,7 @@ class BBM_WebSocketHandlerBase inherits AbstractServiceWebSocketHandler, BBM_Htt
#! The WebSocket service base class; includes a parametrized handler class as well
class BBM_WebSocketServiceBase inherits BBM_HttpServiceGenericBase {
private string getHandlerProtocol() {
return "WebSocket";
}
}
......@@ -13,7 +13,7 @@ hashdecl WebSocketReceivedEventInfo {
#! connection ID string: this ID must be used when sending a message
string cid;
#! unique event ID
string event_id;
int event_id;
#! event type: \c "STRING" or \c "BINARY"
string type;
#! the actual event information
......@@ -29,10 +29,15 @@ hashdecl WebSocketSendEventInfo {
}
#! Service class
class BBM_WebSocketServiceEventSource inherits BBM_WebSocketServiceBase {
class BBM_WebSocketServiceEventSource inherits BBM_WebSocketServiceBase, Observable {
#! Returns the HTTP handler for the service
private AbstractServiceHttpHandler getHandlerImpl() {
return new BBM_WebSocketServiceEventSourceHandler();
return new BBM_WebSocketServiceEventSourceHandler(self);
}
#! The constructor initializes the class
constructor() {
init();
}
#! Connection method for sending WebSocket messages to the client
......@@ -57,16 +62,21 @@ class BBM_WebSocketServiceEventSource inherits BBM_WebSocketServiceBase {
}
#! Handler class
class BBM_WebSocketServiceEventSourceHandler inherits BBM_WebSocketHandlerBase, Observable {
class BBM_WebSocketServiceEventSourceHandler inherits BBM_WebSocketHandlerBase {
public {
BBM_WebSocketServiceEventSource service;
}
constructor(BBM_WebSocketServiceEventSource service) {
self.service = service;
}
BBM_WebSocketServiceEventSourceConnection getConnectionImpl(hash<auto> cx, hash<auto> hdr, string cid) {
return new BBM_WebSocketServiceEventSourceConnection(cx, self);
}
#! Connection method for sending WebSocket messages to the client
#! Method for sending WebSocket messages to the client
/** @param info the message to send
This is listed as an input/output provider to allow it to be chained to other actions, but no output is
returned
*/
sendMessage(hash<WebSocketSendEventInfo> info) {
if (!exists info.msg) {
......@@ -121,6 +131,9 @@ class BBM_WebSocketServiceEventSourceConnection inherits QorusWebSocketConnectio
"listener-id",
"user",
);
#! Connection event ID string; must match the connection parameters in the YAML metadata
const ConnectionId = "BBM_WebSocketServiceEventSource::event (ignored)";
}
constructor(hash<auto> cx, BBM_WebSocketServiceEventSourceHandler handler) : QorusWebSocketConnection(handler) {
......@@ -139,12 +152,11 @@ class BBM_WebSocketServiceEventSourceConnection inherits QorusWebSocketConnectio
private doEvent(string type, data msg) {
# get unique event ID string
string event_id_str = sprintf("%s-%d", cx.id, event_id++);
UserApi::logDebug("event_id: %y msg: %y", event_id_str, msg);
cast<BBM_WebSocketServiceEventSourceHandler>(handler).notifyObservers(event_id_str, <WebSocketReceivedEventInfo>{
UserApi::logDebug("connection: %y msg: %y", ConnectionId, msg);
cast<BBM_WebSocketServiceEventSourceHandler>(handler).service.notifyObservers(ConnectionId, <WebSocketReceivedEventInfo>{
"cx": cx,
"cid": cx.id.toString(),
"event_id": event_id_str,
"event_id": event_id++,
"type": type,
"msg": msg,
});
......
# This is a generated file, don't edit!
type: class
name: BBM_WebSocketServiceEventSource
desc: An event source for WebSocket events in Qorus services
desc: >-
An event source for WebSocket events in Qorus services
This class is designed to be used as an event source for services; the
`webSocketReceiveEvent` connector returns a hash event (output type:
`qoretechnologies/building-blocks/websockets/recv-event`), and the
`webSocketSendEvent` can be used to send a response to the sender (input
type: `qoretechnologies/building-blocks/websockets/send-event`)
The class's constructor initializes the object, so if it's used as an event
source, there is no need to use the `init` connector.
lang: qore
author:
- Qore Technologies, s.r.o.
......@@ -23,6 +35,9 @@ class-connectors:
name: qoretechnologies
can_manage_fields: false
path: /building-blocks/websockets/send-event
- name: init
type: input-output
method: init
requires:
- BBM_WebSocketServiceBase
version: '1.0'
......
......@@ -10,33 +10,33 @@ typeinfo:
name: event_id
desc: the unique event ID
type:
typename: string
name: string
typename: int
name: int
supported_options:
qore.no_null:
type: bool
desc: >-
if True then NULL is not supported on input if NOTHING
is also not accepted
string.encoding:
type: string
desc: the output encoding when writing to the type
string.max_size_chars:
type: integer
desc: the maximum length of the string in characters
options:
qore.no_null: true
base_type: string
base_type: int
mandatory: true
types_accepted:
- string
- int
- integer
types_returned:
- string
- int
- integer
fields: {}