From e3cdfa7c05c17b571001de8e69bf0632e91f19fa Mon Sep 17 00:00:00 2001 From: Vishwac Sena Kannan Date: Mon, 15 Apr 2019 17:45:49 -0700 Subject: [PATCH 1/2] Folder structure and core bot sample for LG --- .../common-expression-language/README.md | 72 + .../api-reference.md | 74 + .../prebuilt-functions.md | 1831 +++++++++++++++++ experimental/language-generation/README.md | 132 ++ .../language-generation/api-reference.md | 88 + .../13.core-bot/AdapterWithErrorHandler.cs | 50 + .../13.core-bot/BookingDetails.cs | 16 + .../13.core-bot/Bots/DialogAndWelcomeBot.cs | 56 + .../13.core-bot/Bots/DialogBot.cs | 50 + .../CognitiveModels/FlightBooking.json | 226 ++ .../ConfigurationCredentialProvider.cs | 16 + .../13.core-bot/Controllers/BotController.cs | 35 + .../13.core-bot/CoreBot.csproj | 16 + .../13.core-bot/DialogExtensions.cs | 26 + .../13.core-bot/Dialogs/BookingDialog.cs | 116 ++ .../Dialogs/CancelAndHelpDialog.cs | 63 + .../13.core-bot/Dialogs/DateResolverDialog.cs | 102 + .../13.core-bot/Dialogs/MainDialog.cs | 102 + .../13.core-bot/LuisHelper.cs | 55 + .../csharp_dotnetcore/13.core-bot/Program.cs | 26 + .../Properties/launchSettings.json | 27 + .../csharp_dotnetcore/13.core-bot/README.md | 100 + .../Resources/AdapterWithErrorHandler.LG | 12 + .../13.core-bot/Resources/BookingDialog.LG | 41 + .../13.core-bot/Resources/MainDialog.LG | 5 + .../13.core-bot/Resources/welcomeCard.LG | 53 + .../csharp_dotnetcore/13.core-bot/Startup.cs | 69 + .../13.core-bot/appsettings.json | 7 + .../deploymentTemplates/README-LUIS.md | 203 ++ .../template-with-new-rg.json | 183 ++ .../template-with-preexisting-rg.json | 154 ++ .../13.core-bot/wwwroot/default.htm | 417 ++++ .../csharp_dotnetcore/csharp_dotnetcore.sln | 25 + .../language-generation/lg-file-format.md | 223 ++ 34 files changed, 4671 insertions(+) create mode 100644 experimental/common-expression-language/README.md create mode 100644 experimental/common-expression-language/api-reference.md create mode 100644 experimental/common-expression-language/prebuilt-functions.md create mode 100644 experimental/language-generation/README.md create mode 100644 experimental/language-generation/api-reference.md create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/AdapterWithErrorHandler.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/BookingDetails.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogAndWelcomeBot.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogBot.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/CognitiveModels/FlightBooking.json create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/ConfigurationCredentialProvider.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Controllers/BotController.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/CoreBot.csproj create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/DialogExtensions.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/BookingDialog.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/CancelAndHelpDialog.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/DateResolverDialog.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/MainDialog.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/LuisHelper.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Program.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Properties/launchSettings.json create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/README.md create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/AdapterWithErrorHandler.LG create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/BookingDialog.LG create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/MainDialog.LG create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/welcomeCard.LG create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/Startup.cs create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/appsettings.json create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/README-LUIS.md create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-new-rg.json create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 experimental/language-generation/csharp_dotnetcore/13.core-bot/wwwroot/default.htm create mode 100644 experimental/language-generation/csharp_dotnetcore/csharp_dotnetcore.sln create mode 100644 experimental/language-generation/lg-file-format.md diff --git a/experimental/common-expression-language/README.md b/experimental/common-expression-language/README.md new file mode 100644 index 0000000000..bdd7d74197 --- /dev/null +++ b/experimental/common-expression-language/README.md @@ -0,0 +1,72 @@ +# Common Expression Language Concepts + +Bots, like any other application, require use of expressions to evaluate outcome of a condition based on runtime information available in memory or to the dialog or the language generation system. + +Common expression language was put together to address this core need as well as to rationalize and snap to a common expression language that will be used across Bot Builder SDK and other conversational AI components that need an expression language. + +See [here](./api-reference.md) for API reference. + +***_An expression is a sequence that can contain one or more [operators](#Operators), [variables](#Variables), [explicit values](#Explicit-values), [pre-built functions](./prebuilt-functions.md) or [Language Generation templates](../fileformats/lg/README.md#Template)._*** + +## Operators + +| Operator | Functionality | Prebuilt function equivalent | +|-----------|-------------------------------------------------------------------------------------------|-----------------------------------| +|+ |Arithmetic operator – addition. E.g. A + B |[add][1] | +|- |Arithmetic operator – subtraction. E.g. A – B |[sub][2] | +|* |Arithmetic operator – multiplication. E.g. A * B |[mul][3] | +|/ |Arithmetic operator – division. E.g. A / B |[div][4] | +|^ |Arithmetic operator – exponentiation. E.g. A ^ B |[exp][5] | +|% |Arithmetic operator – modulus. E.g. A % B |[mod][6] | +|== |Comparison operator – equals. E.g. A == B |[equals][7] | +|!= |Comparison operator – Not equals. E.g. A != B |[not][8]([equals][7]()) | +|> |Comparison operator – Greater than. A > B |[greater][9] | +|< |Comparison operator – Less than. A < B |[less][10] | +|>= |Comparison operator – Greater than or equal. A >= B |[greaterOrEquals][11] | +|<= |Comparison operator – Less than or equal. A <= B |[lessOrEquals][12] | +|& |Concatenation operator. Operands will always be cast to string – E.g. A & B |N/A | +|&& |Logical operator – AND. E.g. exp1 && exp2 |[and][13] | +|\|\| |Logical operator – OR. E.g. exp1 \|\| exp2 |[or][14] | +|! |Logical operator – NOT. E.g. !exp1 |[Not][8] | +|' |Used to wrap a string literal. E.g. 'myValue' |N/A | +|" |Used to wrap a string literal. E.g. "myValue" |N/A | +|[] |Used to denote a Template. E.g. [MyTemplate]. |N/A | +|[] |Used to refer to an item in a list by its index. E.g. A[3] |N/A | +|{} |Used to denote an expression. E.g. {A == B}. |N/A | +|{} |Used to denote a variable in template expansion. E.g. {myVariable} |N/A | +|() |Enforces precedence order and groups sub expressions into larger expressions. E.g. (A+B)*C |N/A | +|. |Property selector. E.g. myObject.Property1 |N/A | +|@{} |Used to denote parts of a multi-line value that requires evaluation |N/A | +|\ |Escape character for templates, expressions. |N/A | +|@entityName|Short hand notation that expands to turn.entities.entityName |N/A | +|$propertyName|Short hand notation that expands to dialog.result.property |N/A | +|#intentName|Short hand notation that expands to turn.intents.intentName |N/A | + +## Variables +Variables are always referenced by their name. E.g. {myVariable} +Variables can be complex objects. In which case they are referenced either using the property selector operator e.g. myParent.myVariable or using the item index selection operator. E.g. myParent.myList[0]. or using the [parameters](TODO) function. + +## Explicit values +Explicit values are enclosed in single quotes 'myExplicitValut' or double quotes - "myExplicitValue". + +## Pre-built functions +See [Here](./prebuilt-functions.md) for a complete list of prebuilt functions supported by the common expression language library. + +## Packages +Packages for C# are available under the [BotBuidler MyGet feed][15] + +[1]:prebuilt-functions.md#add +[2]:prebuilt-functions.md#sub +[3]:prebuilt-functions.md#mul +[4]:prebuilt-functions.md#div +[5]:prebuilt-functions.md#exp +[6]:prebuilt-functions.md#mod +[7]:prebuilt-functions.md#equals +[8]:prebuilt-functions.md#not +[9]:prebuilt-functions.md#greater +[10]:prebuilt-functions.md#less +[11]:prebuilt-functions.md#greaterOrEquals +[12]:prebuilt-functions.md#essOrEquals +[13]:prebuilt-functions.md#and +[14]:prebuilt-functions.md#or +[15]:https://botbuilder.myget.org/feed/botbuilder-declarative/package/nuget/Microsoft.Bot.Builder.Expressions \ No newline at end of file diff --git a/experimental/common-expression-language/api-reference.md b/experimental/common-expression-language/api-reference.md new file mode 100644 index 0000000000..f534cd8e33 --- /dev/null +++ b/experimental/common-expression-language/api-reference.md @@ -0,0 +1,74 @@ +# API reference for Expression + +For Nuget packages, see [this MyGet feed][1] + +### ExpressionEngine Class + +#### Constructors +```C# +/// +/// Constructor +/// +/// If present delegate to lookup evaluation information from type string. +public ExpressionEngine(EvaluatorLookup lookup = null) +``` +#### Methods +```C# +/// +/// Parse the input into an expression. +/// +/// Expression to parse. +/// Expresion tree. +public Expression Parse(string expression) +``` + +### Expression Class + +#### Fields +```C# +/// +/// Type of expression. +/// +public string Type { get; } + +/// +/// Evaluator of this expression +/// +public ExpressionEvaluator Evaluator { get; } + +/// +/// Children expressions. +/// +public Expression[] Children { get; set; } + +/// +/// Expected result of evaluating expression. +/// +public ReturnType ReturnType => Evaluator.ReturnType; +``` + +#### Contructor +```C# +/// +/// Expression constructor. +/// +/// Type of expression from . +/// Information about how to validate and evaluate expression. +/// Child expressions. +public Expression(string type, ExpressionEvaluator evaluator = null, params Expression[] children) +``` + +#### Methods + +```C# +/// +/// Evaluate the expression. +/// +/// +/// Global state to evaluate accessor expressions against. Can be , otherwise reflection is used to access property and then indexer. +/// +/// Computed value and an error string. If the string is non-null, then there was an evaluation error. +public (object value, string error) TryEvaluate(object state) +``` + +[1]:https://botbuilder.myget.org/feed/botbuilder-declarative/package/nuget/Microsoft.Bot.Builder.Expressions \ No newline at end of file diff --git a/experimental/common-expression-language/prebuilt-functions.md b/experimental/common-expression-language/prebuilt-functions.md new file mode 100644 index 0000000000..0516a4479e --- /dev/null +++ b/experimental/common-expression-language/prebuilt-functions.md @@ -0,0 +1,1831 @@ +# Common Expression Language +## Pre-built functions +This document lists the available prebuilt functions ordered by their general purpose, +or you can browse the functions based on [alphabetical order](#alphabetical-list). + +- [String functions](#String-functions) +- [Collection functions](#Collection-functions) +- [Logical comparison functions](#Logical-comparison-functions) +- [Conversion functions](#Conversion-functions) +- [Math functions](#Math-functions) +- [Date and time functions](#Date-and-time-functions) + +### String functions +|Function |Explanation| +|-----------|-----------| +|[replace](#replace)| Replace a substring with the specified string, and return the updated string. case sensitive| +|[replaceIgnoreCase](#replaceIgnoreCase)| Replace a substring with the specified string, and return the updated string. Case in-sensitive | +|[split](#split) |Returns an array that contains substrings based on the delimiter specified.| +|[substring](#substring) |Returns characters from a string. Substring(sourceString, startPos, endPos). startPos cannot be less than 0. endPos greater than source strings length will be taken as the max length of the string | +|[toLower](#toLower) |Convert a string to all upper case characters | +|[toUpper](#toUpper) |Convert a string to all lower case characters | +|[trim](#trim) |Remove leading and trailing white spaces from a string | + + +### Collection functions +|Function |Explanation| +|-----------|-----------| +|[contains](#contains) |Works to find an item in a string or to find an item in an array or to find a parameter in a complex object. E.g. contains(‘hello world, ‘hello); contains([‘1’, ‘2’], ‘1’); contains({“foo”:”bar”}, “foo”) | +|[empty](#empty) |Check if the collection is empty | +|[first](#first) |Returns the first item from the collection | +|[join](#join) |Return a string that has all the items from an array and has each character separated by a delimiter. Join(collection, delimiter). Join(createArray(‘a’,’b’), ‘.’) = “a.b” | +|[last](#last) |Returns the last item from the collection | +|[count](#count) |Returns the number of items in the collection | + + +### Logical comparison functions +|Function |Explanation| +|-----------|-----------| +|[and](#and) |Logical and. Returns true if all specified expressions evaluate to true. | +|[equals](#equals) |Comparison equal. Returns true if specified values are equal | +|[greater](#greater) |Comparison greater than | +|[greaterOrEquals](#greaterOrEquals) | Comparison greater than or equal to. greaterOrEquals(exp1, exp2) | +|[if](#if) | if(exp, valueIfTrue, valueIfFalse) | +|[less](#less) | Comparison less than opearation| +|[lessOrEquals](#lessOrEquals) | Comparison less than or equal operation| +|[not](#not) | Logical not opearator| +|[or](#or) | Logical OR opearation. | +|[exists](#exists) | Evaluates an expression for truthiness | + +### Conversion functions +|Function |Explanation| +|-----------|-----------| +|[float](#float) |Return floating point representation of the specified string or the string itself if conversion is not possible | +|[int](#int) |Return integer representation of the specified string or the string itself if conversion is not possible | +|[string](#string) |Return string version of the specified value | +|[bool](#bool) |Return Boolean representation of the specified string. Bool(‘true’), bool(1) | +|[createArray](#createArray) |Create an array from multiple inputs | + +### Math functions +|Function |Explanation| +|-----------|-----------| +|[add](#add) |Mathematical and. Accepts two parameters | +|[div](#div) |Mathematical division | +|[max](#max) |Returns the largest value from a collection | +|[min](#min) |Returns the smallest value from a collection | +|[mod](#mod) |Returns remainder from dividing two numbers | +|[mul](#mul) |Mathematical multiplication | +|[rand](#rand) |Returns a random number between specified min and max value – rand(\, \) | +|[sub](#sub) |Mathematical subtraction | +|[sum](#sum) |Returns sum of numbers in an array | +|[exp](#exp) |Exponentiation function. Exp(base, exponent) | + +### Date and time functions +|Function |Explanation| +|-----------|-----------| +|[addDays](#addDays) |Add number of specified days to a given timestamp | +|[addHours](#addHours) |Add specified number of hours to a given timestamp | +|[addMinutes](#addMinutes) |Add specified number of minutes to a given timestamp | +|[addSeconds](#addSeconds) |Add specified number of seconds to a given timestamp | +|[dayOfMonth](#dayOfMonth) |Returns day of month for a given timestamp or timex expression. | +|[dayOfWeek](#dayOfWeek) |Returns day of the week for a given timestamp | +|[dayOfYear](#dayOfYear) |Returns day of the year for a given timestamp | +|[formatDateTime](#formatDateTime) |Return a timestamp in the specified format.| +|[subtractFromTime](#subtractFromTime) |Subtract a number of time units from a timestamp.| +|[utcNow](#utcNow) |Returns current timestamp as string | +|[dateReadBack](#dateReadBack) |Uses the date-time library to provide a date readback. dateReadBack(currentDate, targetDate). E.g. dateReadBack(‘2016/05/30’,’2016/05/23’)=>"Yesterday" | +|month |Returns the month of given timestamp | +|date |Returns date for a given timestamp | +|year |Returns year for the given timestamp | +|getTimeOfDay |Returns time of day for a given timestamp (midnight = 12AM, morning = 12:01AM – 11:59PM, noon = 12PM, afternoon = 12:01PM -05:59PM, evening = 06:00PM – 10:00PM, night = 10:01PM – 11:59PM) | + + + + + +### add + +Return the result from adding two numbers. + +``` +add(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*summand_1*>, <*summand_2*> | Yes | Integer, Float, or mixed | The numbers to add | +||||| + +| Return value | Type | Description | +| ------------ | -----| ----------- | +| <*result-sum*> | Integer or Float | The result from adding the specified numbers | +|||| + +*Example* + +This example adds the specified numbers: + +``` +add(1, 1.5) +``` + +And returns this result: `2.5` + + + +### addDays + +Add a number of days to a timestamp. + +``` +addDays('', , ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*days*> | Yes | Integer | The positive or negative number of days to add | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-timestamp*> | String | The timestamp plus the specified number of days | +|||| + +*Example 1* + +This example adds 10 days to the specified timestamp: + +``` +addDays('2018-03-15T13:00:00Z', 10) +``` + +And returns this result: `"2018-03-25T00:00:0000000Z"` + +*Example 2* + +This example subtracts five days from the specified timestamp: + +``` +addDays('2018-03-15T00:00:00Z', -5) +``` + +And returns this result: `"2018-03-10T00:00:0000000Z"` + + + +### addHours + +Add a number of hours to a timestamp. + +``` +addHours('', , ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*hours*> | Yes | Integer | The positive or negative number of hours to add | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-timestamp*> | String | The timestamp plus the specified number of hours | +|||| + +*Example 1* + +This example adds 10 hours to the specified timestamp: + +``` +addHours('2018-03-15T00:00:00Z', 10) +``` + +And returns this result: `"2018-03-15T10:00:0000000Z"` + +*Example 2* + +This example subtracts five hours from the specified timestamp: + +``` +addHours('2018-03-15T15:00:00Z', -5) +``` + +And returns this result: `"2018-03-15T10:00:0000000Z"` + + + +### addMinutes + +Add a number of minutes to a timestamp. + +``` +addMinutes('', , ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*minutes*> | Yes | Integer | The positive or negative number of minutes to add | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-timestamp*> | String | The timestamp plus the specified number of minutes | +|||| + +*Example 1* + +This example adds 10 minutes to the specified timestamp: + +``` +addMinutes('2018-03-15T00:10:00Z', 10) +``` + +And returns this result: `"2018-03-15T00:20:00.0000000Z"` + +*Example 2* + +This example subtracts five minutes from the specified timestamp: + +``` +addMinutes('2018-03-15T00:20:00Z', -5) +``` + +And returns this result: `"2018-03-15T00:15:00.0000000Z"` + + + +### addSeconds + +Add a number of seconds to a timestamp. + +``` +addSeconds('', , ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*seconds*> | Yes | Integer | The positive or negative number of seconds to add | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-timestamp*> | String | The timestamp plus the specified number of seconds | +|||| + +*Example 1* + +This example adds 10 seconds to the specified timestamp: + +``` +addSeconds('2018-03-15T00:00:00Z', 10) +``` + +And returns this result: `"2018-03-15T00:00:10.0000000Z"` + +*Example 2* + +This example subtracts five seconds to the specified timestamp: + +``` +addSeconds('2018-03-15T00:00:30Z', -5) +``` + +And returns this result: `"2018-03-15T00:00:25.0000000Z"` + + + +### and + +Check whether all expressions are true. +Return true when all expressions are true, +or return false when at least one expression is false. + +``` +and(, , ...) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*expression1*>, <*expression2*>, ... | Yes | Boolean | The expressions to check | +||||| + +| Return value | Type | Description | +| ------------ | -----| ----------- | +| true or false | Boolean | Return true when all expressions are true. Return false when at least one expression is false. | +|||| + +*Example 1* + +These examples check whether the specified Boolean values are all true: + +``` +and(true, true) +and(false, true) +and(false, false) +``` + +And returns these results: + +* First example: Both expressions are true, so returns `true`. +* Second example: One expression is false, so returns `false`. +* Third example: Both expressions are false, so returns `false`. + +*Example 2* + +These examples check whether the specified expressions are all true: + +``` +and(equals(1, 1), equals(2, 2)) +and(equals(1, 1), equals(1, 2)) +and(equals(1, 2), equals(1, 3)) +``` + +And returns these results: + +* First example: Both expressions are true, so returns `true`. +* Second example: One expression is false, so returns `false`. +* Third example: Both expressions are false, so returns `false`. + + + +### bool + +Return the Boolean version for a value. + +``` +bool() +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Any | The value to convert | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | The Boolean version for the specified value | +|||| + +*Example* + +These examples convert the specified values to Boolean values: + +``` +bool(1) +bool(0) +``` + +And returns these results: + +* First example: `true` +* Second example: `false` + + + +### contains + +Check whether a collection has a specific item. +Return true when the item is found, +or return false when not found. +This function is case-sensitive. + +``` +contains('', '') +contains([], '') +``` + +Specifically, this function works on these collection types: + +* A *string* to find a *substring* +* An *array* to find a *value* +* A *dictionary* to find a *key* + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | String, Array, or Dictionary | The collection to check | +| <*value*> | Yes | String, Array, or Dictionary, respectively | The item to find | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the item is found. Return false when not found. | +|||| + +*Example 1* + +This example checks the string "hello world" for +the substring "world" and returns true: + +``` +contains('hello world', 'world') +``` + +*Example 2* + +This example checks the string "hello world" for +the substring "universe" and returns false: + +``` +contains('hello world', 'universe') +``` + + +### count + +Return the number of items in a collection. + +``` +count('') +count([]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | String or Array | The collection with the items to count | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*length-or-count*> | Integer | The number of items in the collection | +|||| + +*Example* + +These examples count the number of items in these collections: + +``` +count('abcd') +count(createArray(0, 1, 2, 3)) +``` + +And return this result: `4` + + + +### createArray + +Return an array from multiple inputs. +For single input arrays, see [array()](#array). + +``` +createArray('', '', ...) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*object1*>, <*object2*>, ... | Yes | Any, but not mixed | At least two items to create the array | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| [<*object1*>, <*object2*>, ...] | Array | The array created from all the input items | +|||| + +*Example* + +This example creates an array from these inputs: + +``` +createArray('h', 'e', 'l', 'l', 'o') +``` + +And returns this result: `["h", "e", "l", "l", "o"]` + + + +### dayOfMonth + +Return the day of the month from a timestamp. + +``` +dayOfMonth('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*day-of-month*> | Integer | The day of the month from the specified timestamp | +|||| + +*Example* + +This example returns the number for the day +of the month from this timestamp: + +``` +dayOfMonth('2018-03-15T13:27:36Z') +``` + +And returns this result: `15` + + + +### dayOfWeek + +Return the day of the week from a timestamp. + +``` +dayOfWeek('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*day-of-week*> | Integer | The day of the week from the specified timestamp where Sunday is 0, Monday is 1, and so on | +|||| + +*Example* + +This example returns the number for the day of the week from this timestamp: + +``` +dayOfWeek('2018-03-15T13:27:36Z') +``` + +And returns this result: `3` + + + +### dayOfYear + +Return the day of the year from a timestamp. + +``` +dayOfYear('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*day-of-year*> | Integer | The day of the year from the specified timestamp | +|||| + +*Example* + +This example returns the number of the day of the year from this timestamp: + +``` +dayOfYear('2018-03-15T13:27:36Z') +``` + +And returns this result: `74` + + + +### div + +Return the integer result from dividing two numbers. +To get the remainder result, see [mod()](#mod). + +``` +div(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*dividend*> | Yes | Integer or Float | The number to divide by the *divisor* | +| <*divisor*> | Yes | Integer or Float | The number that divides the *dividend*, but cannot be 0 | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*quotient-result*> | Integer | The integer result from dividing the first number by the second number | +|||| + +*Example* + +Both examples divide the first number by the second number: + +``` +div(10, 5) +div(11, 5) +``` + +And return this result: `2` + + + +### empty + +Check whether a collection is empty. +Return true when the collection is empty, +or return false when not empty. + +``` +empty('') +empty([]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | String, Array, or Object | The collection to check | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the collection is empty. Return false when not empty. | +|||| + +*Example* + +These examples check whether the specified collections are empty: + +``` +empty('') +empty('abc') +``` + +And returns these results: + +* First example: Passes an empty string, so the function returns `true`. +* Second example: Passes the string "abc", so the function returns `false`. + + + +### equals + +Check whether both values, expressions, or objects are equivalent. +Return true when both are equivalent, or return false when they're not equivalent. + +``` +equals('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*object1*>, <*object2*> | Yes | Various | The values, expressions, or objects to compare | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when both are equivalent. Return false when not equivalent. | +|||| + +*Example* + +These examples check whether the specified inputs are equivalent. + +``` +equals(true, 1) +equals('abc', 'abcd') +``` + +And returns these results: + +* First example: Both values are equivalent, so the function returns `true`. +* Second example: Both values aren't equivalent, so the function returns `false`. + + + +### exists + +Evaluates an expression for truthfulness. + +``` +exists(expression) +``` + +| Parameter | Required | Type | Description | +|-----------|----------|------|-------------| +| expression | Yes | expression | expression to evaluate for truthiness | +||||| + +| Return value | Type | Description | +|--------------|------|-------------| +| <*true or false*> | Boolean | Result of evaluating the expression | + +*Example* + +``` +exists(foo.bar) +exists(foo.bar2) +``` +With foo = {"bar":"value"} + +The first example returns TRUE +The second example returns FALSE + + + +### exp + +Return exponentiation of one number to another. + +``` +exp(realNumber, exponentNumber) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| realNumber | Yes | Number | real number to compute exponent of | +| exponentNumber | Yes | Number | exponent number | +||||| + +| Return value | Type | Description | +| ------------ | -----| ----------- | +| <*result-exp*> | Integer or Float | The result from computing exponent of realNumber | +|||| + +*Example* + +This example computes the exponent: + +``` +exp(2, 2) +``` + +And returns this result: `4` + + + +### first + +Return the first item from a string or array. + +``` +first('') +first([]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | String or Array | The collection where to find the first item | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*first-collection-item*> | Any | The first item in the collection | +|||| + +*Example* + +These examples find the first item in these collections: + +``` +first('hello') +first(createArray(0, 1, 2)) +``` + +And return these results: + +* First example: `"h"` +* Second example: `0` + + + + +### float + +Convert a string version for a floating-point +number to an actual floating point number. +You can use this function only when passing custom +parameters to an app, such as a logic app. + +``` +float('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | String | The string that has a valid floating-point number to convert | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*float-value*> | Float | The floating-point number for the specified string | +|||| + +*Example* + +This example creates a string version for this floating-point number: + +``` +float('10.333') +``` + +And returns this result: `10.333` + + + +### formatDateTime + +Return a timestamp in the specified format. + +``` +formatDateTime('', ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*reformatted-timestamp*> | String | The updated timestamp in the specified format | +|||| + +*Example* + +This example converts a timestamp to the specified format: + +``` +formatDateTime('03/15/2018 12:00:00', 'yyyy-MM-ddTHH:mm:ss') +``` + +And returns this result: `"2018-03-15T12:00:00"` + + + + +### greater + +Check whether the first value is greater than the second value. +Return true when the first value is more, +or return false when less. + +``` +greater(, ) +greater('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Integer, Float, or String | The first value to check whether greater than the second value | +| <*compareTo*> | Yes | Integer, Float, or String, respectively | The comparison value | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the first value is greater than the second value. Return false when the first value is equal to or less than the second value. | +|||| + +*Example* + +These examples check whether the first value is greater than the second value: + +``` +greater(10, 5) +greater('apple', 'banana') +``` + +And return these results: + +* First example: `true` +* Second example: `false` + + + +### greaterOrEquals + +Check whether the first value is greater than or equal to the second value. +Return true when the first value is greater or equal, +or return false when the first value is less. + +``` +greaterOrEquals(, ) +greaterOrEquals('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Integer, Float, or String | The first value to check whether greater than or equal to the second value | +| <*compareTo*> | Yes | Integer, Float, or String, respectively | The comparison value | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the first value is greater than or equal to the second value. Return false when the first value is less than the second value. | +|||| + +*Example* + +These examples check whether the first value is greater or equal than the second value: + +``` +greaterOrEquals(5, 5) +greaterOrEquals('apple', 'banana') +``` + +And return these results: + +* First example: `true` +* Second example: `false` + + + +### if + +Check whether an expression is true or false. +Based on the result, return a specified value. + +``` +if(, , ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*expression*> | Yes | Boolean | The expression to check | +| <*valueIfTrue*> | Yes | Any | The value to return when the expression is true | +| <*valueIfFalse*> | Yes | Any | The value to return when the expression is false | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*specified-return-value*> | Any | The specified value that returns based on whether the expression is true or false | +|||| + +*Example* + +This example returns `"yes"` because the +specified expression returns true. +Otherwise, the example returns `"no"`: + +``` +if(equals(1, 1), 'yes', 'no') +``` + + + +### int + +Return the integer version for a string. + +``` +int('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | String | The string to convert | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*integer-result*> | Integer | The integer version for the specified string | +|||| + +*Example* + +This example creates an integer version for the string "10": + +``` +int('10') +``` + +And returns this result: `10` + + + +### join + +Return a string that has all the items from an array +and has each character separated by a *delimiter*. + +``` +join([], '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | Array | The array that has the items to join | +| <*delimiter*> | Yes | String | The separator that appears between each character in the resulting string | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*char1*><*delimiter*><*char2*><*delimiter*>... | String | The resulting string created from all the items in the specified array | +|||| + +*Example* + +This example creates a string from all the items in this +array with the specified character as the delimiter: + +``` +join(createArray('a', 'b', 'c'), '.') +``` + +And returns this result: `"a.b.c"` + + + +### last + +Return the last item from a collection. + +``` +last('') +last([]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*collection*> | Yes | String or Array | The collection where to find the last item | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*last-collection-item*> | String or Array, respectively | The last item in the collection | +|||| + +*Example* + +These examples find the last item in these collections: + +``` +last('abcd') +last(createArray(0, 1, 2, 3)) +``` + +And returns these results: + +* First example: `"d"` +* Second example: `3` + + + +### less + +Check whether the first value is less than the second value. +Return true when the first value is less, +or return false when the first value is more. + +``` +less(, ) +less('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Integer, Float, or String | The first value to check whether less than the second value | +| <*compareTo*> | Yes | Integer, Float, or String, respectively | The comparison item | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the first value is less than the second value. Return false when the first value is equal to or greater than the second value. | +|||| + +*Example* + +These examples check whether the first value is less than the second value. + +``` +less(5, 10) +less('banana', 'apple') +``` + +And return these results: + +* First example: `true` +* Second example: `false` + + + +### lessOrEquals + +Check whether the first value is less than or equal to the second value. +Return true when the first value is less than or equal, +or return false when the first value is more. + +``` +lessOrEquals(, ) +lessOrEquals('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Integer, Float, or String | The first value to check whether less than or equal to the second value | +| <*compareTo*> | Yes | Integer, Float, or String, respectively | The comparison item | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the first value is less than or equal to the second value. Return false when the first value is greater than the second value. | +|||| + +*Example* + +These examples check whether the first value is less or equal than the second value. + +``` +lessOrEquals(10, 10) +lessOrEquals('apply', 'apple') +``` + +And return these results: + +* First example: `true` +* Second example: `false` + + + +### max + +Return the highest value from a list or array with +numbers that is inclusive at both ends. + +``` +max(, , ...) +max([, , ...]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*number1*>, <*number2*>, ... | Yes | Integer, Float, or both | The set of numbers from which you want the highest value | +| [<*number1*>, <*number2*>, ...] | Yes | Array - Integer, Float, or both | The array of numbers from which you want the highest value | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*max-value*> | Integer or Float | The highest value in the specified array or set of numbers | +|||| + +*Example* + +These examples get the highest value from the set of numbers and the array: + +``` +max(1, 2, 3) +max(createArray(1, 2, 3)) +``` + +And return this result: `3` + + + +### min + +Return the lowest value from a set of numbers or an array. + +``` +min(, , ...) +min([, , ...]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*number1*>, <*number2*>, ... | Yes | Integer, Float, or both | The set of numbers from which you want the lowest value | +| [<*number1*>, <*number2*>, ...] | Yes | Array - Integer, Float, or both | The array of numbers from which you want the lowest value | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*min-value*> | Integer or Float | The lowest value in the specified set of numbers or specified array | +|||| + +*Example* + +These examples get the lowest value in the set of numbers and the array: + +``` +min(1, 2, 3) +min(createArray(1, 2, 3)) +``` + +And return this result: `1` + + + +### mod + +Return the remainder from dividing two numbers. +To get the integer result, see [div()](#div). + +``` +mod(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*dividend*> | Yes | Integer or Float | The number to divide by the *divisor* | +| <*divisor*> | Yes | Integer or Float | The number that divides the *dividend*, but cannot be 0. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*modulo-result*> | Integer or Float | The remainder from dividing the first number by the second number | +|||| + +*Example* + +This example divides the first number by the second number: + +``` +mod(3, 2) +``` + +And return this result: `1` + + + +### mul + +Return the product from multiplying two numbers. + +``` +mul(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*multiplicand1*> | Yes | Integer or Float | The number to multiply by *multiplicand2* | +| <*multiplicand2*> | Yes | Integer or Float | The number that multiples *multiplicand1* | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*product-result*> | Integer or Float | The product from multiplying the first number by the second number | +|||| + +*Example* + +These examples multiple the first number by the second number: + +``` +mul(1, 2) +mul(1.5, 2) +``` + +And return these results: + +* First example: `2` +* Second example `3` + + + +### not + +Check whether an expression is false. +Return true when the expression is false, +or return false when true. + +``` +not() +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*expression*> | Yes | Boolean | The expression to check | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when the expression is false. Return false when the expression is true. | +|||| + +*Example 1* + +These examples check whether the specified expressions are false: + +``` +not(false) +not(true) +``` + +And return these results: + +* First example: The expression is false, so the function returns `true`. +* Second example: The expression is true, so the function returns `false`. + +*Example 2* + +These examples check whether the specified expressions are false: + +``` +not(equals(1, 2)) +not(equals(1, 1)) +``` + +And return these results: + +* First example: The expression is false, so the function returns `true`. +* Second example: The expression is true, so the function returns `false`. + + + +### or + +Check whether at least one expression is true. +Return true when at least one expression is true, +or return false when all are false. + +``` +or(, , ...) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*expression1*>, <*expression2*>, ... | Yes | Boolean | The expressions to check | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| true or false | Boolean | Return true when at least one expression is true. Return false when all expressions are false. | +|||| + +*Example 1* + +These examples check whether at least one expression is true: + +``` +or(true, false) +or(false, false) +``` + +And return these results: + +* First example: At least one expression is true, so the function returns `true`. +* Second example: Both expressions are false, so the function returns `false`. + +*Example 2* + +These examples check whether at least one expression is true: + +``` +or(equals(1, 1), equals(1, 2)) +or(equals(1, 2), equals(1, 3)) +``` + +And return these results: + +* First example: At least one expression is true, so the function returns `true`. +* Second example: Both expressions are false, so the function returns `false`. + + + +### rand + +Return a random integer from a specified range, +which is inclusive only at the starting end. + +``` +rand(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*minValue*> | Yes | Integer | The lowest integer in the range | +| <*maxValue*> | Yes | Integer | The integer that follows the highest integer in the range that the function can return | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*random-result*> | Integer | The random integer returned from the specified range | +|||| + +*Example* + +This example gets a random integer from the specified range, excluding the maximum value: + +``` +rand(1, 5) +``` + +And returns one of these numbers as the result: `1`, `2`, `3`, or `4` + + + +### replace + +Replace a substring with the specified string, +and return the result string. This function +is case-sensitive. + +``` +replace('', '', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string that has the substring to replace | +| <*oldText*> | Yes | String | The substring to replace | +| <*newText*> | Yes | String | The replacement string | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-text*> | String | The updated string after replacing the substring

