Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bring up to date #6

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions neon_solvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,38 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from neon_solvers.solver import AbstractSolver

from ovos_plugin_manager.templates.solvers import AbstractSolver
from ovos_plugin_manager.solvers import find_question_solver_plugins, load_question_solver_plugin
from ovos_utils.log import LOG
from ovos_config import Configuration
from ovos_utils.messagebus import FakeBus


class NeonSolversService:
def __init__(self, bus, config=None):
self.config_core = config or {}
def __init__(self, bus=None, config=None):
self.config_core = config or Configuration()
self.loaded_modules = {}
self.bus = bus
self.bus = bus or FakeBus()
self.config = self.config_core.get("solvers") or {}
self.load_plugins()

def load_plugins(self):
for plug_name, plug in find_question_solver_plugins().items():
if plug_name in self.config:
try:
self.loaded_modules[plug_name] = plug()
LOG.info(f"loaded question solver plugin: {plug_name}")
except Exception as e:
LOG.exception(f"Failed to load question solver plugin: {plug_name}")
config = self.config.get(plug_name) or {}
if not config.get("enabled", True):
LOG.debug(f"{plug_name} not enabled in config, it won't be loaded")
continue
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved
try:
self.loaded_modules[plug_name] = plug(config=config)
LOG.info(f"loaded question solver plugin: {plug_name}")
except Exception as e:
LOG.exception(f"Failed to load question solver plugin: {plug_name}")

@property
def modules(self):
return sorted(self.loaded_modules.values(),
key=lambda k: k.priority, reverse=True)
key=lambda k: k.priority)

def shutdown(self):
for module in self.modules:
Expand All @@ -61,11 +66,12 @@ def shutdown(self):
except:
pass

def spoken_answers(self, utterance, context=None):
def spoken_answer(self, utterance, context=None):
for module in self.modules:
try:
ans = module.spoken_answers(utterance, context)
ans = module.spoken_answer(utterance, context)
if ans:
return ans
except:
except Exception as e:
LOG.error(e)
pass
183 changes: 2 additions & 181 deletions neon_solvers/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,184 +25,5 @@
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from json_database import JsonStorageXDG
from ovos_plugin_manager.language import OVOSLangTranslationFactory
from ovos_utils.xdg_utils import xdg_cache_home
from quebra_frases import sentence_tokenize


class AbstractSolver:
def __init__(self, name, priority=50, config=None):
self.config = config or {}
self.supported_langs = self.config.get("supported_langs") or []
self.default_lang = self.config.get("lang", "en")
if self.default_lang not in self.supported_langs:
self.supported_langs.insert(0, self.default_lang)
self.priority = priority
self.translator = OVOSLangTranslationFactory.create()
# cache contains raw data
self.cache = JsonStorageXDG(name + "_data",
xdg_folder=xdg_cache_home(),
subfolder="neon_solvers")
# spoken cache contains dialogs
self.spoken_cache = JsonStorageXDG(name,
xdg_folder=xdg_cache_home(),
subfolder="neon_solvers")

@staticmethod
def sentence_split(text, max_sentences=25):
return sentence_tokenize(text)[:max_sentences]

def _get_user_lang(self, context, lang=None):
context = context or {}
lang = lang or context.get("lang") or self.default_lang
lang = lang.split("-")[0]
return lang

def _tx_query(self, query, context=None, lang=None):
context = context or {}
lang = user_lang = self._get_user_lang(context, lang)

# translate input to default lang
if user_lang not in self.supported_langs:
lang = self.default_lang
query = self.translator.translate(query, lang, user_lang)

context["lang"] = lang

# HACK - cleanup some common translation mess ups
# this is properly solving by using a good translate plugin
# only common mistakes in default libretranslate plugin are handled
query = query.replace("who is is ", "who is ")

return query, context, lang

# plugin methods to override
def get_spoken_answer(self, query, context):
"""
query assured to be in self.default_lang
return a single sentence text response
"""
return ""

def get_data(self, query, context):
"""
query assured to be in self.default_lang
return a dict response
"""
return {"short_answer": self.get_spoken_answer(query, context)}

def get_image(self, query, context=None):
"""
query assured to be in self.default_lang
return path/url to a single image to acompany spoken_answer
"""
return None

def get_expanded_answer(self, query, context=None):
"""
query assured to be in self.default_lang
return a list of ordered steps to expand the answer, eg, "tell me more"

{
"title": "optional",
"summary": "speak this",
"img": "optional/path/or/url
}
:return:
"""
return []

def shutdown(self):
""" module specific shutdown method """
pass

# user facing methods
def search(self, query, context=None, lang=None):
"""
cache and auto translate query if needed
returns translated response from self.get_data
"""
user_lang = self._get_user_lang(context, lang)
query, context, lang = self._tx_query(query, context, lang)
# read from cache
if query in self.cache:
data = self.cache[query]
else:
# search data
try:
data = self.get_data(query, context)
except:
return {}

# save to cache
self.cache[query] = data
self.cache.store()

# translate english output to user lang
if user_lang not in self.supported_langs:
return self.translator.translate_dict(data, user_lang, lang)
return data

def visual_answer(self, query, context=None, lang=None):
"""
cache and auto translate query if needed
returns image that answers query
"""
query, context, lang = self._tx_query(query, context, lang)
return self.get_image(query, context)

def spoken_answer(self, query, context=None, lang=None):
"""
cache and auto translate query if needed
returns chunked and translated response from self.get_spoken_answer
"""
user_lang = self._get_user_lang(context, lang)
query, context, lang = self._tx_query(query, context, lang)

# get answer
if query in self.spoken_cache:
# read from cache
summary = self.spoken_cache[query]
else:
summary = self.get_spoken_answer(query, context)
# save to cache
self.spoken_cache[query] = summary
self.spoken_cache.store()

# summarize
if summary:
# translate english output to user lang
if user_lang not in self.supported_langs:
return self.translator.translate(summary, user_lang, lang)
else:
return summary

def long_answer(self, query, context=None, lang=None):
"""
return a list of ordered steps to expand the answer, eg, "tell me more"
step0 is always self.spoken_answer and self.get_image
{
"title": "optional",
"summary": "speak this",
"img": "optional/path/or/url
}
:return:
"""
user_lang = self._get_user_lang(context, lang)
query, context, lang = self._tx_query(query, context, lang)
steps = self.get_expanded_answer(query, context)

# use spoken_answer as last resort
if not steps:
summary = self.get_spoken_answer(query, context)
if summary:
img = self.get_image(query, context)
steps = [{"title": query, "summary": step0, "img": img}
for step0 in self.sentence_split(summary, -1)]

# translate english output to user lang
if user_lang not in self.supported_langs:
return self.translator.translate_list(steps, user_lang, lang)
return steps
# backwards compat
from ovos_plugin_manager.templates.solvers import AbstractSolver
1 change: 1 addition & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
quebra_frases
json_database
# TODO - opm min version
ovos_plugin_manager
neon-lang-plugin-libretranslate