├── .gitignore
├── src
├── test
│ ├── resources
│ │ ├── test.wav
│ │ └── invalid.wav
│ └── java
│ │ └── ai
│ │ └── sapcai
│ │ └── sdk_android
│ │ └── SdkTests.java
├── main
│ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ ├── AndroidManifest.xml
│ └── java
│ │ └── ai
│ │ └── sapcai
│ │ └── sdk_android
│ │ ├── Intent.java
│ │ ├── Action.java
│ │ ├── SapcaiException.java
│ │ ├── Entity.java
│ │ ├── MemoryEntity.java
│ │ ├── Memory.java
│ │ ├── SapcaiRecorder.java
│ │ ├── MultipartUtility.java
│ │ ├── Request.java
│ │ ├── Response.java
│ │ ├── Client.java
│ │ └── Conversation.java
└── androidTest
│ └── java
│ └── ai
│ └── sapcai
│ └── sdk_android
│ └── ApplicationTest.java
├── LICENSE
├── README.md
└── sdk-android.iml
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | build.gradle
3 | sdk-android.iml
4 |
--------------------------------------------------------------------------------
/src/test/resources/test.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-archive/SDK-Android/HEAD/src/test/resources/test.wav
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | sdk-android
3 |
4 |
--------------------------------------------------------------------------------
/src/test/resources/invalid.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-archive/SDK-Android/HEAD/src/test/resources/invalid.wav
--------------------------------------------------------------------------------
/src/androidTest/java/ai/sapcai/sdk_android/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Intent.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class Intent {
6 | private String name;
7 | private double confidence;
8 |
9 | private Intent(String name, double confidence) {
10 | this.name = name;
11 | this.confidence = confidence;
12 | }
13 |
14 | Intent(JSONObject obj) {
15 | this(obj.optString("slug"), obj.optDouble("confidence"));
16 | }
17 |
18 | public double getConfidence() {
19 | return confidence;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Action.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class Action {
6 |
7 | private String slug;
8 | private Boolean done;
9 | private String reply;
10 |
11 | private Action(String slug, Boolean done, String reply) {
12 | this.slug = slug;
13 | this.done = done;
14 | this.reply = reply;
15 | }
16 |
17 | Action (JSONObject obj) {
18 | this(obj.optString("slug"), obj.optBoolean("done"), obj.optString("reply"));
19 | }
20 |
21 | public String getSlug() {
22 | return slug;
23 | }
24 |
25 | public void setSlug(String slug) {
26 | this.slug = slug;
27 | }
28 |
29 | public Boolean getDone() {
30 | return done;
31 | }
32 |
33 | public void setDone(Boolean done) {
34 | this.done = done;
35 | }
36 |
37 | public String getReply() {
38 | return reply;
39 | }
40 |
41 | public void setReply(String reply) {
42 | this.reply = reply;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 SAP Conversational AI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/SapcaiException.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class SapcaiException extends RuntimeException {
7 | private int statusCode;
8 | public SapcaiException(final String message) {
9 | super(message);
10 | this.statusCode = -1;
11 | }
12 |
13 | public SapcaiException(int statusCode) {
14 | this(getSapcaiErrorMessage(statusCode));
15 | this.statusCode = statusCode;
16 | }
17 |
18 | public SapcaiException(final String message, final Throwable cause) {
19 | super(message, cause);
20 | }
21 |
22 | private static String getSapcaiErrorMessage(int statusCode) {
23 | Map errorMessages = new HashMap<>();
24 | errorMessages.put(400, "400: Bad request");
25 | errorMessages.put(415, "415: Unsupported media type");
26 | errorMessages.put(503, "503: Service unavailable");
27 | errorMessages.put(401, "401: Unauthorized");
28 | return errorMessages.get(statusCode);
29 | }
30 |
31 | public int getStatusCode() {
32 | return this.statusCode;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Entity.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.json.JSONObject;
4 |
5 | /**
6 | * The Entity class represents an entity found by SAP Conversational AI in the user input.
7 | *
8 | * @author Francois Triquet
9 | * @version 2.0.0
10 | * @since 2016-05-17
11 | *
12 | */
13 | public class Entity {
14 |
15 | private String name;
16 | private double confidence;
17 | private JSONObject data;
18 |
19 | Entity (String name, JSONObject o) {
20 | this.data = o;
21 | this.name = name;
22 | if(o != null){
23 | this.confidence = o.optDouble("confidence");
24 | }
25 | }
26 |
27 | /**
28 | * Returns the name of the entity
29 | * @return The name of the entity
30 | */
31 | public String getName() {
32 | return this.name;
33 | }
34 |
35 | /**
36 | * Returns the confidence of the entity
37 | * @return The confidence of the entity
38 | */
39 | public double getConfidence() {
40 | return this.confidence;
41 | }
42 |
43 |
44 | /**
45 | * Returns the fields described by the parameter if it exists or null otherwise
46 | * @param name The name of the field
47 | * @return The value of the field or null
48 | */
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/MemoryEntity.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class MemoryEntity {
6 |
7 | private String name;
8 | private String raw;
9 | private String value;
10 | private double confidence;
11 |
12 | public MemoryEntity(String entityName, JSONObject obj) {
13 | this.name = entityName;
14 | if(obj != null){
15 | this.raw = obj.optString("raw");
16 | this.value = obj.optString("value");
17 | this.confidence = obj.optDouble("confidence");
18 | }
19 | }
20 |
21 | public MemoryEntity(String raw, String value, double confidence){
22 | this.raw = raw;
23 | this.value = value;
24 | this.confidence = confidence;
25 | }
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | public void setName(String name) {
32 | this.name = name;
33 | }
34 |
35 | public String getRaw() {
36 | return raw;
37 | }
38 |
39 | public void setRaw(String raw) {
40 | this.raw = raw;
41 | }
42 |
43 | public String getValue() {
44 | return value;
45 | }
46 |
47 | public void setValue(String value) {
48 | this.value = value;
49 | }
50 |
51 | public double getConfidence() {
52 | return confidence;
53 | }
54 |
55 | public void setConfidence(double confidence) {
56 | this.confidence = confidence;
57 | }
58 |
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Memory.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import java.util.HashMap;
4 | import java.util.Iterator;
5 | import java.util.Map;
6 |
7 | import org.json.JSONObject;
8 |
9 | public class Memory {
10 |
11 | private Map entities;
12 |
13 |
14 | Memory(JSONObject obj) {
15 |
16 |
17 | this.entities = new HashMap();
18 | if (obj.length() != 0) {
19 | Iterator it = obj.keys();
20 |
21 | while (it.hasNext()) {
22 | String entityName = it.next();
23 | JSONObject entity = obj.optJSONObject(entityName);
24 | if(entity != null){
25 | this.entities.put(entityName, new MemoryEntity(entityName, entity));
26 | }else{
27 | this.entities.put(entityName, null);
28 | }
29 | }
30 | }
31 | }
32 |
33 | public String convertMemory(){
34 | String memory = "{";
35 | for(Map.Entry memap : this.entities.entrySet()){
36 | String entityName = memap.getKey();
37 | MemoryEntity me = memap.getValue();
38 | if(me != null){
39 | memory = memory + "\"" + entityName + "\":{\"raw\":\"" + me.getRaw() +
40 | "\", \"value\":\"" + me.getValue() + "\", \"confidence\":\"" +
41 | me.getConfidence() + "\"},";
42 | }else{
43 | memory = memory + "\"" + entityName + "\": null,";
44 | }
45 | }
46 | memory = memory.substring(0, memory.length()-1);
47 | memory = memory + "}";
48 | return memory;
49 | }
50 |
51 | public void setMemory(String name, MemoryEntity newEntity){
52 | this.entities.put(name, newEntity);
53 | }
54 |
55 | public void resetMemory(){
56 | this.entities = new HashMap();
57 | }
58 |
59 | public Map getEntities() {
60 | return entities;
61 | }
62 |
63 |
64 | public void setEntities(Map entities) {
65 | this.entities = entities;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/SapcaiRecorder.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 |
4 | import android.media.AudioFormat;
5 | import android.media.AudioRecord;
6 | import android.media.MediaRecorder;
7 | import android.util.Log;
8 |
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.io.RandomAccessFile;
12 |
13 | class SapcaiRecorder {
14 |
15 | static final int SAMPLE_RATE = 44100;
16 | static final int CHANNELS = 1;
17 | static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
18 | private AudioRecord mRecorder = null;
19 | private boolean mRecording = false;
20 | private int mBufferSize;
21 | private int mFramePeriod;
22 | private static String TAG = "SapcaiRecorder";
23 | private String mFilepath;
24 | private int mSize;
25 | private byte[] mBuffer;
26 | private RandomAccessFile mFileWriter;
27 |
28 | SapcaiRecorder (String file) throws SapcaiException {
29 | mFilepath = file;
30 | mSize = 0;
31 |
32 | mFramePeriod = SAMPLE_RATE * 120 / 1000;
33 | mBufferSize = mFramePeriod * 2 * 16 * 1 / 8; //2 * samples * channels / 8
34 |
35 | if (mBufferSize < AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, FORMAT)) { // Check to make sure buffer size is not smaller than the smallest allowed one
36 | mBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, FORMAT);
37 | // Set frame period and timer interval accordingly
38 | mFramePeriod = mBufferSize / (2 * 16 * 1 / 8);
39 | Log.w(TAG, "Increasing buffer size to " + Integer.toString(mBufferSize));
40 | }
41 |
42 | mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
43 | SAMPLE_RATE,
44 | AudioFormat.CHANNEL_IN_MONO,
45 | FORMAT,
46 | mBufferSize);
47 |
48 | if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
49 | throw new SapcaiException("Record initialization failed");
50 | }
51 | mBuffer = new byte[mBufferSize];
52 | try {
53 | this.initOutputFile();
54 | } catch (IOException e) {
55 | throw new SapcaiException("Unable to open output file for recording");
56 | }
57 |
58 | AudioRecord.OnRecordPositionUpdateListener updater = new AudioRecord.OnRecordPositionUpdateListener() {
59 | @Override
60 | public void onMarkerReached(AudioRecord recorder) {
61 |
62 | }
63 |
64 | @Override
65 | public void onPeriodicNotification(AudioRecord recorder) {
66 | try {
67 | int read = recorder.read(mBuffer, 0, mBuffer.length);
68 | mSize += read;
69 | mFileWriter.write(mBuffer);
70 | } catch (IOException e) {
71 | Log.w(TAG, "An error occured while reading audio input, aborting");
72 | done();
73 | }
74 | }
75 | };
76 |
77 | mRecorder.setRecordPositionUpdateListener(updater);
78 | mRecorder.setPositionNotificationPeriod(mFramePeriod);
79 | }
80 |
81 | public void initOutputFile() throws IOException {
82 | File f = new File(mFilepath);
83 | if (f.exists()) {
84 | f.delete();
85 | f.createNewFile();
86 | } else {
87 | f.createNewFile();
88 | }
89 | //WAV audio header
90 | mFileWriter = new RandomAccessFile(mFilepath, "rw");
91 | mFileWriter.setLength(0);
92 | mFileWriter.writeBytes("RIFF");
93 | mFileWriter.writeInt(0);
94 | mFileWriter.writeBytes("WAVE");
95 | mFileWriter.writeBytes("fmt ");
96 | mFileWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
97 | mFileWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
98 | mFileWriter.writeShort(Short.reverseBytes((short)1));// Number of channels, 1 for mono, 2 for stereo
99 | mFileWriter.writeInt(Integer.reverseBytes(SAMPLE_RATE)); // Sample rate
100 | mFileWriter.writeInt(Integer.reverseBytes(SAMPLE_RATE * 16 * 1 / 8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8
101 | mFileWriter.writeShort(Short.reverseBytes((short) (CHANNELS * 16 / 8))); // Block align, NumberOfChannels*BitsPerSample/8
102 | mFileWriter.writeShort(Short.reverseBytes((short)16)); // Bits per sample
103 | mFileWriter.writeBytes("data");
104 | mFileWriter.writeInt(0); // Data chunk size not known yet, write 0
105 | mBuffer = new byte[mFramePeriod * 16 / 8 * 1];
106 | }
107 |
108 | public void done() {
109 | if (mRecorder != null) {
110 | mRecorder.stop();
111 | mRecorder.release();
112 | mRecorder = null;
113 | }
114 | }
115 |
116 | public void startRecording() throws SapcaiException {
117 | mRecording = true;
118 | mRecorder.startRecording();
119 | mRecorder.read(mBuffer, 0, mBuffer.length);
120 |
121 | }
122 |
123 | public void stopRecording() throws IOException {
124 | mRecording = false;
125 | done();
126 | mFileWriter.seek(4);
127 | mFileWriter.writeInt(Integer.reverseBytes(36 + mSize));
128 | mFileWriter.seek(40);
129 | mFileWriter.writeInt(Integer.reverseBytes(mSize));
130 | mFileWriter.close();
131 | }
132 |
133 | public boolean isRecording() {
134 | return mRecording;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/MultipartUtility.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import java.io.BufferedReader;
4 |
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 | import java.io.OutputStream;
10 | import java.io.OutputStreamWriter;
11 | import java.io.PrintWriter;
12 | import java.net.HttpURLConnection;
13 | import java.net.URL;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * This utility class provides an abstraction layer for sending multipart HTTP
19 | * POST requests to a web server.
20 | *
21 | */
22 | class MultipartUtility {
23 | private final String boundary;
24 | private static final String LINE_FEED = "\r\n";
25 | private HttpURLConnection httpConn;
26 | private String charset;
27 | private OutputStream outputStream;
28 | private PrintWriter writer;
29 |
30 | /**
31 | * This constructor initializes a new HTTP POST request with content type
32 | * is set to multipart/form-data
33 | * @param requestURL
34 | * @param charset
35 | * @throws IOException
36 | */
37 | public MultipartUtility(String requestURL, String charset, String token)
38 | throws IOException {
39 | this.charset = charset;
40 |
41 | // creates a unique boundary based on time stamp
42 | boundary = "===" + System.currentTimeMillis() + "===";
43 |
44 | URL url = new URL(requestURL);
45 | httpConn = (HttpURLConnection) url.openConnection();
46 | httpConn.setUseCaches(false);
47 | httpConn.setDoOutput(true); // indicates POST method
48 | httpConn.setDoInput(true);
49 | httpConn.setRequestProperty("Content-Type",
50 | "multipart/form-data; boundary=" + boundary);
51 | httpConn.setRequestProperty("Authorization", "Token " + token);
52 | outputStream = httpConn.getOutputStream();
53 | writer = new PrintWriter(new OutputStreamWriter(outputStream, charset),
54 | true);
55 | }
56 |
57 | /**
58 | * Adds a form field to the request
59 | * @param name field name
60 | * @param value field value
61 | */
62 | public void addFormField(String name, String value) {
63 | writer.append("--" + boundary).append(LINE_FEED);
64 | writer.append("Content-Disposition: form-data; name=\"" + name + "\"")
65 | .append(LINE_FEED);
66 | writer.append("Content-Type: text/plain; charset=" + charset).append(
67 | LINE_FEED);
68 | writer.append(LINE_FEED);
69 | writer.append(value);
70 | writer.flush();
71 | }
72 |
73 | /**
74 | * Adds a upload file section to the request
75 | * @param fieldName name attribute in
76 | * @param uploadFile a File to be uploaded
77 | * @throws IOException
78 | */
79 | public void addFilePart(String fieldName, File uploadFile)
80 | throws IOException {
81 | String fileName = uploadFile.getName();
82 | writer.append("--" + boundary).append(LINE_FEED);
83 | writer.append(
84 | "Content-Disposition: form-data; name=\"" + fieldName
85 | + "\"; filename=\"" + fileName + "\"")
86 | .append(LINE_FEED);
87 | writer.append("Content-Type: audio/wav").append(LINE_FEED);
88 | writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
89 | writer.append(LINE_FEED);
90 | writer.flush();
91 |
92 | FileInputStream inputStream = new FileInputStream(uploadFile);
93 | byte[] buffer = new byte[4096];
94 | int bytesRead = -1;
95 | while ((bytesRead = inputStream.read(buffer)) != -1) {
96 | outputStream.write(buffer, 0, bytesRead);
97 | }
98 | outputStream.flush();
99 | inputStream.close();
100 |
101 | writer.append(LINE_FEED);
102 | writer.flush();
103 | }
104 |
105 | /**
106 | * Adds a header field to the request.
107 | * @param name - name of the header field
108 | * @param value - value of the header field
109 | */
110 | public void addHeaderField(String name, String value) {
111 | writer.append(name + ": " + value).append(LINE_FEED);
112 | writer.flush();
113 | }
114 |
115 | /**
116 | * Completes the request and receives response from the server.
117 | * @return a list of Strings as response in case the server returned
118 | * status OK, otherwise an exception is thrown.
119 | * @throws IOException
120 | */
121 | public List finish() {
122 | List response = new ArrayList();
123 |
124 | writer.append(LINE_FEED).flush();
125 | writer.append("--" + boundary + "--").append(LINE_FEED);
126 | writer.close();
127 |
128 | try {
129 | // checks server's status code first
130 | int status = httpConn.getResponseCode();
131 | if (status == HttpURLConnection.HTTP_OK) {
132 | BufferedReader reader = new BufferedReader(new InputStreamReader(
133 | httpConn.getInputStream()));
134 | String line = null;
135 | while ((line = reader.readLine()) != null) {
136 | response.add(line);
137 | }
138 | reader.close();
139 | httpConn.disconnect();
140 | } else {
141 | throw new SapcaiException(status);
142 | }
143 | } catch (IOException e) {
144 | throw new SapcaiException("Request Failed", e);
145 | }
146 | return response;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Request.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import android.os.Environment;
4 | import java.net.URL;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 | import java.io.OutputStream;
10 | import javax.net.ssl.HttpsURLConnection;
11 | import java.net.MalformedURLException;
12 | import java.io.File;
13 | import java.util.HashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | public class Request {
18 |
19 | private static final String sapcaiAPI = "https://api.cai.tools.sap/v2/request";
20 | private static final String converseAPI = "https://api.cai.tools.sap/v2/converse";
21 |
22 | public String token;
23 | public String language;
24 |
25 | public Request(String token){
26 | this.token = token;
27 | }
28 |
29 | public Request(String token, String language){
30 | this.token = token;
31 | this.language = language;
32 | }
33 |
34 | /**
35 | * Performs a text request to SAP Conversational AI with the token of the Client
36 | * @param myText The text to be processed
37 | * @return The Response corresponding to the input
38 | * @throws SapcaiException if SAP Conversational AI can't process the text
39 | * @see Response
40 | */
41 | public Response doTextRequest(String myText) throws SapcaiException {
42 | URL obj;
43 | try {
44 | obj = new URL(sapcaiAPI);
45 | String sapcaiJson = this.doApiRequest(myText, this.token, this.language, obj);
46 | return new Response(sapcaiJson);
47 | } catch (MalformedURLException e) {
48 | throw new SapcaiException("Invalid URL", e);
49 | }
50 | }
51 |
52 | /**
53 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format.
54 | * @param myfile The name of the file
55 | * @return The Response corresponding to your input
56 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file
57 | */
58 | public Response doFileRequest (String myfile) throws SapcaiException {
59 | return new Response(this.sendAudioFile(myfile, this.token, this.language));
60 | }
61 |
62 | public Conversation doTextConverse (String myText) {
63 | URL obj;
64 | try {
65 | obj = new URL(converseAPI);
66 | String sapcaiJson = this.doApiRequest(myText, this.token, this.language, obj);
67 | return new Conversation(sapcaiJson, this.token);
68 | } catch (MalformedURLException e) {
69 | throw new SapcaiException("Invalid URL", e);
70 | }
71 | }
72 |
73 | private String sendAudioFile(String name, String token, String language) throws SapcaiException {
74 | String sapcaiJson = "";
75 | StringBuilder sb;
76 | try {
77 | MultipartUtility multipart = new MultipartUtility(sapcaiAPI, "UTF-8", token);
78 | File f = new File(name);
79 | if (!f.exists()) {
80 | throw new SapcaiException("File not found: " + name);
81 | }
82 | multipart.addFilePart("voice", f);
83 | if (language != null) {
84 | multipart.addFormField("language", language);
85 | } else if (this.language != null) {
86 | multipart.addFormField("language", this.language);
87 | }
88 | List response = multipart.finish();
89 | sb = new StringBuilder(response.size() * 2);
90 | for (String line : response) {
91 | sb.append(line);
92 | }
93 | } catch (Exception e) {
94 | e.printStackTrace();
95 | throw new SapcaiException("Error during request", e);
96 | }
97 | return sb.toString();
98 | }
99 |
100 | public String doApiRequest(String text, String token, String language, URL obj) throws SapcaiException {
101 | // URL obj;
102 | HttpsURLConnection con;
103 | OutputStream os;
104 | int responseCode;
105 | String inputLine;
106 | StringBuffer responseBuffer;
107 | String sapcaiJson;
108 |
109 | try {
110 | // obj = new URL(sapcaiAPI);
111 | con = (HttpsURLConnection) obj.openConnection();
112 | con.setRequestMethod("POST");
113 | con.setRequestProperty("Authorization", "Token " + token);
114 | con.setDoOutput(true);
115 | text = "text=" + text;
116 | os = con.getOutputStream();
117 | os.write(text.getBytes());
118 | if (language != null) {
119 | String l = "&language=" + language;
120 | os.write(l.getBytes());
121 | }
122 | os.flush();
123 | os.close();
124 |
125 | responseCode = con.getResponseCode();
126 | } catch (MalformedURLException e) {
127 | throw new SapcaiException("Invalid URL", e);
128 | } catch (IOException e) {
129 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
130 | }
131 |
132 | if (responseCode == HttpsURLConnection.HTTP_OK) {
133 | try {
134 | BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
135 | responseBuffer = new StringBuffer();
136 | while ((inputLine = reader.readLine()) != null) {
137 | responseBuffer.append(inputLine);
138 | }
139 | reader.close();
140 | } catch (IOException e) {
141 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
142 | }
143 | sapcaiJson = responseBuffer.toString();
144 | } else {
145 | System.out.println(responseCode);
146 | throw new SapcaiException(responseCode);
147 | }
148 | return sapcaiJson;
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/test/java/ai/sapcai/sdk_android/SdkTests.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import static org.mockito.Mockito.*;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.Mock;
7 | import org.mockito.exceptions.misusing.UnfinishedStubbingException;
8 | import org.mockito.runners.MockitoJUnitRunner;
9 | import static org.junit.Assert.*;
10 |
11 | @RunWith(MockitoJUnitRunner.class)
12 | public class SdkTests {
13 | static final String FAKE_JSON = "{" +
14 | " \"results\": {" +
15 | " \"source\": \"What is the weather in London tomorrow? And in Paris?\"," +
16 | " \"intents\": [" +
17 | " {" +
18 | " \"name\": \"weather\"," +
19 | " \"confidence\": 0.88" +
20 | " }" +
21 | " ]," +
22 | " \"act\": \"wh-query\"," +
23 | " \"type\": \"desc:desc\"," +
24 | " \"sentiment\": \"neutral\"," +
25 | " \"entities\": {" +
26 | " \"action\": [" +
27 | " {" +
28 | " \"agent\": \"the weather in london\"," +
29 | " \"tense\": \"present\"," +
30 | " \"raw\": \"is\"," +
31 | " \"confidence\": 0.97" +
32 | " }" +
33 | " ]," +
34 | " \"location\": [" +
35 | " {" +
36 | " \"formated\": \"London, london, Greater London, England, United Kingdom\"," +
37 | " \"lng\": -0.1277583," +
38 | " \"lat\": 51.5073509," +
39 | " \"raw\": \"London\"," +
40 | " \"confidence\": 0.97" +
41 | " }," +
42 | " {" +
43 | " \"formated\": \"Paris, Paris, Ile-de-France, France\"," +
44 | " \"lng\": 2.3522219," +
45 | " \"lat\": 48.856614," +
46 | " \"raw\": \"Paris\"," +
47 | " \"confidence\": 0.83" +
48 | " }" +
49 | " ]," +
50 | " \"datetime\": [" +
51 | " {" +
52 | " \"value\": \"2016-07-11T10:00:00+00:00\"," +
53 | " \"raw\": \"tomorrow\"," +
54 | " \"confidence\": 0.83" +
55 | " }" +
56 | " ]" +
57 | " }," +
58 | " \"language\": \"en\"," +
59 | " \"processing_language\": \"en\"," +
60 | " \"version\": \"2.0.0\"," +
61 | " \"timestamp\": \"\"," +
62 | " \"uuid\": \"34b3f548-4aaf-4e3a-add1-f8f29f30e7fb\"," +
63 | " \"status\": 200" +
64 | " }," +
65 | " \"message\": \"Requests rendered with success\"" +
66 | "}";
67 |
68 | static final String INVALID_JSON = "{" +
69 | "},";
70 | @Mock
71 | Client c = new Client("token", "en");
72 |
73 | @Test
74 | public void testResponseGetters() {
75 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON));
76 | Response r = c.textRequest("text");
77 |
78 | assertTrue(r != null);
79 | assertTrue(r.getSource().equals("What is the weather in London tomorrow? And in Paris?"));
80 | assertTrue(r.getVersion().equals("2.0.0"));
81 | assertTrue(r.getRaw().equals(FAKE_JSON));
82 |
83 | assertTrue(r.getAct().equals(Response.ACT_WH_QUERY));
84 | assertTrue(r.getType().equals("desc:desc"));
85 | assertTrue(r.getSentiment().equals(Response.SENTIMENT_NEUTRAL));
86 |
87 | assertTrue(r.getEntities("location").length == 2);
88 | assertTrue(r.getEntities("datetime").length == 1);
89 | assertTrue(r.getEntities("fakename") == null);
90 | assertTrue(r.getEntity("fakename") == null);
91 | assertTrue(r.getStatus() == 200);
92 | assertTrue(r.getLanguage().equals("en"));
93 | assertTrue(r.getProcessingLanguage().equals("en"));
94 | assertTrue(r.getUuid().equals("34b3f548-4aaf-4e3a-add1-f8f29f30e7fb"));
95 |
96 | assertTrue(r.getIntents().length == 1);
97 | }
98 |
99 | @Test
100 | public void testResponseHelpers() {
101 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON));
102 | Response r = c.textRequest("text");
103 |
104 | assertTrue(r.isWhQuery());
105 |
106 | assertTrue(r != null);
107 | assertFalse(r.isAssert());
108 | assertFalse(r.isCommand());
109 | assertFalse(r.isYesNoQuery());
110 |
111 | assertTrue(r.isDescription());
112 |
113 | assertFalse(r.isPositive());
114 | assertFalse(r.isNegative());
115 | assertTrue(r.isNeutral());
116 | assertFalse(r.isVeryPositive());
117 | assertFalse(r.isVeryNegative());
118 |
119 | assertFalse(r.isAbbreviation());
120 | assertFalse(r.isEntity());
121 | assertFalse(r.isHuman());
122 | assertFalse(r.isLocation());
123 | assertFalse(r.isNumber());
124 | }
125 |
126 | @Test
127 | public void testEntities() {
128 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON));
129 | Response r = c.textRequest("text");
130 |
131 | Entity london = r.getEntity("location");
132 | assertTrue(london.getName().equals("location"));
133 | assertTrue((Double)london.getField("lng") == -0.1277583);
134 | assertTrue((Double)london.getField("lat") == 51.5073509);
135 | assertTrue(london.getConfidence() == 0.97);
136 | }
137 |
138 | @Test
139 | public void testIntent() {
140 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON));
141 | Response r = c.textRequest("text");
142 |
143 | Intent i = r.getIntent();
144 | assertTrue(i.getConfidence() == 0.88);
145 | assertTrue(i.getName().equals("weather"));
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Response.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.json.*;
4 |
5 | import java.util.Iterator;
6 | import java.util.Map;
7 | import java.util.HashMap;
8 | import java.util.regex.Pattern;
9 | import java.util.regex.Matcher;
10 |
11 | /**
12 | * The Response class handles responses from SAP Conversational AI API
13 | *
14 | * @author Francois Triquet
15 | * @version 2.0.0
16 | * @since 2016-05-17
17 | *
18 | */
19 | public class Response {
20 | private String source;
21 | private Intent[] intents;
22 | private Map entities;
23 | private String version;
24 | private String timestamp;
25 | private String raw;
26 | private int status;
27 | private String language;
28 | private String processing_language;
29 | private String uuid;
30 |
31 |
32 | private String sentiment;
33 | private String act;
34 | private String type;
35 | private String subtype;
36 |
37 | public static final String ACT_ASSERT = "assert";
38 | public static final String ACT_COMMAND = "command";
39 | public static final String ACT_WH_QUERY = "wh-query";
40 | public static final String ACT_YN_QUERY = "yn-query";
41 |
42 | public static final String TYPE_ABBREVIATION = "abbr:";
43 | public static final String TYPE_ENTITY = "enty:";
44 | public static final String TYPE_DESCRIPTION = "desc:";
45 | public static final String TYPE_HUMAN = "hum:";
46 | public static final String TYPE_NUMBER = "loc:";
47 | public static final String TYPE_LOCATION = "num:";
48 |
49 | public static final String SENTIMENT_POSITIVE = "positive";
50 | public static final String SENTIMENT_VERY_POSITIVE = "vpositive";
51 | public static final String SENTIMENT_NEGATIVE = "negative";
52 | public static final String SENTIMENT_VERY_NEGATIVE = "vnegative";
53 | public static final String SENTIMENT_NEUTRAL = "neutral";
54 |
55 |
56 | Response(String json) throws SapcaiException {
57 | JSONArray resultIntents = null;
58 | JSONObject result;
59 | Pattern typePattern;
60 | this.raw = json;
61 |
62 | try {
63 | result = new JSONObject(json).getJSONObject("results");
64 |
65 | this.source = result.getString("source");
66 | this.version = result.getString("version");
67 | this.timestamp = result.getString("timestamp");
68 | this.status = result.getInt("status");
69 | this.language = result.getString("language");
70 | this.processing_language = result.getString("processing_language");
71 | this.type = result.optString("type", null);
72 | this.act = result.getString("act");
73 | this.sentiment = result.getString("sentiment");
74 | this.uuid = result.getString("uuid");
75 |
76 |
77 | JSONObject resultEntities = result.optJSONObject("entities");
78 | this.entities = new HashMap();
79 | if (resultEntities.length() != 0) {
80 | Iterator it = resultEntities.keys();
81 |
82 | while (it.hasNext()) {
83 | String entityName = it.next();
84 | JSONArray entity = resultEntities.optJSONArray(entityName);
85 |
86 | Entity[] values = new Entity[entity.length()];
87 | for (int i = 0; i < values.length; i++) {
88 | values[i] = new Entity(entityName, entity.optJSONObject(i));
89 | }
90 | this.entities.put(entityName, values);
91 | }
92 | }
93 |
94 | resultIntents = result.getJSONArray("intents");
95 | this.intents = new Intent[resultIntents.length()];
96 | for (int i = 0; i < this.intents.length; ++i) {
97 | this.intents[i] = new Intent(resultIntents.getJSONObject(i));
98 | }
99 |
100 | if(this.type != null){
101 | typePattern = Pattern.compile("(\\w+:).*");
102 | Matcher m = typePattern.matcher(this.type);
103 | if (m.find()) {
104 | this.subtype = m.group(1);
105 | } else {
106 | this.subtype = this.type;
107 | }
108 | }
109 | } catch (Exception e) {
110 | throw new SapcaiException("Invalid JSON", e);
111 | }
112 | }
113 |
114 | /**
115 | * Returns the user input
116 | * @return The input sent to SAP Conversational AI
117 | */
118 | public String getSource() {
119 | return this.source;
120 | }
121 |
122 | /**
123 | * Returns the uuid of the response
124 | * @return The uuid of the response
125 | */
126 | public String getUuid() { return this.uuid; }
127 |
128 | /**
129 | * Returns the intent that matches with the input or null otherwise
130 | * @return The matched intent
131 | */
132 | public Intent getIntent() {
133 | if (this.intents.length > 0) {
134 | return this.intents[0];
135 | }
136 | return null;
137 | }
138 |
139 | public String getLanguage() {
140 | return this.language;
141 | }
142 |
143 | public String getProcessingLanguage() {
144 | return this.processing_language;
145 | }
146 |
147 | /**
148 | * Returns an array of all the intents, ordered by propability
149 | * @return All matched intents
150 | */
151 | public Intent[] getIntents() {
152 | return this.intents;
153 | }
154 |
155 | /**
156 | * Returns the json received from SAP Conversational AI
157 | * @return The raw json string
158 | */
159 | public String getRaw() {
160 | return this.raw;
161 | }
162 |
163 |
164 | /**
165 | * Returns the status of the request
166 | * @return The request status
167 | */
168 | public int getStatus() {
169 | return this.status;
170 | }
171 |
172 | /**
173 | * Returns the version of the JSON
174 | * @return The SAP Conversational AI version
175 | */
176 | public String getVersion() {
177 | return this.version;
178 | }
179 |
180 | /**
181 | * Returns the timestamp of the request
182 | * @return The timestampt of the request
183 | */
184 | public String getTimestamp() {
185 | return this.timestamp;
186 | }
187 |
188 | /**
189 | * Returns the first entity that matches with the parameter if ther is one, or null otherwise
190 | * @param name The name of the entity
191 | * @return The entity matched or null
192 | * @see Entity
193 | */
194 | public Entity getEntity(String name) {
195 | Entity e;
196 |
197 | Entity[] ents = this.entities.get(name);
198 | if (ents == null || ents.length == 0) {
199 | return null;
200 | }
201 | return ents[0];
202 | }
203 |
204 | /**
205 | * Returns an array of all the entities in the input if there are some, or null otherwise
206 | * @param name The name of the Entity
207 | * @return An array of entities
208 | * @see Entity
209 | */
210 | public Entity[] getEntities(String name) {
211 | return this.entities.get(name);
212 | }
213 |
214 | public String getAct() {
215 | return act;
216 | }
217 |
218 | public String getType() {
219 | return type;
220 | }
221 |
222 | public String getSentiment() {
223 | return sentiment;
224 | }
225 |
226 | public boolean isCommand() {
227 | return this.act.equals(ACT_COMMAND);
228 | }
229 |
230 | public boolean isAssert() {
231 | return this.act.equals(ACT_ASSERT);
232 | }
233 |
234 | public boolean isWhQuery() {
235 | return this.act.equals(ACT_WH_QUERY);
236 | }
237 |
238 | public boolean isYesNoQuery() {
239 | return this.act.equals(ACT_YN_QUERY);
240 | }
241 |
242 | public boolean isPositive() { return this.sentiment.equals(SENTIMENT_POSITIVE); }
243 |
244 | public boolean isVeryPositive() {
245 | return this.sentiment.equals(SENTIMENT_VERY_POSITIVE);
246 | }
247 |
248 | public boolean isNeutral() {
249 | return this.sentiment.equals(SENTIMENT_NEUTRAL);
250 | }
251 |
252 | public boolean isNegative() {
253 | return this.sentiment.equals(SENTIMENT_NEGATIVE);
254 | }
255 |
256 | public boolean isVeryNegative() {
257 | return this.sentiment.equals(SENTIMENT_VERY_NEGATIVE);
258 | }
259 |
260 | public boolean isAbbreviation() {
261 | return this.subtype.equals(TYPE_ABBREVIATION);
262 | }
263 |
264 | public boolean isEntity() { return this.subtype.equals(TYPE_ENTITY); }
265 |
266 | public boolean isDescription() { return this.subtype.equals(TYPE_DESCRIPTION); }
267 |
268 | public boolean isHuman() {
269 | return this.subtype.equals(TYPE_HUMAN);
270 | }
271 |
272 | public boolean isLocation() {
273 | return this.subtype.equals(TYPE_LOCATION);
274 | }
275 |
276 | public boolean isNumber() {
277 | return this.subtype.equals(TYPE_NUMBER);
278 | }
279 |
280 | }
281 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SAP Conversational AI - SDK-Android
2 | 
3 |
4 | # 🚨 Sunset of Open Source SDKs for SAP Conversational AI
5 |
6 | SAP Conversational AI provides several SDKs, which are all open-source and hosted on GitHub.
7 | Starting from January 2021, please note that we inform you that the SDKs will not be available anymore and the public repository of the project will be archived from GitHub.
8 |
9 | ## ✨ Why are we sunsetting our SDKs?
10 |
11 | Firstly, we noticed over the past year that these SDKs were not used much by our users.
12 | This is because our platform usage has become easier, including the APIs.
13 |
14 | Secondly, our APIs have undergone major changes. We would need to adapt the SDKs in order to keep them working, which will lead to a significant cost from our side.
15 |
16 | Hence, we decided to sunset this open source version starting from January 2021.
17 |
18 | ## ✨ What does it mean for me as a user?
19 |
20 | Any changes in our API’s will not be reflected in our SDKs. Hence, the code might not work unless you adjust the same.
21 |
22 | ## ✨ What are the next steps?
23 |
24 | If you are interested in taking the ownership of the project on GitHub, please get in touch with us and we can discuss the process. Otherwise, if there are no objections from anyone, we would archive the project following the open source sunset process.
25 |
26 | Please use the platform SAP Answers if you have any other questions related to this topic.
27 |
28 | Happy bot building!
29 |
30 | The SAP Conversational AI team
31 |
32 | ---
33 |
34 |
35 | ## Synopsis
36 | This module is an Android interface to the [SAP Conversational AI](https://cai.tools.sap) API. It allows you to make requests to your bots.
37 |
38 | ## Installation
39 | Just update your build.gradle files:
40 |
41 | In your module:
42 | ```gradle
43 | dependencies {
44 | compile 'ai.sapcai.sdk_android:sdk-android:4.0.0'
45 | }
46 | ```
47 |
48 | ## Usage
49 | ```java
50 | import ai.sapcai.sdk_android.Client;
51 |
52 | Client client = new Client(YOUR_TOKEN);
53 | Response resp;
54 |
55 | try {
56 | resp = client.textRequest(YOUR_TEXT);
57 | } catch (SapcaiException e) {
58 | // error
59 | }
60 | ```
61 |
62 | ## Specs
63 | ### Classes
64 |
65 | This module contains 9 main classes as follows:
66 | * Client is the client allowing you to make requests to SAP Conversational AI API.
67 | * Response contains the response from [SAP Conversational AI](https://cai.tools.sap).
68 | * Entity represents an entity found in you user's input as defined in [SAP Conversational AI Manual](https://cai.tools.sap/docs/#list-of-entities)
69 | * Intent represents the intent of your user
70 | * Action represents the actions of a conversation
71 | * Memory represents the memory of a conversation
72 | * MemoryEntity represents the entity inside the memory object
73 | * Conversation allowing you to handle a conversation
74 | * SapcaiException is the error thrown by the module.
75 |
76 | ### Class Client
77 | The SAP Conversational AI Client can be instanciated with a token and provides the following methods:
78 |
79 | #### Request:
80 | * textRequest(String text)
81 | * fileRequest(String filename)
82 |
83 | These methods both return a Response object.
84 |
85 | #### Conversation
86 | * textConverse(String text)
87 |
88 | This method return a Conversation object.
89 |
90 | #### Audio Recording:
91 | * startRecording() *Starts the audio recording to a file*
92 | * stopRecording() *Stops the audio recording, sends the audio to SAP Conversational AI and returns a Response object*
93 |
94 | Note that all these methods should be called in separated tasks because they do http requests.
95 |
96 |
97 | ```java
98 | import ai.sapcai.sdk_android.Client;
99 |
100 | Client client = new Client(YOUR_TOKEN);
101 | Response resp;
102 |
103 | try {
104 | resp = client.textRequest("Hello World!");
105 | // Do your code...
106 | } catch (Exception e) {
107 | // Handle error
108 | }
109 |
110 | try {
111 | resp = client.fileRequest("my_file.wav");
112 | // Do you code..
113 | } catch (Exception e) {
114 | // Handle error
115 | }
116 |
117 | try {
118 | Conversation conversation = client.textConverse("Hello, my name is Paul");
119 | } catch (Exception e) {
120 | // Handle error
121 | }
122 | ```
123 |
124 | ### Class Response
125 | The SAP Conversational AI Response is generated after a call with the Client methods and contains the following methods:
126 | * getAct() *Returns the act of the sentence*
127 | * getType() *Returns the type of the sentence*
128 | * getSentiment() *Returns the sentiment of the sentence*
129 | * getEntity(String name) *Returns the first entity matching -name- or null*
130 | * getEntities(String name) *Returns an array of all entities matching -name- or null*
131 | * getStatus() *Returns the status of the Response*
132 | * getSource() *Returns the source of the input*
133 | * getIntent() *Returns the main intent detected by SAP Conversational AI*
134 | * getIntents() *Returns all intents ordered by probability*
135 | * getVersion() *Returns the version of the JSON*
136 | * getUuid() *Returns the uuid of the response*
137 | * IsAbbreviation() *IsAbbreviation returns whether or not the sentence is asking for an abbreviation*
138 | * IsEntity() *IsEntity returns whether or not the sentence is asking for an entity*
139 | * IsDescription() *IsDescription returns whether or not the sentence is asking for an description*
140 | * IsHuman() *IsHuman returns whether or not the sentence is asking for an human*
141 | * IsLocation() *IsLocation returns whether or not the sentence is asking for an location*
142 | * IsNumber() *IsNumber returns whether or not the sentence is asking for an number*
143 | * IsPositive() *IsPositive returns whether or not the sentiment is positive*
144 | * IsVeryPositive() *IsVeryPositive returns whether or not the sentiment is very positive*
145 | * IsNeutral() *IsNeutral returns whether or not the sentiment is neutral*
146 | * IsNegative() *IsNegative returns whether or not the sentiment is negative*
147 | * IsVeryNegative() *IsVeryNegative returns whether or not the sentiment is very negative*
148 | * IsAssert() *IsAssert returns whether or not the sentence is an assertion*
149 | * IsCommand() *IsCommand returns whether or not the sentence is a command*
150 | * IsWhQuery() *IsWhQuery returns whether or not the sentence is a wh query*
151 | * IsYnQuery() *IsYnQuery returns whether or not the sentence is a yes-no question*
152 | * Get(name string) *Returns the first entity matching -name-*
153 | * All(name string) *Returns all entities matching -name-*
154 |
155 |
156 | ```java
157 | resp = client.textRequest("Give me a recipe with Asparagus.");
158 | String intent = resp.getIntent();
159 | if (intent != null && intent.equals("recipe")) {
160 | //get all the entities matching 'ingredient'
161 | Entities[] entities = resp.getEntities("ingredient");
162 |
163 | // ...
164 | }
165 | ```
166 |
167 | ### Class Entity
168 | The SAP Conversational AI Entity is returned by Response and provides the following methods:
169 | * String getName() *Returns the name of the entity*
170 | * String getRaw() *Returns the raw text on which the entity was detected*
171 | * Object getField(String fieldName)
172 |
173 |
174 | In addition to getName and getRaw, more attributes can be accessed by the getField method which can be one of the following:
175 | * hex
176 | * value
177 | * deg
178 | * formatted
179 | * lng
180 | * lat
181 | * unit
182 | * code
183 | * person
184 | * number
185 | * gender
186 | * next
187 | * grain
188 | * order
189 |
190 | SAP Conversational AI entity fields types are dependant on the entity itself, so value returned must be casted depending on the entity you wait.
191 |
192 | Refer to [SAP Conversational AI Entities Manual](https://cai.tools.sap/docs/#list-of-entities) for details about entities
193 |
194 | ```java
195 | Response resp = client.textRequest("What's the weather in San Francisco?");
196 | if (resp.getIntent() != null && resp.getIntent().equals("weather")) {
197 | Entity e = resp.getEntity("location");
198 | if (e != null) {
199 | System.out.printf("You asked me for a weather in %s\n", (String)e.getField("formated"));
200 | }
201 | }
202 | ```
203 |
204 | ### Class SapcaiException
205 | This exception is thrown when an error occurs during the request
206 |
207 | # More
208 |
209 | You can view the whole API reference at [cai.tools.sap/docs/api-reference](https://cai.tools.sap/docs/api-reference).
210 |
211 | You can follow us on Twitter at [@sapcai](https://twitter.com/sapcai) for updates and releases.
212 |
213 | ## License
214 |
215 | Copyright (c) [2019] [SAP Conversational AI](https://cai.tools.sap)
216 |
217 | Permission is hereby granted, free of charge, to any person obtaining a copy
218 | of this software and associated documentation files (the "Software"), to deal
219 | in the Software without restriction, including without limitation the rights
220 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
221 | copies of the Software, and to permit persons to whom the Software is
222 | furnished to do so, subject to the following conditions:
223 |
224 | The above copyright notice and this permission notice shall be included in all
225 | copies or substantial portions of the Software.
226 |
227 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
228 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
229 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
230 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
231 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
232 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
233 | SOFTWARE.
234 |
--------------------------------------------------------------------------------
/sdk-android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Client.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 |
4 | import android.os.Environment;
5 | import java.net.URL;
6 |
7 | import java.io.BufferedReader;
8 | import java.io.IOException;
9 | import java.io.InputStreamReader;
10 | import java.io.OutputStream;
11 | import javax.net.ssl.HttpsURLConnection;
12 | import java.net.MalformedURLException;
13 | import java.io.File;
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | * The Client class handles requests to SAP Conversational AI API.
20 | * Note that requests methods and stopRecording should not be called in the main thread of your application because they process http requests.
21 | *
22 | * @author Francois Triquet
23 | * @version 2.0.0
24 | * @since 2016-05-17
25 | *
26 | */
27 | public class Client {
28 | private static final String sapcaiAPI = "https://api.cai.tools.sap/v2/request";
29 | private String token;
30 | private String language;
31 | private SapcaiRecorder recorder;
32 |
33 | public Request request;
34 |
35 | /**
36 | * Initialize a SAP Conversational AI Client with a authentication token
37 | * @param token Your token from SAP Conversational AI
38 | */
39 | public Client(String token) {
40 | this.token = token;
41 | this.language = null;
42 | this.recorder = null;
43 |
44 | this.request = new Request(token);
45 | }
46 |
47 | public Client(String token, String language) {
48 | this.token = token;
49 | this.language = language;
50 | this.recorder = null;
51 |
52 | this.request = new Request(token, language);
53 | }
54 |
55 | /**
56 | * Initialize a SAP Conversational AI Client without authentication token
57 | */
58 | public Client() {
59 | this("", null);
60 | }
61 |
62 | /**
63 | * Sets the token of the Client
64 | * @param token The token to authenticate to SAP Conversational AI
65 | */
66 | public void setToken(String token) {
67 | this.token = token;
68 | }
69 |
70 | /**
71 | * Sets the language of the Client
72 | * @param language The language for the language processing
73 | */
74 | public void setLanguage(String language) {
75 | this.language = language;
76 | }
77 |
78 | private static String getOutputFile() {
79 | File sapcaiDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/SAPConversationalAI");
80 | if (!sapcaiDir.exists())
81 | sapcaiDir.mkdir();
82 | String filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
83 | return filepath + "/SAPConversationalAI/sapcai_audio.wav";
84 | }
85 |
86 | /**
87 | * Starts recording audio from the microphone. Note that the audio must be shorter than 10 seconds to be processed by SAP Conversational AI
88 | * @throws SapcaiException if the client is already recording (the recording will stop)
89 | */
90 | public synchronized void startRecording() throws SapcaiException {
91 | if (recorder != null) {
92 | try {
93 | this.stopRecording();
94 | } catch (Exception ignore) {}
95 | throw new SapcaiException("Invalid recording state");
96 | }
97 | recorder = new SapcaiRecorder(getOutputFile());
98 | recorder.startRecording();
99 | }
100 |
101 | /**
102 | * Stops recording from the microphone and returns the Response corresponding to the audio input after beeing processed
103 | * @return A SAP Conversational AI Response
104 | * @throws SapcaiException if the Client is not recording of if an error occurs (invalid audio...)
105 | * @see Response
106 | */
107 | public synchronized Response stopRecording() throws SapcaiException {
108 | Response r;
109 |
110 | if (this.recorder == null || !this.recorder.isRecording()) {
111 | throw new SapcaiException("Illegal recording state");
112 | }
113 | try {
114 | recorder.stopRecording();
115 | File f = new File(getOutputFile());
116 | r = fileRequest(getOutputFile());
117 | } catch (IOException e) {
118 | throw new SapcaiException("Unable to record audio", e);
119 | } finally {
120 | recorder = null;
121 | }
122 | return r;
123 | }
124 |
125 | /**
126 | * Performs a text request to SAP Conversational AI
127 | * @param text The text to be processed
128 | * @param options A map of parameters to the request. Parameters can be "token" and "language"
129 | * @return The Response corresponding to the input
130 | * @throws SapcaiException if SAP Conversational AI can't process the text
131 | * @see Response
132 | */
133 | public Response textRequest(String text, Map options) throws SapcaiException {
134 | String sapcaiJson;
135 | String token;
136 | String language;
137 |
138 | token = options.get("token");
139 | if (token == null)
140 | token = this.token;
141 | language = options.get("language");
142 | if (language == null)
143 | language = this.language;
144 | sapcaiJson = this.doApiRequest(text, token, language);
145 | return new Response(sapcaiJson);
146 | }
147 |
148 | /**
149 | * Performs a text request to SAP Conversational AI with the token of the Client
150 | * @param text The text to be processed
151 | * @return The Response corresponding to the input
152 | * @throws SapcaiException if SAP Conversational AI can't process the text
153 | * @see Response
154 | */
155 | public Response textRequest(String text) throws SapcaiException {
156 | Map params = new HashMap<>();
157 | params.put("language", this.language);
158 | params.put("token", this.token);
159 | return this.textRequest(text, params);
160 | }
161 |
162 |
163 |
164 | private String sendAudioFile(String name, String token, String language) throws SapcaiException {
165 | String sapcaiJson = "";
166 | StringBuilder sb;
167 | try {
168 | MultipartUtility multipart = new MultipartUtility(sapcaiAPI, "UTF-8", token);
169 | File f = new File(name);
170 | if (!f.exists()) {
171 | throw new SapcaiException("File not found: " + name);
172 | }
173 | multipart.addFilePart("voice", f);
174 | if (language != null) {
175 | multipart.addFormField("language", language);
176 | } else if (this.language != null) {
177 | multipart.addFormField("language", this.language);
178 | }
179 | List response = multipart.finish();
180 | sb = new StringBuilder(response.size() * 2);
181 | for (String line : response) {
182 | sb.append(line);
183 | }
184 | } catch (Exception e) {
185 | e.printStackTrace();
186 | throw new SapcaiException("Error during request", e);
187 | }
188 | return sb.toString();
189 | }
190 |
191 | /**
192 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format.
193 | * @param filename The name of the file
194 | * @return The Response corresponding to your input
195 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file
196 | */
197 | public Response fileRequest(String filename) throws SapcaiException {
198 | return new Response(this.sendAudioFile(filename, this.token, null));
199 | }
200 |
201 | /**
202 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format.
203 | * @param filename The name of the file
204 | * @param options A map of parameters for the request. This map can contains "token" and/or "language".If a parameter is not provided, the request will use the token or language of the Client. I it has not language, the language used will be the default language of the corresponding bot
205 | * @return The Response corresponding to your input
206 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file
207 | */
208 | public Response fileRequest(String filename, Map options) throws SapcaiException {
209 | String token;
210 | String language;
211 |
212 | token = options.get("token");
213 | if (token == null)
214 | token = this.token;
215 | language = options.get("language");
216 | if (language == null)
217 | language = this.language;
218 | String resp = sendAudioFile(filename, token, language);
219 | return new Response(resp);
220 | }
221 |
222 |
223 | public String doApiRequest(String text, String token, String language) throws SapcaiException {
224 | URL obj;
225 | HttpsURLConnection con;
226 | OutputStream os;
227 | int responseCode;
228 | String inputLine;
229 | StringBuffer responseBuffer;
230 | String sapcaiJson;
231 |
232 | try {
233 | obj = new URL(sapcaiAPI);
234 | con = (HttpsURLConnection) obj.openConnection();
235 | con.setRequestMethod("POST");
236 | con.setRequestProperty("Authorization", "Token " + token);
237 | con.setDoOutput(true);
238 | text = "text=" + text;
239 | os = con.getOutputStream();
240 | os.write(text.getBytes());
241 | if (language != null) {
242 | String l = "&language=" + language;
243 | os.write(l.getBytes());
244 | }
245 | os.flush();
246 | os.close();
247 |
248 | responseCode = con.getResponseCode();
249 | } catch (MalformedURLException e) {
250 | throw new SapcaiException("Invalid URL", e);
251 | } catch (IOException e) {
252 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
253 | }
254 |
255 | if (responseCode == HttpsURLConnection.HTTP_OK) {
256 | try {
257 | BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
258 | responseBuffer = new StringBuffer();
259 | while ((inputLine = reader.readLine()) != null) {
260 | responseBuffer.append(inputLine);
261 | }
262 | reader.close();
263 | } catch (IOException e) {
264 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
265 | }
266 | sapcaiJson = responseBuffer.toString();
267 | } else {
268 | System.out.println(responseCode);
269 | throw new SapcaiException(responseCode);
270 | }
271 | return sapcaiJson;
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/main/java/ai/sapcai/sdk_android/Conversation.java:
--------------------------------------------------------------------------------
1 | package ai.sapcai.sdk_android;
2 |
3 | import org.apache.http.HttpResponse;
4 | import org.apache.http.ParseException;
5 | import org.apache.http.client.methods.HttpPost;
6 | import org.apache.http.client.methods.HttpPut;
7 | import org.apache.http.entity.StringEntity;
8 | import org.apache.http.impl.client.CloseableHttpClient;
9 | import org.apache.http.impl.client.HttpClientBuilder;
10 | import org.apache.http.util.EntityUtils;
11 | import org.json.*;
12 |
13 | import java.util.Iterator;
14 | import java.util.Map;
15 |
16 | import java.io.IOException;
17 | import java.io.UnsupportedEncodingException;
18 | import java.util.HashMap;
19 |
20 |
21 | public class Conversation {
22 |
23 | private static final String converseAPI = "https://api.cai.tools.sap/v2/converse";
24 |
25 | private String raw; // String: the raw unparsed json response ok
26 | private String uuid; // String: the universal unique id of the api call ok
27 | private String source; // String: the user input ok
28 | private String[] replies; // Array[String]: all the replies ok
29 | private Action action; // Object: the action of the conversation ok
30 | private String sentiment; // String: the sentiment of the input ok
31 | private Action[] nextActions; // Array[Object]: the next actions of the conversation ok
32 | private Memory memory; // Object: the memory of the conversation ok
33 | private Map entities; // Array[Entity]: the array of entities ok
34 | private Intent[] intents; // Array[Object]: all the matched intents ok
35 | private String conversationToken; // String: the conversation token ok
36 | private String language; // String: the language of the input ok
37 | private String processing_language; // String: the language of the input ok
38 | private String version; // String: the API version ok
39 | private String timestamp; // String: the timestamp at the end of the processing ok
40 | private int status; // String: the status of the response ok
41 |
42 | private String token;
43 |
44 | public static final String SENTIMENT_POSITIVE = "positive";
45 | public static final String SENTIMENT_VERY_POSITIVE = "vpositive";
46 | public static final String SENTIMENT_NEGATIVE = "negative";
47 | public static final String SENTIMENT_VERY_NEGATIVE = "vnegative";
48 | public static final String SENTIMENT_NEUTRAL = "neutral";
49 |
50 | public Conversation(String json, String token) throws SapcaiException {
51 | this.setToken(token);
52 | JSONArray resultIntents = null;
53 | JSONArray resultNextActions = null;
54 | JSONArray resultReplies = null;
55 | JSONObject result;
56 | this.raw = json;
57 |
58 | try {
59 | result = new JSONObject(json).getJSONObject("results");
60 |
61 | this.source = result.getString("source");
62 | this.version = result.getString("version");
63 | this.timestamp = result.getString("timestamp");
64 | this.status = result.getInt("status");
65 | this.language = result.getString("language");
66 | this.processing_language = result.getString("processing_language");
67 | this.sentiment = result.getString("sentiment");
68 | this.uuid = result.getString("uuid");
69 | this.conversationToken = result.getString("conversation_token");
70 |
71 | JSONObject resultEntities = result.optJSONObject("entities");
72 | this.entities = new HashMap();
73 | if (resultEntities.length() != 0) {
74 | Iterator it = resultEntities.keys();
75 |
76 | while (it.hasNext()) {
77 | String entityName = it.next();
78 | JSONArray entity = resultEntities.optJSONArray(entityName);
79 | Entity[] values = new Entity[entity.length()];
80 | for (int i = 0; i < values.length; i++) {
81 | values[i] = new Entity(entityName, entity.optJSONObject(i));
82 | }
83 | this.entities.put(entityName, values);
84 | }
85 | }
86 |
87 | resultIntents = result.getJSONArray("intents");
88 | this.intents = new Intent[resultIntents.length()];
89 | for (int i = 0; i < this.intents.length; ++i) {
90 | this.intents[i] = new Intent(resultIntents.getJSONObject(i));
91 | }
92 |
93 | this.action = new Action(result.getJSONObject("action"));
94 |
95 | resultNextActions = result.getJSONArray("next_actions");
96 | this.nextActions = new Action[resultNextActions.length()];
97 | for(int i = 0; i < this.nextActions.length; ++i){
98 | this.nextActions[i] = new Action(resultNextActions.getJSONObject(i));
99 | }
100 |
101 | this.memory = new Memory(result.getJSONObject("memory"));
102 |
103 | resultReplies = result.getJSONArray("replies");
104 | this.replies = new String[resultReplies.length()];
105 | for(int i = 0; i < this.replies.length; ++i){
106 | this.replies[i] = resultReplies.optString(i);
107 | }
108 |
109 | } catch (Exception e) {
110 | throw new SapcaiException("Invalid JSON", e);
111 | }
112 | }
113 |
114 | public String reply(){
115 | return this.replies[0];
116 | }
117 |
118 | public Action nextAction(){
119 | return this.nextActions[0];
120 | }
121 |
122 | public String joinedReplies(){
123 | String replies = "";
124 | for(String rep : this.replies){
125 | replies = replies + rep;
126 | }
127 | return replies;
128 | }
129 |
130 | public boolean isPositive() { return this.sentiment.equals(SENTIMENT_POSITIVE); }
131 |
132 | public boolean isVeryPositive() {
133 | return this.sentiment.equals(SENTIMENT_VERY_POSITIVE);
134 | }
135 |
136 | public boolean isNeutral() {
137 | return this.sentiment.equals(SENTIMENT_NEUTRAL);
138 | }
139 |
140 | public boolean isNegative() {
141 | return this.sentiment.equals(SENTIMENT_NEGATIVE);
142 | }
143 |
144 | public boolean isVeryNegative() {
145 | return this.sentiment.equals(SENTIMENT_VERY_NEGATIVE);
146 | }
147 |
148 | public void setMemoryEntity(String name, MemoryEntity newMemoryEntity){
149 | this.memory.setMemory(name, newMemoryEntity);
150 | String memory = this.memory.convertMemory();
151 | String body = "{\"conversation_token\":\"" + this.conversationToken + "\", \"memory\":" + memory + "}";
152 | this.doApiRequest(body, 0);
153 | }
154 |
155 | public void resetMemory(){
156 | this.memory.resetMemory();
157 | String body = "{\"conversation_token\":\"" + this.conversationToken + "\",";
158 | body = body + "\"memory\":null}";
159 | this.doApiRequest(body, 0);
160 | }
161 |
162 | public void resetConversation(){
163 | String body = "{\"conversation_token\": \"" + this.conversationToken + "\"}";
164 | this.doApiRequest(body, 1);
165 | }
166 |
167 | public String doApiRequest(String body, int putOrDelete){
168 | System.out.println("body: " + body);
169 | String url = converseAPI;
170 | try {
171 | CloseableHttpClient httpClient = HttpClientBuilder.create().build();
172 | HttpDelete requestD = new HttpDelete(url);
173 | HttpPut request = new HttpPut(url);
174 | String json = null;
175 | if(putOrDelete == 1){
176 | requestD.addHeader("Authorization", "Token " + this.token);
177 | HttpResponse result = httpClient.execute(requestD);
178 | json = EntityUtils.toString(result.getEntity(), "UTF-8");
179 | } else {
180 | StringEntity params;
181 | params = new StringEntity(body);
182 | request.addHeader("Authorization", "Token " + this.token);
183 | request.setEntity(params);
184 | HttpResponse result = httpClient.execute(request);
185 | json = EntityUtils.toString(result.getEntity(), "UTF-8");
186 | }
187 | return json;
188 | } catch (UnsupportedEncodingException e) {
189 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
190 | } catch (ParseException e) {
191 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
192 | } catch (IOException e) {
193 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e);
194 | }
195 |
196 | }
197 |
198 | public String getRaw() {
199 | return raw;
200 | }
201 |
202 | public void setRaw(String raw) {
203 | this.raw = raw;
204 | }
205 |
206 | public String getUuid() {
207 | return uuid;
208 | }
209 |
210 | public void setUuid(String uuid) {
211 | this.uuid = uuid;
212 | }
213 |
214 | public String getSource() {
215 | return source;
216 | }
217 |
218 | public void setSource(String source) {
219 | this.source = source;
220 | }
221 |
222 | public String[] getReplies() {
223 | return replies;
224 | }
225 |
226 | public void setReplies(String[] replies) {
227 | this.replies = replies;
228 | }
229 |
230 | public Action getAction() {
231 | return action;
232 | }
233 |
234 | public void setAction(Action action) {
235 | this.action = action;
236 | }
237 |
238 | public Action[] getNextActions() {
239 | return nextActions;
240 | }
241 |
242 | public void setNextActions(Action[] nextActions) {
243 | this.nextActions = nextActions;
244 | }
245 |
246 | public Memory getMemory() {
247 | return memory;
248 | }
249 |
250 | public void setMemory(Memory memory) {
251 | this.memory = memory;
252 | }
253 |
254 | public Map getEntities() {
255 | return entities;
256 | }
257 |
258 | public void setEntities(Map entities) {
259 | this.entities = entities;
260 | }
261 |
262 | public Intent[] getIntents() {
263 | return intents;
264 | }
265 |
266 | public void setIntents(Intent[] intents) {
267 | this.intents = intents;
268 | }
269 |
270 | public String getConversationToken() {
271 | return conversationToken;
272 | }
273 |
274 | public void setConversationToken(String conversationToken) {
275 | this.conversationToken = conversationToken;
276 | }
277 |
278 | public String getLanguage() {
279 | return language;
280 | }
281 |
282 | public void setLanguage(String language) {
283 | this.language = language;
284 | }
285 |
286 | public String getVersion() {
287 | return version;
288 | }
289 |
290 | public void setVersion(String version) {
291 | this.version = version;
292 | }
293 |
294 | public String getTimestamp() {
295 | return timestamp;
296 | }
297 |
298 | public void setTimestamp(String timestamp) {
299 | this.timestamp = timestamp;
300 | }
301 |
302 | public int getStatus() {
303 | return status;
304 | }
305 |
306 | public void setStatus(int status) {
307 | this.status = status;
308 | }
309 |
310 | public String getToken() {
311 | return token;
312 | }
313 |
314 | public void setToken(String token) {
315 | this.token = token;
316 | }
317 |
318 | }
319 |
--------------------------------------------------------------------------------