Skip to content

Commit

Permalink
Add support for generic Conversation requests (#397)
Browse files Browse the repository at this point in the history
* Add initializer and toJSON for Conversation models

* Add entities, intents, and output to Conversation message request

* Add generic Conversation message endpoint

* Add tests for generic Conversation message endpoint
  • Loading branch information
glennrfisher committed Aug 17, 2016
1 parent 97a7176 commit 1407448
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 114 deletions.
37 changes: 36 additions & 1 deletion Source/ConversationV1/Conversation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,43 @@ public class Conversation {
failure: (NSError -> Void)? = nil,
success: MessageResponse -> Void)
{
let input = InputData(text: text)
message(workspaceID, input: input, context: context, failure: failure, success: success)
}

/**
Start a new conversation or get a response to a user's input.
- parameter workspaceID: The unique identifier of the workspace to use.
- parameter input: An input object that includes the input text.
- parameter context: The context, or state, associated with this request.
- parameter entities: An array of terms that shall be identified as entities
- parameter intents: An array of terms that shall be identified as intents.
- parameter output: An output object that includes the response to the user,
the nodes that were hit, and messages from the log.
- parameter failure: A function executed if an error occurs.
- parameter success: A function executed with the conversation service's response.
*/
public func message(
workspaceID: WorkspaceID,
input: InputData,
context: Context? = nil,
entities: [Entity]? = nil,
intents: [Intent]? = nil,
output: OutputData? = nil,
failure: (NSError -> Void)? = nil,
success: MessageResponse -> Void)
{
// construct message request
let messageRequest = MessageRequest(
input: input,
context: context,
entities: entities,
intents: intents,
output: output
)

// construct body
let messageRequest = MessageRequest(text: text, context: context)
guard let body = try? messageRequest.toJSON().serialize() else {
let failureReason = "MessageRequest could not be serialized to JSON."
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
Expand Down
19 changes: 17 additions & 2 deletions Source/ConversationV1/Models/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct Context: JSONEncodable, JSONDecodable {
- parameter conversationID: The unique identifier of the conversation.
- parameter system: A system object that includes information about the dialog.
*/
public init(conversationID: String? = nil, system: SystemResponse? = nil) {
public init(conversationID: String?, system: SystemResponse? = nil) {
self.conversationID = conversationID
self.system = system
}
Expand Down Expand Up @@ -69,7 +69,22 @@ public struct SystemResponse: JSONEncodable, JSONDecodable {
/// `dialogTurnCounter` when multiple inputs are needed before a response can be returned.
public let dialogRequestCounter: Int

/// Used internally to serialize a `SystemResponse` model from JSON.
/**
Create a `SystemResponse`.
- parameter dialogStack: An array of dialog node ids that are in focus in the conversation.
- parameter dialogTurnCounter: The number of cycles of user input and response in the conversation.
- parameter dialogRequestCounter: The number of inputs in this conversation. This counter might
be higher than the `dialogTurnCounter` when multiple inputs are needed before a response
can be returned.
*/
public init(dialogStack: [String], dialogTurnCounter: Int, dialogRequestCounter: Int) {
self.dialogStack = dialogStack
self.dialogTurnCounter = dialogTurnCounter
self.dialogRequestCounter = dialogRequestCounter
}

/// Used internally to initialize a `SystemResponse` model from JSON.
public init(json: JSON) throws {
dialogStack = try json.arrayOf("dialog_stack", type: Swift.String)
dialogTurnCounter = try json.int("dialog_turn_counter")
Expand Down
104 changes: 104 additions & 0 deletions Source/ConversationV1/Models/Entity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import Foundation
import Freddy

/** A term from the request that was identified as an entity. */
public struct Entity: JSONEncodable, JSONDecodable {

/// The name of the recognized entity.
public let entity: String?

/// The location where the entity value begins and ends in the input text.
public let location: EntityLocation?

/// The term in the input text that was recognized.
public let value: String?

/**
Create an `Entity`.
- parameter entity: The name of the recognized entity.
- parameter location: The location where the entity value begins and ends in the input text.
- parameter value: The term in the input text that was recognized.
*/
public init(entity: String?, location: EntityLocation?, value: String?) {
self.entity = entity
self.location = location
self.value = value
}

/// Used internally to initialize an `Entity` model from JSON.
public init(json: JSON) throws {
entity = try? json.string("entity")
location = try? json.decode("location")
value = try? json.string("value")
}

/// Used internally to serialize an `Entity` model to JSON.
public func toJSON() -> JSON {
var json = [String: JSON]()
if let entity = entity {
json["entity"] = .String(entity)
}
if let location = location {
json["location"] = location.toJSON()
}
if let value = value {
json["value"] = .String(value)
}
return JSON.Dictionary(json)
}
}

/** The location where an entity value begins and ends in the input text. */
public struct EntityLocation: JSONEncodable, JSONDecodable {

/// The zero-based character offset that indicates
/// where an entity value begins in the input text.
public let startIndex: Int

/// The zero-based character offset that indicates
/// where an entity value ends in the input text.
public let endIndex: Int

/**
Create an `EntityLocation`.
- parameter startIndex: The zero-based character offset that
indicates where an entity value begins in the input text.
- parameter endIndex: The zero-based character offset that
indicates where an entity value ends in the input text.
*/
public init(startIndex: Int, endIndex: Int) {
self.startIndex = startIndex
self.endIndex = endIndex
}

/// Used internally to initialize an `EntityLocation` model from JSON.
public init(json: JSON) throws {
let indices = try json.arrayOf(type: Swift.Int)
startIndex = indices[0]
endIndex = indices[1]
}

/// Used internally to serialize an `EntityLocation` model to JSON.
public func toJSON() -> JSON {
let json = [JSON.Int(startIndex), JSON.Int(endIndex)]
return JSON.Array(json)
}
}
4 changes: 2 additions & 2 deletions Source/ConversationV1/Models/InputData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ public struct InputData: JSONEncodable, JSONDecodable {
- parameter text: The user's input text.
*/
internal init(text: String? = nil) {
public init(text: String?) {
self.text = text
}

/// Used internally to serialize an `InputData` model from JSON.
/// Used internally to initialize an `InputData` model from JSON.
public init(json: JSON) throws {
text = try? json.string("text")
}
Expand Down
57 changes: 57 additions & 0 deletions Source/ConversationV1/Models/Intent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import Foundation
import Freddy

/** A term from the request that was identified as an intent. */
public struct Intent: JSONEncodable, JSONDecodable {

/// The name of the recognized intent.
public let intent: String?

/// The confidence score of the intent, between 0 and 1.
public let confidence: Double?

/**
Create an `Intent`.
- parameter intent: The name of the recognized intent.
- parameter confidence: The confidence score of the intent, between 0 and 1.
*/
init(intent: String?, confidence: Double?) {
self.intent = intent
self.confidence = confidence
}

/// Used internally to initialize an `Intent` model from JSON.
public init(json: JSON) throws {
intent = try? json.string("intent")
confidence = try? json.double("confidence")
}

/// Used internally to serialize an `Intent` model to JSON.
public func toJSON() -> JSON {
var json = [String: JSON]()
if let intent = intent {
json["intent"] = .String(intent)
}
if let confidence = confidence {
json["confidence"] = .Double(confidence)
}
return JSON.Dictionary(json)
}
}
40 changes: 27 additions & 13 deletions Source/ConversationV1/Models/MessageRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,32 @@ internal struct MessageRequest: JSONEncodable {

private let input: InputData
private let context: Context?
private let entities: [Entity]?
private let intents: [Intent]?
private let output: OutputData?

/**
Create a `MessageRequest` with input and context.
Create a `MessageRequest`.
- parameter input: An input object that includes the input text.
- parameter context: The context, or state, associated with this request.
- parameter entities: An array of terms that shall be identified as entities
- parameter intents: An array of terms that shall be identified as intents.
- parameter output: An output object that includes the response to the user,
the nodes that were hit, and messages from the log.
*/
init(input: InputData, context: Context? = nil) {
init(
input: InputData,
context: Context? = nil,
entities: [Entity]? = nil,
intents: [Intent]? = nil,
output: OutputData? = nil)
{
self.input = input
self.context = context
}

/**
Create a `MessageRequest` with text and context.
- parameter text: The user's input text.
- parameter context: The context, or state, associated with this request.
*/
init(text: String? = nil, context: Context? = nil) {
let input = InputData(text: text)
self.init(input: input, context: context)
self.entities = entities
self.intents = intents
self.output = output
}

/// Used internally to serialize a `MessageRequest` model to JSON.
Expand All @@ -52,6 +57,15 @@ internal struct MessageRequest: JSONEncodable {
if let context = context {
json["context"] = context.toJSON()
}
if let entities = entities {
json["entities"] = .Array(entities.map {$0.toJSON()})
}
if let intents = intents {
json["intents"] = .Array(intents.map {$0.toJSON()})
}
if let output = output {
json["output"] = output.toJSON()
}
return JSON.Dictionary(json)
}
}

0 comments on commit 1407448

Please sign in to comment.