Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#3403] Rasa Connector #3611

Merged
merged 23 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ java_library(
],
)

java_library(
name = "feign",
exports = [
"@maven//:io_github_openfeign_feign_core",
"@maven//:io_github_openfeign_feign_jackson",
"@maven//:io_github_openfeign_feign_okhttp",
],
)

java_plugin(
name = "lombok_plugin",
generates_api = True,
Expand Down
50 changes: 50 additions & 0 deletions backend/connectors/rasa-connector/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
load("//tools/build:springboot.bzl", "springboot")
load("//tools/build:junit5.bzl", "junit5")
load("//tools/build:container_release.bzl", "container_release")
load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg")

check_pkg(name = "buildifier")

app_deps = [
"//:spring",
"//:springboot",
"//:springboot_actuator",
"//:jackson",
"//:lombok",
"//backend/model/message",
"//backend/model/metadata",
"//:feign",
"//lib/java/log",
"//lib/java/spring/kafka/core:spring-kafka-core",
"//lib/java/spring/core:spring-core",
"//lib/java/spring/kafka/streams:spring-kafka-streams",
"//lib/java/spring/async:spring-async",
]

springboot(
name = "rasa-connector",
srcs = glob(["src/main/java/**/*.java"]),
main_class = "co.airy.spring.core.AirySpringBootApplication",
deps = app_deps,
)

[
junit5(
size = "medium",
file = file,
resources = glob(["src/test/resources/**/*"]),
deps = [
":app",
"//backend:base_test",
"//lib/java/test",
"//lib/java/kafka/test:kafka-test",
"//lib/java/spring/test:spring-test",
] + app_deps,
)
for file in glob(["src/test/java/**/*Test.java"])
]

container_release(
registry = "ghcr.io/airyhq/connectors",
repository = "rasa-connector",
)
28 changes: 28 additions & 0 deletions backend/connectors/rasa-connector/helm/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("@rules_pkg//:pkg.bzl", "pkg_tar")
load("@com_github_airyhq_bazel_tools//helm:helm.bzl", "helm_template_test")
load("//tools/build:helm.bzl", "helm_push")

filegroup(
name = "files",
srcs = glob(
["**/*"],
exclude = ["BUILD"],
),
visibility = ["//visibility:public"],
)

pkg_tar(
name = "package",
srcs = [":files"],
extension = "tgz",
strip_prefix = "./",
)

helm_template_test(
name = "template",
chart = ":package",
)

helm_push(
chart = ":package",
)
5 changes: 5 additions & 0 deletions backend/connectors/rasa-connector/helm/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v2
appVersion: "1.0"
description: A Helm chart for the Rasa connector
name: rasa-connector
version: 1.0
11 changes: 11 additions & 0 deletions backend/connectors/rasa-connector/helm/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Values.component }}"
labels:
core.airy.co/managed: "true"
core.airy.co/mandatory: "{{ .Values.mandatory }}"
core.airy.co/component: "{{ .Values.component }}"
core.airy.co/enterprise: "false"
annotations:
core.airy.co/enabled: "{{ .Values.enabled }}"
75 changes: 75 additions & 0 deletions backend/connectors/rasa-connector/helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.component }}
labels:
app: {{ .Values.component }}
core.airy.co/managed: "true"
core.airy.co/mandatory: "{{ .Values.mandatory }}"
core.airy.co/component: {{ .Values.component }}
spec:
replicas: {{ if .Values.enabled }} 1 {{ else }} 0 {{ end }}
selector:
matchLabels:
app: {{ .Values.component }}
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: {{ .Values.component }}
spec:
containers:
- name: app
image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ default .Chart.Version }}"
imagePullPolicy: Always
envFrom:
- configMapRef:
name: security
- configMapRef:
name: kafka-config
env:
- name: RASA_WEBHOOK_URL
valueFrom:
configMapKeyRef:
key: rasaWebhookUrl
name: {{ .Values.component }}
- name: SERVICE_NAME
value: {{ .Values.component }}
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: REQUESTED_CPU
valueFrom:
resourceFieldRef:
containerName: app
resource: requests.cpu
- name: LIMIT_CPU
valueFrom:
resourceFieldRef:
containerName: app
resource: limits.cpu
- name: LIMIT_MEMORY
valueFrom:
resourceFieldRef:
containerName: app
resource: limits.memory
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
httpHeaders:
- name: Health-Check
value: health-check
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 3
volumes:
- name: {{ .Values.component }}
configMap:
name: {{ .Values.component }}
14 changes: 14 additions & 0 deletions backend/connectors/rasa-connector/helm/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: {{ .Values.component }}
name: {{ .Values.component }}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: {{ .Values.component }}
type: ClusterIP
7 changes: 7 additions & 0 deletions backend/connectors/rasa-connector/helm/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
component: rasa-connector
mandatory: false
enabled: false
image: connectors/rasa-connector
global:
containerRegistry: ghcr.io/airyhq
resources: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package co.airy.core.rasa_connector;

