-
Notifications
You must be signed in to change notification settings - Fork 575
feat: add Amazon Lex V2 event support (#518) #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package events_test | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/aws/aws-lambda-go/events" | ||
| ) | ||
|
|
||
| func ExampleLexV2Event() { | ||
| event := events.LexV2Event{ | ||
| MessageVersion: "1.0", | ||
| InvocationSource: "FulfillmentCodeHook", | ||
| InputMode: "Text", | ||
| SessionID: "12345678-1234-1234-1234-123456789012", | ||
| InputTranscript: "check my balance", | ||
| Bot: events.LexV2Bot{ | ||
| ID: "BOTID", | ||
| Name: "BankingBot", | ||
| AliasID: "ALIASID", | ||
| LocaleID: "en_US", | ||
| Version: "1", | ||
| }, | ||
| SessionState: events.LexV2SessionState{ | ||
| Intent: &events.LexV2Intent{ | ||
| Name: "CheckBalance", | ||
| State: "ReadyForFulfillment", | ||
| ConfirmationState: "None", | ||
| Slots: map[string]events.LexV2Slot{}, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| fmt.Printf("Bot: %s, Intent: %s\n", event.Bot.Name, event.SessionState.Intent.Name) | ||
| // Output: Bot: BankingBot, Intent: CheckBalance | ||
| } | ||
|
|
||
| func ExampleLexV2Response() { | ||
| response := events.LexV2Response{ | ||
| SessionState: events.LexV2SessionState{ | ||
| DialogAction: &events.LexV2DialogAction{ | ||
| Type: "Close", | ||
| }, | ||
| Intent: &events.LexV2Intent{ | ||
| Name: "CheckBalance", | ||
| State: "Fulfilled", | ||
| ConfirmationState: "None", | ||
| Slots: map[string]events.LexV2Slot{}, | ||
| }, | ||
| }, | ||
| Messages: []events.LexV2Message{ | ||
| { | ||
| ContentType: "PlainText", | ||
| Content: "Your balance is $1,234.56", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| fmt.Printf("Action: %s, Message: %s\n", response.SessionState.DialogAction.Type, response.Messages[0].Content) | ||
| // Output: Action: Close, Message: Your balance is $1,234.56 | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,189 @@ | ||||||||
| package events | ||||||||
|
|
||||||||
| // Amazon Lex V2 event structures for Lambda functions. | ||||||||
| // | ||||||||
| // Lex V2 provides a conversational interface for building chatbots and voice assistants. | ||||||||
| // These structures represent the input events sent from Lex V2 to Lambda functions | ||||||||
| // and the expected response format. | ||||||||
| // | ||||||||
| // Key differences from Lex V1: | ||||||||
| // - sessionId instead of userId | ||||||||
| // - interpretations array with confidence scores | ||||||||
| // - enhanced slot structure with shape and values | ||||||||
| // - sessionState replaces separate sessionAttributes | ||||||||
| // - dialogAction moved into sessionState | ||||||||
| // | ||||||||
| // Note: This library uses plain string types for simplicity. For type-safe constants | ||||||||
| // and validation, refer to the AWS documentation for valid values: | ||||||||
| // https://docs.aws.amazon.com/lexv2/latest/dg/lambda-input-format.html | ||||||||
| // https://docs.aws.amazon.com/lexv2/latest/dg/lambda-response-format.html | ||||||||
| // | ||||||||
| // For more information, see: | ||||||||
| // https://docs.aws.amazon.com/lexv2/latest/dg/lambda.html | ||||||||
|
|
||||||||
| // LexV2Event represents the input event from Amazon Lex V2 to Lambda. | ||||||||
| // https://docs.aws.amazon.com/lexv2/latest/dg/lambda-input-format.html | ||||||||
| type LexV2Event struct { | ||||||||
| MessageVersion string `json:"messageVersion"` | ||||||||
| InvocationSource string `json:"invocationSource"` | ||||||||
| InputMode string `json:"inputMode"` | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would recommend a data type for // InputMode represents the mode of innput from the user.
type InputMode string
const (
InputModeText InputMode = "Text"
InputModeVoice InputMode = "Voice"
InputModeDTMF InputMode = "DTMF"
)
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. intentionally kept these as plain AWS can add new enum values without requiring library updates. Developers are pointed to the official AWS docs for valid values if they want to add type-safe constants in their own code. https://docs.aws.amazon.com/lexv2/latest/dg/lambda-input-format.html This approach keeps the library minimal while remaining flexible as AWS evolves the service. |
||||||||
| ResponseContentType string `json:"responseContentType"` | ||||||||
| SessionID string `json:"sessionId"` | ||||||||
| InputTranscript string `json:"inputTranscript,omitempty"` | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The LexV2Event struct is missing the optional
Suggested change
The documentation does not describes this field as optional, but testing has shown that the field is optional, so it can be represented as a string pointer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks will add InputTranscript: The documentation not explicitly mark |
||||||||
| InvocationLabel *string `json:"invocationLabel,omitempty"` | ||||||||
| Bot LexV2Bot `json:"bot"` | ||||||||
| Interpretations []LexV2Interpretation `json:"interpretations"` | ||||||||
| ProposedNextState *LexV2ProposedNextState `json:"proposedNextState,omitempty"` | ||||||||
| RequestAttributes map[string]string `json:"requestAttributes"` | ||||||||
| SessionState LexV2SessionState `json:"sessionState"` | ||||||||
| Transcriptions []LexV2Transcription `json:"transcriptions,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Bot struct { | ||||||||
| ID string `json:"id"` | ||||||||
| Name string `json:"name"` | ||||||||
| AliasID string `json:"aliasId"` | ||||||||
| AliasName string `json:"aliasName,omitempty"` | ||||||||
| LocaleID string `json:"localeId"` | ||||||||
| Version string `json:"version"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Interpretation struct { | ||||||||
| Intent LexV2Intent `json:"intent"` | ||||||||
| InterpretationSource string `json:"interpretationSource,omitempty"` | ||||||||
| NLUConfidence *LexV2NLUConfidence `json:"nluConfidence,omitempty"` | ||||||||
| SentimentResponse *LexV2SentimentResponse `json:"sentimentResponse,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2NLUConfidence struct { | ||||||||
| Score float64 `json:"score,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Intent struct { | ||||||||
| ConfirmationState string `json:"confirmationState"` | ||||||||
| Name string `json:"name"` | ||||||||
| Slots map[string]LexV2Slot `json:"slots"` | ||||||||
| State string `json:"state"` | ||||||||
| KendraResponse *LexV2KendraResponse `json:"kendraResponse,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Slot struct { | ||||||||
| Shape string `json:"shape,omitempty"` | ||||||||
| Value *LexV2SlotValue `json:"value,omitempty"` | ||||||||
| Values []LexV2SlotValue `json:"values,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SlotValue struct { | ||||||||
| OriginalValue string `json:"originalValue"` | ||||||||
| InterpretedValue string `json:"interpretedValue"` | ||||||||
| ResolvedValues []string `json:"resolvedValues,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| // LexV2KendraResponse contains information about the results of a Kendra search query. | ||||||||
| // This field only appears if the intent is a KendraSearchIntent. | ||||||||
| // For the complete structure, see: | ||||||||
| // https://docs.aws.amazon.com/kendra/latest/dg/API_Query.html#API_Query_ResponseSyntax | ||||||||
| type LexV2KendraResponse struct { | ||||||||
| // The response structure matches the Kendra Query API response. | ||||||||
| // Using interface{} to accommodate the full Kendra response without | ||||||||
| // duplicating the entire Kendra API structure here. | ||||||||
| // Users should unmarshal this to the appropriate Kendra types if needed. | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SentimentResponse struct { | ||||||||
| Sentiment string `json:"sentiment"` | ||||||||
| SentimentScore LexV2SentimentScore `json:"sentimentScore"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SentimentScore struct { | ||||||||
| Mixed float64 `json:"mixed"` | ||||||||
| Negative float64 `json:"negative"` | ||||||||
| Neutral float64 `json:"neutral"` | ||||||||
| Positive float64 `json:"positive"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2ProposedNextState struct { | ||||||||
| DialogAction *LexV2DialogAction `json:"dialogAction,omitempty"` | ||||||||
| Intent *LexV2Intent `json:"intent,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SessionState struct { | ||||||||
| ActiveContexts []LexV2ActiveContext `json:"activeContexts,omitempty"` | ||||||||
| SessionAttributes map[string]string `json:"sessionAttributes,omitempty"` | ||||||||
| RuntimeHints *LexV2RuntimeHints `json:"runtimeHints,omitempty"` | ||||||||
| DialogAction *LexV2DialogAction `json:"dialogAction,omitempty"` | ||||||||
| Intent *LexV2Intent `json:"intent,omitempty"` | ||||||||
| OriginatingRequestID string `json:"originatingRequestId,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2ActiveContext struct { | ||||||||
| Name string `json:"name"` | ||||||||
| ContextAttributes map[string]string `json:"contextAttributes"` | ||||||||
| TimeToLive LexV2TimeToLive `json:"timeToLive"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2TimeToLive struct { | ||||||||
| TimeToLiveInSeconds int `json:"timeToLiveInSeconds"` | ||||||||
| TurnsToLive int `json:"turnsToLive"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2RuntimeHints struct { | ||||||||
| SlotHints map[string]map[string]LexV2SlotHint `json:"slotHints,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SlotHint struct { | ||||||||
| RuntimeHintValues []LexV2RuntimeHintValue `json:"runtimeHintValues"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2RuntimeHintValue struct { | ||||||||
| Phrase string `json:"phrase"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Transcription struct { | ||||||||
| Transcription string `json:"transcription"` | ||||||||
| TranscriptionConfidence float64 `json:"transcriptionConfidence,omitempty"` | ||||||||
| ResolvedContext *LexV2ResolvedContext `json:"resolvedContext,omitempty"` | ||||||||
| ResolvedSlots map[string]LexV2Slot `json:"resolvedSlots,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2ResolvedContext struct { | ||||||||
| Intent string `json:"intent"` | ||||||||
| } | ||||||||
|
|
||||||||
| // LexV2Response represents the response from Lambda to Lex V2. | ||||||||
| // https://docs.aws.amazon.com/lexv2/latest/dg/lambda-response-format.html | ||||||||
| type LexV2Response struct { | ||||||||
| SessionState LexV2SessionState `json:"sessionState"` | ||||||||
| Messages []LexV2Message `json:"messages,omitempty"` | ||||||||
| RequestAttributes map[string]string `json:"requestAttributes,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2DialogAction struct { | ||||||||
| Type string `json:"type"` | ||||||||
| SlotToElicit string `json:"slotToElicit,omitempty"` | ||||||||
| SlotElicitationStyle string `json:"slotElicitationStyle,omitempty"` | ||||||||
| SubSlotToElicit *LexV2SubSlotToElicit `json:"subSlotToElicit,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2SubSlotToElicit struct { | ||||||||
| Name string `json:"name"` | ||||||||
| SubSlotToElicit *LexV2SubSlotToElicit `json:"subSlotToElicit,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Message struct { | ||||||||
| ContentType string `json:"contentType"` | ||||||||
| Content string `json:"content,omitempty"` | ||||||||
| ImageResponseCard *LexV2ImageResponseCard `json:"imageResponseCard,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2ImageResponseCard struct { | ||||||||
| Title string `json:"title"` | ||||||||
| Subtitle string `json:"subtitle,omitempty"` | ||||||||
| ImageURL string `json:"imageUrl,omitempty"` | ||||||||
| Buttons []LexV2Button `json:"buttons,omitempty"` | ||||||||
| } | ||||||||
|
|
||||||||
| type LexV2Button struct { | ||||||||
| Text string `json:"text"` | ||||||||
| Value string `json:"value"` | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package events | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/aws/aws-lambda-go/events/test" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestLexV2EventMarshaling(t *testing.T) { | ||
| inputJSON := test.ReadJSONFromFile(t, "./testdata/lexv2-event.json") | ||
|
|
||
| var inputEvent LexV2Event | ||
| if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { | ||
| t.Errorf("could not unmarshal event. details: %v", err) | ||
| } | ||
|
|
||
| outputJSON, err := json.Marshal(inputEvent) | ||
| if err != nil { | ||
| t.Errorf("could not marshal event. details: %v", err) | ||
| } | ||
|
|
||
| assert.JSONEq(t, string(inputJSON), string(outputJSON)) | ||
| } | ||
|
|
||
| func TestLexV2ResponseMarshaling(t *testing.T) { | ||
| inputJSON := test.ReadJSONFromFile(t, "./testdata/lexv2-response.json") | ||
|
|
||
| var inputEvent LexV2Response | ||
| if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { | ||
| t.Errorf("could not unmarshal event. details: %v", err) | ||
| } | ||
|
|
||
| outputJSON, err := json.Marshal(inputEvent) | ||
| if err != nil { | ||
| t.Errorf("could not marshal event. details: %v", err) | ||
| } | ||
|
|
||
| assert.JSONEq(t, string(inputJSON), string(outputJSON)) | ||
| } | ||
|
|
||
| func TestLexV2MarshalingMalformedJson(t *testing.T) { | ||
| test.TestMalformedJson(t, LexV2Event{}) | ||
| } | ||
|
|
||
| func TestLexV2ResponseMalformedJson(t *testing.T) { | ||
| test.TestMalformedJson(t, LexV2Response{}) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "messageVersion": "1.0", | ||
| "invocationSource": "FulfillmentCodeHook", | ||
| "inputMode": "Text", | ||
| "responseContentType": "text/plain; charset=utf-8", | ||
| "sessionId": "12345678-1234-1234-1234-123456789012", | ||
| "inputTranscript": "I want to check my balance", | ||
| "bot": { | ||
| "id": "BOTID", | ||
| "name": "BankingBot", | ||
| "aliasId": "ALIASID", | ||
| "aliasName": "Prod", | ||
| "localeId": "en_US", | ||
| "version": "1" | ||
| }, | ||
| "interpretations": [ | ||
| { | ||
| "intent": { | ||
| "confirmationState": "None", | ||
| "name": "CheckBalance", | ||
| "slots": { | ||
| "accountType": { | ||
| "value": { | ||
| "originalValue": "checking", | ||
| "interpretedValue": "checking", | ||
| "resolvedValues": ["CHECKING"] | ||
| } | ||
| } | ||
| }, | ||
| "state": "ReadyForFulfillment" | ||
| }, | ||
| "nluConfidence": { | ||
| "score": 0.95 | ||
| } | ||
| } | ||
| ], | ||
| "requestAttributes": { | ||
| "x-amz-lex:channel-type": "Twilio-SMS" | ||
| }, | ||
| "sessionState": { | ||
| "sessionAttributes": { | ||
| "customerId": "12345" | ||
| }, | ||
| "intent": { | ||
| "confirmationState": "None", | ||
| "name": "CheckBalance", | ||
| "slots": { | ||
| "accountType": { | ||
| "value": { | ||
| "originalValue": "checking", | ||
| "interpretedValue": "checking", | ||
| "resolvedValues": ["CHECKING"] | ||
| } | ||
| } | ||
| }, | ||
| "state": "ReadyForFulfillment" | ||
| }, | ||
| "originatingRequestId": "request-123" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "sessionState": { | ||
| "dialogAction": { | ||
| "type": "Close" | ||
| }, | ||
| "intent": { | ||
| "name": "CheckBalance", | ||
| "slots": { | ||
| "accountType": { | ||
| "value": { | ||
| "originalValue": "checking", | ||
| "interpretedValue": "checking", | ||
| "resolvedValues": ["CHECKING"] | ||
| } | ||
| } | ||
| }, | ||
| "state": "Fulfilled", | ||
| "confirmationState": "None" | ||
| }, | ||
| "sessionAttributes": { | ||
| "customerId": "12345" | ||
| } | ||
| }, | ||
| "messages": [ | ||
| { | ||
| "contentType": "PlainText", | ||
| "content": "Your checking account balance is $1,234.56" | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would recommend a data type for InvocationSource: