diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1680fc1c38ea..00abede8b1db 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,10 +44,11 @@ /dataproc/**/* @GoogleCloudPlatform/python-samples-reviewers /datastore/**/* @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/python-samples-reviewers /dialogflow/**/* @GoogleCloudPlatform/dee-data-ai @GoogleCloudPlatform/python-samples-reviewers +/dialogflow-cx/**/* @GoogleCloudPlatform/dee-data-ai @GoogleCloudPlatform/python-samples-reviewers /dns/**/* @GoogleCloudPlatform/python-samples-reviewers /documentai/**/* @GoogleCloudPlatform/dee-data-ai @GoogleCloudPlatform/python-samples-reviewers /endpoints/**/* @GoogleCloudPlatform/python-samples-reviewers -/enterpriseknowledgegraph/**/* @GoogleCloudPlatform/dee-data-ai @GoogleCloudPlatform/python-samples-reviewers +/enterpriseknowledgegraph/**/* @GoogleCloudPlatform/dee-data-ai @GoogleCloudPlatform/python-samples-reviewers /eventarc/**/* @GoogleCloudPlatform/aap-dpes @GoogleCloudPlatform/python-samples-reviewers /error_reporting/**/* @GoogleCloudPlatform/python-samples-reviewers /firestore/**/* @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/python-samples-reviewers diff --git a/dialogflow-cx/README.rst b/dialogflow-cx/README.rst new file mode 100644 index 000000000000..7c403a4d7121 --- /dev/null +++ b/dialogflow-cx/README.rst @@ -0,0 +1,221 @@ + +.. This file is automatically generated. Do not edit this file directly. + +Dialogflow CX API Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=/README.rst + + +This directory contains samples for Dialogflow CX API. The `Dialogflow CX API`_ enables you to create conversational experiences across devices and platforms. + + + + +.. _Dialogflow CX API: https://cloud.google.com/dialogflow/cx/docs/ + + +Setup +------------------------------------------------------------------------------- + + + +Authentication +++++++++++++++ + +This sample requires you to have authentication setup. Refer to the +`Authentication Getting Started Guide`_ for instructions on setting up +credentials for applications. + +.. _Authentication Getting Started Guide: + https://cloud.google.com/docs/authentication/getting-started + + + + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-docs-samples and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 3.6+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + + + + + + +Samples +------------------------------------------------------------------------------- + + +Detect Intent Text ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=/detect_intent_texts.py,/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python detect_intent_texts.py + + + usage: detect_intent_texts.py [-h] --agent AGENT [--session-id SESSION_ID] + [--language-code LANGUAGE_CODE] + texts [texts ...] + + DialogFlow API Detect Intent Python sample with text inputs. + + Examples: + python detect_intent_texts.py -h + python detect_intent_texts.py --agent AGENT --session-id SESSION_ID "hello" "book a meeting room" "Mountain View" + python detect_intent_texts.py --agent AGENT --session-id SESSION_ID "tomorrow" "10 AM" "2 hours" "10 people" "A" "yes" + + positional arguments: + texts Text inputs. + + optional arguments: + -h, --help show this help message and exit + --agent AGENT Agent resource name. Required. + --session-id SESSION_ID + Identifier of the DetectIntent session. Defaults to a + random UUID. + --language-code LANGUAGE_CODE + Language code of the query. Defaults to "en-US". + + + + + +Detect Intent Audio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=/detect_intent_audio.py,/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python detect_intent_audio.py + + + usage: detect_intent_audio.py [-h] --agent AGENT [--session-id SESSION_ID] + [--language-code LANGUAGE_CODE] + --audio-file-path AUDIO_FILE_PATH + + DialogFlow API Detect Intent Python sample with audio file. + + Examples: + python detect_intent_audio.py -h + python detect_intent_audio.py --agent AGENT --session-id SESSION_ID --audio-file-path resources/hello.wav + + optional arguments: + -h, --help show this help message and exit + --agent AGENT Agent resource name. Required. + --session-id SESSION_ID + Identifier of the DetectIntent session. Defaults to a + random UUID. + --language-code LANGUAGE_CODE + Language code of the query. Defaults to "en-US". + --audio-file-path AUDIO_FILE_PATH + Path to the audio file. + + + + + +Detect Intent Stream ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=/detect_intent_stream.py,/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python detect_intent_stream.py + + + usage: detect_intent_stream.py [-h] --agent AGENT [--session-id SESSION_ID] + [--language-code LANGUAGE_CODE] + --audio-file-path AUDIO_FILE_PATH + + DialogFlow API Detect Intent Python sample with audio files processed as an audio stream. + + Examples: + python detect_intent_stream.py -h + python detect_intent_stream.py --agent AGENT --session-id SESSION_ID --audio-file-path resources/hello.wav + + optional arguments: + -h, --help show this help message and exit + --agent AGENT Agent resource name. Required. + --session-id SESSION_ID + Identifier of the DetectIntent session. Defaults to a + random UUID. + --language-code LANGUAGE_CODE + Language code of the query. Defaults to "en-US". + --audio-file-path AUDIO_FILE_PATH + Path to the audio file. + + + + + + + + + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. _Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. _browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. _report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ diff --git a/dialogflow-cx/README.rst.in b/dialogflow-cx/README.rst.in new file mode 100644 index 000000000000..043c9b772158 --- /dev/null +++ b/dialogflow-cx/README.rst.in @@ -0,0 +1,25 @@ +# This file is used to generate README.rst + +product: + name: Dialogflow CX API + short_name: Dialogflow CX API + url: https://cloud.google.com/dialogflow/cx/docs/ + description: > + The `Dialogflow CX API`_ enables you to create conversational experiences across devices and platforms. + +setup: +- auth +- install_deps + +samples: +- name: Detect Intent Text + file: detect_intent_texts.py + show_help: True +- name: Detect Intent Audio + file: detect_intent_audio.py + show_help: True +- name: Detect Intent Stream + file: detect_intent_stream.py + show_help: True + +cloud_client_library: true diff --git a/dialogflow-cx/create_agent.py b/dialogflow-cx/create_agent.py new file mode 100644 index 000000000000..541571b88784 --- /dev/null +++ b/dialogflow-cx/create_agent.py @@ -0,0 +1,40 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""DialogFlow API Create Agent Sample""" + +## [START dialogflow_cx_create_agent_sample] +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.types.agent import Agent + + +def create_agent(project_id, display_name): + + parent = "projects/" + project_id + "/locations/global" + + agents_client = AgentsClient() + + agent = Agent( + display_name=display_name, + default_language_code="en", + time_zone="America/Los_Angeles", + ) + + response = agents_client.create_agent(request={"agent": agent, "parent": parent}) + + return response + + +## [END dialogflow_cx_create_agent_sample] diff --git a/dialogflow-cx/create_agent_test.py b/dialogflow-cx/create_agent_test.py new file mode 100644 index 000000000000..dbb60b094e74 --- /dev/null +++ b/dialogflow-cx/create_agent_test.py @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test for create_agent""" + +import os +import uuid + +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.types.agent import DeleteAgentRequest + +import pytest + +from create_agent import create_agent + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +pytest.AGENT_PATH = "" + + +def delete_agent(name): + agents_client = AgentsClient() + request = DeleteAgentRequest(name=name) + agents_client.delete_agent(request=request) + + +def test_create_agent(): + agentName = f"fake_agent_{uuid.uuid4()}" + response = create_agent(PROJECT_ID, agentName) + delete_agent(response.name) + + assert response.display_name == agentName diff --git a/dialogflow-cx/detect_intent_audio.py b/dialogflow-cx/detect_intent_audio.py new file mode 100644 index 000000000000..7ff892aa08ce --- /dev/null +++ b/dialogflow-cx/detect_intent_audio.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow API Detect Intent Python sample with audio file. + +Examples: + python detect_intent_audio.py -h + python detect_intent_audio.py --agent AGENT \ + --session-id SESSION_ID --audio-file-path resources/hello.wav +""" + +import argparse +import uuid + +from google.cloud.dialogflowcx_v3.services.agents import AgentsClient +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import audio_config +from google.cloud.dialogflowcx_v3.types import session + + +# [START dialogflow_detect_intent_audio] +def run_sample(): + # TODO(developer): Replace these values when running the function + project_id = "YOUR-PROJECT-ID" + # For more information about regionalization see https://cloud.google.com/dialogflow/cx/docs/how/region + location_id = "YOUR-LOCATION-ID" + # For more info on agents see https://cloud.google.com/dialogflow/cx/docs/concept/agent + agent_id = "YOUR-AGENT-ID" + agent = f"projects/{project_id}/locations/{location_id}/agents/{agent_id}" + # For more information on sessions see https://cloud.google.com/dialogflow/cx/docs/concept/session + session_id = str(uuid.uuid4()) + audio_file_path = "YOUR-AUDIO-FILE-PATH" + # For more supported languages see https://cloud.google.com/dialogflow/es/docs/reference/language + language_code = "en-us" + + detect_intent_audio(agent, session_id, audio_file_path, language_code) + + +def detect_intent_audio(agent, session_id, audio_file_path, language_code): + """Returns the result of detect intent with an audio file as input. + + Using the same `session_id` between requests allows continuation + of the conversation.""" + session_path = f"{agent}/sessions/{session_id}" + print(f"Session path: {session_path}\n") + client_options = None + agent_components = AgentsClient.parse_agent_path(agent) + location_id = agent_components["location"] + if location_id != "global": + api_endpoint = f"{location_id}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + + input_audio_config = audio_config.InputAudioConfig( + audio_encoding=audio_config.AudioEncoding.AUDIO_ENCODING_LINEAR_16, + sample_rate_hertz=24000, + ) + + with open(audio_file_path, "rb") as audio_file: + input_audio = audio_file.read() + + audio_input = session.AudioInput(config=input_audio_config, audio=input_audio) + query_input = session.QueryInput(audio=audio_input, language_code=language_code) + request = session.DetectIntentRequest(session=session_path, query_input=query_input) + response = session_client.detect_intent(request=request) + + print("=" * 20) + print(f"Query text: {response.query_result.transcript}") + response_messages = [ + " ".join(msg.text.text) for msg in response.query_result.response_messages + ] + print(f"Response text: {' '.join(response_messages)}\n") + + +# [END dialogflow_detect_intent_audio] + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--agent", help="Agent resource name. Required.", required=True + ) + parser.add_argument( + "--session-id", + help="Identifier of the DetectIntent session. " "Defaults to a random UUID.", + default=str(uuid.uuid4()), + ) + parser.add_argument( + "--language-code", + help='Language code of the query. Defaults to "en-US".', + default="en-US", + ) + parser.add_argument( + "--audio-file-path", help="Path to the audio file.", required=True + ) + + args = parser.parse_args() + + detect_intent_audio( + args.agent, args.session_id, args.audio_file_path, args.language_code + ) diff --git a/dialogflow-cx/detect_intent_audio_test.py b/dialogflow-cx/detect_intent_audio_test.py new file mode 100644 index 000000000000..e5dd3ab00795 --- /dev/null +++ b/dialogflow-cx/detect_intent_audio_test.py @@ -0,0 +1,49 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_texts.""" + +from __future__ import absolute_import + +import os +import uuid + + +from detect_intent_audio import detect_intent_audio + +DIRNAME = os.path.realpath(os.path.dirname(__file__)) +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") +AGENT_ID_US_CENTRAL1 = os.getenv("AGENT_ID_US_CENTRAL1") +AGENT = f"projects/{PROJECT_ID}/locations/global/agents/{AGENT_ID}" +AGENT_US_CENTRAL1 = ( + f"projects/{PROJECT_ID}/locations/us-central1/agents/{AGENT_ID_US_CENTRAL1}" +) +SESSION_ID = uuid.uuid4() +AUDIO_PATH = os.getenv("AUDIO_PATH") +AUDIO = f"{DIRNAME}/{AUDIO_PATH}" + + +def test_detect_intent_texts(capsys): + detect_intent_audio(AGENT, SESSION_ID, AUDIO, "en-US") + out, _ = capsys.readouterr() + + assert "Response text: Hi! I'm the virtual flights agent." in out + + +def test_detect_intent_texts_regional(capsys): + detect_intent_audio(AGENT_US_CENTRAL1, SESSION_ID, AUDIO, "en-US") + out, _ = capsys.readouterr() + + assert "Response text: Hi! I'm the virtual flights agent." in out diff --git a/dialogflow-cx/detect_intent_disabled_webhook.py b/dialogflow-cx/detect_intent_disabled_webhook.py new file mode 100644 index 000000000000..e5965859bcce --- /dev/null +++ b/dialogflow-cx/detect_intent_disabled_webhook.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow Detect Intent Python sample, with a disabled webhook.""" + + +# [START dialogflow_cx_detect_intent_with_disabled_webhook] +import uuid + +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import session + + +def run_sample(): + # TODO(developer): Update these values when running the function + project_id = "YOUR-PROJECT-ID" + location = "YOUR-LOCATION-ID" + agent_id = "YOUR-AGENT-ID" + text = "Perfect!" + language_code = "en-us" + + detect_intent_disabled_webhook( + project_id, + location, + agent_id, + text, + language_code, + ) + + +def detect_intent_disabled_webhook( + project_id, + location, + agent_id, + text, + language_code, +): + """Returns the result of detect intent with sentiment analysis""" + + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + + # Prepare request + text_input = session.TextInput(text=text) + query_input = session.QueryInput(text=text_input, language_code=language_code) + query_params = session.QueryParameters( + disable_webhook=True, + ) + request = session.DetectIntentRequest( + session=session_path, + query_input=query_input, + query_params=query_params, + ) + + response = session_client.detect_intent(request=request) + print(f"Detect Intent Request: {request.query_params.disable_webhook}") + response_text = [] + for message in response.query_result.response_messages: + if message.text: + curr_response_text = message.text.text + print(f"Agent Response: {curr_response_text}") + response_text.append(curr_response_text) + return response_text + + +# [END dialogflow_cx_detect_intent_with_disabled_webhook] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/detect_intent_disabled_webhook_test.py b/dialogflow-cx/detect_intent_disabled_webhook_test.py new file mode 100644 index 000000000000..64422d3b5c99 --- /dev/null +++ b/dialogflow-cx/detect_intent_disabled_webhook_test.py @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_with_sentiment_analysis.py""" + +from __future__ import absolute_import + +import os + +from detect_intent_disabled_webhook import detect_intent_disabled_webhook + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") + + +def test_detect_intent_positive(): + response_text_list = detect_intent_disabled_webhook( + PROJECT_ID, + "global", + AGENT_ID, + "Perfect!", + "en-us", + ) + for response_text in response_text_list: + assert response_text[0] in [ + "You are welcome!", + "It's my pleasure.", + "Anytime.", + "Of course.", + "It's my pleasure to serve you.", + ] diff --git a/dialogflow-cx/detect_intent_event.py b/dialogflow-cx/detect_intent_event.py new file mode 100644 index 000000000000..2cd4790b23a0 --- /dev/null +++ b/dialogflow-cx/detect_intent_event.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow Detects intent using EventInput.""" + + +# [START dialogflow_cx_v3_detect_intent_event_input_async] +import uuid + +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import session + + +def run_sample(): + # TODO(developer): Update these values when running the function + # project_id = "YOUR-PROJECT-ID" + # location = "YOUR-LOCATION-ID" + # agent_id = "YOUR-AGENT-ID" + # event = "YOUR-EVENT" + # language_code = "YOUR-LANGUAGE-CODE" + + project_id = "dialogflow-cx-demo-1-348717" + location = "global" + agent_id = "8caa6b47-5dd7-4380-b86e-ea4301d565b0" + event = "sys.no-match-default" + language_code = "en-us" + + detect_intent_with_event_input( + project_id, + location, + agent_id, + event, + language_code, + ) + + +def detect_intent_with_event_input( + project_id, + location, + agent_id, + event, + language_code, +): + """Detects intent using EventInput""" + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + + # Construct detect intent request: + event = session.EventInput(event=event) + query_input = session.QueryInput(event=event, language_code=language_code) + request = session.DetectIntentRequest( + session=session_path, + query_input=query_input, + ) + + response = session_client.detect_intent(request=request) + response_text = response.query_result.response_messages[0].text.text[0] + print(f"Response: {response_text}") + return response_text + + +# [END dialogflow_cx_v3_detect_intent_event_input_async] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/detect_intent_event_test.py b/dialogflow-cx/detect_intent_event_test.py new file mode 100644 index 000000000000..dfbca5e0ff36 --- /dev/null +++ b/dialogflow-cx/detect_intent_event_test.py @@ -0,0 +1,49 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_with_sentiment_analysis.py""" + +from __future__ import absolute_import + +import os + +from detect_intent_event import detect_intent_with_event_input + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") + + +def test_detect_intent_positive(): + response_text = detect_intent_with_event_input( + PROJECT_ID, + "global", + AGENT_ID, + "sys.no-match-default", + "en-us", + ) + assert response_text in [ + "Can you say that again?", + "I didn't get that. Can you repeat?", + "I didn't get that. Can you say it again?", + "I missed that, say that again?", + "I missed what you said. What was that?", + "One more time?", + "Say that one more time?", + "Sorry, can you say that again?", + "Sorry, could you say that again?", + "Sorry, I didn't get that. Can you rephrase?", + "Sorry, what was that?", + "What was that?", + ] diff --git a/dialogflow-cx/detect_intent_stream.py b/dialogflow-cx/detect_intent_stream.py new file mode 100644 index 000000000000..244fecaf03b8 --- /dev/null +++ b/dialogflow-cx/detect_intent_stream.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow API Detect Intent Python sample with audio files processed as an audio stream. + +Examples: + python detect_intent_stream.py -h + python detect_intent_stream.py --agent AGENT \ + --session-id SESSION_ID --audio-file-path resources/hello.wav +""" + +import argparse +import uuid + +from google.cloud.dialogflowcx_v3beta1.services.agents import AgentsClient +from google.cloud.dialogflowcx_v3beta1.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3beta1.types import audio_config +from google.cloud.dialogflowcx_v3beta1.types import session + + +# [START dialogflow_detect_intent_stream] +def run_sample(): + # TODO(developer): Replace these values when running the function + project_id = "YOUR-PROJECT-ID" + # For more information about regionalization see https://cloud.google.com/dialogflow/cx/docs/how/region + location_id = "YOUR-LOCATION-ID" + # For more info on agents see https://cloud.google.com/dialogflow/cx/docs/concept/agent + agent_id = "YOUR-AGENT-ID" + agent = f"projects/{project_id}/locations/{location_id}/agents/{agent_id}" + # For more information on sessions see https://cloud.google.com/dialogflow/cx/docs/concept/session + session_id = uuid.uuid4() + audio_file_path = "YOUR-AUDIO-FILE-PATH" + # For more supported languages see https://cloud.google.com/dialogflow/es/docs/reference/language + language_code = "en-us" + + detect_intent_stream(agent, session_id, audio_file_path, language_code) + + +def detect_intent_stream(agent, session_id, audio_file_path, language_code): + """Returns the result of detect intent with streaming audio as input. + + Using the same `session_id` between requests allows continuation + of the conversation.""" + session_path = f"{agent}/sessions/{session_id}" + print(f"Session path: {session_path}\n") + client_options = None + agent_components = AgentsClient.parse_agent_path(agent) + location_id = agent_components["location"] + if location_id != "global": + api_endpoint = f"{location_id}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + + input_audio_config = audio_config.InputAudioConfig( + audio_encoding=audio_config.AudioEncoding.AUDIO_ENCODING_LINEAR_16, + sample_rate_hertz=24000, + ) + + def request_generator(): + audio_input = session.AudioInput(config=input_audio_config) + query_input = session.QueryInput(audio=audio_input, language_code=language_code) + voice_selection = audio_config.VoiceSelectionParams() + synthesize_speech_config = audio_config.SynthesizeSpeechConfig() + output_audio_config = audio_config.OutputAudioConfig() + + # Sets the voice name and gender + voice_selection.name = "en-GB-Standard-A" + voice_selection.ssml_gender = ( + audio_config.SsmlVoiceGender.SSML_VOICE_GENDER_FEMALE + ) + + synthesize_speech_config.voice = voice_selection + + # Sets the audio encoding + output_audio_config.audio_encoding = ( + audio_config.OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_UNSPECIFIED + ) + output_audio_config.synthesize_speech_config = synthesize_speech_config + + # The first request contains the configuration. + yield session.StreamingDetectIntentRequest( + session=session_path, + query_input=query_input, + output_audio_config=output_audio_config, + ) + + # Here we are reading small chunks of audio data from a local + # audio file. In practice these chunks should come from + # an audio input device. + with open(audio_file_path, "rb") as audio_file: + while True: + chunk = audio_file.read(4096) + if not chunk: + break + # The later requests contains audio data. + audio_input = session.AudioInput(audio=chunk) + query_input = session.QueryInput(audio=audio_input) + yield session.StreamingDetectIntentRequest(query_input=query_input) + + responses = session_client.streaming_detect_intent(requests=request_generator()) + + print("=" * 20) + for response in responses: + print(f'Intermediate transcript: "{response.recognition_result.transcript}".') + + # Note: The result from the last response is the final transcript along + # with the detected content. + response = response.detect_intent_response + print(f"Query text: {response.query_result.transcript}") + response_messages = [ + " ".join(msg.text.text) for msg in response.query_result.response_messages + ] + print(f"Response text: {' '.join(response_messages)}\n") + + +# [END dialogflow_detect_intent_stream] + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--agent", help="Agent resource name. Required.", required=True + ) + parser.add_argument( + "--session-id", + help="Identifier of the DetectIntent session. " "Defaults to a random UUID.", + default=str(uuid.uuid4()), + ) + parser.add_argument( + "--language-code", + help='Language code of the query. Defaults to "en-US".', + default="en-US", + ) + parser.add_argument( + "--audio-file-path", help="Path to the audio file.", required=True + ) + + args = parser.parse_args() + + detect_intent_stream( + args.agent, args.session_id, args.audio_file_path, args.language_code + ) diff --git a/dialogflow-cx/detect_intent_stream_test.py b/dialogflow-cx/detect_intent_stream_test.py new file mode 100644 index 000000000000..4fd19ebe61c8 --- /dev/null +++ b/dialogflow-cx/detect_intent_stream_test.py @@ -0,0 +1,51 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_texts.""" + +from __future__ import absolute_import + +import os +import uuid + + +from detect_intent_stream import detect_intent_stream + +DIRNAME = os.path.realpath(os.path.dirname(__file__)) +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") +AGENT_ID_US_CENTRAL1 = os.getenv("AGENT_ID_US_CENTRAL1") +AGENT = f"projects/{PROJECT_ID}/locations/global/agents/{AGENT_ID}" +AGENT_US_CENTRAL1 = ( + f"projects/{PROJECT_ID}/locations/us-central1/agents/{AGENT_ID_US_CENTRAL1}" +) +SESSION_ID = uuid.uuid4() +AUDIO_PATH = os.getenv("AUDIO_PATH") +AUDIO = f"{DIRNAME}/{AUDIO_PATH}" + + +def test_detect_intent_texts(capsys): + detect_intent_stream(AGENT, SESSION_ID, AUDIO, "en-US") + out, _ = capsys.readouterr() + + assert "Intermediate transcript:" in out + assert "Response text: Hi! I'm the virtual flights agent." in out + + +def test_detect_intent_texts_regional(capsys): + detect_intent_stream(AGENT_US_CENTRAL1, SESSION_ID, AUDIO, "en-US") + out, _ = capsys.readouterr() + + assert "Intermediate transcript:" in out + assert "Response text: Hi! I'm the virtual flights agent." in out diff --git a/dialogflow-cx/detect_intent_synthesize_tts_response.py b/dialogflow-cx/detect_intent_synthesize_tts_response.py new file mode 100644 index 000000000000..96e8fbecbeaf --- /dev/null +++ b/dialogflow-cx/detect_intent_synthesize_tts_response.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Detects intent and returns a synthesized Text-to-Speech (TTS) response + +# [START dialogflow_cx_v3_detect_intent_synthesize_tts_response_async] +import uuid + +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import audio_config +from google.cloud.dialogflowcx_v3.types import session + + +def run_sample(): + # TODO(developer): Update these values when running the function + project_id = "YOUR-PROJECT-ID" + location = "YOUR-LOCATION-ID" + agent_id = "YOUR-AGENT-ID" + text = "YOUR-TEXT" + audio_encoding = "YOUR-AUDIO-ENCODING" + language_code = "YOUR-LANGUAGE-CODE" + output_file = "YOUR-OUTPUT-FILE" + + detect_intent_synthesize_tts_response( + project_id, + location, + agent_id, + text, + audio_encoding, + language_code, + output_file, + ) + + +def detect_intent_synthesize_tts_response( + project_id, + location, + agent_id, + text, + audio_encoding, + language_code, + output_file, +): + """Returns the result of detect intent with synthesized response.""" + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + + # Constructs the audio query request + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + text_input = session.TextInput(text=text) + query_input = session.QueryInput(text=text_input, language_code=language_code) + synthesize_speech_config = audio_config.SynthesizeSpeechConfig( + speaking_rate=1.25, + pitch=10.0, + ) + output_audio_config = audio_config.OutputAudioConfig( + synthesize_speech_config=synthesize_speech_config, + audio_encoding=audio_config.OutputAudioEncoding[audio_encoding], + ) + request = session.DetectIntentRequest( + session=session_path, + query_input=query_input, + output_audio_config=output_audio_config, + ) + + response = session_client.detect_intent(request=request) + print( + "Speaking Rate: " + f"{response.output_audio_config.synthesize_speech_config.speaking_rate}" + ) + print("Pitch: " f"{response.output_audio_config.synthesize_speech_config.pitch}") + with open(output_file, "wb") as fout: + fout.write(response.output_audio) + print(f"Audio content written to file: {output_file}") + + +# [END dialogflow_cx_v3_detect_intent_synthesize_tts_response_async] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/detect_intent_synthesize_tts_response_test.py b/dialogflow-cx/detect_intent_synthesize_tts_response_test.py new file mode 100644 index 000000000000..3c64f9874b0c --- /dev/null +++ b/dialogflow-cx/detect_intent_synthesize_tts_response_test.py @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_with_sentiment_analysis.py""" + +from __future__ import absolute_import + +import os + +from detect_intent_synthesize_tts_response import detect_intent_synthesize_tts_response + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") + + +def test_detect_intent_positive(capsys, tmp_path_factory): + + output_file = tmp_path_factory.mktemp("data") / "tmp.wav" + + detect_intent_synthesize_tts_response( + PROJECT_ID, + "global", + AGENT_ID, + "Perfect!", + "OUTPUT_AUDIO_ENCODING_LINEAR_16", + "en-us", + output_file, + ) + out, _ = capsys.readouterr() + assert f"Audio content written to file: {output_file}" in out diff --git a/dialogflow-cx/detect_intent_texts.py b/dialogflow-cx/detect_intent_texts.py new file mode 100644 index 000000000000..9eb77afb94a5 --- /dev/null +++ b/dialogflow-cx/detect_intent_texts.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow API Detect Intent Python sample with text inputs. + +Examples: + python detect_intent_texts.py -h + python detect_intent_texts.py --agent AGENT \ + --session-id SESSION_ID \ + "hello" "book a meeting room" "Mountain View" + python detect_intent_texts.py --agent AGENT \ + --session-id SESSION_ID \ + "tomorrow" "10 AM" "2 hours" "10 people" "A" "yes" +""" + +import argparse +import uuid + +from google.cloud.dialogflowcx_v3beta1.services.agents import AgentsClient +from google.cloud.dialogflowcx_v3beta1.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3beta1.types import session + + +# [START dialogflow_cx_detect_intent_text] +def run_sample(): + # TODO(developer): Replace these values when running the function + project_id = "YOUR-PROJECT-ID" + # For more information about regionalization see https://cloud.google.com/dialogflow/cx/docs/how/region + location_id = "YOUR-LOCATION-ID" + # For more info on agents see https://cloud.google.com/dialogflow/cx/docs/concept/agent + agent_id = "YOUR-AGENT-ID" + agent = f"projects/{project_id}/locations/{location_id}/agents/{agent_id}" + # For more information on sessions see https://cloud.google.com/dialogflow/cx/docs/concept/session + session_id = uuid.uuid4() + texts = ["Hello"] + # For more supported languages see https://cloud.google.com/dialogflow/es/docs/reference/language + language_code = "en-us" + + detect_intent_texts(agent, session_id, texts, language_code) + + +def detect_intent_texts(agent, session_id, texts, language_code): + """Returns the result of detect intent with texts as inputs. + + Using the same `session_id` between requests allows continuation + of the conversation.""" + session_path = f"{agent}/sessions/{session_id}" + print(f"Session path: {session_path}\n") + client_options = None + agent_components = AgentsClient.parse_agent_path(agent) + location_id = agent_components["location"] + if location_id != "global": + api_endpoint = f"{location_id}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + + for text in texts: + text_input = session.TextInput(text=text) + query_input = session.QueryInput(text=text_input, language_code=language_code) + request = session.DetectIntentRequest( + session=session_path, query_input=query_input + ) + response = session_client.detect_intent(request=request) + + print("=" * 20) + print(f"Query text: {response.query_result.text}") + response_messages = [ + " ".join(msg.text.text) for msg in response.query_result.response_messages + ] + print(f"Response text: {' '.join(response_messages)}\n") + + +# [END dialogflow_cx_detect_intent_text] + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--agent", help="Agent resource name. Required.", required=True + ) + parser.add_argument( + "--session-id", + help="Identifier of the DetectIntent session. " "Defaults to a random UUID.", + default=str(uuid.uuid4()), + ) + parser.add_argument( + "--language-code", + help='Language code of the query. Defaults to "en-US".', + default="en-US", + ) + parser.add_argument("texts", nargs="+", type=str, help="Text inputs.") + + args = parser.parse_args() + + detect_intent_texts(args.agent, args.session_id, args.texts, args.language_code) diff --git a/dialogflow-cx/detect_intent_texts_test.py b/dialogflow-cx/detect_intent_texts_test.py new file mode 100644 index 000000000000..7000520134c8 --- /dev/null +++ b/dialogflow-cx/detect_intent_texts_test.py @@ -0,0 +1,47 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_texts.""" + +from __future__ import absolute_import + +import os +import uuid + + +from detect_intent_texts import detect_intent_texts + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") +AGENT = f"projects/{PROJECT_ID}/locations/global/agents/{AGENT_ID}" +SESSION_ID = uuid.uuid4() +TEXTS = ["hello", "book a flight"] +AGENT_ID_US_CENTRAL1 = os.getenv("AGENT_ID_US_CENTRAL1") +AGENT_US_CENTRAL1 = ( + f"projects/{PROJECT_ID}/locations/us-central1/agents/{AGENT_ID_US_CENTRAL1}" +) + + +def test_detect_intent_texts(capsys): + detect_intent_texts(AGENT, SESSION_ID, TEXTS, "en-US") + out, _ = capsys.readouterr() + + assert "Response text: I can help you find a ticket" in out + + +def test_detect_intent_texts_regional(capsys): + detect_intent_texts(AGENT_US_CENTRAL1, SESSION_ID, TEXTS, "en-US") + out, _ = capsys.readouterr() + + assert "Response text: I can help you find a ticket" in out diff --git a/dialogflow-cx/detect_intent_with_intent_input.py b/dialogflow-cx/detect_intent_with_intent_input.py new file mode 100644 index 000000000000..9a1e77c27486 --- /dev/null +++ b/dialogflow-cx/detect_intent_with_intent_input.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow Detect Intent Python sample with specified intent.""" + + +# [START dialogflow_cx_v3_detect_intent_with_intent_input_async] +import uuid + +from google.cloud.dialogflowcx_v3.services.intents import IntentsClient +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import session + + +def run_sample(): + # TODO(developer): Update these values when running the function + project_id = "YOUR-PROJECT-ID" + location = "YOUR-LOCATION-ID" + agent_id = "YOUR-AGENT-ID" + intent_id = "YOUR-INTENT-ID" + language_code = "en-us" + + detect_intent_with_intent_input( + project_id, + location, + agent_id, + intent_id, + language_code, + ) + + +def detect_intent_with_intent_input( + project_id, + location, + agent_id, + intent_id, + language_code, +): + """Returns the result of detect intent with sentiment analysis""" + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + intents_client = IntentsClient() + + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + intent_path = intents_client.intent_path( + project=project_id, + location=location, + agent=agent_id, + intent=intent_id, + ) + + intent = session.IntentInput(intent=intent_path) + query_input = session.QueryInput(intent=intent, language_code=language_code) + request = session.DetectIntentRequest( + session=session_path, + query_input=query_input, + ) + + response = session_client.detect_intent(request=request) + response_text = [] + for response_message in response.query_result.response_messages: + response_text.append(response_message.text.text) + print(response_message.text.text) + return response_text + + +# [END dialogflow_cx_v3_detect_intent_with_intent_input_async] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/detect_intent_with_intent_input_test.py b/dialogflow-cx/detect_intent_with_intent_input_test.py new file mode 100644 index 000000000000..28899d482ce6 --- /dev/null +++ b/dialogflow-cx/detect_intent_with_intent_input_test.py @@ -0,0 +1,39 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_with_sentiment_analysis.py""" + +from __future__ import absolute_import + +import os + +from detect_intent_with_intent_input import detect_intent_with_intent_input + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") +INTENT_ID = os.getenv("INTENT_ID") + + +def test_detect_intent_with_intent_input(): + response_text = detect_intent_with_intent_input( + PROJECT_ID, + "global", + AGENT_ID, + INTENT_ID, + "en-us", + ) + assert len(response_text) == 2 + assert response_text[0] == ["Let's find a one-way ticket for you. "] + assert response_text[1] == ["Which city are you leaving from?"] diff --git a/dialogflow-cx/detect_intent_with_sentiment_analysis.py b/dialogflow-cx/detect_intent_with_sentiment_analysis.py new file mode 100644 index 000000000000..57009e5fe9ad --- /dev/null +++ b/dialogflow-cx/detect_intent_with_sentiment_analysis.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DialogFlow Detect Intent Python sample, text input and sentiment analysis.""" + + +# [START dialogflow_cx_v3_detect_intent_sentiment_analysis_async] +import uuid + +from google.cloud.dialogflowcx_v3beta1.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3beta1.types import session + + +def run_sample(): + # TODO(developer): Update these values when running the function + project_id = "YOUR-PROJECT-ID" + location = "YOUR-LOCATION-ID" + agent_id = "YOUR-AGENT-ID" + text = "Perfect!" + language_code = "en-us" + + detect_intent_with_sentiment_analysis( + project_id, + location, + agent_id, + text, + language_code, + ) + + +def detect_intent_with_sentiment_analysis( + project_id, + location, + agent_id, + text, + language_code, +): + """Returns the result of detect intent with sentiment analysis""" + + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + + text_input = session.TextInput(text=text) + query_input = session.QueryInput(text=text_input, language_code=language_code) + query_params = session.QueryParameters( + analyze_query_text_sentiment=True, + ) + request = session.DetectIntentRequest( + session=session_path, + query_input=query_input, + query_params=query_params, + ) + + response = session_client.detect_intent(request=request) + score = response.query_result.sentiment_analysis_result.score + print("Sentiment Score: {score}") + return score + + +# [END dialogflow_cx_v3_detect_intent_sentiment_analysis_async] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/detect_intent_with_sentiment_analysis_test.py b/dialogflow-cx/detect_intent_with_sentiment_analysis_test.py new file mode 100644 index 000000000000..640d15816830 --- /dev/null +++ b/dialogflow-cx/detect_intent_with_sentiment_analysis_test.py @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_with_sentiment_analysis.py""" + +from __future__ import absolute_import + +import os + +import pytest + +from detect_intent_with_sentiment_analysis import detect_intent_with_sentiment_analysis + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") + + +@pytest.mark.parametrize( + "text, expected_score_min, expected_score_max", + (["Perfect", 0.5, 1], ["I am not happy", -1, -0.5]), +) +def test_detect_intent_positive(text, expected_score_min, expected_score_max): + + score = detect_intent_with_sentiment_analysis( + PROJECT_ID, + "global", + AGENT_ID, + text, + "en-us", + ) + assert expected_score_min < score < expected_score_max diff --git a/dialogflow-cx/list_testcase_results.py b/dialogflow-cx/list_testcase_results.py new file mode 100644 index 000000000000..8723f2db2857 --- /dev/null +++ b/dialogflow-cx/list_testcase_results.py @@ -0,0 +1,35 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START dialogflow_list_test_case_results_sample] + +from google.cloud.dialogflowcx_v3.services.test_cases.client import TestCasesClient +from google.cloud.dialogflowcx_v3.types.test_case import ListTestCaseResultsRequest + + +def list_test_case(project_id, agent_id, test_id, location): + + req = ListTestCaseResultsRequest() + req.parent = f"projects/{project_id}/locations/{location}/agents/{agent_id}/testCases/{test_id}" + req.filter = "environment=draft" + client = TestCasesClient( + client_options={"api_endpoint": f"{location}-dialogflow.googleapis.com"} + ) + # Makes a call to list all test case results that match filter + result = client.list_test_case_results(request=req) + print(result) + return result + + +# [END dialogflow_list_test_case_results_sample] diff --git a/dialogflow-cx/list_testcase_results_test.py b/dialogflow-cx/list_testcase_results_test.py new file mode 100644 index 000000000000..35f1ac636afd --- /dev/null +++ b/dialogflow-cx/list_testcase_results_test.py @@ -0,0 +1,30 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import google.auth + +from list_testcase_results import list_test_case + +LOCATION = "global" + +_, PROJECT_ID = google.auth.default() +AGENT_ID = "143dee60-56fe-4191-a8d8-095f569f6cd8" +TEST_ID = "3c48d39e-71c0-4cb0-b974-3d5c596d347e" + + +def test_list_testcase_results(): + result = list_test_case(PROJECT_ID, AGENT_ID, TEST_ID, LOCATION) + + assert "Hello! How can I help you?" in str(result) diff --git a/dialogflow-cx/list_training_phrases.py b/dialogflow-cx/list_training_phrases.py new file mode 100644 index 000000000000..6fcfc1ee038b --- /dev/null +++ b/dialogflow-cx/list_training_phrases.py @@ -0,0 +1,41 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START dialogflow_list_training_phrases] + + +def list_training_phrases(project_id, agent_id, intent_id, location): + """Returns all training phrases for a specified intent.""" + + from google.cloud import dialogflowcx + + # Create the intents client + intent_client = dialogflowcx.IntentsClient() + + # Specify working intent + intent_name = intent_client.intent_path(project_id, location, agent_id, intent_id) + + # Compose the get-intent request + get_intent_request = dialogflowcx.GetIntentRequest(name=intent_name) + + intent = intent_client.get_intent(get_intent_request) + + # Iterate through the training phrases. + for phrase in intent.training_phrases: + print(phrase) + + return intent.training_phrases + + +# [END dialogflow_list_training_phrases] diff --git a/dialogflow-cx/list_training_phrases_test.py b/dialogflow-cx/list_training_phrases_test.py new file mode 100644 index 000000000000..8527d436f2cc --- /dev/null +++ b/dialogflow-cx/list_training_phrases_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import google.auth + +import list_training_phrases + + +_, PROJECT_ID = google.auth.default() +INTENT_ID = os.getenv("INTENT_ID") +LOCATION = "global" +AGENT_ID = os.getenv("AGENT_ID") + + +def test_list_training_phrases(capsys): + training_phrases = list_training_phrases.list_training_phrases( + PROJECT_ID, AGENT_ID, INTENT_ID, LOCATION + ) + assert len(training_phrases) >= 15 # Number of training phrases at this point. diff --git a/dialogflow-cx/long_running_operation.py b/dialogflow-cx/long_running_operation.py new file mode 100644 index 000000000000..c1efa5e59086 --- /dev/null +++ b/dialogflow-cx/long_running_operation.py @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" DialogFlow CX long running operation code snippet """ + +## [START dialogflow_cx_long_running_snippet] +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.types.agent import ExportAgentRequest + + +def export_long_running_agent(project_id, agent_id, location): + + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + client_options = {"api_endpoint": api_endpoint} + + agents_client = AgentsClient(client_options=client_options) + + export_request = ExportAgentRequest() + + export_request.name = ( + f"projects/{project_id}/locations/{location}/agents/{agent_id}" + ) + + # export_agent returns a long running operation + operation = agents_client.export_agent(request=export_request) + + # Returns the result of the operation when the operation is done + return operation.result() + + +## [END dialogflow_cx_long_running_snippet] diff --git a/dialogflow-cx/long_running_operation_test.py b/dialogflow-cx/long_running_operation_test.py new file mode 100644 index 000000000000..287ccaeb1cc5 --- /dev/null +++ b/dialogflow-cx/long_running_operation_test.py @@ -0,0 +1,67 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.types.agent import Agent, DeleteAgentRequest + +import pytest + +from long_running_operation import export_long_running_agent + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +pytest.AGENT_ID = None +pytest.PARENT = None + + +def create_agent(project_id, display_name): + parent = "projects/" + project_id + "/locations/global" + + agents_client = AgentsClient() + + agent = Agent( + display_name=display_name, + default_language_code="en", + time_zone="America/Los_Angeles", + ) + + response = agents_client.create_agent(request={"agent": agent, "parent": parent}) + + return response + + +def delete_agent(name): + agents_client = AgentsClient() + agent = DeleteAgentRequest(name=name) + agents_client.delete_agent(request=agent) + + +@pytest.fixture(scope="function", autouse=True) +def setup_teardown(): + agentName = "temp_agent_" + str(uuid.uuid4()) + pytest.PARENT = create_agent(PROJECT_ID, agentName).name + pytest.AGENT_ID = pytest.PARENT.split("/")[5] + print("Created Agent in setUp") + + yield + + delete_agent(pytest.PARENT) + + +def test_export_agent(): + actualResponse = export_long_running_agent(PROJECT_ID, pytest.AGENT_ID, "global") + + assert pytest.AGENT_ID in str(actualResponse) diff --git a/dialogflow-cx/noxfile_config.py b/dialogflow-cx/noxfile_config.py new file mode 100644 index 000000000000..6a9082c602ef --- /dev/null +++ b/dialogflow-cx/noxfile_config.py @@ -0,0 +1,40 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be inported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": { + "AGENT_ID": "53516802-3e2a-4016-80b6-a3df0d240240", + "AGENT_ID_US_CENTRAL1": "edf8372c-c66a-4984-83ba-b85885e95e2a", + "AUDIO_PATH": "resources/hello.wav", + "INTENT_ID": "164428bd-647a-4e30-ab0f-cc7f3e3b76f9", + }, +} diff --git a/dialogflow-cx/page_management.py b/dialogflow-cx/page_management.py new file mode 100644 index 000000000000..4478502ea305 --- /dev/null +++ b/dialogflow-cx/page_management.py @@ -0,0 +1,80 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.cloud.dialogflowcx_v3 import PagesAsyncClient +from google.cloud.dialogflowcx_v3.types.page import ( + CreatePageRequest, + DeletePageRequest, + ListPagesRequest, + Page, +) + + +# [START dialogflow_cx_create_page] +async def create_page(project_id, agent_id, flow_id, location, displayName): + pages_client = PagesAsyncClient() + + page = Page() + page.display_name = displayName + + request = CreatePageRequest() + request.parent = ( + "projects/" + + project_id + + "/locations/" + + location + + "/agents/" + + agent_id + + "/flows/" + + flow_id + ) + request.page = page + + response = await pages_client.create_page(request=request) + return response + + +# [END dialogflow_cx_create_page] + + +# [START dialogflow_cx_list_page] +async def list_page(project_id, agent_id, flow_id, location): + pages_client = PagesAsyncClient() + + request = ListPagesRequest() + request.parent = ( + f"projects/{project_id}/locations/{location}/agents/{agent_id}/flows/{flow_id}" + ) + + request.language_code = "en" + + response = await pages_client.list_pages(request=request) + return response + + +# [END dialogflow_cx_list_page] + + +# [START dialogflow_cx_delete_page] +async def delete_page(project_id, agent_id, flow_id, page_id, location): + pages_client = PagesAsyncClient() + + request = DeletePageRequest() + request.name = f"projects/{project_id}/locations/{location}/agents/{agent_id}/flows/{flow_id}/pages/{page_id}" + + response = await pages_client.delete_page(request=request) + return response + + +# [END dialogflow_cx_delete_page] diff --git a/dialogflow-cx/page_management_test.py b/dialogflow-cx/page_management_test.py new file mode 100644 index 000000000000..ba9137342008 --- /dev/null +++ b/dialogflow-cx/page_management_test.py @@ -0,0 +1,115 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from copy import Error +import os +import uuid + +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.types.agent import Agent, DeleteAgentRequest + +import pytest + +from page_management import create_page, delete_page, list_page + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +pytest.AGENT_ID = None +pytest.PARENT = None +pytest.CREATED_PAGE = None +pytest.PAGE_ID = None + + +def delete_agent(name): + agents_client = AgentsClient() + agent = DeleteAgentRequest(name=name) + agents_client.delete_agent(request=agent) + + +@pytest.fixture +def loop(): + loop = asyncio.new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="module", autouse=True) +def setup_teardown(): + loop = asyncio.new_event_loop() + agentName = "temp_agent_" + str(uuid.uuid4()) + + parent = "projects/" + PROJECT_ID + "/locations/global" + + agents_client = AgentsClient() + + agent = Agent( + display_name=agentName, + default_language_code="en", + time_zone="America/Los_Angeles", + ) + + response = agents_client.create_agent(request={"agent": agent, "parent": parent}) + pytest.PARENT = response.name + + pytest.AGENT_ID = pytest.PARENT.split("/")[5] + print("Created Agent in setUp") + + yield + + delete_agent(pytest.PARENT) + loop.close() + + +def test_create_page(loop: asyncio.AbstractEventLoop): + pytest.CREATED_PAGE = f"fake_page_{uuid.uuid4()}" + actualResponse = loop.run_until_complete( + create_page( + PROJECT_ID, + pytest.AGENT_ID, + "00000000-0000-0000-0000-000000000000", + "global", + pytest.CREATED_PAGE, + ) + ) + + pytest.PAGE_ID = actualResponse.name.split("/")[9] + assert actualResponse.display_name == pytest.CREATED_PAGE + + +def test_list_page(loop: asyncio.AbstractEventLoop): + actualResponse = loop.run_until_complete( + list_page( + PROJECT_ID, + pytest.AGENT_ID, + "00000000-0000-0000-0000-000000000000", + "global", + ) + ) + + assert pytest.PAGE_ID in str(actualResponse) + + +def test_delete_page(loop: asyncio.AbstractEventLoop): + try: + loop.run_until_complete( + delete_page( + PROJECT_ID, + pytest.AGENT_ID, + "00000000-0000-0000-0000-000000000000", + pytest.PAGE_ID, + "global", + ) + ) + except Error: + pytest.fail("Unexpected MyError ..") diff --git a/dialogflow-cx/requirements-test.txt b/dialogflow-cx/requirements-test.txt new file mode 100644 index 000000000000..805eb2a9f845 --- /dev/null +++ b/dialogflow-cx/requirements-test.txt @@ -0,0 +1 @@ +pytest==7.2.1 diff --git a/dialogflow-cx/requirements.txt b/dialogflow-cx/requirements.txt new file mode 100644 index 000000000000..7a2cd9f3d4ae --- /dev/null +++ b/dialogflow-cx/requirements.txt @@ -0,0 +1,3 @@ +google-cloud-dialogflow-cx==1.18.0 +Flask==2.2.2 +python-dateutil==2.8.2 diff --git a/dialogflow-cx/resources/hello.wav b/dialogflow-cx/resources/hello.wav new file mode 100644 index 000000000000..0aadf16c7116 Binary files /dev/null and b/dialogflow-cx/resources/hello.wav differ diff --git a/dialogflow-cx/streaming_detect_intent_partial_response.py b/dialogflow-cx/streaming_detect_intent_partial_response.py new file mode 100644 index 000000000000..1042b14cdc5f --- /dev/null +++ b/dialogflow-cx/streaming_detect_intent_partial_response.py @@ -0,0 +1,126 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# [START dialogflow_cx_streaming_detect_intent_enable_partial_response] +import uuid + +from google.cloud.dialogflowcx_v3.services.sessions import SessionsClient +from google.cloud.dialogflowcx_v3.types import audio_config +from google.cloud.dialogflowcx_v3.types import InputAudioConfig +from google.cloud.dialogflowcx_v3.types import session + + +def run_sample(): + """ + TODO(developer): Modify these variables before running the sample. + """ + project_id = "YOUR-PROJECT-ID" + location = "YOUR-LOCATION-ID" + agent_id = "YOUR-AGENT-ID" + audio_file_name = "YOUR-AUDIO-FILE-PATH" + encoding = "AUDIO_ENCODING_LINEAR_16" + sample_rate_hertz = 16000 + language_code = "en" + + streaming_detect_intent_partial_response( + project_id, + location, + agent_id, + audio_file_name, + encoding, + sample_rate_hertz, + language_code, + ) + + +def streaming_detect_intent_partial_response( + project_id, + location, + agent_id, + audio_file_name, + encoding, + sample_rate_hertz, + language_code, +): + + client_options = None + if location != "global": + api_endpoint = f"{location}-dialogflow.googleapis.com:443" + print(f"API Endpoint: {api_endpoint}\n") + client_options = {"api_endpoint": api_endpoint} + session_client = SessionsClient(client_options=client_options) + session_id = str(uuid.uuid4()) + + session_path = session_client.session_path( + project=project_id, + location=location, + agent=agent_id, + session=session_id, + ) + + def request_generator(): + audio_encoding = audio_config.AudioEncoding[encoding] + config = InputAudioConfig( + audio_encoding=audio_encoding, + sample_rate_hertz=sample_rate_hertz, + single_utterance=True, + ) + audio_input = session.AudioInput(config=config) + query_input = session.QueryInput(audio=audio_input, language_code=language_code) + yield session.StreamingDetectIntentRequest( + session=session_path, + query_input=query_input, + enable_partial_response=True, + ) + # Here we are reading small chunks of audio data from a local + # audio file. In practice these chunks should come from + # an audio input device. + with open(audio_file_name, "rb") as audio_file: + while True: + chunk = audio_file.read(4096) + if not chunk: + break + # The later requests contains audio data. + audio_input = session.AudioInput(audio=chunk, config=config) + query_input = session.QueryInput( + audio=audio_input, language_code=language_code + ) + yield session.StreamingDetectIntentRequest( + session=session_path, + query_input=query_input, + enable_partial_response=True, + ) + + responses = session_client.streaming_detect_intent(requests=request_generator()) + + print("=" * 20) + for response in responses: + print(f'Intermediate transcript: "{response.recognition_result.transcript}".') + + # Note: The result from the last response is the final transcript along + # with the detected content. + response = response.detect_intent_response + print(f"Query text: {response.query_result.transcript}") + response_messages = [ + " ".join(msg.text.text) for msg in response.query_result.response_messages + ] + print(f"Response text: {' '.join(response_messages)}\n") + + +# [END dialogflow_cx_streaming_detect_intent_enable_partial_response] + + +if __name__ == "__main__": + run_sample() diff --git a/dialogflow-cx/streaming_detect_intent_partial_response_test.py b/dialogflow-cx/streaming_detect_intent_partial_response_test.py new file mode 100644 index 000000000000..e67061991efd --- /dev/null +++ b/dialogflow-cx/streaming_detect_intent_partial_response_test.py @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for detect_intent_texts.""" + +from __future__ import absolute_import + +import os + +from streaming_detect_intent_partial_response import ( + streaming_detect_intent_partial_response, +) + + +DIRNAME = os.path.realpath(os.path.dirname(__file__)) +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +AGENT_ID = os.getenv("AGENT_ID") +AUDIO_PATH = os.getenv("AUDIO_PATH") +AUDIO = f"{DIRNAME}/{AUDIO_PATH}" + + +def test_streaming_detect_intent_partial_response(capsys): + + encoding = "AUDIO_ENCODING_LINEAR_16" + sample_rate_hertz = 24000 + + streaming_detect_intent_partial_response( + PROJECT_ID, + "global", + AGENT_ID, + AUDIO, + encoding, + sample_rate_hertz, + "en-US", + ) + out, _ = capsys.readouterr() + + assert "Intermediate transcript:" in out + assert "Response text: Hi! I'm the virtual flights agent." in out diff --git a/dialogflow-cx/update_intent.py b/dialogflow-cx/update_intent.py new file mode 100644 index 000000000000..ccbf7ee408ae --- /dev/null +++ b/dialogflow-cx/update_intent.py @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START dialogflow_cx_update_intent] +from google.cloud.dialogflowcx_v3.services.intents import IntentsClient +from google.protobuf import field_mask_pb2 + + +def update_intent(project_id, agent_id, intent_id, location, displayName): + + intents_client = IntentsClient() + + intent_name = intents_client.intent_path(project_id, location, agent_id, intent_id) + + intent = intents_client.get_intent(request={"name": intent_name}) + + intent.display_name = displayName + update_mask = field_mask_pb2.FieldMask(paths=["display_name"]) + response = intents_client.update_intent(intent=intent, update_mask=update_mask) + return response + + +# [END dialogflow_cx_update_intent] diff --git a/dialogflow-cx/update_intent_test.py b/dialogflow-cx/update_intent_test.py new file mode 100644 index 000000000000..e3870af36573 --- /dev/null +++ b/dialogflow-cx/update_intent_test.py @@ -0,0 +1,83 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +from google.cloud.dialogflowcx_v3.services.agents.client import AgentsClient +from google.cloud.dialogflowcx_v3.services.intents.client import IntentsClient +from google.cloud.dialogflowcx_v3.types.agent import Agent, DeleteAgentRequest +from google.cloud.dialogflowcx_v3.types.intent import CreateIntentRequest, Intent + +import pytest + +from update_intent import update_intent + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") +pytest.INTENT_ID = None +pytest.AGENT_ID = None +pytest.PARENT = None + + +def create_agent(project_id, display_name): + parent = "projects/" + project_id + "/locations/global" + + agents_client = AgentsClient() + + agent = Agent( + display_name=display_name, + default_language_code="en", + time_zone="America/Los_Angeles", + ) + + response = agents_client.create_agent(request={"agent": agent, "parent": parent}) + + return response + + +def delete_agent(name): + agents_client = AgentsClient() + agent = DeleteAgentRequest(name=name) + agents_client.delete_agent(request=agent) + + +@pytest.fixture(scope="function", autouse=True) +def setup_teardown(): + agentName = "temp_agent_" + str(uuid.uuid4()) + pytest.PARENT = create_agent(PROJECT_ID, agentName).name + pytest.AGENT_ID = pytest.PARENT.split("/")[5] + print("Created Agent in setUp") + intentClient = IntentsClient() + intent = Intent() + + intent.display_name = "fake_intent" + + req = CreateIntentRequest() + req.parent = pytest.PARENT + req.intent = intent + + pytest.INTENT_ID = intentClient.create_intent(request=req).name.split("/")[7] + + yield + + delete_agent(pytest.PARENT) + + +def test_fieldmaskTest(): + fake_intent = f"fake_intent_{uuid.uuid4()}" + actualResponse = update_intent( + PROJECT_ID, pytest.AGENT_ID, pytest.INTENT_ID, "global", fake_intent + ) + + assert actualResponse.display_name == fake_intent diff --git a/dialogflow-cx/webhook.py b/dialogflow-cx/webhook.py new file mode 100644 index 000000000000..bf3e8627cf5c --- /dev/null +++ b/dialogflow-cx/webhook.py @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" handle_webhook will return the correct fullfilment response dependong the tag that is sent in the request""" + +# [START dialogflow_cx_webhook] + +# TODO(developer): change entry point to handle_webhook in cloud function + + +def handle_webhook(request): + + req = request.get_json() + + tag = req["fulfillmentInfo"]["tag"] + + if tag == "Default Welcome Intent": + text = "Hello from a GCF Webhook" + elif tag == "get-name": + text = "My name is Flowhook" + else: + text = f"There are no fulfillment responses defined for {tag} tag" + + # You can also use the google.cloud.dialogflowcx_v3.types.WebhookRequest protos instead of manually writing the json object + # Please see https://googleapis.dev/python/dialogflow/latest/dialogflow_v2/types.html?highlight=webhookresponse#google.cloud.dialogflow_v2.types.WebhookResponse for an overview + res = {"fulfillment_response": {"messages": [{"text": {"text": [text]}}]}} + + # Returns json + return res + + +# [END dialogflow_cx_webhook] diff --git a/dialogflow-cx/webhook_configure_session_parameters.py b/dialogflow-cx/webhook_configure_session_parameters.py new file mode 100644 index 000000000000..e29bda3703c5 --- /dev/null +++ b/dialogflow-cx/webhook_configure_session_parameters.py @@ -0,0 +1,39 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" DialogFlow CX: webhook to configure new session parameters.""" + +# [START dialogflow_cx_v3_webhook_configure_session_parameters] + +# TODO (developer): change entry point to configure_session_params in Cloud Function + + +def configure_session_params(request): + """Webhook to validate or configure new session parameters.""" + + order_number = 123 + + json_response = { + "sessionInfo": { + "parameters": { + "orderNumber": order_number, + }, + }, + } + + return json_response + + +# [END dialogflow_cx_v3_webhook_configure_session_parameters] diff --git a/dialogflow-cx/webhook_configure_session_parameters_test.py b/dialogflow-cx/webhook_configure_session_parameters_test.py new file mode 100644 index 000000000000..2a30ada0f823 --- /dev/null +++ b/dialogflow-cx/webhook_configure_session_parameters_test.py @@ -0,0 +1,36 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test configure new session parameters""" + +import flask +import pytest + +from webhook_configure_session_parameters import configure_session_params + + +@pytest.fixture(name="app", scope="module") +def fixture_app(): + """Flask fixture to pass a flask.Request to the test function.""" + return flask.Flask(__name__) + + +def test_validate_parameter(app): + """Test for configure new session parameters.""" + + request = {"fulfillmentInfo": {"tag": "configure-session-parameter"}} + + with app.test_request_context(json=request): + res = configure_session_params(flask.request) + assert "orderNumber" in res["sessionInfo"]["parameters"] diff --git a/dialogflow-cx/webhook_log_session_info.py b/dialogflow-cx/webhook_log_session_info.py new file mode 100644 index 000000000000..c6bec04687c4 --- /dev/null +++ b/dialogflow-cx/webhook_log_session_info.py @@ -0,0 +1,46 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" DialogFlow CX: webhook to log session ID for each request.""" + +# [START dialogflow_cx_v3_webhook_log_session_id] + +import re + + +def log_session_id_for_troubleshooting(request): + """Webhook will log session id corresponding to request.""" + + req = request.get_json() + # You can read more about SessionInfo at https://cloud.google.com/dialogflow/cx/docs/reference/rest/v3/SessionInfo + # Use a regex pattern to get the session ID + session_id_regex = r".+\/sessions\/(.+)" + session = req["sessionInfo"]["session"] + regex_match = re.search(session_id_regex, session) + session_id = regex_match.group(1) + + # Instead of printing, use the logging tools available to you + print(f"Debug Node: session ID = {session_id}") + + # Return a generic response + res = { + "fulfillment_response": { + "messages": [{"text": {"text": [f"Request Session ID: {session_id}"]}}] + } + } + + # Returns json + return res + + +# [END dialogflow_cx_v3_webhook_log_session_id] diff --git a/dialogflow-cx/webhook_log_session_info_test.py b/dialogflow-cx/webhook_log_session_info_test.py new file mode 100644 index 000000000000..0d3633126db2 --- /dev/null +++ b/dialogflow-cx/webhook_log_session_info_test.py @@ -0,0 +1,65 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test validate form webhook request session ID snippet.""" + +import flask +import pytest + +from webhook_log_session_info import log_session_id_for_troubleshooting + + +@pytest.fixture(name="app", scope="module") +def fixture_app(): + """Flask fixture to pass a flask.Request to the test function""" + return flask.Flask(__name__) + + +@pytest.fixture +def session_id(): + return "d0bdaa0c-0d00-0000-b0eb-b00b0db000b0" + + +@pytest.fixture +def session_prefix(): + agent_id = "000000f0-f000-00b0-0000-af00d0e00000" + return f"projects/test_project/locations/us-central1/agents/{agent_id}" + + +@pytest.fixture +def session(session_prefix, session_id): + """Session string without environment path""" + return f"{session_prefix}/sessions/{session_id}" + + +@pytest.fixture +def env_session(session_prefix, session_id): + """Session string with environment path""" + environment = "0d0000f0-0aac-0d0c-0a00-b00b0000a000" + return f"{session_prefix}/environments/{environment}/sessions/{session_id}" + + +def test_logging_session_id(app, session, session_id): + """Parameterized test for regular session string.""" + request = {"sessionInfo": {"session": session}} + with app.test_request_context(json=request): + res = log_session_id_for_troubleshooting(flask.request) + assert session_id in str(res) + + +def test_logging_session_id_with_env_path(app, env_session, session_id): + """Parameterized test for session string with environment path.""" + request = {"sessionInfo": {"session": env_session}} + with app.test_request_context(json=request): + res = log_session_id_for_troubleshooting(flask.request) + assert session_id in str(res) diff --git a/dialogflow-cx/webhook_prebuilt_telecom.py b/dialogflow-cx/webhook_prebuilt_telecom.py new file mode 100644 index 000000000000..01c9c118e467 --- /dev/null +++ b/dialogflow-cx/webhook_prebuilt_telecom.py @@ -0,0 +1,300 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" handle_webhook will return the correct fullfilment response depending on the tag that is sent in the request""" +# [START dialogflow_cx_v3_webhook_prebuilt_telecom] +import copy +import logging + + +def cxPrebuiltAgentsTelecom(request): + logging.info("Cloud Function:" + "Invoked cloud function from Dialogflow") + request_dict = request.get_json() + + # Get the parameters in current page + parameter_info_list = request_dict["pageInfo"]["formInfo"]["parameterInfo"] + parameter_dict = {} + for parameter_info in parameter_info_list: + key = parameter_info["displayName"] + parameter_dict[key] = parameter_info["value"] + + # Get the tag + tag = request_dict["fulfillmentInfo"]["tag"] + + # BEGIN detectCustomerAnomaly + if tag == "detectCustomerAnomaly": + logging.info(tag + " was triggered.") + phone_number = parameter_dict["phone_number"] + bill_state = parameter_dict["bill_state"] + parameters = copy.deepcopy(parameter_dict) + bill_amount = None + product_line = None + anomaly_detect = "false" + purchase = "The Godfather" + purchase_amount = 9.99 + total_bill_amount = 64.33 + bill_without_purchase = 54.34 + updated_parameters = {} + + month_name, first_of_month, last_month_name = get_date_details(bill_state) + logging.info(month_name, first_of_month, last_month_name) + + # Getting the month name based on the bill state - current or previous + # For example, if the current month is December, we get the values as + # December, December 1st, November + + # Only 999999 will have anomaly detection + if str(phone_number) == "999999": + anomaly_detect = "true" + product_line = "phone" + purchase = "device protection" + updated_parameters["product_line"] = product_line + updated_parameters["bill_month"] = month_name + updated_parameters["last_month"] = last_month_name + + # If bill hike amount is given - we just add it to the total bill + if "bill_amount" in parameters: + bill_amount = parameters["bill_amount"] + purchase_amount = bill_amount["amount"] + total_bill_amount = 54.34 + purchase_amount + + # Adding the updated session parameters to the new parameters json + updated_parameters["anomaly_detect"] = anomaly_detect + updated_parameters["purchase"] = purchase + updated_parameters["purchase_amount"] = purchase_amount + updated_parameters["bill_without_purchase"] = bill_without_purchase + updated_parameters["total_bill"] = total_bill_amount + updated_parameters["first_month"] = first_of_month + + res = {"sessionInfo": {"parameters": updated_parameters}} + + # BEGIN validatePhoneLine + elif tag == "validatePhoneLine": + logging.info(tag + " was triggered.") + phone = parameter_dict["phone_number"] + phone_line_verified = "false" + line_index = None + domestic_coverage = "false" + covered_lines = ["5555555555", "5105105100", "1231231234", "9999999999"] + + # Loop over the covered lines array + for index, line in enumerate(covered_lines): + # For each phone line in the array, check if the last 4 digits are + # included in the string. when true, update the line_index variable + if phone == line: + line_index = index + logging.info("This is the index " + str(line_index)) + + # Only 9999999999 will fail + if line_index == 3: + phone_line_verified = "false" + else: + phone_line_verified = "true" + + # Only 1231231234 will have domestic coverage + if line_index == 2: + domestic_coverage = "true" + else: + domestic_coverage = "false" + + res = { + "sessionInfo": { + "parameters": { + "phone_line_verified": phone_line_verified, + "domestic_coverage": domestic_coverage, + } + } + } + + # BEGIN cruisePlanCoverage + elif tag == "cruisePlanCoverage": + logging.info(tag + " was triggered.") + port = parameter_dict["destination"] + port_is_covered = None + # Sample list of covered cruise ports. + covered_ports = [ + "mexico", + "canada", + "anguilla", + ] + + if port.lower() in covered_ports: + port_is_covered = "true" + else: + port_is_covered = "false" + + res = { + "sessionInfo": { + "parameters": { + "port_is_covered": port_is_covered, + } + } + } + + # BEGIN internationalCoverage + elif tag == "internationalCoverage": + logging.info(tag + " was triggered.") + destination = parameter_dict["destination"] + coverage = None + # Sample list of covered international monthly destinations. + covered_by_monthly = [ + "anguilla", + "australia", + "brazil", + "canada", + "chile", + "england", + "france", + "india", + "japan", + "mexico", + "russia", + "singapore", + ] + # Sample list of covered international daily destinations. + covered_by_daily = [ + "anguilla", + "australia", + "brazil", + "canada", + "chile", + "england", + "france", + "india", + "japan", + "mexico", + "singapore", + ] + if ( + destination.lower() in covered_by_monthly + and destination.lower() in covered_by_daily + ): + coverage = "both" + elif ( + destination.lower() in covered_by_monthly + and destination.lower() not in covered_by_daily + ): + coverage = "monthly_only" + elif ( + destination.lower() not in covered_by_monthly + and destination.lower() not in covered_by_daily + ): + coverage = "neither" + else: + # This should never happen, because covered_by_daily is a subset of + # covered_by_monthly + coverage = "daily_only" + + res = { + "sessionInfo": { + "parameters": { + "coverage": coverage, + } + } + } + + # BEGIN cheapestPlan + elif tag == "cheapestPlan": + logging.info(tag + " was triggered.") + trip_duration = parameter_dict["trip_duration"] + monthly_cost = None + daily_cost = None + suggested_plan = None + + # Can only suggest cheapest if both are valid for location. + + # When trip is longer than 30 days, calculate per-month cost (example $ + # amounts). Suggest monthly plan. + if trip_duration > 30: + monthly_cost = (int(trip_duration / 30)) * 70 + daily_cost = trip_duration * 10 + suggested_plan = "monthly" + + # When trip is <= 30 days, but greater than 6 days, calculate monthly + # plan cost and daily plan cost. Suggest monthly b/c it is the cheaper + # one. + elif trip_duration <= 30 and trip_duration > 6: + monthly_cost = 70 + daily_cost = trip_duration * 10 + suggested_plan = "monthly" + + # When trip is <= 6 days, calculate daily plan cost. Suggest daily + # plan. + elif trip_duration <= 6 and trip_duration > 0: + monthly_cost = 70 + daily_cost = trip_duration * 10 + suggested_plan = "daily" + + else: + # This should never happen b/c trip_duration would have to be + # negative + suggested_plan = "null" + + res = { + "sessionInfo": { + "parameters": { + "monthly_cost": monthly_cost, + "daily_cost": daily_cost, + "suggested_plan": suggested_plan, + } + } + } + + # Default Case + else: + res = None + logging.info(f'{"default case called"}') + + # Returns json + return res + + +# Get the current month, first day of current month and last month values +# based on today's date +def get_date_details(bill_state): + from datetime import date + from dateutil.relativedelta import relativedelta + + monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + today = date.today() + # index starts with 0 + first_month_name = monthNames[(today.month - 1)] + firstDay = today.replace(day=1) + first_day_str = str(firstDay) + + last_month_name = monthNames[(today.month - 1) - 1] + last_month_first_day_str = str( + today.replace(day=1, month=(today - relativedelta(months=1)).month) + ) + second_last_month_name = monthNames[(today.month - 1) - 2] + if bill_state == "current": + return [first_month_name, first_day_str, last_month_name] + else: + return [last_month_name, last_month_first_day_str, second_last_month_name] + + +# [END dialogflow_cx_v3_webhook_prebuilt_telecom] diff --git a/dialogflow-cx/webhook_prebuilt_telecom_test.py b/dialogflow-cx/webhook_prebuilt_telecom_test.py new file mode 100644 index 000000000000..a1a655ff9e5f --- /dev/null +++ b/dialogflow-cx/webhook_prebuilt_telecom_test.py @@ -0,0 +1,312 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test validate form parameter webhook snippet.""" + +import flask +import pytest + +from webhook_prebuilt_telecom import cxPrebuiltAgentsTelecom + + +@pytest.fixture(name="app", scope="module") +def fixture_app(): + """Flask fixture to pass a flask.Request to the test function""" + return flask.Flask(__name__) + + +def test_detect_customeranomaly_current(app): + """Parameterized test for detecting customer anomaly webhook snippet.""" + + from datetime import date + + request = { + "fulfillmentInfo": {"tag": "detectCustomerAnomaly"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [ + {"displayName": "phone_number", "value": 999999}, + {"displayName": "bill_state", "value": "current"}, + {"displayName": "bill_amount", "value": {"amount": 1000}}, + ] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["anomaly_detect"] == "true" + assert res["sessionInfo"]["parameters"]["total_bill"] == 1054.34 + assert res["sessionInfo"]["parameters"]["first_month"] == str( + date.today().replace(day=1) + ) + + +def test_detect_customeranomaly_other(app): + """Parameterized test for detecting customer anomaly webhook snippet.""" + + from datetime import date + + request = { + "fulfillmentInfo": {"tag": "detectCustomerAnomaly"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [ + {"displayName": "phone_number", "value": 8231234789}, + {"displayName": "bill_state", "value": "other situation"}, + {"displayName": "bill_amount", "value": {"amount": 1000}}, + ] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + today = date.today() + assert res["sessionInfo"]["parameters"]["anomaly_detect"] == "false" + assert res["sessionInfo"]["parameters"]["total_bill"] == 1054.34 + assert res["sessionInfo"]["parameters"]["first_month"] == str( + today.replace(day=1, month=1 + ((today.month - 2) % 12)) + ) + + +def test_validate_phoneline(app): + """Parameterized test for validate form parameter webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "validatePhoneLine"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [ + {"displayName": "phone_number", "value": "5105105100"} + ] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["phone_line_verified"] == "true" + + +def test_invalid_phoneline(app): + """Parameterized test for validate form parameter webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "validatePhoneLine"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [ + {"displayName": "phone_number", "value": "9999999999"} + ] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["phone_line_verified"] == "false" + + +def test_invalid_phoneline2(app): + """Parameterized test for validate form parameter webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "validatePhoneLine"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [ + {"displayName": "phone_number", "value": "1231231234"} + ] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["domestic_coverage"] == "true" + + +def test_cruiseplan_coverage(app): + """Parameterized test for cruise plan coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "cruisePlanCoverage"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "destination", "value": "mexico"}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["port_is_covered"] == "true" + + +def test_cruiseplan_notcovered(app): + """Parameterized test for cruise plan coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "cruisePlanCoverage"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "destination", "value": "china"}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["port_is_covered"] == "false" + + +def test_international_coverage1(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "internationalCoverage"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "destination", "value": "singapore"}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["coverage"] == "both" + + +def test_international_coverage2(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "internationalCoverage"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "destination", "value": "russia"}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["coverage"] == "monthly_only" + + +def test_international_coverage3(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "internationalCoverage"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "destination", "value": "china"}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["coverage"] == "neither" + + +def test_cheapest_plan1(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "cheapestPlan"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "trip_duration", "value": 40}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["monthly_cost"] == 70 + assert res["sessionInfo"]["parameters"]["daily_cost"] == 400 + assert res["sessionInfo"]["parameters"]["suggested_plan"] == "monthly" + + +def test_cheapest_plan2(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "cheapestPlan"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "trip_duration", "value": 20}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["monthly_cost"] == 70 + assert res["sessionInfo"]["parameters"]["daily_cost"] == 200 + assert res["sessionInfo"]["parameters"]["suggested_plan"] == "monthly" + + +def test_cheapest_plan3(app): + """Parameterized test for international coverage webhook snippet.""" + + request = { + "fulfillmentInfo": {"tag": "cheapestPlan"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "trip_duration", "value": 5}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["monthly_cost"] == 70 + assert res["sessionInfo"]["parameters"]["daily_cost"] == 50 + assert res["sessionInfo"]["parameters"]["suggested_plan"] == "daily" + + +def test_cheapest_plan4(app): + """Invalid Case: This happens only when customer enters a negative number""" + + request = { + "fulfillmentInfo": {"tag": "cheapestPlan"}, + "pageInfo": { + "formInfo": { + "parameterInfo": [{"displayName": "trip_duration", "value": -1}] + } + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res["sessionInfo"]["parameters"]["suggested_plan"] == "null" + + +def test_default_tag(app): + """Default Case.""" + + request = { + "fulfillmentInfo": {"tag": None}, + "pageInfo": { + "formInfo": {"parameterInfo": [{"displayName": None, "value": None}]} + }, + } + + with app.test_request_context(json=request): + res = cxPrebuiltAgentsTelecom(flask.request) + assert res is None diff --git a/dialogflow-cx/webhook_test.py b/dialogflow-cx/webhook_test.py new file mode 100644 index 000000000000..b7948a26fbdd --- /dev/null +++ b/dialogflow-cx/webhook_test.py @@ -0,0 +1,35 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test webhook""" + +import flask +import pytest + +from webhook import handle_webhook + +# Create a fake 'app' for generating test request contexts. + +request = {"fulfillmentInfo": {"tag": "Default Welcome Intent"}} + + +@pytest.fixture(scope="module") +def app(): + return flask.Flask(__name__) + + +def test_handle_webhook(app): + with app.test_request_context(json=request): + res = handle_webhook(flask.request) + assert "Hello from a GCF Webhook" in str(res) diff --git a/dialogflow-cx/webhook_validate_form_parameter.py b/dialogflow-cx/webhook_validate_form_parameter.py new file mode 100644 index 000000000000..4c073a6347a6 --- /dev/null +++ b/dialogflow-cx/webhook_validate_form_parameter.py @@ -0,0 +1,46 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" DialogFlow CX: webhook to validate or invalidate form parameters snippet.""" + + +# [START dialogflow_cx_v3_webhook_validate_form_parameter] + + +def validate_parameter(request): + """Webhook will validate or invalidate parameter based on logic configured by the user.""" + return { + "page_info": { + "form_info": { + "parameter_info": [ + { + "displayName": "orderNumber", + "required": True, + "state": "INVALID", + "value": 123, + }, + ], + }, + }, + "sessionInfo": { + "parameters": { + # Set session parameter to None if the form parameter is 'INVALID' and your agent needs to reprompt the user + "orderNumber": None, + }, + }, + } + + +# [END dialogflow_cx_v3_webhook_validate_form_parameter] diff --git a/dialogflow-cx/webhook_validate_form_parameter_test.py b/dialogflow-cx/webhook_validate_form_parameter_test.py new file mode 100644 index 000000000000..0a0b650621f5 --- /dev/null +++ b/dialogflow-cx/webhook_validate_form_parameter_test.py @@ -0,0 +1,36 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test validate form parameter webhook snippet.""" + +import flask +import pytest + +from webhook_validate_form_parameter import validate_parameter + + +@pytest.fixture(name="app", scope="module") +def fixture_app(): + """Flask fixture to pass a flask.Request to the test function""" + return flask.Flask(__name__) + + +def test_validate_parameter(app): + """Parameterized test for validate form parameter webhook snippet.""" + + request = {"pageInfo": {"formInfo": {"parameterInfo": [{"value": 123}]}}} + + with app.test_request_context(json=request): + res = validate_parameter(flask.request) + assert res["page_info"]["form_info"]["parameter_info"][0]["state"] == "INVALID"