wit/wit.py (408 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import json
import logging
import os
import requests
from prompt_toolkit import prompt
from prompt_toolkit.history import InMemoryHistory
WIT_API_HOST = os.getenv('WIT_URL', 'https://api.wit.ai')
WIT_API_VERSION = os.getenv('WIT_API_VERSION', '20200513')
INTERACTIVE_PROMPT = '> '
LEARN_MORE = 'Learn more at https://wit.ai/docs/quickstart'
class WitError(Exception):
pass
def req(logger, access_token, meth, path, params, **kwargs):
full_url = WIT_API_HOST + path
logger.debug('%s %s %s', meth, full_url, params)
headers = {
'authorization': 'Bearer ' + access_token,
'accept': 'application/vnd.wit.' + WIT_API_VERSION + '+json'
}
headers.update(kwargs.pop('headers', {}))
rsp = requests.request(
meth,
full_url,
headers=headers,
params=params,
**kwargs
)
if rsp.status_code > 200:
raise WitError('Wit responded with status: ' + str(rsp.status_code) +
' (' + rsp.reason + ')')
json = rsp.json()
if 'error' in json:
raise WitError('Wit responded with an error: ' + json['error'])
logger.debug('%s %s %s', meth, full_url, json)
return json
class Wit(object):
access_token = None
_sessions = {}
def __init__(self, access_token, logger=None):
self.access_token = access_token
self.logger = logger or logging.getLogger(__name__)
def message(self, msg, context=None, n=None, verbose=None):
params = {}
if n is not None:
params['n'] = n
if msg:
params['q'] = msg
if context:
params['context'] = json.dumps(context)
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'GET', '/message', params)
return resp
def speech(self, audio_file, headers=None, verbose=None):
""" Sends an audio file to the /speech API.
Uses the streaming feature of requests (see `req`), so opening the file
in binary mode is strongly recommended (see
http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads).
Add Content-Type header as specified here: https://wit.ai/docs/http/20200513#post--speech-link
:param audio_file: an open handler to an audio file
:param headers: an optional dictionary with request headers
:param verbose: for legacy versions, get extra information
:return:
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
resp = req(self.logger, self.access_token, 'POST', '/speech', params,
data=audio_file, headers=headers)
return resp
def interactive(self, handle_message=None, context=None):
"""Runs interactive command line chat between user and bot. Runs
indefinitely until EOF is entered to the prompt.
handle_message -- optional function to customize your response.
context -- optional initial context. Set to {} if omitted
"""
if context is None:
context = {}
history = InMemoryHistory()
while True:
try:
message = prompt(INTERACTIVE_PROMPT, history=history, mouse_support=True).rstrip()
except (KeyboardInterrupt, EOFError):
return
if handle_message is None:
print(self.message(message, context))
else:
print(handle_message(self.message(message, context)))
def intent_list(self, headers=None, verbose=None):
"""
Returns names of all intents associated with your app.
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
resp = req(self.logger, self.access_token, 'GET', '/intents', params, headers=headers)
return resp
def detect_language(self, msg, n=None, headers=None, verbose=None):
"""
Returns the list of the top detected locales for the text message.
"""
params = {}
headers = headers or {}
if msg:
params['q'] = msg
if verbose:
params['verbose'] = True
if n is not None:
params['n'] = n
resp = req(self.logger, self.access_token, 'GET', '/language', params, headers=headers)
return resp
def intent_info(self, intent_name, headers=None, verbose=None):
"""
Returns all available information about an intent.
:param intent_name: name of existing intent
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/intents/' + intent_name
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def entity_list(self, headers=None, verbose=None):
"""
Returns list of all entities associated with your app.
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
resp = req(self.logger, self.access_token, 'GET', '/entities', params, headers=headers)
return resp
def entity_info(self, entity_name, headers=None, verbose=None):
"""
Returns all available information about an entity.
:param entity_name: name of existing entity
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/entities/' + entity_name
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def trait_list(self, headers=None, verbose=None):
"""
Returns list of all traits associated with your app.
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
resp = req(self.logger, self.access_token, 'GET', '/traits', params, headers=headers)
return resp
def trait_info(self, trait_name, headers=None, verbose=None):
"""
Returns all available information about a trait.
:param trait_name: name of existing trait
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/traits/' + trait_name
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def delete_intent(self, intent_name, headers=None, verbose=None):
"""
Delete an intent associated with your app.
:param intent_name: name of intent to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/intents/' + intent_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_entity(self, entity_name, headers=None, verbose=None):
"""
Delete an entity associated with your app.
:param entity_name: name of entity to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/entities/' + entity_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_role(self, entity_name, role_name, headers=None, verbose=None):
"""
Deletes a role associated with the entity.
:param entity_name: name of entity whose particular role is to be deleted
:param role_name: name of role to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/entities/' + entity_name + ":" + role_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_keyword(self, entity_name, keyword_name, headers=None, verbose=None):
"""
Deletes a keyword associated with the entity.
:param entity_name: name of entity whose particular keyword is to be deleted
:param keyword_name: name of keyword to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/entities/' + entity_name + "/keywords/" + keyword_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_synonym(self, entity_name, keyword_name, synonym_name, headers=None, verbose=None):
"""
Delete a synonym of the keyword of the entity.
:param entity_name: name of entity whose particular keyword is to be deleted
:param keyword_name: name of keyword to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/entities/' + entity_name + "/keywords/" + keyword_name + "/synonyms/" + synonym_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_trait(self, trait_name, headers=None, verbose=None):
"""
Delete a trait associated with your app.
:param intent_name: name of intent to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/traits/' + trait_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def delete_trait_value(self, trait_name, value_name, headers=None, verbose=None):
"""
Deletes a value associated with the trait.
:param trait_name: name of trait whose particular value is to be deleted
:param value_name: name of value to be deleted
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/traits/' + trait_name + "/values/" + value_name
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def get_utterances(self, limit, offset=None, intents=None, headers=None, verbose=None):
"""
Returns a JSON array of utterances.
:param limit: number of utterances to return
:param offset: number of utterances to skip
:param intents: list of intents to filter the utterances
"""
params = {}
headers = headers or {}
if limit is not None:
params['limit'] = limit
if offset:
params['offset'] = offset
if intents:
params['intents'] = intents
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'GET', '/utterances', params)
return resp
def delete_utterances(self, utterances, headers=None, verbose=None):
"""
Delete utterances from your app.
:param utterances: list of utterances to be deleted
"""
params = {}
headers = headers or {}
data = []
for utterance in utterances:
data.append({"text":utterance})
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'DELETE', '/utterances', params, json=data , headers=headers)
return resp
def get_apps(self, limit, offset=None, headers=None, verbose=None):
"""
Returns an array of all your apps.
:param limit: number of apps to return
:param offset: number of utterances to skip
"""
params = {}
headers = headers or {}
if limit is not None:
params['limit'] = limit
if offset:
params['offset'] = offset
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'GET', '/apps', params)
return resp
def app_info(self, app_id, headers=None, verbose=None):
"""
Returns an object representation of the specified app.
:param app_id: ID of existing app
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/apps/' + app_id
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def delete_app(self, app_id, headers=None, verbose=None):
"""
Returns an object representation of the specified app.
:param app_id: ID of existing app
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/apps/' + app_id
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def app_versions(self, app_id, headers=None, verbose=None):
"""
Returns an array of all tag groups for an app.
:param app_id: ID of existing app
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/apps/' + app_id + '/tags'
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def app_version_info(self, app_id, tag_id, headers=None, verbose=None):
"""
Returns an object representation of the specified app.
:param app_id: ID of existing app
:param tag_id: name of tag
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
endpoint = '/apps/' + app_id + "/tags/" + tag_id
resp = req(self.logger, self.access_token, 'GET', endpoint, params, headers=headers)
return resp
def create_app_version(self, app_id, tag_name, headers=None, verbose=None):
"""
Create a new version of your app.
:param app_id: ID of existing app
:param tag_name: name of tag
"""
params = {}
headers = headers or {}
data = {"tag":tag_name}
endpoint = '/apps/' + app_id + "/tags/"
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def delete_app_version(self, app_id, tag_name, headers=None, verbose=None):
"""
Delete a specific version of your app.
:param app_id: ID of existing app
:param tag_name: name of tag
"""
params = {}
headers = headers or {}
endpoint = '/apps/' + app_id + "/tags/" + tag_name
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'DELETE', endpoint, params, headers=headers)
return resp
def export(self, headers=None, verbose=None):
"""
Get a URL where you can download a ZIP file containing all of your app data.
"""
params = {}
headers = headers or {}
if verbose:
params['verbose'] = True
resp = req(self.logger, self.access_token, 'GET', '/export', params, headers=headers)
return resp
def import_app(self, name, private, zip_file, headers=None, verbose=None):
"""
Create a new app with all the app data from the exported app.
:param name: name of the new app
:param private: private if true
"""
params = {}
headers = headers or {}
if name is not None:
params['name'] = name
if private:
params['private'] = private
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', '/import', params, data=zip_file)
return resp
def create_intent(self, intent_name, headers=None, verbose=None):
"""
Creates a new intent with the given attributes.
:param intent_name: name of intent to be created
"""
params = {}
headers = headers or {}
data = {"name":intent_name}
endpoint = '/intents'
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def create_entity(self, entity_name, roles, lookups=None, headers=None, verbose=None):
"""
Creates a new intent with the given attributes.
:param entity_name: name of entity to be created
:param roles: list of roles you want to create for the entity
:param lookups: list of lookup strategies
"""
params = {}
headers = headers or {}
data = {"name":entity_name, "roles":roles}
endpoint = '/entities'
if lookups:
data['lookups'] = lookups
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def update_entity(self, current_entity_name, new_entity_name, roles, lookups=None, headers=None, verbose=None):
"""
Updates the attributes of an entity.
:param entity_name: name of entity to be updated
:param roles: updated list of roles
:param lookups: updated list of lookup strategies
"""
params = {}
headers = headers or {}
data = {"name":new_entity_name, "roles":roles}
endpoint = '/entities/' + current_entity_name
if lookups:
data['lookups'] = lookups
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'PUT', endpoint, params, json=data , headers=headers)
return resp
def add_keyword_value(self, entity_name, data, headers=None, verbose=None):
"""
Add a possible value into the list of keywords for the keywords entity.
:param entity_name: name of entity to which keyword is to be added
"""
params = {}
headers = headers or {}
endpoint = '/entities/' + entity_name + "/keywords"
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def create_synonym(self, entity_name, keyword_name, synonym, headers=None, verbose=None):
"""
Create a new synonym of the canonical value of the keywords entity.
:param entity_name: name of entity to which synonym is to be added
:param keyword_name: name of keyword to which synonym is to be added
:param synonym: name of synonym to be created
"""
params = {}
headers = headers or {}
endpoint = '/entities/' + entity_name + "/keywords/" + keyword_name + "/synonyms"
data = {"synonym":synonym}
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def create_trait(self, trait_name, values, headers=None, verbose=None):
"""
Creates a new trait with the given attributes.
:param trait_name: name of trait to be created
:param values: list of values for the trait
"""
params = {}
headers = headers or {}
data = {"name":trait_name, "values":values}
endpoint = '/traits'
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def create_trait_value(self, trait_name, new_value, headers=None, verbose=None):
"""
Creates a new trait with the given attributes.
:param trait_name: name of trait to which new value is to be added
:param new_value: name of new trait value
"""
params = {}
headers = headers or {}
data = {"value":new_value}
endpoint = '/traits/' + trait_name + "/values"
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def train(self, data, headers=None, verbose=None):
"""
Train your utterances.
:param data: array of utterances with required arguments
"""
params = {}
headers = headers or {}
endpoint = '/utterances'
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def create_app(self, app_name, lang, private, timezone=None, headers=None, verbose=None):
"""
Creates a new app for an existing user.
:param app_name: name of new app
:param lang: language code in ISO 639-1 format
:param private: private if true
:param timezone: default timezone of the app
"""
params = {}
headers = headers or {}
data = {"name":app_name, "lang":lang, "private":private}
endpoint = '/apps'
if timezone:
params['timezone'] = timezone
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'POST', endpoint, params, json=data , headers=headers)
return resp
def update_app(self, app_id, app_name=None, lang=None, private=None, timezone=None, headers=None, verbose=None):
"""
Updates existing app with given attributes.
:param app_name: new_name
:param lang: language code in ISO 639-1 format
:param private: private if true
:param timezone: default timezone of the app
"""
params = {}
headers = headers or {}
data = {}
endpoint = '/apps/' + app_id
if app_name:
data['name'] = app_name
if lang:
data['lang'] = lang
if private:
data['private'] = private
if timezone:
data['timezone'] = timezone
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'PUT', endpoint, params, json=data , headers=headers)
return resp
def update_app_version(self, app_id, tag_name, new_name=None, desc=None, move_to=None, headers=None, verbose=None):
"""
Update the tag's name or description, or move the tag to point to another tag.
:param app_id: ID of existing app
:param tag_name: name of existing tag
:param new_name: name of new tag
:param desc: new description of tag
:param move_to: new name of tag
"""
params = {}
headers = headers or {}
data = {}
endpoint = '/apps/' + app_id + "/tags/" + tag_name
if new_name:
data['tag'] = new_name
if desc:
data['desc'] = desc
if move_to:
data['move_to'] = move_to
if verbose:
params['verbose'] = verbose
resp = req(self.logger, self.access_token, 'PUT', endpoint, params, json=data , headers=headers)
return resp