If the substring is not found, return the original string. | +|||| + +*Example* + +This example finds the "old" substring in "the old string" +and replaces "old" with "new": + +``` +replace('the old string', 'old', 'new') +``` + +And returns this result: `"the new string"` + + + +### replaceIgnoreCase + +Replace a substring with the specified string, +and return the result string. This function +is case-insensitive. + +``` +replaceIgnoreCase('', '', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string that has the substring to replace | +| <*oldText*> | Yes | String | The substring to replace | +| <*newText*> | Yes | String | The replacement string | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-text*> | String | The updated string after replacing the substring

If the substring is not found, return the original string. | +|||| + +*Example* + +This example finds the "old" substring in "the old string" +and replaces "old" with "new": + +``` +replace('the old string', 'old', 'new') +``` + +And returns this result: `"the new string"` + + + +### split + +Return an array that contains substrings, separated by commas, +based on the specified delimiter character in the original string. + +``` +split('', '') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string to separate into substrings based on the specified delimiter in the original string | +| <*delimiter*> | Yes | String | The character in the original string to use as the delimiter | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| [<*substring1*>,<*substring2*>,...] | Array | An array that contains substrings from the original string, separated by commas | +|||| + +*Example* + +This example creates an array with substrings from the specified +string based on the specified character as the delimiter: + +``` +split('a_b_c', '_') +``` + +And returns this array as the result: `["a","b","c"]` + + + +### string + +Return the string version for a value. + +``` +string() +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*value*> | Yes | Any | The value to convert | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*string-value*> | String | The string version for the specified value | +|||| + +*Example 1* + +This example creates the string version for this number: + +``` +string(10) +``` + +And returns this result: `"10"` + +*Example 2* + +This example creates a string for the specified JSON object +and uses the backslash character (\\) +as an escape character for the double-quotation mark ("). + +``` +string( { "name": "Sophie Owen" } ) +``` + +And returns this result: `"{ \\"name\\": \\"Sophie Owen\\" }"` + + + +### sub + +Return the result from subtracting the second number from the first number. + +``` +sub(, ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*minuend*> | Yes | Integer or Float | The number from which to subtract the *subtrahend* | +| <*subtrahend*> | Yes | Integer or Float | The number to subtract from the *minuend* | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*result*> | Integer or Float | The result from subtracting the second number from the first number | +|||| + +*Example* + +This example subtracts the second number from the first number: + +``` +sub(10.3, .3) +``` + +And returns this result: `10` + + + +### substring + +Return characters from a string, +starting from the specified position, or index. +Index values start with the number 0. + +``` +substring('', , ) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string whose characters you want | +| <*startIndex*> | Yes | Integer | A positive number equal to or greater than 0 that you want to use as the starting position or index value | +| <*length*> | Yes | Integer | A positive number of characters that you want in the substring | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*substring-result*> | String | A substring with the specified number of characters, starting at the specified index position in the source string | +|||| + +*Example* + +This example creates a five-character substring from the specified string, +starting from the index value 6: + +``` +substring('hello world', 6, 5) +``` + +And returns this result: `"world"` + + + +### subtractFromTime + +Subtract a number of time units from a timestamp. +See also [getPastTime](#getPastTime). + +``` +subtractFromTime('', , '', ''?) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*timestamp*> | Yes | String | The string that contains the timestamp | +| <*interval*> | Yes | Integer | The number of specified time units to subtract | +| <*timeUnit*> | Yes | String | The unit of time to use with *interval*: "Second", "Minute", "Hour", "Day", "Week", "Month", "Year" | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updated-timestamp*> | String | The timestamp minus the specified number of time units | +|||| + +*Example 1* + +This example subtracts one day from this timestamp: + +``` +subtractFromTime('2018-01-02T00:00:00Z', 1, 'Day') +``` + +And returns this result: `"2018-01-01T00:00:00:0000000Z"` + +*Example 2* + +This example subtracts one day from this timestamp: + +``` +subtractFromTime('2018-01-02T00:00:00Z', 1, 'Day', 'D') +``` + +And returns this result using the optional "D" format: `"Monday, January, 1, 2018"` + + + +### sum + +Return the result from adding numbers in a list. + +``` +add([]) +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| [\] | Yes | List | The numbers to add | +||||| + +| Return value | Type | Description | +| ------------ | -----| ----------- | +| <*result-sum*> | Integer or Float | The result from adding the specified numbers | +|||| + +*Example* + +This example adds the specified numbers: + +``` +add([1, 1.5]) +``` + +And returns this result: `2.5` + + + +### toLower + +Return a string in lowercase format. If a character +in the string doesn't have a lowercase version, +that character stays unchanged in the returned string. + +``` +toLower('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string to return in lowercase format | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*lowercase-text*> | String | The original string in lowercase format | +|||| + +*Example* + +This example converts this string to lowercase: + +``` +toLower('Hello World') +``` + +And returns this result: `"hello world"` + + + +### toUpper + +Return a string in uppercase format. If a character +in the string doesn't have an uppercase version, +that character stays unchanged in the returned string. + +``` +toUpper('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string to return in uppercase format | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*uppercase-text*> | String | The original string in uppercase format | +|||| + +*Example* + +This example converts this string to uppercase: + +``` +toUpper('Hello World') +``` + +And returns this result: `"HELLO WORLD"` + + + +### trim + +Remove leading and trailing whitespace from a string, +and return the updated string. + +``` +trim('') +``` + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*text*> | Yes | String | The string that has the leading and trailing whitespace to remove | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*updatedText*> | String | An updated version for the original string without leading or trailing whitespace | +|||| + +*Example* + +This example removes the leading and trailing +whitespace from the string " Hello World ": + +``` +trim(' Hello World ') +``` + +And returns this result: `"Hello World"` + + + +### utcNow + +Return the current timestamp. + +``` +utcNow('') +``` + +Optionally, you can specify a different format with the <*format*> parameter. + + +| Parameter | Required | Type | Description | +| --------- | -------- | ---- | ----------- | +| <*format*> | No | String | Either a [single format specifier](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) or a [custom format pattern](https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings). The default format for the timestamp is ["o"](https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings) (yyyy-MM-ddTHH:mm:ss:fffffffK), which complies with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and preserves time zone information. | +||||| + +| Return value | Type | Description | +| ------------ | ---- | ----------- | +| <*current-timestamp*> | String | The current date and time | +|||| + +*Example 1* + +Suppose today is April 15, 2018 at 1:00:00 PM. +This example gets the current timestamp: + +``` +utcNow() +``` + +And returns this result: `"2018-04-15T13:00:00.0000000Z"` + +*Example 2* + +Suppose today is April 15, 2018 at 1:00:00 PM. +This example gets the current timestamp using the optional "D" format: + +``` +utcNow('D') +``` + +And returns this result: `"Sunday, April 15, 2018"` + +", entitiesCollection)); +``` + +For NodeJS +``` + await turnContext.sendActivity(lgEngine.evaluateTemplate("", entitiesCollection)); +``` + +If your template needs specific entity values to be passed for resolution/ expansion, you can pass them in on the call to `evaluateTemplate` + +For C# +``` + await turnContext.SendActivityAsync(lgEngine.EvaluateTemplate("WordGameReply", new { GameName = "MarcoPolo" } )); + +``` + +For NodeJS +``` + await turnContext.sendActivity(lgEngine.evaluateTemplate("WordGameReply", { GameName = "MarcoPolo" } )); +``` + +## Speak .vs. display adaptation +By design, the .lg file format does not explicitly support the ability to provide speak .vs. display adaptation. The file format supports simple constructs that are composable and supports resolution on multi-line text and so you can have syntax and semantics for speak .vs. display adaptation, cards, suggested actions etc that can be interpreted as simple text and transformed into the Bot Framework [activity][1] by a layer above language generation. + +Bot Builder SDK supports a short hand notation that can parse and transform a piece of text separated by `displayText`||`spokenText` into speak and display text. + +```markdown +# greetingTemplate +- hi || hi there +- hello || hello, what can I help with today +``` + +You can use the `TextMessageActivityGenerator.CreateActityFromText` method to transform the command into a Bot Framework activity to post back to the user. + +## Using Chatdown style cards + +[Chatdown][6] introduced a simple markdown based way to write mock conversations. Also introduced as part of the [.chat][7] file format was the ability to express different [message commands][9] via simple text representation. Message commands include [cards][10], [Attachments][11] and suggested actions. + +You can include message commands via multi-line text in the .lg file format and use the `TextMessageActivityGenerator.CreateActityFromText` method to transform the command into a Bot Framework activity to post back to the user. + +See [here][8] for examples of how different card types are represented in .chat file format. + +Here is an example of a card definition. + +```markdown + # HeroCardTemplate(buttonsCollection) + - ``` + [Herocard + title=@{[TitleText]} + subtitle=@{[SubText]} + text=@{[DescriptionText]} + images=@{[CardImages]} + buttons=@{join(buttonsCollection, '|')] + ``` + + # TitleText + - Here are some [TitleSuffix] + + # TitleSuffix + - cool photos + - pictures + - nice snaps + + # SubText + - What is your favorite? + - Don't they all look great? + - sorry, some of them are repeats + + # DescriptionText + - This is description for the hero card + + # CardImages + - https://picsum.photos/200/200?image=100 + - https://picsum.photos/300/200?image=200 + - https://picsum.photos/200/200?image=400 +``` + +## Grammar check and correction +The current library does not include any capabilities for grammar check or correction. + +## Packages +Packages for C# are available under the [BotBuidler MyGet feed][12] + +[1]:https://github.com/Microsoft/BotBuilder/blob/master/specs/botframework-activity/botframework-activity.md +[2]:api-reference.md +[3]:lg-file-format.md +[6]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown +[7]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#chat-file-format +[8]:https://github.com/Microsoft/botbuilder-tools/blob/master/packages/Chatdown/Examples/CardExamples.chat +[9]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-commands +[10]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-cards +[11]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-attachments +[12]:https://botbuilder.myget.org/feed/botbuilder-declarative/package/nuget/Microsoft.Bot.Builder.LanguageGeneration diff --git a/experimental/language-generation/api-reference.md b/experimental/language-generation/api-reference.md new file mode 100644 index 0000000000..88939aedb9 --- /dev/null +++ b/experimental/language-generation/api-reference.md @@ -0,0 +1,88 @@ +# API reference for LG + +For Nuget packages, see [this MyGet feed][1] + +### TemplateEngine Class + +#### Fields +``` C# +///

