answerific 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a6b5905e26045c85b62eadee1a917f7a6f33edb5
4
+ data.tar.gz: b1721baa87c03b00cf36e614dc50561dc8473a35
5
+ SHA512:
6
+ metadata.gz: b30eed839c2149e237033351b458427bb56274d970db991977a689232a5cecd55e7ece89d78a8f1cd40c5ce72b8bb60622fb9f7cd159b87e45b8ac3f14359927
7
+ data.tar.gz: 8b347d026f099cbb41b02b626bd524724edc135b7c61f1fd038b7af1efd93a66c302d1d4107e62b79b2009b90dc86b6f3fa70be826185e9d863de8aeae2053e3
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in answerific.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,13 @@
1
+ clearing :on
2
+
3
+ guard 'rspec', cmd: "bundle exec rspec --format=doc --format=Nc" do
4
+ # watch /lib/ files
5
+ watch(%r{^lib/(.+).rb$}) do |m|
6
+ "spec/#{m[1]}_spec.rb"
7
+ end
8
+
9
+ # watch /spec/ files
10
+ watch(%r{^spec/(.+).rb$}) do |m|
11
+ "spec/#{m[1]}.rb"
12
+ end
13
+ end
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Answerific
2
+
3
+ AI Bot that can answer questions posed in natural language.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'answerific'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install answerific
20
+
21
+ ## Usage
22
+
23
+ bot = Answerific::Bot.new()
24
+ bot.answer('what is the composition of Pluto?')
25
+
26
+ ## How it works
27
+
28
+ Given an input, answerific will
29
+
30
+ 1. Preprocess the input
31
+ 2. Detect the type of question
32
+ 3. Parse and rearrange the input given the type of question
33
+ 4. Extract information from the web for that parsed input
34
+ 5. Select and return the best answer
35
+
36
+ ## Roadmap
37
+
38
+ * Add options at initialization
39
+ * Sentence split on dot: handle abbreviations
40
+ * Return special message when no result found? Or just nil?
41
+ * Better support for wh-words (atm, the bot just gets rid of them)
42
+ * Better support for yes-no questions: answer with definite yes-no instead of statement
43
+
44
+ ## Development
45
+
46
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
47
+
48
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it ( https://github.com/[my-github-username]/answerific/fork )
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format=d', '--format=Nc']
8
+ end
9
+
10
+ task :default => :spec
11
+
12
+ task :console do
13
+ exec "irb -r answerific -I ./lib"
14
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'answerific/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "answerific"
8
+ spec.version = Answerific::VERSION
9
+ spec.authors = ["Justin Domingue"]
10
+ spec.email = ["justin.domingue@hotmail.com"]
11
+
12
+ spec.summary = 'Bot that answers natural language questions.'
13
+ spec.homepage = 'https://github.com/justindomingue/answerific'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.9"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rspec-nc"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-rspec"
26
+
27
+ spec.add_runtime_dependency "google-search"
28
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "answerific"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/answerific.rb ADDED
@@ -0,0 +1,166 @@
1
+ require "answerific/version"
2
+ require "google-search"
3
+
4
+ module Answerific
5
+ class Bot
6
+
7
+ def answer(question)
8
+ mine(parse(preprocess(question)))
9
+ end
10
+
11
+ # === SELECT RESPONSE ===
12
+
13
+ def process_google_results(results, query)
14
+ candidates = select_responses(results, query)
15
+ select_best_response(candidates)
16
+ end
17
+
18
+ # Returns a single response from the list of responses
19
+ # TODO how to select the best? right now, return the first one
20
+ def select_best_response(responses)
21
+ responses.sample
22
+ end
23
+
24
+ # Returns the responses from `results` that have a the words in `query`
25
+ def select_responses(results, query)
26
+ sentences = results.map { |r| split_at_dot r }.flatten
27
+ query_words = query.split ' '
28
+
29
+ # Select the responses, only keeping the sentence that contain the search query
30
+ selected = sentences.select do |sentence|
31
+ query_words.all? { |w| sentence.include? w } # contains all query words
32
+ end
33
+
34
+ return selected
35
+ end
36
+
37
+ # === EXTRACT INFO ===
38
+
39
+ def mine(query)
40
+ results = []
41
+
42
+ Google::Search::Web.new(query: query).each do |r|
43
+ results << clean_google_result(r.content)
44
+ end
45
+
46
+ process_google_results(results, query)
47
+ end
48
+
49
+ # === PARSE AND REARRANGE === (prepare for search engines)
50
+
51
+ def parse(question)
52
+ type = broad_question_type question
53
+ parsed = ''
54
+
55
+ case type
56
+ when 'wh'
57
+ parsed = parse_wh_question question
58
+ when 'yes-no'
59
+ parsed = parse_yes_no_question question
60
+ when 'declarative'
61
+ parsed = parse_declarative_question question
62
+ end
63
+
64
+ return parsed
65
+ end
66
+
67
+ # TODO consider verb permutations
68
+ # TODO consider wh-word: where is the sun => the sun is [located]
69
+ # Parses the wh-question `question` by removing the wh-word and moving the main verb at the end
70
+ # Assumptions:
71
+ # * wh-word is at the beginning
72
+ # * main verb follows the wh-word
73
+ # (TODO not accurate for which/whose but should be ok for the others)
74
+ # Example:
75
+ # question: 'where is the Kuiper belt'
76
+ # returns : 'the Kuiper belt is'
77
+ def parse_wh_question(question)
78
+ words = question.split ' '
79
+ parsed = words[2..-1] << words[1]
80
+ parsed.join " "
81
+ end
82
+
83
+ # Returns an array of permutations of the main verb in the question without the wh-word
84
+ # Parses the wh-question `question` by removing the wh-word
85
+ # Assumptions:
86
+ # * wh-word is at the beginning
87
+ # * main verb follows the wh-word
88
+ # (TODO not accurate for which/whose but should be ok for the others)
89
+ # Example:
90
+ # question: 'where is the Kuiper belt'
91
+ # returns : ['is the Kuiper belt',
92
+ # 'the is Kuiper belt',
93
+ # 'the Kuiper is belt',
94
+ # 'the Kuiper belt is']
95
+ # def parse_wh_question(question)
96
+
97
+ # end
98
+
99
+ # Returns `question` without the yes-no verb
100
+ # Example:
101
+ # question: 'is pluto closer to the sun than saturn'
102
+ # returns : 'pluto closer to the sun than saturn'
103
+ def parse_yes_no_question(question)
104
+ words = question.split ' '
105
+ return words[1..-1].join ' '
106
+ end
107
+
108
+ # Returns `question` without the declarative statement
109
+ # Example:
110
+ # question: 'tell me what is Pluto'
111
+ # returns : 'what is Pluto'
112
+ def parse_declarative_question(question)
113
+ declarative_expressions = [ 'tell me', 'I want to know' ]
114
+ return question.gsub(/^#{Regexp.union(*declarative_expressions)}/, '').strip
115
+ end
116
+
117
+ # === DETECT TYPE OF QUESTION ===
118
+
119
+ def broad_question_type(question)
120
+ return 'wh' if is_wh_question question
121
+ return 'yes-no' if is_yes_no_question question
122
+ return 'declarative'
123
+ end
124
+
125
+ # Returns true if question starts with a wh-question word
126
+ def is_wh_question(question)
127
+ wh_words = %w(who where when why what which how)
128
+ return /^#{Regexp.union(*wh_words)}/ === question
129
+ end
130
+
131
+ # Returns true if question starts with a yes-no question expression
132
+ def is_yes_no_question(question)
133
+ yes_no_words = %w(am are is was were have has do does did can could should may)
134
+ return /^#{Regexp.union(*yes_no_words)}/ === question
135
+ end
136
+
137
+ # === PREPROCESSING ===
138
+
139
+ # Returns cleaned `input`
140
+ def preprocess(input)
141
+ clean(input)
142
+ end
143
+
144
+ # Cleans the string `input` by removing non alpha-numeric characters
145
+ def clean(input)
146
+ ret = input.downcase
147
+ ret.gsub(/[^0-9a-z ]/i, '').strip
148
+ end
149
+
150
+ # === OTHER FORMATTING ===
151
+
152
+ def clean_google_result(string)
153
+ string
154
+ .downcase
155
+ .gsub(/[^\.]+\.{3,}/, '') # remove incomplete sentences
156
+ .gsub(/<("[^"]*"|'[^']*'|[^'">])*>/, '') # html tags
157
+ .gsub(/\w{3} \d{1,2}, \d{4} \.{3} /, '') # dates (27 Jan, 2015)
158
+ .gsub("\n",'') # new lines
159
+ end
160
+
161
+ def split_at_dot(string)
162
+ re = /([a-z]{2})[\.\?!] ?/i # regex to match *aa. where a is any letter
163
+ string.split(re).each_slice(2).map(&:join)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ module Answerific
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: answerific
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Domingue
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-nc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: google-search
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - justin.domingue@hotmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".travis.yml"
120
+ - Gemfile
121
+ - Guardfile
122
+ - README.md
123
+ - Rakefile
124
+ - answerific.gemspec
125
+ - bin/console
126
+ - bin/setup
127
+ - lib/answerific.rb
128
+ - lib/answerific/version.rb
129
+ homepage: https://github.com/justindomingue/answerific
130
+ licenses: []
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.4.5
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Bot that answers natural language questions.
152
+ test_files: []