Skip to content
This repository was archived by the owner on Oct 19, 2023. It is now read-only.

Commit 81ed359

Browse files
committed
google-assistant-sdk: add text assistant sample
Change-Id: I090af3a4c05525594a79953d17c53a34ab007695
1 parent 8e1574c commit 81ed359

File tree

4 files changed

+233
-33
lines changed

4 files changed

+233
-33
lines changed

google-assistant-grpc/nox.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
@nox.session
2121
def lint(session):
22-
session.interpreter = 'python3.4'
22+
session.interpreter = 'python3'
2323
session.install('pip', 'setuptools')
2424
session.install('docutils', 'flake8')
2525
session.run('flake8', 'nox.py', 'setup.py')
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Copyright (C) 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Sample that implements a text client for the Google Assistant Service."""
16+
17+
import os
18+
import logging
19+
import json
20+
21+
import click
22+
import google.auth.transport.grpc
23+
import google.auth.transport.requests
24+
import google.oauth2.credentials
25+
26+
from google.assistant.embedded.v1alpha2 import (
27+
embedded_assistant_pb2,
28+
embedded_assistant_pb2_grpc
29+
)
30+
31+
try:
32+
from . import (
33+
assistant_helpers,
34+
)
35+
except SystemError:
36+
import assistant_helpers
37+
38+
39+
ASSISTANT_API_ENDPOINT = 'embeddedassistant.googleapis.com'
40+
DEFAULT_GRPC_DEADLINE = 60 * 3 + 5
41+
42+
43+
class SampleTextAssistant(object):
44+
"""Sample Assistant that supports text based conversations.
45+
46+
Args:
47+
language_code: language for the conversation.
48+
device_model_id: identifier of the device model.
49+
device_id: identifier of the registered device instance.
50+
channel: authorized gRPC channel for connection to the
51+
Google Assistant API.
52+
deadline_sec: gRPC deadline in seconds for Google Assistant API call.
53+
"""
54+
55+
def __init__(self, language_code, device_model_id, device_id,
56+
channel, deadline_sec):
57+
self.language_code = language_code
58+
self.device_model_id = device_model_id
59+
self.device_id = device_id
60+
self.conversation_state = None
61+
self.assistant = embedded_assistant_pb2_grpc.EmbeddedAssistantStub(
62+
channel
63+
)
64+
self.deadline = deadline_sec
65+
66+
def __enter__(self):
67+
return self
68+
69+
def __exit__(self, etype, e, traceback):
70+
if e:
71+
return False
72+
73+
def assist(self, text_query):
74+
"""Send a text request to the Assistant and playback the response.
75+
"""
76+
def iter_assist_requests():
77+
dialog_state_in = embedded_assistant_pb2.DialogStateIn(
78+
language_code=self.language_code,
79+
conversation_state=b''
80+
)
81+
if self.conversation_state:
82+
dialog_state_in.conversation_state = self.conversation_state
83+
config = embedded_assistant_pb2.AssistConfig(
84+
audio_out_config=embedded_assistant_pb2.AudioOutConfig(
85+
encoding='LINEAR16',
86+
sample_rate_hertz=16000,
87+
volume_percentage=0,
88+
),
89+
dialog_state_in=dialog_state_in,
90+
device_config=embedded_assistant_pb2.DeviceConfig(
91+
device_id=self.device_id,
92+
device_model_id=self.device_model_id,
93+
),
94+
text_query=text_query,
95+
)
96+
req = embedded_assistant_pb2.AssistRequest(config=config)
97+
assistant_helpers.log_assist_request_without_audio(req)
98+
yield req
99+
100+
display_text = None
101+
for resp in self.assistant.Assist(iter_assist_requests(),
102+
self.deadline):
103+
assistant_helpers.log_assist_response_without_audio(resp)
104+
if resp.dialog_state_out.conversation_state:
105+
conversation_state = resp.dialog_state_out.conversation_state
106+
self.conversation_state = conversation_state
107+
if resp.dialog_state_out.supplemental_display_text:
108+
display_text = resp.dialog_state_out.supplemental_display_text
109+
return display_text
110+
111+
112+
@click.command()
113+
@click.option('--api-endpoint', default=ASSISTANT_API_ENDPOINT,
114+
metavar='<api endpoint>', show_default=True,
115+
help='Address of Google Assistant API service.')
116+
@click.option('--credentials',
117+
metavar='<credentials>', show_default=True,
118+
default=os.path.join(click.get_app_dir('google-oauthlib-tool'),
119+
'credentials.json'),
120+
help='Path to read OAuth2 credentials.')
121+
@click.option('--device-model-id',
122+
metavar='<device model id>',
123+
help=(('Unique device model identifier, '
124+
'if not specifed, it is read from --device-config')))
125+
@click.option('--device-id',
126+
metavar='<device id>',
127+
help=(('Unique registered device instance identifier, '
128+
'if not specified, it is read from --device-config, '
129+
'if no device_config found: a new device is registered '
130+
'using a unique id and a new device config is saved')))
131+
@click.option('--lang', show_default=True,
132+
metavar='<language code>',
133+
default='en-US',
134+
help='Language code of the Assistant')
135+
@click.option('--verbose', '-v', is_flag=True, default=False,
136+
help='Verbose logging.')
137+
@click.option('--grpc-deadline', default=DEFAULT_GRPC_DEADLINE,
138+
metavar='<grpc deadline>', show_default=True,
139+
help='gRPC deadline in seconds')
140+
def main(api_endpoint, credentials,
141+
device_model_id, device_id, lang, verbose,
142+
grpc_deadline, *args, **kwargs):
143+
# Setup logging.
144+
logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
145+
146+
# Load OAuth 2.0 credentials.
147+
try:
148+
with open(credentials, 'r') as f:
149+
credentials = google.oauth2.credentials.Credentials(token=None,
150+
**json.load(f))
151+
http_request = google.auth.transport.requests.Request()
152+
credentials.refresh(http_request)
153+
except Exception as e:
154+
logging.error('Error loading credentials: %s', e)
155+
logging.error('Run google-oauthlib-tool to initialize '
156+
'new OAuth 2.0 credentials.')
157+
return
158+
159+
# Create an authorized gRPC channel.
160+
grpc_channel = google.auth.transport.grpc.secure_authorized_channel(
161+
credentials, http_request, api_endpoint)
162+
logging.info('Connecting to %s', api_endpoint)
163+
164+
with SampleTextAssistant(lang, device_model_id, device_id,
165+
grpc_channel, grpc_deadline) as assistant:
166+
while True:
167+
text_query = click.prompt('')
168+
click.echo('<you> %s' % text_query)
169+
display_text = assistant.assist(text_query=text_query)
170+
click.echo('<@assistant> %s' % display_text)
171+
172+
173+
if __name__ == '__main__':
174+
main()

google-assistant-sdk/nox.py

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,10 @@
1515

1616
import nox
1717

18-
import io
19-
import json
20-
import os.path
21-
import tempfile
22-
2318

2419
@nox.session
2520
def lint(session):
26-
session.interpreter = 'python3.4'
21+
session.interpreter = 'python3'
2722
session.install('pip', 'setuptools')
2823
session.install('docutils', 'flake8')
2924
session.run('flake8',
@@ -34,45 +29,25 @@ def lint(session):
3429

3530

3631
@nox.session
37-
@nox.parametrize('python_version', ['2.7', '3.4'])
32+
@nox.parametrize('python_version', ['2.7', '3'])
3833
def unittest(session, python_version):
3934
session.interpreter = 'python' + python_version
4035
session.install('pip', 'setuptools')
41-
session.install('pytest')
36+
session.install('pytest', 'future')
4237
session.install('../google-assistant-grpc/')
4338
session.install('-e', '.[samples]')
44-
session.run('py.test', 'tests')
39+
session.run('py.test', '-k', 'not test_endtoend', 'tests')
4540

4641

4742
@nox.session
48-
@nox.parametrize('python_version', ['2.7', '3.4'])
43+
@nox.parametrize('python_version', ['2.7', '3'])
4944
def endtoend_test(session, python_version):
5045
session.interpreter = 'python' + python_version
5146
session.install('pip', 'setuptools')
47+
session.install('pytest', 'future')
5248
session.install('../google-assistant-grpc/')
5349
session.install('-e', '.[samples]')
54-
old_credentials = os.path.expanduser(
55-
'~/.config/googlesamples-assistant/assistant_credentials.json')
56-
new_credentials = os.path.expanduser(
57-
'~/.config/google-oauthlib-tool/credentials.json')
58-
if not os.path.exists(new_credentials):
59-
# use previous credentials location
60-
# TODO(proppy): remove when e2e job is using google-oauthlib-tool
61-
def migrate_credentials(old, new):
62-
with io.open(old) as f:
63-
creds = json.load(f)
64-
del creds['access_token']
65-
with io.open(new, 'w') as f:
66-
json.dump(f, creds)
67-
session.run(migrate_credentials, old_credentials, new_credentials)
68-
temp_dir = tempfile.mkdtemp()
69-
audio_out_file = os.path.join(temp_dir, 'out.raw')
70-
session.run('python', '-m', 'googlesamples.assistant.grpc.pushtotalk',
71-
'--device-model-id', 'test-device-model',
72-
'--device-id', 'test-device',
73-
'-i', 'tests/data/whattimeisit.riff',
74-
'-o', audio_out_file)
75-
session.run('test', '-s', audio_out_file)
50+
session.run('py.test', '-k', 'test_endtoend', 'tests')
7651

7752

7853
@nox.session
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/python
2+
# Copyright (C) 2017 Google Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import tempfile
17+
import os.path
18+
import subprocess
19+
20+
21+
import builtins
22+
23+
24+
def test_endtoend_pushtotalk():
25+
temp_dir = tempfile.mkdtemp()
26+
audio_out_file = os.path.join(temp_dir, 'out.raw')
27+
out = subprocess.check_output(['python', '-m',
28+
'googlesamples.assistant.grpc.pushtotalk',
29+
'--verbose',
30+
'--device-model-id', 'test-device-model',
31+
'--device-id', 'test-device',
32+
'-i', 'tests/data/whattimeisit.riff',
33+
'-o', audio_out_file],
34+
stderr=subprocess.STDOUT)
35+
assert 'what time is it' in builtins.str(out).lower()
36+
assert os.path.getsize(audio_out_file) > 0
37+
38+
39+
def test_endtoend_textinput():
40+
p = subprocess.Popen(['python', '-m',
41+
'googlesamples.assistant.grpc.textinput',
42+
'--verbose',
43+
'--device-model-id', 'test-device-model',
44+
'--device-id', 'test-device'],
45+
stdin=subprocess.PIPE,
46+
stdout=subprocess.PIPE)
47+
out, err = p.communicate(b'how do you say grapefruit in French?')
48+
out = builtins.str(out).lower()
49+
assert err is None
50+
assert 'grapefruit' in out
51+
assert 'pamplemousse' in out

0 commit comments

Comments
 (0)