-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example/ccai-agentassist-five9-grpc (#1323)
* example/ccai-agentassist-five9-grpc * fix pyflakes * Update exclusion_list.txt Include examples/ccai-agentassist-five9-grpc due to walrus operator --------- Co-authored-by: Andrew Gold <41129777+agold-rh@users.noreply.github.com>
- Loading branch information
1 parent
35db92b
commit c40cd7e
Showing
15 changed files
with
1,225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
SERVER_ADDESS=0.0.0.0 | ||
PORT=8080 | ||
PROJECT_ID=<YOUR_PROJECT_ID> | ||
CONVERSATION_PROFILE_ID=<YOUR_CONVERSATION_PROFILE_ID> | ||
CHUNK_SIZE=1024 | ||
RESTART_TIMEOUT=160 | ||
MAX_LOOKBACK=3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
Copyright 2024 Google. This software is provided as-is, without warranty or | ||
representation for any use or purpose. Your use of it is subject to your | ||
agreement with Google. | ||
|
||
# Five9 Voicestream Integration with Agent Assist | ||
|
||
This is a PoC to integrate Five9 Voicestream with Agent Assist. | ||
|
||
## Project Structure | ||
|
||
``` | ||
. | ||
├── assets | ||
│ └── FAQ.csv | ||
├── client | ||
│ ├── audio | ||
│ │ ├── END_USER.wav | ||
│ │ └── HUMAN_AGENT.wav | ||
│ └── client_voicestream.py | ||
├── .env | ||
├── proto | ||
│ ├── voice_pb2_grpc.py | ||
│ ├── voice_pb2.py | ||
│ └── voice.proto | ||
├── README.md | ||
├── requirements.txt | ||
└── server | ||
├── server.py | ||
├── services | ||
│ └── get_suggestions.py | ||
└── utils | ||
├── conversation_management.py | ||
└── participant_management.py | ||
``` | ||
|
||
## Components | ||
- Agent Assist | ||
- Five9 with VoiceStream | ||
|
||
## Setup Instructions | ||
|
||
### GCP Project Setup | ||
|
||
#### Creating a Project in the Google Cloud Platform Console | ||
|
||
If you haven't already created a project, create one now. Projects enable you to | ||
manage all Google Cloud Platform resources for your app, including deployment, | ||
access control, billing, and services. | ||
|
||
1. Open the [Cloud Platform Console][cloud-console]. | ||
1. In the drop-down menu at the top, select **Create a project**. | ||
1. Give your project a name. | ||
1. Make a note of the project ID, which might be different from the project | ||
name. The project ID is used in commands and in configurations. | ||
|
||
[cloud-console]: https://console.cloud.google.com/ | ||
|
||
#### Enabling billing for your project. | ||
|
||
If you haven't already enabled billing for your project, [enable | ||
billing][enable-billing] now. Enabling billing allows is required to use Cloud | ||
Bigtable and to create VM instances. | ||
|
||
[enable-billing]: https://console.cloud.google.com/project/_/settings | ||
|
||
#### Install the Google Cloud SDK. | ||
|
||
If you haven't already installed the Google Cloud SDK, [install the Google | ||
Cloud SDK][cloud-sdk] now. The SDK contains tools and libraries that enable you | ||
to create and manage resources on Google Cloud Platform. | ||
|
||
[cloud-sdk]: https://cloud.google.com/sdk/ | ||
|
||
#### Setting Google Application Default Credentials | ||
|
||
Set your [Google Application Default | ||
Credentials][application-default-credentials] by [initializing the Google Cloud | ||
SDK][cloud-sdk-init] with the command: | ||
|
||
``` | ||
gcloud init | ||
``` | ||
|
||
Generate a credentials file by running the | ||
[application-default login](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) | ||
command: | ||
|
||
``` | ||
gcloud auth application-default login | ||
``` | ||
|
||
[cloud-sdk-init]: https://cloud.google.com/sdk/docs/initializing | ||
|
||
[application-default-credentials]: https://developers.google.com/identity/protocols/application-default-credentials | ||
|
||
#### Create a Knowledge Base | ||
|
||
Agent Assist follows a conversation between a human agent and an end-user and provide the human agent with relevant document suggestions. These suggestions are based on knowledge bases, namely, collections of documents that you upload to Agent Assist. These documents are called knowledge documents and can be either articles (for use with Article Suggestion) or FAQ documents (for use with FAQ Assist). | ||
|
||
In this specific implementation, a CSV sheet with FAQ will be used as knowledge document. | ||
> [FAQ CSV file](./assets/FAQ.csv) | ||
> [Create a Knowledge Base](https://cloud.google.com/agent-assist/docs/knowledge-base) | ||
#### Create a Conversation Profile | ||
|
||
A conversation profile configures a set of parameters that control the suggestions made to an agent. | ||
|
||
> [Create/Edit an Agent Assist Conversation Profile](https://cloud.google.com/agent-assist/docs/conversation-profile#create_and_edit_a_conversation_profile) | ||
While creating the the conversation profile, check the FAQs box. In the "Knowledge bases" input box, select the recently created Knowledge Base. The other values in the section should be set as default. | ||
|
||
Once the conversation profile is created, you can find the CONVERSATION_PROFILE_ID (Integration ID) in the following ways: | ||
|
||
> Open [Agent Assist](https://agentassist.cloud.google.com/), then Conversation | ||
> Profiles on the left bottom | ||
### Usage Pre-requisites | ||
|
||
- FAQs Suggestions should be enabled in the Agent Assist Conversation Profile | ||
- Agent Assist will only give you suggestions to conversations with Human Agents. It will not | ||
give suggestions if the conversation is being guided by virtual agents. | ||
|
||
|
||
### Local Development Set Up | ||
|
||
This application is designed to run on port 8080. Upon launch, the | ||
application will initialize and bind to port 8080, making it accessible for | ||
incoming connections. This can be changed in the .env file. | ||
|
||
#### Protocol Buffer Compiler: | ||
|
||
This implementation leverages from Buffer compilers for service definitions and data serialization. In this case, protoc is used to compile Five9's protofile. | ||
|
||
``` | ||
NOTE: The compilation of the Five9's Voicestream protofile was already made, therefore this step can be skipped. But if an update of the protofile is needed, please follow these steps to properly output the required python files. | ||
``` | ||
|
||
> [Protocol Buffer Compiler Installation](https://grpc.io/docs/protoc-installation/) | ||
> [Five9's Voicestream protofile](./proto/voice.proto) | ||
To compile the protofile: | ||
> Open a terminal window | ||
> Go to the root where your proto folder is | ||
> Run the following command: | ||
``` | ||
python3 -m grpc_tools.protoc -I proto --python_out=proto --grpc_python_out=proto proto/voice.proto | ||
``` | ||
> Two python files will be generated inside the proto folder. | ||
> [voice_pb2_grpc.py](./proto/voice_pb2_grpc.py) | ||
> [voice_pb2.py](./proto/voice_pb2.py) | ||
|
||
#### Set of variables: | ||
|
||
The following variables need to be set up in the .env file inside the root folder | ||
|
||
``` | ||
SERVER_ADDRESS : | ||
Target server address | ||
PORT : | ||
Connection Port | ||
PROJECT_ID : | ||
GCP Project ID where the Agent Assist Conversation Profile is deployed. | ||
CONVERSATION_PROFILE_ID : | ||
Agent Assist Conversation Profile ID | ||
CHUNK_SIZE : | ||
Number of bytes of audio to be sent each time | ||
RESTART_TIMEOUT : | ||
Timeout of one stream | ||
MAX_LOOKBACK : | ||
Lookback for unprocessed audio data | ||
``` | ||
|
||
### Steps to follow | ||
|
||
## Start gRPC Server | ||
|
||
Start the gRPC Server controller. This will start a server on port 8080, where the voicestream client will send the data. | ||
|
||
> [Server Controller](./server/server.py) | ||
Inside the server folder, run the following command: | ||
|
||
``` | ||
python server.py | ||
``` | ||
|
||
## Start gRPC Client | ||
|
||
According to Five9's Self Service Developer Guide: | ||
|
||
``` | ||
VoiceStream does not support multi-channel streaming. VoiceStream | ||
transmits each distinct audio stream over a separate gRPC session: one | ||
for audio from the agent, and one for audio to the agent. | ||
``` | ||
|
||
In order to simulate this behaviour using our local environment, the same script should be run simultaneously. One that sends the customer audio (END_USER) and one that sends the agent audio (HUMAN_AGENT) | ||
|
||
> [Five9 Voicestream Client](./client/client_voicestream.py) | ||
Inside the client folder, run the following command to send the human agent audio: | ||
|
||
``` | ||
python client_voicestream.py --role=HUMAN_AGENT --call_id=<CALL_ID> | ||
``` | ||
In another terminal, run the following command to send the customer audio: | ||
``` | ||
python client_voicestream.py --role=END_USER --call_id=<CALL_ID> | ||
``` | ||
|
||
In order for both streams to be associated to the same conversation it is fundamental to specify a destination CONVERSATION_ID. For this to happen, the CALL_ID specified in the initial configuration sent by Five9 will be passed to the Agent Assist as the internal CONVERSATION_ID. In this implementation, we are manually defining this CALL_ID for testing purposes. | ||
|
||
|
||
# References | ||
1.[Agent Assist Documentation](https://cloud.google.com/agent-assist/docs) | ||
2.[Dialogflow](https://cloud.google.com/dialogflow/docs) | ||
3.[Five9 VoiceStream](https://www.five9.com/news/news-releases/five9-announces-five9-voicestream) | ||
4,[Five9 VoiceStream Release Notes](https://releasenotes.five9.com/space/RNA/23143057870/VoiceStream) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Where is my package? It hasn't arrived yet,I can help you track it down. Please provide the tracking number. You can also track your package directly on our website at www.everestexpeditions.com/tracking using your tracking number. | ||
Do you know the specific time my package will arrive?,Unfortunately we do not have access to individual driver information. However most deliveries arrive before 6 PM. |
Binary file not shown.
Binary file not shown.
107 changes: 107 additions & 0 deletions
107
examples/ccai-agentassist-five9-grpc/client/client_voicestream.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
""" | ||
Copyright 2024 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 | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
""" | ||
|
||
import sys | ||
import grpc | ||
import logging | ||
import wave | ||
import argparse | ||
import os | ||
from dotenv import load_dotenv | ||
|
||
sys.path.append("../proto") | ||
from voice_pb2 import VoiceConfig, StreamingConfig, StreamingVoiceRequest #pylint: disable=wrong-import-position | ||
from voice_pb2_grpc import VoiceStub #pylint: disable=wrong-import-position | ||
|
||
load_dotenv('../.env') | ||
server_address = os.getenv("SERVER_ADDRESS", "0.0.0.0") | ||
server_port = os.getenv("PORT", "8080") | ||
size = int(os.getenv("CHUNK_SIZE", 1024)) | ||
|
||
def generate_chunks(config, audio, chunk_size): | ||
"""Send initial audio configuration and audio content in chunks""" | ||
# Send initial configuration | ||
yield StreamingVoiceRequest(streaming_config=config) | ||
|
||
# Send audio content | ||
while chunk := audio.readframes(chunk_size): # NOQA | ||
yield StreamingVoiceRequest(audio_content=chunk) | ||
|
||
def run(role, call_id): | ||
"""Send requests to the server""" | ||
|
||
audio_path = f"audio/{role}.wav" | ||
|
||
#Map call_leg | ||
#0 Agent - 1 Customer. Ignoring supervisor | ||
call_leg = 0 if role=="HUMAN_AGENT" else 1 | ||
print(call_leg) | ||
|
||
# Get Audio config | ||
wf = wave.open(audio_path, "rb") | ||
sample_rate = wf.getframerate() #Number of frames per second | ||
chunk_size = size #Number of frames to be sent in each request | ||
|
||
# Voice Config | ||
voice_config = VoiceConfig(encoding=1, #1 for LINEAR16 | ||
sample_rate_hertz=sample_rate | ||
) | ||
|
||
config = StreamingConfig(voice_config=voice_config, | ||
vcc_call_id=call_id, | ||
domain_id="customer_domain", | ||
campaign_id="campaing_associated", | ||
agent_id="agent_identifier", | ||
call_leg=call_leg, | ||
trust_token="trust_token_123", | ||
subscription_id="sub_id_123", | ||
skill_id="skill_identifier" | ||
) | ||
|
||
|
||
with grpc.insecure_channel(f"{server_address}:{server_port}") as channel: | ||
stub = VoiceStub(channel) | ||
responses = stub.StreamingVoice(generate_chunks(config=config, | ||
audio=wf, | ||
chunk_size=chunk_size)) | ||
|
||
# If the response has a status code | ||
# and it is different from SRV_REQ_START_STREAMING (1001), then stop | ||
for response in responses: | ||
print(f"Client received: {response}") | ||
|
||
#Verify if the initial handshake was successful | ||
if response.status.code and response.status.code != 1001: | ||
break | ||
|
||
|
||
if __name__ == "__main__": | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
#Parse arguments | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--role", | ||
type=str, | ||
default=None, | ||
help="Please specify the role as HUMAN_AGENT or END_USER") | ||
parser.add_argument("--call_id", | ||
type=str, | ||
default=None, | ||
help="Please specify the CallId") | ||
|
||
args = parser.parse_args() | ||
|
||
run(role=args.role, call_id=args.call_id) |
Oops, something went wrong.