"Happy 14th birthday Kylee!"
"Hi Kathy. Looks like I missed it. But happy 73rd birthday anyway."
"Hey Mike. Happy 40th birthday in advance."
"Wow, a leap year baby! You turn what, 5 this year? Just kidding.
Happy 18th birthday Marco!"
Agents,
Intents,
and Entities
Define the parameters an intent is seeking to fill
May be system defined (name, date, location) or custom built
A backend webhook capable of performing more complex business logic
Takes in the intent's context, action, and parameter data
Responds with text to be spoken or displayed to the user
Visual Studio Code
F#
.NET Core 2.0
Azure Functions Core Tools
>
=
=
/
/
=
apt
Ionide
Azure Functions
Nuget
Step 1 - Open Visual Studio Code
Step 2 - Open the Azure Functions sidebar
Step 3 - Create New Project
Step 4 - Choose C# as the project language
Step 5 - Project Created
Extension
Recommendations ---->
for workspace
Launch configuration ---->
for debugging
Workspace Settings ---->
for Azure Functions
Tasks to build,
clean, and run ---->
the project
Project is initialized
as a Git repository
with a .gitignore ---->
file tailored for
Azure Functions
development
Azure Functions
host configuration ---->
and local app settings
The project file ---->
Change the extension ---->
to .fsproj
Package 'Microsoft.AspNet.WebApi.Client 5.2.2' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETStandard,Version=v2.0'.
Update the NuGet package:
Microsoft.NET.Sdk.Functions
<ItemGroup>
<Compile Include="Util.fs"/>
<Compile Include="Model.fs"/>
<Compile Include="BirthdayGreeting.fs"/>
</ItemGroup>
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
Attribute values are used at build time to generate the function.json file, which until recently had to be manually kept up to date.
The FunctionName attribute defines the name of the function within Azure.
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
The log parameter is passed in by the runtime, and provides a way to write to the Azure trace log.
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
[<FunctionName("BirthdayGreeting")>]
let run
([<HttpTrigger(Extensions.Http.AuthorizationLevel.Anonymous, "post", Route = null)>]
req: HttpRequest,
log: TraceWriter) =
log.Info("BirthdayGreeting is processing a request.")
async {
try
use reader = new StreamReader(req.Body)
let! requestBody = reader.ReadToEndAsync() |> Async.AwaitTask
log.Info(sprintf "Raw Request Body: %s" requestBody)
let data = JsonConvert.DeserializeObject<DialogFlowRequest>(requestBody)
log.Info(sprintf "Deserialized Request Body: %A" data)
let givenName = data.queryResult.parameters.``given-name``
let birthdate = data.queryResult.parameters.birthdate.Date
let greeting = getGreeting givenName birthdate
log.Info(sprintf "Responding with greeting: %s" greeting)
let result = createDialogFlowResponse greeting
return JsonResult result :> IActionResult
with ex ->
log.Error(sprintf "Something went horribly wrong!", ex)
return BadRequestObjectResult ex.Message :> IActionResult
} |> Async.RunSynchronously
type QueryParameters = {
``given-name``: string
``last-name``: string
birthdate: System.DateTime
}
type QueryResult = {
parameters: QueryParameters
}
type DialogFlowRequest = {
queryResult: QueryResult
}
type SimpleResponse = {
textToSpeech: string
}
type RichResponseItem = {
simpleResponse: SimpleResponse
}
type RichResponse = {
items: RichResponseItem list
}
type GooglePayload = {
richResponse: RichResponse
}
type Payload = {
google: GooglePayload
}
type DialogFlowResponse = {
payload: Payload
}
let createDialogFlowResponse response =
{ payload =
{ google =
{ richResponse =
{ items =
[{ simpleResponse =
{ textToSpeech = response }}]}}}}
{
"queryResult": {
"parameters": {
"given-name": "Mike",
"last-name": "Sigsworth",
"birthdate": "2000-12-29T00:00:00"
}
}
}
{
"payload": {
"google": {
"richResponse": {
"items": [{
"simpleResponse": {
"textToSpeech": "Happy 18th birthday in advance, Mike. Hope it's a good one!"
}
}]
}
}
}
}
let getAge (birthdate: DateTime) =
let today = DateTime.Today
let age = today.Year - birthdate.Year
if (birthdate > today.AddYears(age)) then age - 1 else age
let ordinal (num: int) =
let ones = num % 10
let tens = floor ((num |> float) / 10.0) % 10.0
if (tens = 1.0) then
"th"
else
match ones with
| 1 -> "st"
| 2 -> "nd"
| 3 -> "rd"
| _ -> "th"
let getLeapAge (birthdate: DateTime) =
let today = DateTime.Today
birthdate.Year
|> Seq.unfold (fun year ->
if (year > today.Year) then None
else Some(DateTime.IsLeapYear year, year+1))
|> Seq.filter id
|> Seq.length
let isLeapYearBaby (birthdate: DateTime) =
DateTime.IsLeapYear birthdate.Year &&
birthdate.Month = 2 &&
birthdate.Day = 29
let isBirthday (birthdate: DateTime) =
let today = DateTime.Today
birthdate.Month = today.Month && birthdate.Day = today.Day
let birthdateForYear (year: int) (birthdate : DateTime) =
if (birthdate.Month = 2 && birthdate.Day = 29 && not (DateTime.IsLeapYear year)) then
DateTime(year, 3, 1) // Leap year birthday
else
DateTime(year, birthdate.Month, birthdate.Day)
let isBelated (birthdate: DateTime) =
let tolerance = 150.0
let today = DateTime.Today
let thisYear = today.Year
let lastYear = thisYear - 1
let thisYearsBirthdate = birthdateForYear thisYear birthdate
let lastYearsBirthdate = birthdateForYear lastYear birthdate
today.AddDays(-tolerance) < thisYearsBirthdate && thisYearsBirthdate < today ||
today.AddDays(-tolerance) < lastYearsBirthdate && lastYearsBirthdate < today
let getGreeting givenName birthdate =
let age = getAge birthdate
let ageStr =
if (age > 0) then
sprintf "%d%s " age (ordinal age)
else ""
let msg =
if (isBirthday birthdate) then
sprintf "Happy %sbirthday %s!!" ageStr givenName
elif (isBelated birthdate) then
sprintf "Looks like I missed it %s. But happy belated %sbirthday anyway!" givenName ageStr
else
sprintf "Happy %sbirthday in advance, %s. Hope it's a good one!" ageStr givenName
if (isLeapYearBaby birthdate) then
let leapAge = getLeapAge birthdate
let leapAgeStr =
if (leapAge > 0) then
sprintf "You turn what, %d this year? Just kidding. Anyways... " leapAge
else ""
sprintf "Wow, a leap year baby! %s%s" leapAgeStr msg
else msg
http://localhost:7071/api/BirthdayGreeting
Request
Response
@aspnetdev
github.com/mikesigs/talk-to-your-functions
slides.com/mikesigs/talk-to-your-functions
discardchanges.com