+/// Parsed LG templates +/// +public List Templates = new List(); +``` + +#### Constructors +```C# +/// +/// Return an empty engine, you can then use AddFile\AddFiles to add files to it, +/// or you can just use this empty engine to evaluate inline template +/// +public TemplateEngine() +``` +```C# +/// +/// Create a template engine from files, a shorthand for +/// new TemplateEngine().AddFiles(filePath) +/// +/// paths to LG files +/// Engine created +public static TemplateEngine FromFiles(params string[] filePaths) +``` + +```C# +/// +/// Create a template engine from text, equivalent to +/// new TemplateEngine.AddText(text) +/// +/// Content of lg file +/// Engine created +public static TemplateEngine FromText(string text) +``` + +#### Methods +```C# +/// +/// Load .lg files into template engine +/// +/// You can add one file, or mutlple file as once +/// +/// If you have multiple files referencing each other, make sure you add them all at once, +/// otherwise static checking won't allow you to add it one by one +/// +/// Paths to .lg files +/// Teamplate engine with parsed files +public TemplateEngine AddFiles(params string[] filePaths) +``` + + +```C# +/// +/// Add text as lg file content to template engine +/// +/// Text content contains lg templates +/// Template engine with the parsed content +public TemplateEngine AddText(string text) +``` + +```C# +/// +/// Evaluate a template with given name and scope +/// +/// Template name to be evaluated +/// The state visible in the evaluation +/// Optional methodBinder to extend or override functions +/// +public string EvaluateTemplate(string templateName, object scope, IGetMethod methodBinder = null) +``` + +```C# +/// +/// Analyze a given template and return it's referenced variables +/// +/// Template name +/// list of variable names +public List AnalyzeTemplate(string templateName) +``` + +[1]:https://botbuilder.myget.org/feed/botbuilder-declarative/package/nuget/Microsoft.Bot.Builder.LanguageGeneration \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/AdapterWithErrorHandler.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..b048edd2b4 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/AdapterWithErrorHandler.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Bot.Builder.LanguageGeneration; +using System.IO; + +namespace Microsoft.BotBuilderSamples +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + private TemplateEngine _lgEngine; + + public AdapterWithErrorHandler(ICredentialProvider credentialProvider, ILogger logger, ConversationState conversationState = null) + : base(credentialProvider) + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "AdapterWithErrorHandler.LG" }; + string fullPath = Path.Combine(paths); + _lgEngine = TemplateEngine.FromFiles(fullPath); + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + logger.LogError($"Exception caught : {exception.Message}"); + + // Send a catch-all apology to the user. + await turnContext.SendActivityAsync(_lgEngine.EvaluateTemplate("SomethingWentWrong", null)); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteAsync(turnContext); + } + catch (Exception e) + { + logger.LogError($"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + }; + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/BookingDetails.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/BookingDetails.cs new file mode 100644 index 0000000000..3b5861c1e1 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/BookingDetails.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.BotBuilderSamples +{ + public class BookingDetails + { + public string Destination { get; set; } + + public string Origin { get; set; } + + public string TravelDate { get; set; } + + public string travelDateMsg { get; set; } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogAndWelcomeBot.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogAndWelcomeBot.cs new file mode 100644 index 0000000000..529b9d3f78 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogAndWelcomeBot.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Microsoft.Bot.Builder.LanguageGeneration; +using System; + +namespace Microsoft.BotBuilderSamples.Bots +{ + public class DialogAndWelcomeBot : DialogBot where T : Dialog + { + private TemplateEngine _lgEngine; + + public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + : base(conversationState, userState, dialog, logger) + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "welcomeCard.LG" }; + string fullPath = Path.Combine(paths); + _lgEngine = TemplateEngine.FromFiles(fullPath); + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. + if (member.Id != turnContext.Activity.Recipient.Id) + { + var welcomeCard = CreateAdaptiveCardAttachment(); + var response = MessageFactory.Attachment(welcomeCard); + await turnContext.SendActivityAsync(response, cancellationToken); + } + } + } + + // Load attachment from file. + private Attachment CreateAdaptiveCardAttachment() + { + return new Attachment() + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = JsonConvert.DeserializeObject(_lgEngine.EvaluateTemplate("WelcomeCard", null)) + }; + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogBot.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogBot.cs new file mode 100644 index 0000000000..0928c27061 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Bots/DialogBot.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples.Bots +{ + // This IBot implementation can run any type of Dialog. The use of type parameterization is to allows multiple different bots + // to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types + // each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity. + // The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, + // and the requirement is that all BotState objects are saved at the end of a turn. + public class DialogBot : ActivityHandler where T : Dialog + { + protected readonly Dialog Dialog; + protected readonly BotState ConversationState; + protected readonly BotState UserState; + protected readonly ILogger Logger; + + public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + { + ConversationState = conversationState; + UserState = userState; + Dialog = dialog; + Logger = logger; + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) + { + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occured during the turn. + await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); + await UserState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Logger.LogInformation("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. + await Dialog.Run(turnContext, ConversationState.CreateProperty("DialogState"), cancellationToken); + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/CognitiveModels/FlightBooking.json b/experimental/language-generation/csharp_dotnetcore/13.core-bot/CognitiveModels/FlightBooking.json new file mode 100644 index 0000000000..0a0d6c4a7f --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/CognitiveModels/FlightBooking.json @@ -0,0 +1,226 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "Airline Reservation", + "desc": "A LUIS model that uses intent and entities.", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "Book flight" + }, + { + "name": "Cancel" + }, + { + "name": "None" + } + ], + "entities": [], + "composites": [ + { + "name": "From", + "children": [ + "Airport" + ], + "roles": [] + }, + { + "name": "To", + "children": [ + "Airport" + ], + "roles": [] + } + ], + "closedLists": [ + { + "name": "Airport", + "subLists": [ + { + "canonicalForm": "Paris", + "list": [ + "paris" + ] + }, + { + "canonicalForm": "London", + "list": [ + "london" + ] + }, + { + "canonicalForm": "Berlin", + "list": [ + "berlin" + ] + }, + { + "canonicalForm": "New York", + "list": [ + "new york" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "datetimeV2", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [], + "utterances": [ + { + "text": "book flight from london to paris on feb 14th", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 27, + "endPos": 31 + }, + { + "entity": "From", + "startPos": 17, + "endPos": 22 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "Book flight", + "entities": [ + { + "entity": "From", + "startPos": 22, + "endPos": 27 + }, + { + "entity": "To", + "startPos": 32, + "endPos": 36 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + }, + { + "entity": "From", + "startPos": 21, + "endPos": 26 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 19, + "endPos": 23 + }, + { + "entity": "From", + "startPos": 9, + "endPos": 14 + } + ] + }, + { + "text": "go to paris", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 6, + "endPos": 10 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 20, + "endPos": 25 + }, + { + "entity": "From", + "startPos": 11, + "endPos": 15 + } + ] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel to paris", + "intent": "Book flight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + } + ], + "settings": [] +} \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/ConfigurationCredentialProvider.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/ConfigurationCredentialProvider.cs new file mode 100644 index 0000000000..e00b5ef201 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/ConfigurationCredentialProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotBuilderSamples +{ + public class ConfigurationCredentialProvider : SimpleCredentialProvider + { + public ConfigurationCredentialProvider(IConfiguration configuration) + : base(configuration["MicrosoftAppId"], configuration["MicrosoftAppPassword"]) + { + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Controllers/BotController.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Controllers/BotController.cs new file mode 100644 index 0000000000..35514d52ea --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Controllers/BotController.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotBuilderSamples.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter Adapter; + private readonly IBot Bot; + + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + Adapter = adapter; + Bot = bot; + } + + [HttpPost] + public async Task PostAsync() + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await Adapter.ProcessAsync(Request, Response, Bot); + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/CoreBot.csproj b/experimental/language-generation/csharp_dotnetcore/13.core-bot/CoreBot.csproj new file mode 100644 index 0000000000..fff441a347 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/CoreBot.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/DialogExtensions.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/DialogExtensions.cs new file mode 100644 index 0000000000..ca68cfa499 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/DialogExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; + +namespace Microsoft.BotBuilderSamples +{ + public static class DialogExtensions + { + public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor accessor, CancellationToken cancellationToken = default(CancellationToken)) + { + var dialogSet = new DialogSet(accessor); + dialogSet.Add(dialog); + + var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken); + var results = await dialogContext.ContinueDialogAsync(cancellationToken); + if (results.Status == DialogTurnStatus.Empty) + { + await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken); + } + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/BookingDialog.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/BookingDialog.cs new file mode 100644 index 0000000000..9658f1fead --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/BookingDialog.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; +using Microsoft.Bot.Builder.LanguageGeneration; +using System.IO; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class BookingDialog : CancelAndHelpDialog + { + private TemplateEngine _lgEngine; + + public BookingDialog() + : base(nameof(BookingDialog)) + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "BookingDialog.LG" }; + string fullPath = Path.Combine(paths); + _lgEngine = TemplateEngine.FromFiles(fullPath); + + AddDialog(new TextPrompt(nameof(TextPrompt))); + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new DateResolverDialog()); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + DestinationStepAsync, + OriginStepAsync, + TravelDateStepAsync, + ConfirmStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task DestinationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + if (bookingDetails.Destination == null) + { + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text(_lgEngine.EvaluateTemplate("PromptForMissingInformation", bookingDetails)) }, cancellationToken); + } + else + { + return await stepContext.NextAsync(bookingDetails.Destination, cancellationToken); + } + } + + private async Task OriginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.Destination = (string)stepContext.Result; + + if (bookingDetails.Origin == null) + { + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text(_lgEngine.EvaluateTemplate("PromptForMissingInformation", bookingDetails)) }, cancellationToken); + } + else + { + return await stepContext.NextAsync(bookingDetails.Origin, cancellationToken); + } + } + private async Task TravelDateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.Origin = (string)stepContext.Result; + + if (bookingDetails.TravelDate == null || IsAmbiguous(bookingDetails.TravelDate)) + { + return await stepContext.BeginDialogAsync(nameof(DateResolverDialog), bookingDetails, cancellationToken); + } + else + { + return await stepContext.NextAsync(bookingDetails.TravelDate, cancellationToken); + } + } + + private async Task ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.TravelDate = (string)stepContext.Result; + + return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text(_lgEngine.EvaluateTemplate("PromptForMissingInformation", bookingDetails)) }, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if ((bool)stepContext.Result) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + return await stepContext.EndDialogAsync(bookingDetails, cancellationToken); + } + else + { + return await stepContext.EndDialogAsync(null, cancellationToken); + } + } + + private static bool IsAmbiguous(string timex) + { + var timexProperty = new TimexProperty(timex); + return !timexProperty.Types.Contains(Constants.TimexTypes.Definite); + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/CancelAndHelpDialog.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/CancelAndHelpDialog.cs new file mode 100644 index 0000000000..7916752010 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/CancelAndHelpDialog.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class CancelAndHelpDialog : ComponentDialog + { + public CancelAndHelpDialog(string id) + : base(id) + { + } + + protected override async Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnBeginDialogAsync(innerDc, options, cancellationToken); + } + + protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnContinueDialogAsync(innerDc, cancellationToken); + } + + private async Task InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken) + { + if (innerDc.Context.Activity.Type == ActivityTypes.Message) + { + var text = innerDc.Context.Activity.Text.ToLowerInvariant(); + + switch (text) + { + case "help": + case "?": + await innerDc.Context.SendActivityAsync($"Show Help...", cancellationToken: cancellationToken); + return new DialogTurnResult(DialogTurnStatus.Waiting); + + case "cancel": + case "quit": + await innerDc.Context.SendActivityAsync($"Cancelling", cancellationToken: cancellationToken); + return await innerDc.CancelAllDialogsAsync(); + } + } + + return null; + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/DateResolverDialog.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/DateResolverDialog.cs new file mode 100644 index 0000000000..7029556833 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/DateResolverDialog.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; +using Microsoft.Bot.Builder.LanguageGeneration; +using System.IO; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class DateResolverDialog : CancelAndHelpDialog + { + private TemplateEngine _lgEngine; + + public DateResolverDialog(string id = null) + : base(id ?? nameof(DateResolverDialog)) + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "BookingDialog.LG" }; + string fullPath = Path.Combine(paths); + _lgEngine = TemplateEngine.FromFiles(fullPath); + + AddDialog(new DateTimePrompt(nameof(DateTimePrompt), DateTimePromptValidator)); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + InitialStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + var timex = bookingDetails.TravelDate; + + var promptMsg = _lgEngine.EvaluateTemplate("PromptForTravelDate", null); + var repromptMsg = _lgEngine.EvaluateTemplate("InvalidDateReprompt", null); + + if (timex == null) + { + // We were not given any date at all so prompt the user. + return await stepContext.PromptAsync(nameof(DateTimePrompt), + new PromptOptions + { + Prompt = MessageFactory.Text(promptMsg), + RetryPrompt = MessageFactory.Text(repromptMsg) + }, cancellationToken); + } + else + { + // We have a Date we just need to check it is unambiguous. + var timexProperty = new TimexProperty(timex); + if (!timexProperty.Types.Contains(Constants.TimexTypes.Definite)) + { + // This is essentially a "reprompt" of the data we were given up front. + return await stepContext.PromptAsync(nameof(DateTimePrompt), + new PromptOptions + { + Prompt = MessageFactory.Text(repromptMsg) + }, cancellationToken); + } + else + { + return await stepContext.NextAsync(new DateTimeResolution { Timex = timex }, cancellationToken); + } + } + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var timex = ((List)stepContext.Result)[0].Timex; + return await stepContext.EndDialogAsync(timex, cancellationToken); + } + + private static Task DateTimePromptValidator(PromptValidatorContext> promptContext, CancellationToken cancellationToken) + { + if (promptContext.Recognized.Succeeded) + { + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part. + // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year. + var timex = promptContext.Recognized.Value[0].Timex.Split('T')[0]; + + // If this is a definite Date including year, month and day we are good otherwise reprompt. + // A better solution might be to let the user know what part is actually missing. + var isDefinite = new TimexProperty(timex).Types.Contains(Constants.TimexTypes.Definite); + + return Task.FromResult(isDefinite); + } + else + { + return Task.FromResult(false); + } + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/MainDialog.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/MainDialog.cs new file mode 100644 index 0000000000..47da424bca --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Dialogs/MainDialog.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; +using Microsoft.Bot.Builder.LanguageGeneration; +using System.IO; +using System.Collections.Generic; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class MainDialog : ComponentDialog + { + protected readonly IConfiguration Configuration; + protected readonly ILogger Logger; + protected TemplateEngine _lgEngine; + + public MainDialog(IConfiguration configuration, ILogger logger) + : base(nameof(MainDialog)) + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "MainDialog.LG" }; + string fullPath = Path.Combine(paths); + _lgEngine = TemplateEngine.FromFiles(fullPath); + + Configuration = configuration; + Logger = logger; + + AddDialog(new TextPrompt(nameof(TextPrompt))); + AddDialog(new BookingDialog()); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + IntroStepAsync, + ActStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(Configuration["LuisAppId"]) || string.IsNullOrEmpty(Configuration["LuisAPIKey"]) || string.IsNullOrEmpty(Configuration["LuisAPIHostName"])) + { + await stepContext.Context.SendActivityAsync( + MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file."), cancellationToken); + + return await stepContext.NextAsync(null, cancellationToken); + } + else + { + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text(_lgEngine.EvaluateTemplate("IntroPrompt", null)) }, cancellationToken); + } + } + + private async Task ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) + var bookingDetails = stepContext.Result != null + ? + await LuisHelper.ExecuteLuisQuery(Configuration, Logger, stepContext.Context, cancellationToken) + : + new BookingDetails(); + + // In this sample we only have a single Intent we are concerned with. However, typically a scenario + // will have multiple different Intents each corresponding to starting a different child Dialog. + + // Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder. + return await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // If the child dialog ("BookingDialog") was cancelled or the user failed to confirm, the Result here will be null. + if (stepContext.Result != null) + { + var result = (BookingDetails)stepContext.Result; + + // Now we have all the booking details call the booking service. + + // If the call to the booking service was successful tell the user. + + var timeProperty = new TimexProperty(result.TravelDate); + result.travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now); + await stepContext.Context.SendActivityAsync(MessageFactory.Text(_lgEngine.EvaluateTemplate("BookingConfirmation", result)), cancellationToken); + } + else + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken); + } + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/LuisHelper.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/LuisHelper.cs new file mode 100644 index 0000000000..61ec233f2d --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/LuisHelper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.AI.Luis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public static class LuisHelper + { + public static async Task ExecuteLuisQuery(IConfiguration configuration, ILogger logger, ITurnContext turnContext, CancellationToken cancellationToken) + { + var bookingDetails = new BookingDetails(); + + try + { + // Create the LUIS settings from configuration. + var luisApplication = new LuisApplication( + configuration["LuisAppId"], + configuration["LuisAPIKey"], + "https://" + configuration["LuisAPIHostName"] + ); + + var recognizer = new LuisRecognizer(luisApplication); + + // The actual call to LUIS + var recognizerResult = await recognizer.RecognizeAsync(turnContext, cancellationToken); + + var (intent, score) = recognizerResult.GetTopScoringIntent(); + if (intent == "Book_flight") + { + // We need to get the result from the LUIS JSON which at every level returns an array. + bookingDetails.Destination = recognizerResult.Entities["To"]?.FirstOrDefault()?["Airport"]?.FirstOrDefault()?.FirstOrDefault()?.ToString(); + bookingDetails.Origin = recognizerResult.Entities["From"]?.FirstOrDefault()?["Airport"]?.FirstOrDefault()?.FirstOrDefault()?.ToString(); + + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part. + // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year. + bookingDetails.TravelDate = recognizerResult.Entities["datetime"]?.FirstOrDefault()?["timex"]?.FirstOrDefault()?.ToString().Split('T')[0]; + } + } + catch (Exception e) + { + logger.LogWarning($"LUIS Exception: {e.Message} Check your LUIS configuration."); + } + + return bookingDetails; + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Program.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Program.cs new file mode 100644 index 0000000000..43e91da93e --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Program.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureLogging((logging) => + { + logging.AddDebug(); + logging.AddConsole(); + }) + .UseStartup(); + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Properties/launchSettings.json b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Properties/launchSettings.json new file mode 100644 index 0000000000..6902b926b9 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3978/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "CoreBot": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:3978/" + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/README.md b/experimental/language-generation/csharp_dotnetcore/13.core-bot/README.md new file mode 100644 index 0000000000..37ae85dd49 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/README.md @@ -0,0 +1,100 @@ +# CoreBot +Bot Framework v4 core bot sample. + +This bot has been created using [Bot Framework][1], it shows how to: +- Use [LUIS][11] to implement core AI capabilities +- Implement a multi-turn conversation using Dialogs +- Handle user interruptions for such things as `Help` or `Cancel` +- Prompt for and validate requests for information from the user + +## Prerequisites +This sample **requires** prerequisites in order to run. + +### Overview +This bot uses [LUIS][11], an AI based cognitive service, to implement language understanding. + +### Install .NET Core and CLI Tooling +- [.NET Core SDK][4] version 2.1 + ```bash + # determine dotnet version + dotnet --version + ``` +- If you don't have an Azure subscription, create a [free account][2]. +- Install the latest version of the [Azure CLI][3] tool. Version 2.0.54 or higher. + +### Create a LUIS Application to enable language understanding +LUIS language model setup, training, and application configuration steps can be found [here][7]. + + +## To try this sample +- In a terminal, navigate to `13.core-bot` + ```bash + # change into project folder + cd 13.core-bot + ``` +- Run the bot from a terminal or from Visual Studio, choose option A or B. + + A) From a terminal + ```bash + # run the bot + dotnet run + ``` + + B) Or from Visual Studio + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `13.core-bot` folder + - Select `CoreBot.csproj` file + - Press `F5` to run the project + +## Testing the bot using Bot Framework Emulator +[Bot Framework Emulator][5] is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here][6] + +### Connect to the bot using Bot Framework Emulator +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure][40] for a complete list of deployment instructions. + +## Further reading +- [Bot Framework Documentation][20] +- [Bot Basics][32] +- [Prompt types][23] +- [Waterfall dialogs][24] +- [Ask the user questions][26] +- [Activity processing][25] +- [Azure Bot Service Introduction][21] +- [Azure Bot Service Documentation][22] +- [.NET Core CLI tools][23] +- [Azure CLI][7] +- [Azure Portal][10] +- [Language Understanding using LUIS][11] +- [Channels and Bot Connector Service][27] + + +[1]: https://dev.botframework.com +[2]: https://azure.microsoft.com/free/ +[3]: https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest +[4]: https://dotnet.microsoft.com/download +[5]: https://github.com/microsoft/botframework-emulator +[6]: https://github.com/Microsoft/BotFramework-Emulator/releases +[7]: https://docs.microsoft.com/cli/azure/?view=azure-cli-latest +[8]: https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest +[10]: https://portal.azure.com +[11]: https://www.luis.ai +[20]: https://docs.botframework.com +[21]: https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[22]: https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0 +[23]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0 +[24]: https://docs.microsoft.com/en-us/javascript/api/botbuilder-dialogs/waterfall +[25]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0 +[26]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-tutorial-waterfall?view=azure-bot-service-4.0 +[27]: https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0 +[30]: https://www.npmjs.com/package/restify +[31]: https://www.npmjs.com/package/dotenv +[32]: https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 +[40]: https://aka.ms/azuredeployment diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/AdapterWithErrorHandler.LG b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/AdapterWithErrorHandler.LG new file mode 100644 index 0000000000..a83795f28a --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/AdapterWithErrorHandler.LG @@ -0,0 +1,12 @@ +# SomethingWentWrong +- [ErrorPrefix], [ErrorSuffix] + +# ErrorSuffix +- it looks like something went wrong. +- I seem to have run into a snag. We need to start over. +- something is not right. We need to start over. + +# ErrorPrefix +- Oops +- Sorry +- I apologize \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/BookingDialog.LG b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/BookingDialog.LG new file mode 100644 index 0000000000..b6d84736a5 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/BookingDialog.LG @@ -0,0 +1,41 @@ +# PromptForDestinationCity +- Where would you like to travel to? +- What is your destination city? + +# PromptForDepartureCity +- Where are you traveling from? +- What is your departure city? + +# ConfirmPrefix +- Please confirm, +- Can you please confirm this is right? +- Does this sound righ to you? + +# ConfirmMessage +- I have you traveling to: {Destination} from: {Origin} on: {TravelDate} +- on {TravelDate}, travelling from {Origin} to {Destination} + +# ConfirmBooking +- [ConfirmPrefix] [ConfirmMessage] + +# PromptForTravelDate +- When would you like to travel? +- What is your departure date? +- Can you please give me your intended date of departure? + +# PromptForMissingInformation +- IF: {Destination == null} + - [PromptForDestinationCity] +- ELSEIF: {Origin == null} + - [PromptForDepartureCity] +- ELSEIF: {TravelDate == null} + - [PromptForTravelDate] +- ELSE: + - [ConfirmBooking] + + # ApologyPrefix + - I'm sorry, + - Unfortunately that does not work. + + # InvalidDateReprompt + - [ApologyPrefix] to make your booking please enter a full travel date including Day Month and Year. diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/MainDialog.LG b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/MainDialog.LG new file mode 100644 index 0000000000..4090388a80 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/MainDialog.LG @@ -0,0 +1,5 @@ +# BookingConfirmation +- I have you booked to {Destination} from {Origin} on {travelDateMsg}. + +# IntroPrompt +- What can I help you with today? diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/welcomeCard.LG b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/welcomeCard.LG new file mode 100644 index 0000000000..b221b9ca63 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Resources/welcomeCard.LG @@ -0,0 +1,53 @@ +# WelcomeCard +- ``` +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "@{lgTemplate("HeaderText")}", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": "yes", + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} +``` + +# HeaderText +- Welcome to Bot Framework! +- Welcome to core bot with Language Generation! \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/Startup.cs b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Startup.cs new file mode 100644 index 0000000000..9ed4a988e0 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/Startup.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.DependencyInjection; + +using Microsoft.BotBuilderSamples.Bots; +using Microsoft.BotBuilderSamples.Dialogs; + +namespace Microsoft.BotBuilderSamples +{ + public class Startup + { + public Startup() + { + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Create the credential provider to be used with the Bot Framework Adapter. + services.AddSingleton(); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Create the User state. (Used in this bot's Dialog implementation.) + services.AddSingleton(); + + // Create the Conversation state. (Used by the Dialog system itself.) + services.AddSingleton(); + + // The Dialog that will be run by the bot. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient>(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + //app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/appsettings.json b/experimental/language-generation/csharp_dotnetcore/13.core-bot/appsettings.json new file mode 100644 index 0000000000..0b41e920d5 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/appsettings.json @@ -0,0 +1,7 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "LuisAppId": "1195bcf1-4610-4285-982e-3a97cce409a2", + "LuisAPIKey": "a95d07785b374f0a9d7d40700e28a285", + "LuisAPIHostName": "https://westus.api.cognitive.microsoft.com" +} diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/README-LUIS.md b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/README-LUIS.md new file mode 100644 index 0000000000..9df7e35b02 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/README-LUIS.md @@ -0,0 +1,203 @@ +# Setting up LUIS via CLI: + +This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. + +> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ +> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ +> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ + + [Quickstart-create]: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/get-started-portal-build-app + [Quickstart-deploy]:https://docs.microsoft.com/en-us/azure/cognitive-services/luis/get-started-portal-deploy-app + + +## Table of Contents: +- [Prerequisites](#Prerequisites) +- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) +- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) + +___ + +## [Prerequisites](#Table-of-Contents): + +#### Install Azure CLI >=2.0.61: +Visit the following page to find the correct installer for your OS: +- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest + +#### Install LUIS CLI >=2.4.0: +Open a CLI of your choice and type the following: +```bash +npm i -g luis-apis@^2.4.0 +``` + +#### LUIS portal account: +You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. + +After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. + + [LUIS-Authoring-Regions]: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] + [LUIS-AKey]: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-concept-keys#authoring-key + +___ + +## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) + +### 1. Import the local LUIS application to luis.ai +```bash +luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "CoreBot-FlightBooking" --in "./cognitiveModels/FlightBooking.json" +``` + +Outputs the following JSON: +```json +{ + "id": "########-####-####-####-############", + "name": "CoreBot-FlightBooking", + "description": "A LUIS model that uses intent and entities.", + "culture": "en-us", + "usageScenario": "", + "domain": "", + "versionsCount": 1, + "createdDateTime": "2019-03-29T18:32:02Z", + "endpoints": {}, + "endpointHitsCount": 0, + "activeVersion": "0.1", + "ownerEmail": "bot@contoso.com", + "tokenizerVersion": "1.0.0" +} +``` + +For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. + +### 2. Train the LUIS Application +```bash +luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait +``` + +### 3. Publish the LUIS Application +```bash +luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" +``` + +> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
+> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
+> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
+> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. + + [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 + + +Outputs the following: +```json + { + "versionId": "0.1", + "isStaging": false, + "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", + "region": "westus", + "assignedEndpointKey": null, + "endpointRegion": "westus", + "failedRegions": "", + "publishedDateTime": "2019-03-29T18:40:32Z", + "directVersionPublish": false +} +``` + +To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. + + [README-LUIS]: ./README-LUIS.md + +___ + +## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) + +### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI + +> _Note:_
+> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ +> ```bash +> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" +> ``` +> _To see a list of valid locations, use `az account list-locations`_ + + +```bash +# Use Azure CLI to create the LUIS Key resource on Azure +az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +The command will output a response similar to the JSON below: +```json +{ + "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", + "etag": "\"########-####-####-####-############\"", + "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", + "internalId": "################################", + "kind": "luis", + "location": "westus", + "name": "NewLuisResourceName", + "provisioningState": "Succeeded", + "resourceGroup": "ResourceGroupName", + "sku": { + "name": "S0", + "tier": null + }, + "tags": null, + "type": "Microsoft.CognitiveServices/accounts" +} +``` + + + +Take the output from the previous command and create a JSON file in the following format: +```json +{ + "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", + "resourceGroup": "ResourceGroupName", + "accountName": "NewLuisResourceName" +} +``` + +### 2. Retrieve ARM access token via Azure CLI +```bash +az account get-access-token --subscription "AzureSubscriptionGuid" +``` + +This will return an object that looks like this: +```json +{ + "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", + "expiresOn": "2200-12-31 23:59:59.999999", + "subscription": "AzureSubscriptionGuid", + "tenant": "tenant-guid", + "tokenType": "Bearer" +} +``` + +The value needed for the next step is the `"accessToken"`. + + +### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application + +```bash +luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" +``` + +If successful, it should yield a response like this: +```json +{ + "code": "Success", + "message": "Operation Successful" +} +``` + +### 4. See the LUIS Cognitive Services' keys + +```bash +az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +This will return an object that looks like this: +```json +{ + "key1": "9a69####dc8f####8eb4####399f####", + "key2": "####f99e####4b1a####fb3b####6b9f" +} +``` diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-new-rg.json b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..06b8284158 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..43943b6581 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/experimental/language-generation/csharp_dotnetcore/13.core-bot/wwwroot/default.htm b/experimental/language-generation/csharp_dotnetcore/13.core-bot/wwwroot/default.htm new file mode 100644 index 0000000000..639552a5ff --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/13.core-bot/wwwroot/default.htm @@ -0,0 +1,417 @@ + + + + + + + Core Bot Sample + + + + + +
+
+
+
Core Bot Sample
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+
+
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/experimental/language-generation/csharp_dotnetcore/csharp_dotnetcore.sln b/experimental/language-generation/csharp_dotnetcore/csharp_dotnetcore.sln new file mode 100644 index 0000000000..4072140ae9 --- /dev/null +++ b/experimental/language-generation/csharp_dotnetcore/csharp_dotnetcore.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.156 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBot", "13.core-bot\CoreBot.csproj", "{7193E992-8ADA-4817-B060-C1078F404FB7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7193E992-8ADA-4817-B060-C1078F404FB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7193E992-8ADA-4817-B060-C1078F404FB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7193E992-8ADA-4817-B060-C1078F404FB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7193E992-8ADA-4817-B060-C1078F404FB7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {21FAF271-4057-4CCA-9834-97919007CD5E} + EndGlobalSection +EndGlobal diff --git a/experimental/language-generation/lg-file-format.md b/experimental/language-generation/lg-file-format.md new file mode 100644 index 0000000000..29ee038b8b --- /dev/null +++ b/experimental/language-generation/lg-file-format.md @@ -0,0 +1,223 @@ +# .LG file format +.lg file will be a lot similar to the [.lu][1] file. As an overarching goal, we use simple markdown conventions as much as possible and add additional syntax and semantics only where needed. + +.lg files help describe language generation templates with entity references and their composition. The rest of this document covers the various concepts expressed via the .lg file format. See [here][2] for API-reference. + +## Comments +Comments are prefixed with '>' character. All lines that have this prefix will be skipped by the parser. + +```markdown +> this is a comment. +``` +## Escape character +- Use '\' as escape character. E.g. "You can say cheese and tomato \[toppings are optional\]" + +## Template +At the core of rule based LG is the concept of a template. Each template has a +- Name +- List of one-of variation text values .or. +- A collection of conditions, each with a + - Condition expression which is expressed using the [Common expression language][3] and + - List of one-of variation text values per condition. + +Templates are defined via # \ notation. Here is an example of a simple template that includes 2 variations. +Templates follow the markdown header definition. Variations are expressed as markdown list; so you can prefix them using either '-' or '*' or '+'. + +```markdown +> Greeting template with 2 variations. One of the variation is picked up by the template resolution runtime. +# GreetingPrefix +- Hi +- Hello +``` + +Here are few examples of a conditional template. All conditions are expressed using the [Common expression language][3]. Condition expressions are enclosed in curly brackets - {}. Conditions are evaluated in the order specified via the IF ... ELSE or IF ... ELSEIF ... ELSE prefixes. + +Here is an example that shows the simple IF ... ELSE conditional response template definition. + + +```markdown +> time of day greeting reply template with conditions. +# timeOfDayGreeting +- IF: {timeOfDay == 'morning'} + - good morning +- ELSE: + - good evening +``` + +Here's another example that shows IF ... ELSEIF ... ELSE conditional response template definition. + +```markdown +# timeOfDayGreeting +- IF: {timeOfDay == 'morning'} + - good morning +- ELSEIF: {timeOfDay == 'afternoon'} + - good afternoon +- ELSE: + - good evening +``` + +### References to templates +Variation text can include references to another named template to aid with composition and resolution of sophisticated responses. +Reference to another named template are denoted using markdown link notation by enclosing the target template name in square brackets - [TemplateName]. + +```markdown +> Example of a template that includes composition reference to another template +# GreetingReply +- [GreetingPrefix], [timeOfDayGreeting] + +# GreetingPrefix +- Hi +- Hello + +# timeOfDayGreeting +- IF: {timeOfDay == 'morning'} + - good morning +- ELSEIF: {timeOfDay == 'afternoon'} + - good afternoon +- ELSE: + - good evening +``` + +Calling the `GreetingReply` template can result in one of the following expansion resolutions - + +``` +Hi, good morning +Hi, good afternoon +Hi, good evening +Hello, good morning +Hello, good afternoon +Hello, good evening +``` + +### Parametrization of templates +To aid with contextual re-usability, templates can be parametrized. With this different callers to the template can pass in different values for use in expansion resolution. + +Here is an example of a template parametrization. + +```markdown +# timeOfDayGreetingTemplate (param1) +- IF: {param1 == 'morning'} + - good morning +- ELSEIF: {param1 == 'afternoon'} + - good afternoon +- ELSE: + - good evening + +# morningGreeting +- timeOfDayGreetingTemplate('morning') + +# timeOfDayGreeting +- timeOfDayGreetingTemplate(timeOfDay) +``` + +## Entities +- When used directly within a one-of variation text, entity references are denoted by enclosing them in curly brackets - {`entityName`} +- When used as a parameter within a + - [pre-built function][4] or + - as a parameter to [template resolution call](#Parametrization-of-templates) or + - a condition in [conditional response template](#conditional-response-template) +they are simply expressed as `entityName`. + +## Multi-line text in variations +Each one-of variation can include multi-line text enclosed in ```...```. + +Here is an example - +```markdown + # MultiLineExample + - ```This is a multi-line list + - one + - two + ``` + - ```This is a multi-line variation + - three + - four + ``` +``` + +Multi-line variation can request template expansion and entity substitution by enclosing the requested operation in @{}. + +Here is an example - +```markdown +# MultiLineExample + - ``` + Here is what I have for the order + - Title: @{reservation.title} + - Location: @{reservation.location} + - Date/ time: @{reservation.dateTimeReadBack} + ``` +``` + +With multi-line support, you can have the language generation sub-system fully resolve a complex JSON or XML (e.g. SSML wrapped text to control bot's spoken reply). + +Here is an example of complex object that your bot's code will parse out and render appropriately. + +```markdown + # TitleText + - Here are some [TitleSuffix] + + # TitleSuffix + - cool photos + - pictures + - nice snaps + + # SubText + - What is your favorite? + - Don't they all look great? + - sorry, some of them are repeats + + # CardImages + - https://picsum.photos/200/200?image=100 + - https://picsum.photos/300/200?image=200 + - https://picsum.photos/200/200?image=400 + + # ImageGalleryTemplate + - ``` + { + "titleText": "@{[TitleText]}", + "subTitle": "@{[SubText]}", + "images": [ + { + "type": "Image", + "url": "@{[CardImages]}" + }, + { + "type": "Image", + "url": "@{[CardImages]}" + } + ] + } + ``` +``` + +Calling the `ImageGalleryTemplate` for template resolution will result in a json string that has a 'titleText', 'subTitle' and randomly selected images from the `CardImages` template. + +## Using pre-built functions in variations +[Pre-built functions][4] supported by the [Common expression language][3] can also be used inline in a one-of variation text to achieve even more powerful text composition. To use an expression inline, simply wrap it in curly brackets - {}. + +Here is an example that illustrates that - + +```markdown +# RecentTasks +- IF: {count(recentTasks) == 1} + - Your most recent task is {recentTasks[0]}. You can let me know if you want to add or complete a task. +- ELSEIF: {count(recentTasks) == 2} + - Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- ELSEIF: {count(recentTasks) > 2} + - Your most recent {count(recentTasks)} tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- ELSE: + - You don't have any tasks. +``` + +The above example uses the [join][5] pre-built function to list all values in the `recentTasks` collection. + +[1]:https://github.com/Microsoft/botbuilder-tools/blob/master/packages/Ludown/docs/lu-file-format.md +[2]:api-reference.md +[3]:../common-expression-language/readme.md +[4]:../common-expression-language/prebuilt-functions.md +[5]:../common-expression-language/prebuilt-functions.md#join +[6]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown +[7]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#chat-file-format +[8]:https://github.com/Microsoft/botbuilder-tools/blob/master/packages/Chatdown/Examples/CardExamples.chat +[9]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-commands +[10]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-cards +[11]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-attachments \ No newline at end of file From fb5b1a04361ba17c565fb644b01cf26dce79d213 Mon Sep 17 00:00:00 2001 From: Vishwac Sena Kannan Date: Mon, 15 Apr 2019 17:46:23 -0700 Subject: [PATCH 2/2] add preview --- experimental/common-expression-language/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/common-expression-language/README.md b/experimental/common-expression-language/README.md index bdd7d74197..57c64aaaf2 100644 --- a/experimental/common-expression-language/README.md +++ b/experimental/common-expression-language/README.md @@ -1,4 +1,4 @@ -# Common Expression Language Concepts +# Common Expression Language ***_[PREVIEW]_*** Bots, like any other application, require use of expressions to evaluate outcome of a condition based on runtime information available in memory or to the dialog or the language generation system.