answerific 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Guardfile +13 -0
- data/README.md +56 -0
- data/Rakefile +14 -0
- data/answerific.gemspec +28 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/answerific.rb +166 -0
- data/lib/answerific/version.rb +3 -0
- metadata +152 -0
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
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
data/answerific.gemspec
ADDED
@@ -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
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
|
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: []
|