Skip to content

Commit

Permalink
example/ccai-agentassist-five9-grpc (#1323)
Browse files Browse the repository at this point in the history
* 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
Carlos-Olivares and agold-rh authored Aug 5, 2024
1 parent 35db92b commit c40cd7e
Show file tree
Hide file tree
Showing 15 changed files with 1,225 additions and 0 deletions.
7 changes: 7 additions & 0 deletions examples/ccai-agentassist-five9-grpc/.env
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
229 changes: 229 additions & 0 deletions examples/ccai-agentassist-five9-grpc/README.md
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)

2 changes: 2 additions & 0 deletions examples/ccai-agentassist-five9-grpc/assets/FAQ.csv
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 examples/ccai-agentassist-five9-grpc/client/client_voicestream.py
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)
Loading

0 comments on commit c40cd7e

Please sign in to comment.