lib/wit.rb (187 lines of code) (raw):

# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. require 'json' require 'logger' require 'net/http' require 'securerandom' class Wit class Error < StandardError; end WIT_API_HOST = ENV['WIT_URL'] || 'https://api.wit.ai' WIT_API_VERSION = ENV['WIT_API_VERSION'] || '20200513' LEARN_MORE = 'Learn more at https://wit.ai/docs/quickstart' def initialize(opts = {}) @access_token = opts[:access_token] if opts[:logger] @logger = opts[:logger] end end def logger @logger ||= begin x = Logger.new(STDOUT) x.level = Logger::INFO x end end def message(msg, n=nil, verbose=nil) params = {} params[:q] = msg unless msg.nil? params[:n] = n unless n.nil? params[:verbose] = verbose unless verbose.nil? res = req(logger, @access_token, Net::HTTP::Get, '/message', params) return res end def interactive(handle_message=nil, context={}) while true print '> ' msg = STDIN.gets.strip if msg == '' next end begin if handle_message.nil? puts(message(msg)) else puts(handle_message.call(message(msg))) end rescue Error => exp logger.error("error: #{exp.message}") end end rescue Interrupt => _exp puts end def create_new_app(payload, set_new_app_token = false) response = req(logger, @access_token, Net::HTTP::Post, "/apps", {}, payload) @access_token = response['access_token'] if set_new_app_token return response end def post_utterances(payload) payload = [payload] if payload.is_a? Hash payload.each do |utterance| unless utterance[:entities].empty? utterance[:entities] = validate_entities utterance[:entities] end validate_payload utterance end req(logger, @access_token, Net::HTTP::Post, "/utterances", {}, payload) end def get_intents req(logger, @access_token, Net::HTTP::Get, "/intents") end def get_intent(intent) req(logger, @access_token, Net::HTTP::Get, "/intents/#{URI.encode(intent)}") end def post_intents(payload) req(logger, @access_token, Net::HTTP::Post, "/intents", {}, payload) end def delete_intents(intent) req(logger, @access_token, Net::HTTP::Delete, "/intents/#{URI.encode(intent)}") end def get_entities req(logger, @access_token, Net::HTTP::Get, "/entities") end def post_entities(payload) payload = payload.map {|k, v| [(k.to_sym rescue k), v]}.to_h.reject{ |k| ![:name, :roles, :lookups, :keywords].include?(k) } validate_payload payload req(logger, @access_token, Net::HTTP::Post, "/entities", {}, payload) end def get_entity(entity) req(logger, @access_token, Net::HTTP::Get, "/entities/#{URI.encode(entity)}") end def put_entities(entity, payload) payload = payload.map {|k, v| [(k.to_sym rescue k), v]}.to_h.reject{ |k| ![:name, :roles, :lookups, :keywords].include?(k) } validate_payload payload req(logger, @access_token, Net::HTTP::Put, "/entities/#{URI.encode(entity)}", {}, payload) end def delete_entities(entity) req(logger, @access_token, Net::HTTP::Delete, "/entities/#{URI.encode(entity)}") end def post_entities_keywords(entity, payload) payload = payload.map {|k, v| [(k.to_sym rescue k), v]}.to_h.reject{ |k| ![:keyword, :synonyms].include?(k) } validate_payload payload req(logger, @access_token, Net::HTTP::Post, "/entities/#{URI.encode(entity)}/keywords", {}, payload) end def delete_entities_keywords(entity, keyword) req(logger, @access_token, Net::HTTP::Delete, "/entities/#{URI.encode(entity)}/keywords/#{URI.encode(keyword)}") end def post_entities_keywords_synonyms(entity, keyword, payload) payload = payload.map {|k, v| [(k.to_sym rescue k), v]}.to_h.reject{ |k| ![:synonym].include?(k) } validate_payload payload req(logger,@access_token, Net::HTTP::Post, "/entities/#{URI.encode(entity)}/keywords/#{URI.encode(keyword)}/synonyms", {}, payload) end def delete_entities_keywords_synonyms(entity, keyword, synonym) req(logger,@access_token, Net::HTTP::Delete, "/entities/#{URI.encode(entity)}/keywords/#{URI.encode(keyword)}/synonyms/#{URI.encode(synonym)}") end def get_traits req(logger, @access_token, Net::HTTP::Get, "/traits") end def get_trait(trait) req(logger, @access_token, Net::HTTP::Get, "/traits/#{URI.encode(trait)}") end def post_traits(payload) req(logger, @access_token, Net::HTTP::Post, "/traits", {}, payload) end def post_traits_values(trait, payload) req(logger, @access_token, Net::HTTP::Post, "/traits/#{URI.encode(trait)}/values", {}, payload) end def delete_traits_values(trait, value) req(logger, @access_token, Net::HTTP::Delete, "/traits/#{URI.encode(trait)}/values/#{URI.encode(value)}") end def delete_traits(trait) req(logger, @access_token, Net::HTTP::Delete, "/traits/#{URI.encode(trait)}") end private def validate_payload(payload) key_types = { id: String, name: String, roles: Array, lookups: Array, keywords: Array, text: String, intent: String, entities: Array, traits: Array } payload.each do |k, v| raise Error.new("#{k.to_s} in request body must be #{key_types[k].to_s} type") unless key_types[k] == v.class end end def validate_entities(entities) entity_keys = { entity: String, start: Integer, end: Integer, body: String, entities: Array } entities.each do |entity| entity = entity.map {|k, v| [(k.to_sym rescue k), v]}.to_h.reject{ |k| !entity_keys.keys.include?(k) } entity.each do |k, v| if k == :entities && !v.empty? validate_entities(v) end raise Error.new("#{k.to_s} in entities body must be #{entity_keys[k].to_s} type") unless entity_keys[k] == v.class end end return entities end def req(logger, access_token, meth_class, path, params={}, payload={}) uri = URI(WIT_API_HOST + path) uri.query = URI.encode_www_form(params) logger.debug("#{meth_class} #{uri}") request = meth_class.new(uri) request['authorization'] = 'Bearer ' + access_token request['accept'] = 'application/vnd.wit.' + WIT_API_VERSION + '+json' request.add_field 'Content-Type', 'application/json' request.body = payload.to_json Net::HTTP.start(uri.host, uri.port, {:use_ssl => uri.scheme == 'https'}) do |http| rsp = http.request(request) json = JSON.parse(rsp.body) if rsp.code.to_i != 200 error_msg = (json.is_a?(Hash) and json.has_key?('error')) ? json['error'] : json raise Error.new("Wit responded with an error: #{error_msg}") end logger.debug("#{meth_class} #{uri} #{json}") json end end end