import co.airy.avro.communication.DeliveryState;
import co.airy.avro.communication.Message;
import co.airy.core.rasa_connector.models.MessageSendResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.Map;
import java.util.UUID;

@Service
public class MessageHandler {
private final ObjectMapper mapper = new ObjectMapper();

MessageHandler() {
}

public Message getMessage(Message contactMessage, MessageSendResponse response) throws Exception {
String content = getContent(contactMessage.getSource(), response);
if (content == null) {
throw new Exception("Unable to map rasa reply to source response.");
}

return Message.newBuilder()
.setId(UUID.randomUUID().toString())
.setChannelId(contactMessage.getChannelId())
.setContent(content)
.setConversationId(contactMessage.getConversationId())
.setHeaders(Map.of())
.setDeliveryState(DeliveryState.PENDING)
.setSource(contactMessage.getSource())
.setSenderId("rasa-bot")
.setSentAt(Instant.now().toEpochMilli())
.setIsFromContact(false)
.build();
}

public String getContent(String source, MessageSendResponse response) throws JsonProcessingException {
final String text = response.getText();
if (text == null) {
return null;
}

final ObjectNode node = getNode();
switch (source) {
case "google": {
final ObjectNode representative = getNode();
representative.put("representativeType", "BOT");
node.set("representative", representative);
node.put("text", text);
return mapper.writeValueAsString(node);
}
case "viber": {
node.put("text", text);
node.put("type", text);
return mapper.writeValueAsString(node);
}
case "chatplugin":
case "instagram":
case "facebook": {
node.put("text", text);
return mapper.writeValueAsString(node);
}
case "twilio.sms":
case "twilio.whatsapp": {
node.put("Body", text);
return mapper.writeValueAsString(node);
}
case "whatsapp": {
node.put("Body", text);
return mapper.writeValueAsString(node);
}

default: {
return null;
}
}
}

private ObjectNode getNode() {
final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
return jsonNodeFactory.objectNode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.airy.core.rasa_connector;

import co.airy.core.rasa_connector.models.MessageSend;
import co.airy.core.rasa_connector.models.MessageSendResponse;
import feign.Headers;
import feign.RequestLine;

import java.util.List;

public interface RasaClient {
@RequestLine("POST /webhooks/rest/webhook")
@Headers("Content-Type: application/json")
List<MessageSendResponse> sendMessage(MessageSend content);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package co.airy.core.rasa_connector;


import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RasaClientConfig {
@Bean
public RasaClient rasaClient(@Value("${rasa.rest-webhook-url}") String rasaRestUrl) {
return Feign.builder()
.client(new OkHttpClient())
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logger(new feign.Logger.ErrorLogger())
.logLevel(feign.Logger.Level.FULL)
.target(RasaClient.class, rasaRestUrl);
Comment on lines +15 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no authentication token? How do you authenticate witht he rasa api?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to just call the REST API endpoint, no authentication is needed. You need authentication when calling other endpoints like /models.parse etc. (i.e for a human-handoff like feature) but not for getting plain responses (without any other data).

}
}
Loading