ovos-solver-bm25-plugin 0.0.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ from ovos_bm25_solver.base import BM25MultipleChoiceSolver, BM25EvidenceSolverPlugin
2
+ from ovos_bm25_solver.corpus import BM25CorpusSolver, BM25QACorpusSolver
3
+ from ovos_bm25_solver.embed import JsonEmbeddingsDB, BM25TextEmbeddingsStore
4
+ from ovos_bm25_solver.demo import BM25FreebaseQASolver, BM25SquadQASolver
@@ -0,0 +1,161 @@
1
+ from typing import List, Optional, Union, Tuple
2
+
3
+ from ovos_plugin_manager.templates.solvers import MultipleChoiceSolver, EvidenceSolver
4
+ from ovos_utils.log import LOG
5
+ from quebra_frases import sentence_tokenize
6
+
7
+ from ovos_bm25_solver.corpus import BM25CorpusSolver
8
+
9
+
10
+ class BM25MultipleChoiceSolver(MultipleChoiceSolver):
11
+ """Select the best answer to a question from a list of options using BM25 ranking."""
12
+
13
+ def rerank(self, query: str, options: List[str],
14
+ lang: Optional[str] = None,
15
+ return_index: bool = False) -> List[Tuple[float, Union[str, int]]]:
16
+ """
17
+ Rank the list of options based on their relevance to the query.
18
+
19
+ Args:
20
+ query (str): The query to rank options against.
21
+ options (List[str]): A list of options to be ranked.
22
+ lang (Optional[str]): Optional language code for translation.
23
+ return_index (bool): Whether to return the index of the options instead of the option text.
24
+
25
+ Returns:
26
+ List[Tuple[float, Union[str, int]]]: A list of tuples containing the score and either the option text or its index.
27
+ """
28
+ bm25 = BM25CorpusSolver(internal_lang=self.default_lang,
29
+ translator=self.translator,
30
+ detector=self.detector,
31
+ enable_tx=self.enable_tx)
32
+ bm25.load_corpus(options)
33
+ ranked: List[Tuple[float, str]] = list(bm25.retrieve_from_corpus(query, lang=lang, k=len(options)))
34
+ if return_index:
35
+ ranked = [(r[0], options.index(r[1])) for r in ranked]
36
+ return ranked
37
+
38
+
39
+ class BM25EvidenceSolverPlugin(EvidenceSolver):
40
+ """Extract the best sentence from text that answers the question using BM25 algorithm."""
41
+
42
+ def get_best_passage(self, evidence: str, question: str,
43
+ lang: Optional[str] = None) -> Optional[str]:
44
+ """
45
+ Extract the most relevant passage from the evidence that answers the question.
46
+
47
+ Args:
48
+ evidence (str): The text to search for the answer.
49
+ question (str): The question to find an answer for.
50
+ lang (Optional[str]): Optional language code for translation.
51
+
52
+ Returns:
53
+ Optional[str]: The best passage that answers the question, or None if no passage is found.
54
+ """
55
+ bm25 = BM25MultipleChoiceSolver(internal_lang=self.default_lang,
56
+ translator=self.translator,
57
+ detector=self.detector,
58
+ enable_tx=self.enable_tx)
59
+ sents = []
60
+ for s in evidence.split("\n"):
61
+ sents += sentence_tokenize(s)
62
+ sents = [s.strip() for s in sents if s]
63
+ return bm25.select_answer(question, sents, lang=lang)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ from ovos_bm25_solver.demo import BM25FreebaseQASolver, BM25SquadQASolver
68
+
69
+ LOG.set_level("DEBUG")
70
+ p = BM25MultipleChoiceSolver()
71
+ a = p.rerank("what is the speed of light", [
72
+ "very fast", "10m/s", "the speed of light is C"
73
+ ], lang="en")
74
+ print(a)
75
+ # 2024-07-22 15:03:10.295 - OVOS - __main__:load_corpus:61 - DEBUG - indexed 3 documents
76
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 1 (score: 0.7198746800422668): the speed of light is C
77
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 2 (score: 0.0): 10m/s
78
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 3 (score: 0.0): very fast
79
+ # [(0.7198747, 'the speed of light is C'), (0.0, '10m/s'), (0.0, 'very fast')]
80
+
81
+ a = p.select_answer("what is the speed of light", [
82
+ "very fast", "10m/s", "the speed of light is C"
83
+ ], lang="en")
84
+ print(a) # the speed of light is C
85
+
86
+ config = {
87
+ "lang": "en-us",
88
+ "min_conf": 0.4,
89
+ "n_answer": 1
90
+ }
91
+ solver = BM25EvidenceSolverPlugin(config)
92
+
93
+ text = """Mars is the fourth planet from the Sun. It is a dusty, cold, desert world with a very thin atmosphere.
94
+ Mars is also a dynamic planet with seasons, polar ice caps, canyons, extinct volcanoes, and evidence that it was even more active in the past.
95
+ Mars is one of the most explored bodies in our solar system, and it's the only planet where we've sent rovers to roam the alien landscape.
96
+ NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
97
+ """
98
+ query = "how many rovers are currently exploring Mars"
99
+ answer = solver.get_best_passage(evidence=text, question=query, lang="en")
100
+ print("Query:", query)
101
+ print("Answer:", answer)
102
+ # 2024-07-22 15:05:14.209 - OVOS - __main__:load_corpus:61 - DEBUG - indexed 5 documents
103
+ # 2024-07-22 15:05:14.209 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 1 (score: 1.39238703250885): NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
104
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 2 (score: 0.38667747378349304): Mars is one of the most explored bodies in our solar system, and it's the only planet where we've sent rovers to roam the alien landscape.
105
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 3 (score: 0.15732118487358093): Mars is the fourth planet from the Sun.
106
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 4 (score: 0.10177625715732574): Mars is also a dynamic planet with seasons, polar ice caps, canyons, extinct volcanoes, and evidence that it was even more active in the past.
107
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 5 (score: 0.0): It is a dusty, cold, desert world with a very thin atmosphere.
108
+ # Query: how many rovers are currently exploring Mars
109
+ # Answer: NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
110
+
111
+ # Create your corpus here
112
+ corpus = [
113
+ "a cat is a feline and likes to purr",
114
+ "a dog is the human's best friend and loves to play",
115
+ "a bird is a beautiful animal that can fly",
116
+ "a fish is a creature that lives in water and swims",
117
+ ]
118
+
119
+ s = BM25CorpusSolver({})
120
+ s.load_corpus(corpus)
121
+
122
+ query = "does the fish purr like a cat?"
123
+ print(s.spoken_answer(query, lang="en"))
124
+
125
+ # 2024-07-19 20:03:29.979 - OVOS - ovos_plugin_manager.utils.config:get_plugin_config:40 - DEBUG - Loaded configuration: {'module': 'ovos-translate-plugin-server', 'lang': 'en-us'}
126
+ # 2024-07-19 20:03:30.024 - OVOS - __main__:load_corpus:28 - DEBUG - indexed 4 documents
127
+ # 2024-07-19 20:03:30.025 - OVOS - __main__:retrieve_from_corpus:37 - DEBUG - Rank 1 (score: 1.0584375858306885): a cat is a feline and likes to purr
128
+ # 2024-07-19 20:03:30.025 - OVOS - __main__:retrieve_from_corpus:37 - DEBUG - Rank 2 (score: 0.481589138507843): a fish is a creature that lives in water and swims
129
+ # a cat is a feline and likes to purr. a fish is a creature that lives in water and swims
130
+
131
+ # hotpotqa dataset
132
+ # data = requests.get("http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_dev_fullwiki_v1.json").json()
133
+ # data = requests.get("http://curtis.ml.cmu.edu/datasets/hotpot/hotpot_train_v1.1.json").json()
134
+ # for qa in data:
135
+ # corpus[qa["question"]] = qa["answer"]
136
+ # len_hotpot = len(corpus) - len_squad - len_freebase
137
+ # print(len_hotpot, "qa pairs imported from hotpotqa dataset")
138
+
139
+ # s = BM25QACorpusSolver({})
140
+ # s.load_corpus(corpus)
141
+
142
+ s = BM25FreebaseQASolver()
143
+ query = "What is the capital of France"
144
+ print("Query:", query)
145
+ print("Answer:", s.spoken_answer(query, lang="en"))
146
+ # 2024-07-19 22:31:09.468 - OVOS - __main__:load_corpus:60 - DEBUG - indexed 20357 documents
147
+ # Query: What is the capital of France
148
+ # 2024-07-19 22:31:09.468 - OVOS - __main__:retrieve_from_corpus:69 - DEBUG - Rank 1 (score: 5.996074199676514): what is the capital of france
149
+ # 2024-07-19 22:31:09.469 - OVOS - __main__:retrieve_from_corpus:93 - DEBUG - closest question in corpus: what is the capital of france
150
+ # Answer: paris
151
+
152
+ s = BM25SquadQASolver()
153
+ query = "is there life on mars"
154
+ print("Query:", query)
155
+ print("Answer:", s.spoken_answer(query, lang="en"))
156
+ # 2024-07-19 22:31:12.625 - OVOS - __main__:load_corpus:60 - DEBUG - indexed 86769 documents
157
+ # 2024-07-19 22:31:12.625 - OVOS - __main__:load_squad_corpus:119 - INFO - Loaded and indexed 86769 question-answer pairs from SQuAD dataset
158
+ # Query: is there life on mars
159
+ # 2024-07-19 22:31:12.628 - OVOS - __main__:retrieve_from_corpus:69 - DEBUG - Rank 1 (score: 6.334013938903809): How is it postulated that Mars life might have evolved?
160
+ # 2024-07-19 22:31:12.628 - OVOS - __main__:retrieve_from_corpus:93 - DEBUG - closest question in corpus: How is it postulated that Mars life might have evolved?
161
+ # Answer: similar to Antarctic
@@ -0,0 +1,139 @@
1
+ from typing import List, Optional, Tuple, Iterable
2
+
3
+ import bm25s
4
+ from ovos_plugin_manager.templates.language import LanguageTranslator, LanguageDetector
5
+ from ovos_plugin_manager.templates.solvers import QACorpusSolver, CorpusSolver
6
+ from ovos_utils.log import LOG
7
+
8
+
9
+ class BM25CorpusSolver(CorpusSolver):
10
+ """
11
+ A corpus solver that uses the BM25 algorithm for information retrieval.
12
+
13
+ Attributes:
14
+ METHODS (List[str]): List of supported BM25 methods.
15
+ IDF_METHODS (List[str]): List of supported IDF methods.
16
+ """
17
+
18
+ METHODS = ["robertson", "lucene", "bm25l", "bm25+", "atire", "rank-bm25", "bm25-pt"]
19
+ IDF_METHODS = ['robertson', 'lucene', 'atire', 'bm25l', 'bm25+']
20
+
21
+ def __init__(self, config: Optional[dict] = None,
22
+ translator: Optional[LanguageTranslator] = None,
23
+ detector: Optional[LanguageDetector] = None,
24
+ priority: int = 50,
25
+ enable_tx: bool = False,
26
+ enable_cache: bool = False,
27
+ internal_lang: Optional[str] = None,
28
+ *args, **kwargs):
29
+ """
30
+ Initialize the BM25CorpusSolver with optional configurations.
31
+
32
+ Args:
33
+ config (Optional[dict]): Configuration dictionary. Default is None.
34
+ translator (Optional[LanguageTranslator]): Optional language translator.
35
+ detector (Optional[LanguageDetector]): Optional language detector.
36
+ priority (int): Priority level for the solver. Default is 50.
37
+ enable_tx (bool): Whether to enable translation. Default is False.
38
+ enable_cache (bool): Whether to enable caching. Default is False.
39
+ internal_lang (Optional[str]): Language code for internal processing. Default is None.
40
+ *args: Additional arguments.
41
+ **kwargs: Additional keyword arguments.
42
+ """
43
+ config = config or {"min_conf": 0.0,
44
+ "n_answer": 1,
45
+ "method": None,
46
+ "idf_method": None}
47
+ super().__init__(config, translator, detector, priority,
48
+ enable_tx, enable_cache, internal_lang,
49
+ *args, **kwargs)
50
+ # Create the BM25 model
51
+ self.retriever = None
52
+ self.corpus = None
53
+
54
+ @property
55
+ def method(self) -> Optional[str]:
56
+ """
57
+ Get the BM25 method from configuration.
58
+
59
+ Returns:
60
+ Optional[str]: The BM25 method or None if not configured.
61
+ """
62
+ m = self.config.get("method")
63
+ if m is None:
64
+ return None
65
+ if m not in self.METHODS:
66
+ LOG.warning(f"{m} is not a valid method, choose one of {self.METHODS}")
67
+ m = None
68
+ return m
69
+
70
+ @property
71
+ def idf_method(self) -> Optional[str]:
72
+ """
73
+ Get the IDF method from configuration.
74
+
75
+ Returns:
76
+ Optional[str]: The IDF method or None if not configured. Defaults to "lucene" if invalid.
77
+ """
78
+ m = self.config.get("idf_method")
79
+ if m is None:
80
+ return None
81
+ if m not in self.IDF_METHODS:
82
+ LOG.warning(f"{m} is not a valid method, choose one of {self.IDF_METHODS}")
83
+ m = "lucene"
84
+ return m
85
+
86
+ def load_corpus(self, corpus: List[str]):
87
+ """
88
+ Load and index the given corpus using the BM25 algorithm.
89
+
90
+ Args:
91
+ corpus (List[str]): A list of documents to be indexed.
92
+ """
93
+ if self.method == "rank-bm25":
94
+ self.retriever = bm25s.BM25(method="atire", idf_method="robertson")
95
+ elif self.method == "bm25-pt":
96
+ self.retriever = bm25s.BM25(method="atire", idf_method="lucene")
97
+ elif self.method is not None:
98
+ self.retriever = bm25s.BM25(method=self.method, idf_method=self.idf_method)
99
+ else:
100
+ self.retriever = bm25s.BM25()
101
+ self.corpus = corpus
102
+ # Tokenize the corpus and only keep the ids (faster and saves memory)
103
+ stopwords = self.default_lang.split("-")[0]
104
+ if stopwords != "en":
105
+ stopwords = []
106
+
107
+ corpus_tokens = bm25s.tokenize(corpus, stopwords=stopwords)
108
+ # index the corpus
109
+ self.retriever.index(corpus_tokens)
110
+ LOG.debug(f"indexed {len(corpus)} documents")
111
+
112
+ def query(self, query: str, lang: Optional[str] = None, k: int = 3) -> Iterable[Tuple[str, float]]:
113
+ """
114
+ Query the indexed corpus and yield the top-k results.
115
+
116
+ Args:
117
+ query (str): The query string to search for.
118
+ lang (Optional[str]): Optional language code for tokenization.
119
+ k (int): The number of top results to return. Defaults to 3.
120
+
121
+ Yields:
122
+ Tuple[str, float]: Tuples containing the document ID and its score.
123
+ """
124
+ lang = lang or self.default_lang
125
+ stopwords = lang.split("-")[0]
126
+ if stopwords != "en":
127
+ stopwords = []
128
+ query_tokens = bm25s.tokenize(query, stopwords=stopwords)
129
+ # Get top-k results as a tuple of (doc ids, scores). Both are arrays of shape (n_queries, k)
130
+ results, scores = self.retriever.retrieve(query_tokens, corpus=self.corpus, k=k)
131
+ for i in range(results.shape[1]):
132
+ doc, score = results[0, i], scores[0, i]
133
+ yield doc, score
134
+
135
+
136
+ class BM25QACorpusSolver(QACorpusSolver, BM25CorpusSolver):
137
+ """
138
+ A QA corpus solver that combines BM25 retrieval with question answering capabilities.
139
+ """
@@ -0,0 +1,113 @@
1
+ from typing import Optional, Dict, Any
2
+
3
+ import requests
4
+ from ovos_plugin_manager.templates.language import LanguageTranslator, LanguageDetector
5
+ from ovos_utils.log import LOG
6
+
7
+ from ovos_bm25_solver.corpus import BM25QACorpusSolver
8
+
9
+
10
+ ## Demo subclasses
11
+ class BM25SquadQASolver(BM25QACorpusSolver):
12
+ """
13
+ A QA solver that uses the BM25 algorithm and loads data from the SQuAD dataset.
14
+
15
+ Attributes:
16
+ internal_lang (str): Language code used internally.
17
+ """
18
+
19
+ def __init__(self, config: Optional[dict] = None,
20
+ translator: Optional[LanguageTranslator] = None,
21
+ detector: Optional[LanguageDetector] = None,
22
+ priority: int = 50,
23
+ enable_tx: bool = False,
24
+ enable_cache: bool = False,
25
+ *args, **kwargs):
26
+ """
27
+ Initialize the BM25SquadQASolver with optional configurations.
28
+
29
+ Args:
30
+ config (Optional[dict]): Configuration dictionary. Defaults to {"n_answer": 1}.
31
+ translator (Optional[LanguageTranslator]): Optional language translator.
32
+ detector (Optional[LanguageDetector]): Optional language detector.
33
+ priority (int): Priority level for the solver. Defaults to 50.
34
+ enable_tx (bool): Whether to enable translation. Defaults to False.
35
+ enable_cache (bool): Whether to enable caching. Defaults to False.
36
+ *args: Additional arguments.
37
+ **kwargs: Additional keyword arguments.
38
+ """
39
+ internal_lang = "en-us"
40
+ config = config or {"n_answer": 1}
41
+ super().__init__(config, translator, detector, priority,
42
+ enable_tx, enable_cache, internal_lang,
43
+ *args, **kwargs)
44
+
45
+ def load_squad_corpus(self):
46
+ """
47
+ Load and index the SQuAD dataset.
48
+
49
+ This method fetches the SQuAD dataset from the specified URL, processes it, and
50
+ loads it into the BM25 corpus.
51
+ """
52
+ corpus: Dict[str, str] = {}
53
+ response = requests.get("https://github.com/chrischute/squad/raw/master/data/train-v2.0.json")
54
+ data = response.json()
55
+ for s in data["data"]:
56
+ for p in s["paragraphs"]:
57
+ for qa in p["qas"]:
58
+ if "question" in qa and qa["answers"]:
59
+ corpus[qa["question"]] = qa["answers"][0]["text"]
60
+ self.load_corpus(corpus)
61
+ LOG.info(f"Loaded and indexed {len(corpus)} question-answer pairs from SQuAD dataset")
62
+
63
+
64
+ class BM25FreebaseQASolver(BM25QACorpusSolver):
65
+ """
66
+ A QA solver that uses the BM25 algorithm and loads data from the FreebaseQA dataset.
67
+
68
+ Attributes:
69
+ internal_lang (str): Language code used internally.
70
+ """
71
+
72
+ def __init__(self, config: Optional[dict] = None,
73
+ translator: Optional[LanguageTranslator] = None,
74
+ detector: Optional[LanguageDetector] = None,
75
+ priority: int = 50,
76
+ enable_tx: bool = False,
77
+ enable_cache: bool = False,
78
+ *args, **kwargs):
79
+ """
80
+ Initialize the BM25FreebaseQASolver with optional configurations.
81
+
82
+ Args:
83
+ config (Optional[dict]): Configuration dictionary. Defaults to {"n_answer": 1}.
84
+ translator (Optional[LanguageTranslator]): Optional language translator.
85
+ detector (Optional[LanguageDetector]): Optional language detector.
86
+ priority (int): Priority level for the solver. Defaults to 50.
87
+ enable_tx (bool): Whether to enable translation. Defaults to False.
88
+ enable_cache (bool): Whether to enable caching. Defaults to False.
89
+ *args: Additional arguments.
90
+ **kwargs: Additional keyword arguments.
91
+ """
92
+ internal_lang = "en-us"
93
+ config = config or {"n_answer": 1}
94
+ super().__init__(config, translator, detector, priority,
95
+ enable_tx, enable_cache, internal_lang,
96
+ *args, **kwargs)
97
+ self._load_freebase_dataset()
98
+
99
+ def _load_freebase_dataset(self):
100
+ """
101
+ Load and index the FreebaseQA dataset.
102
+
103
+ This method fetches the FreebaseQA dataset from the specified URL, processes it, and
104
+ loads it into the BM25 corpus.
105
+ """
106
+ corpus: Dict[str, str] = {}
107
+ response = requests.get("https://github.com/kelvin-jiang/FreebaseQA/raw/master/FreebaseQA-train.json")
108
+ data = response.json()
109
+ for qa in data["Questions"]:
110
+ q = qa["ProcessedQuestion"]
111
+ a = qa["Parses"][0]["Answers"][0]["AnswersName"][0]
112
+ corpus[q] = a
113
+ self.load_corpus(corpus)
@@ -0,0 +1,179 @@
1
+ from typing import List, Tuple, Union, Optional, Dict
2
+
3
+ from json_database import JsonStorageXDG
4
+ from ovos_utils.log import LOG
5
+ from ovos_utils.parse import MatchStrategy, fuzzy_match, match_all
6
+
7
+ from ovos_bm25_solver import BM25CorpusSolver
8
+ from ovos_plugin_manager.templates.embeddings import EmbeddingsDB, TextEmbeddingsStore
9
+
10
+
11
+ class JsonEmbeddingsDB(EmbeddingsDB):
12
+ """An implementation of EmbeddingsDB using JSON-based storage for embeddings."""
13
+
14
+ def __init__(self, path: str):
15
+ """Initialize the JsonEmbeddingsDB.
16
+
17
+ Args:
18
+ path (str): The path to the JSON storage.
19
+ """
20
+ super().__init__()
21
+ self.corpus = JsonStorageXDG(name=path, subfolder="json_fake_embeddings")
22
+ LOG.debug(f"JsonEmbeddingsDB index path: {self.corpus.path}")
23
+
24
+ @property
25
+ def documents(self) -> List[str]:
26
+ """List of document keys in the database.
27
+
28
+ Returns:
29
+ List[str]: A list of document keys.
30
+ """
31
+ return list(self.corpus.keys())
32
+
33
+ def add_embeddings(self, key: str, _, metadata: Optional[Dict[str, any]] = None) -> str:
34
+ """Add or update an embedding in the database.
35
+
36
+ Args:
37
+ key (str): The unique key for the embedding.
38
+ metadata (Optional[Dict[str, any]]): Optional metadata associated with the embedding.
39
+
40
+ Returns:
41
+ str: The key of the added or updated embedding.
42
+ """
43
+ self.corpus[key] = metadata or {}
44
+ self.corpus.store()
45
+ return key
46
+
47
+ def delete_embedding(self, key: str) -> Optional[Dict[str, any]]:
48
+ """Delete an embedding from the database.
49
+
50
+ Args:
51
+ key (str): The unique key for the embedding to delete.
52
+
53
+ Returns:
54
+ Optional[Dict[str, any]]: The metadata of the deleted embedding, or None if the key was not found.
55
+ """
56
+ if key in self.corpus:
57
+ return self.corpus.pop(key)
58
+
59
+ def get_embeddings(self, key: str) -> Optional[Dict[str, any]]:
60
+ """Retrieve an embedding from the database.
61
+
62
+ Args:
63
+ key (str): The unique key for the embedding to retrieve.
64
+
65
+ Returns:
66
+ Optional[Dict[str, any]]: The metadata of the embedding, or None if the key was not found.
67
+ """
68
+ return self.corpus.get(key)
69
+
70
+ def query(self, embedding: str, top_k: int = 5, return_metadata: bool = False) -> List[Tuple[str, float]]:
71
+ """Query the database for the closest embeddings to the given query embedding.
72
+
73
+ Args:
74
+ embedding (str): The query embedding to match.
75
+ top_k (int, optional): The number of top results to return. Defaults to 5.
76
+ return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False.
77
+
78
+ Returns:
79
+ List[Tuple[str, float]]: A list of tuples containing the matched document and the similarity score.
80
+ """
81
+ matches: List[Tuple[str, float]] = match_all(embedding, self.documents,
82
+ strategy=MatchStrategy.DAMERAU_LEVENSHTEIN_SIMILARITY)[:top_k]
83
+ if return_metadata:
84
+ return [(k, v, self.get_embeddings(k)) for k, v in matches]
85
+ return matches
86
+
87
+
88
+ class BM25TextEmbeddingsStore(TextEmbeddingsStore):
89
+ """A text embeddings store using BM25 for document retrieval."""
90
+
91
+ def __init__(self, db: Union[EmbeddingsDB, str]):
92
+ """Initialize the BM25TextEmbeddingsStore.
93
+
94
+ Args:
95
+ db (Union[EmbeddingsDB, str]): An instance of EmbeddingsDB or a path to initialize JsonEmbeddingsDB.
96
+
97
+ Raises:
98
+ ValueError: If `db` is not an instance of JsonEmbeddingsDB.
99
+ """
100
+ if isinstance(db, str):
101
+ db = JsonEmbeddingsDB(path=db)
102
+ super().__init__(db)
103
+ if not isinstance(self.db, JsonEmbeddingsDB):
104
+ raise ValueError("'db' should be a JsonEmbeddingsDB instance")
105
+
106
+ def get_text_embeddings(self, text: str) -> str:
107
+ """Convert text to its corresponding embeddings.
108
+
109
+ Args:
110
+ text (str): The input text to be converted.
111
+
112
+ Returns:
113
+ str: The text itself (no transformation applied in this implementation).
114
+ """
115
+ return text
116
+
117
+ def query(self, document: str, top_k: int = 5, return_metadata: bool = False) -> List[Tuple[str, float, Optional[Dict[str, any]]]]:
118
+ """Query the database for the top_k closest documents to the given document.
119
+
120
+ Args:
121
+ document (str): The document to query.
122
+ top_k (int, optional): The number of top results to return. Defaults to 5.
123
+ return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False.
124
+
125
+ Returns:
126
+ List[Tuple[str, float, Optional[Dict[str, any]]]]: A list of tuples containing the document, score, and optionally metadata.
127
+ """
128
+
129
+ bm25 = BM25CorpusSolver()
130
+ bm25.load_corpus(self.db.documents)
131
+ if return_metadata:
132
+ return [(txt, conf, self.db.get_embeddings(txt)) for conf, txt in
133
+ bm25.retrieve_from_corpus(document, k=top_k)]
134
+ return [(txt, conf) for conf, txt in
135
+ bm25.retrieve_from_corpus(document, k=top_k)]
136
+
137
+ def distance(self, text_a: str, text_b: str,
138
+ metric: MatchStrategy = MatchStrategy.DAMERAU_LEVENSHTEIN_SIMILARITY) -> float:
139
+ """Calculate the distance between two texts using a specified metric.
140
+
141
+ Args:
142
+ text_a (str): The first text.
143
+ text_b (str): The second text.
144
+ metric (MatchStrategy): The match strategy to use. Defaults to DAMERAU_LEVENSHTEIN_SIMILARITY.
145
+
146
+ Returns:
147
+ float: The calculated distance between the texts.
148
+
149
+ Raises:
150
+ ValueError: If `metric` is not an instance of MatchStrategy.
151
+ """
152
+ if not isinstance(metric, MatchStrategy):
153
+ raise ValueError("'metric' must be a MatchStrategy for BM25 index")
154
+ return fuzzy_match(text_a, text_b, strategy=metric)
155
+
156
+
157
+ if __name__ == "__main__":
158
+ LOG.set_level("DEBUG")
159
+
160
+ # Initialize the database
161
+ db = JsonEmbeddingsDB("bm25_index")
162
+ index = BM25TextEmbeddingsStore(db=db)
163
+
164
+ # Add documents
165
+ text = "hello world"
166
+ text2 = "goodbye cruel world"
167
+ index.add_document(text)
168
+ index.add_document(text2)
169
+
170
+ # Query with fuzzy match
171
+ results = db.query("the world", top_k=2)
172
+ print(results)
173
+
174
+ # Query with BM25
175
+ results = index.query("the world", top_k=2)
176
+ print(results)
177
+
178
+ # Compare strings via fuzzy match - DAMERAU_LEVENSHTEIN_SIMILARITY
179
+ print(index.distance(text, text2))
@@ -0,0 +1,6 @@
1
+ # START_VERSION_BLOCK
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 0
4
+ VERSION_BUILD = 0
5
+ VERSION_ALPHA = 0
6
+ # END_VERSION_BLOCK
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.1
2
+ Name: ovos-solver-bm25-plugin
3
+ Version: 0.0.0
4
+ Summary: A question solver plugin for OVOS
5
+ Home-page: https://github.com/TigreGotico/ovos-solver-BM25-plugin
6
+ Author: jarbasai
7
+ Author-email: jarbasai@mailfence.com
8
+ License: MIT
9
+ Keywords: OVOS openvoiceos plugin utterance fallback query
10
+ Platform: UNKNOWN
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: ovos-plugin-manager
13
+ Requires-Dist: bm25s
14
+ Requires-Dist: quebra-frases
15
+ Requires-Dist: rapidfuzz
16
+ Requires-Dist: json-database
17
+
18
+ # BM25CorpusSolver Plugin
19
+
20
+ BM25CorpusSolver is an OVOS (OpenVoiceOS) plugin designed to retrieve answers from a corpus of documents using the [BM25](https://en.wikipedia.org/wiki/Okapi_BM25)
21
+ algorithm. This solver is ideal for question-answering systems that require efficient and accurate retrieval of
22
+ information from a predefined set of documents.
23
+
24
+ - [Features](#features)
25
+ - [Retrieval Chatbots](#retrieval-chatbots)
26
+ - [Example Solvers](#example-solvers)
27
+ - [SquadQASolver](#squadqasolver)
28
+ - [FreebaseQASolver](#freebaseqasolver)
29
+ - [Implementing a Retrieval Chatbot](#implementing-a-retrieval-chatbot)
30
+ - [BM25CorpusSolver](#bm25corpussolver)
31
+ - [BM25QACorpusSolver](#bm25qacorpussolver)
32
+ - [Limitations of Retrieval Chatbots](#limitations-of-retrieval-chatbots)
33
+ - [ReRanking](#reranking)
34
+ - [BM25MultipleChoiceSolver](#bm25multiplechoicesolver)
35
+ - [BM25EvidenceSolverPlugin](#bm25evidencesolverplugin)
36
+ - [Embeddings Store](#embeddings-store)
37
+ - [Integrating with Persona Framework](#integrating-with-persona-framework)
38
+
39
+
40
+ ## Features
41
+
42
+ - **BM25 Algorithm**: Utilizes the BM25 ranking function for information retrieval, providing relevance-based document scoring.
43
+ - **Configurable**: Allows customization of language, minimum confidence score, and the number of answers to retrieve.
44
+ - **Logging**: Integrates with OVOS logging system for debugging and monitoring.
45
+ - **BM25QACorpusSolver**: Extends `BM25CorpusSolver` to handle question-answer pairs, optimizing the retrieval process for QA datasets.
46
+ - **BM25MultipleChoiceSolver**: Reranks multiple-choice options based on relevance to the query.
47
+ - **BM25EvidenceSolverPlugin**: Extracts the best sentence from a text that answers a question using the BM25 algorithm.
48
+
49
+ ## Retrieval Chatbots
50
+
51
+ Retrieval chatbots use BM25CorpusSolver to provide answers to user queries by searching through a preloaded corpus of documents or QA pairs.
52
+
53
+ These chatbots excel in environments where the information is structured and the queries are straightforward.
54
+
55
+ ### Example solvers
56
+
57
+ #### SquadQASolver
58
+
59
+ The SquadQASolver is a subclass of BM25QACorpusSolver that automatically loads and indexes the [SQuAD dataset](https://rajpurkar.github.io/SQuAD-explorer/) upon initialization.
60
+
61
+ This solver is suitable for usage with the ovos-persona framework.
62
+
63
+ ```python
64
+ from ovos_bm25_solver import SquadQASolver
65
+
66
+ s = SquadQASolver()
67
+ query = "is there life on mars"
68
+ print("Query:", query)
69
+ print("Answer:", s.spoken_answer(query))
70
+ # 2024-07-19 22:31:12.625 - OVOS - __main__:load_corpus:60 - DEBUG - indexed 86769 documents
71
+ # 2024-07-19 22:31:12.625 - OVOS - __main__:load_squad_corpus:119 - INFO - Loaded and indexed 86769 question-answer pairs from SQuAD dataset
72
+ # Query: is there life on mars
73
+ # 2024-07-19 22:31:12.628 - OVOS - __main__:retrieve_from_corpus:69 - DEBUG - Rank 1 (score: 6.334013938903809): How is it postulated that Mars life might have evolved?
74
+ # 2024-07-19 22:31:12.628 - OVOS - __main__:retrieve_from_corpus:93 - DEBUG - closest question in corpus: How is it postulated that Mars life might have evolved?
75
+ # Answer: similar to Antarctic
76
+ ```
77
+
78
+ #### FreebaseQASolver
79
+
80
+ The FreebaseQASolver is a subclass of BM25QACorpusSolver that automatically loads and indexes the [FreebaseQA dataset](https://github.com/kelvin-jiang/FreebaseQA) upon initialization.
81
+
82
+ This solver is suitable for usage with the ovos-persona framework.
83
+
84
+ ```python
85
+ from ovos_bm25_solver import FreebaseQASolver
86
+
87
+ s = FreebaseQASolver()
88
+ query = "What is the capital of France"
89
+ print("Query:", query)
90
+ print("Answer:", s.spoken_answer(query))
91
+ # 2024-07-19 22:31:09.468 - OVOS - __main__:load_corpus:60 - DEBUG - indexed 20357 documents
92
+ # Query: What is the capital of France
93
+ # 2024-07-19 22:31:09.468 - OVOS - __main__:retrieve_from_corpus:69 - DEBUG - Rank 1 (score: 5.996074199676514): what is the capital of france
94
+ # 2024-07-19 22:31:09.469 - OVOS - __main__:retrieve_from_corpus:93 - DEBUG - closest question in corpus: what is the capital of france
95
+ # Answer: paris
96
+ ```
97
+
98
+ ### Implementing a Retrieval Chatbot
99
+
100
+ To use the BM25CorpusSolver, you need to create an instance of the solver, load your corpus, and then query it.
101
+
102
+ #### BM25CorpusSolver
103
+
104
+ This class is meant to be used to create your own solvers with a dedicated corpus.
105
+
106
+ ```python
107
+ from ovos_bm25_solver import BM25CorpusSolver
108
+
109
+ config = {
110
+ "lang": "en-us",
111
+ "min_conf": 0.4,
112
+ "n_answer": 2
113
+ }
114
+ solver = BM25CorpusSolver(config)
115
+
116
+ corpus = [
117
+ "a cat is a feline and likes to purr",
118
+ "a dog is the human's best friend and loves to play",
119
+ "a bird is a beautiful animal that can fly",
120
+ "a fish is a creature that lives in water and swims",
121
+ ]
122
+ solver.load_corpus(corpus)
123
+
124
+ query = "does the fish purr like a cat?"
125
+ answer = solver.get_spoken_answer(query)
126
+ print(answer)
127
+
128
+ # Expected Output:
129
+ # 2024-07-19 20:03:29.979 - OVOS - ovos_plugin_manager.utils.config:get_plugin_config:40 - DEBUG - Loaded configuration: {'module': 'ovos-translate-plugin-server', 'lang': 'en-us'}
130
+ # 2024-07-19 20:03:30.024 - OVOS - __main__:load_corpus:28 - DEBUG - indexed 4 documents
131
+ # 2024-07-19 20:03:30.025 - OVOS - __main__:retrieve_from_corpus:37 - DEBUG - Rank 1 (score: 1.0584375858306885): a cat is a feline and likes to purr
132
+ # 2024-07-19 20:03:30.025 - OVOS - __main__:retrieve_from_corpus:37 - DEBUG - Rank 2 (score: 0.481589138507843): a fish is a creature that lives in water and swims
133
+ # a cat is a feline and likes to purr. a fish is a creature that lives in water and swims
134
+ ```
135
+
136
+ #### BM25QACorpusSolver
137
+
138
+ This class is meant to be used to create your own solvers with a dedicated corpus
139
+
140
+ BM25QACorpusSolver is an extension of BM25CorpusSolver, designed to work with question-answer pairs. It is particularly
141
+ useful when working with datasets like SQuAD, FreebaseQA, or similar QA datasets.
142
+
143
+ ```python
144
+ import requests
145
+ from ovos_bm25_solver import BM25QACorpusSolver
146
+
147
+ # Load SQuAD dataset
148
+ corpus = {}
149
+ data = requests.get("https://github.com/chrischute/squad/raw/master/data/train-v2.0.json").json()
150
+ for s in data["data"]:
151
+ for p in s["paragraphs"]:
152
+ for qa in p["qas"]:
153
+ if "question" in qa and qa["answers"]:
154
+ corpus[qa["question"]] = qa["answers"][0]["text"]
155
+
156
+ # Load FreebaseQA dataset
157
+ data = requests.get("https://github.com/kelvin-jiang/FreebaseQA/raw/master/FreebaseQA-train.json").json()
158
+ for qa in data["Questions"]:
159
+ q = qa["ProcessedQuestion"]
160
+ a = qa["Parses"][0]["Answers"][0]["AnswersName"][0]
161
+ corpus[q] = a
162
+
163
+ # Initialize BM25QACorpusSolver with config
164
+ config = {
165
+ "lang": "en-us",
166
+ "min_conf": 0.4,
167
+ "n_answer": 1
168
+ }
169
+ solver = BM25QACorpusSolver(config)
170
+ solver.load_corpus(corpus)
171
+
172
+ query = "is there life on mars?"
173
+ answer = solver.get_spoken_answer(query)
174
+ print("Query:", query)
175
+ print("Answer:", answer)
176
+
177
+ # Expected Output:
178
+ # 86769 qa pairs imports from squad dataset
179
+ # 20357 qa pairs imports from freebaseQA dataset
180
+ # 2024-07-19 21:49:31.360 - OVOS - ovos_plugin_manager.language:create:233 - INFO - Loaded the Language Translation plugin ovos-translate-plugin-server
181
+ # 2024-07-19 21:49:31.360 - OVOS - ovos_plugin_manager.utils.config:get_plugin_config:40 - DEBUG - Loaded configuration: {'module': 'ovos-translate-plugin-server', 'lang': 'en-us'}
182
+ # 2024-07-19 21:49:32.759 - OVOS - __main__:load_corpus:61 - DEBUG - indexed 107126 documents
183
+ # Query: is there life on mars
184
+ # 2024-07-19 21:49:32.760 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 1 (score: 6.037893295288086): How is it postulated that Mars life might have evolved?
185
+ # 2024-07-19 21:49:32.760 - OVOS - __main__:retrieve_from_corpus:94 - DEBUG - closest question in corpus: How is it postulated that Mars life might have evolved?
186
+ # Answer: similar to Antarctic
187
+ ```
188
+
189
+ In this example, BM25QACorpusSolver is used to load a large corpus of question-answer pairs from the SQuAD and
190
+ FreebaseQA datasets. The solver retrieves the best matching answer for the given query.
191
+
192
+ ### Limitations of Retrieval Chatbots
193
+
194
+ Retrieval chatbots, while powerful, have certain limitations. These include:
195
+
196
+ 1. **Dependence on Corpus Quality and Size**: The accuracy of a retrieval chatbot heavily relies on the quality and comprehensiveness of the underlying corpus. A limited or biased corpus can lead to inaccurate or irrelevant responses.
197
+ 2. **Static Knowledge Base**: Unlike generative models, retrieval chatbots can't generate new information or answers. They can only retrieve and rephrase content from the pre-existing corpus.
198
+ 3. **Contextual Understanding**: While advanced algorithms like BM25 can rank documents based on relevance, they may still struggle with understanding nuanced or complex queries, especially those requiring deep contextual understanding.
199
+ 4. **Scalability**: As the size of the corpus increases, the computational resources required for indexing and retrieving relevant documents also increase, potentially impacting performance.
200
+ 5. **Dynamic Updates**: Keeping the corpus updated with the latest information can be challenging, especially in fast-evolving domains.
201
+
202
+ Despite these limitations, retrieval chatbots are effective for domains where the corpus is well-defined and relatively static, such as FAQs, documentation, and knowledge bases.
203
+
204
+ ### ReRanking
205
+
206
+ ReRanking is a technique used to refine a list of potential answers by evaluating their relevance to a given query.
207
+ This process is crucial in scenarios where multiple options or responses need to be assessed to determine the most appropriate one.
208
+
209
+ In retrieval chatbots, ReRanking helps in selecting the best answer from a set of retrieved documents or options, enhancing the accuracy of the response provided to the user.
210
+
211
+ `MultipleChoiceSolver` are integrated into the OVOS Common Query framework, where they are used to select the most relevant answer from a set of multiple skill responses.
212
+
213
+ #### BM25MultipleChoiceSolver
214
+
215
+ BM25MultipleChoiceSolver is designed to select the best answer to a question from a list of options.
216
+
217
+ In the context of retrieval chatbots, BM25MultipleChoiceSolver is useful for scenarios where a user query results in a list of predefined answers or options.
218
+ The solver ranks these options based on their relevance to the query and selects the most suitable one.
219
+
220
+
221
+ ```python
222
+ from ovos_bm25_solver import BM25MultipleChoiceSolver
223
+
224
+ solver = BM25MultipleChoiceSolver()
225
+ a = solver.rerank("what is the speed of light", [
226
+ "very fast", "10m/s", "the speed of light is C"
227
+ ])
228
+ print(a)
229
+ # 2024-07-22 15:03:10.295 - OVOS - __main__:load_corpus:61 - DEBUG - indexed 3 documents
230
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 1 (score: 0.7198746800422668): the speed of light is C
231
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 2 (score: 0.0): 10m/s
232
+ # 2024-07-22 15:03:10.297 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 3 (score: 0.0): very fast
233
+ # [(0.7198747, 'the speed of light is C'), (0.0, '10m/s'), (0.0, 'very fast')]
234
+
235
+ # NOTE: select_answer is part of the MultipleChoiceSolver base class and uses rerank internally
236
+ a = solver.select_answer("what is the speed of light", [
237
+ "very fast", "10m/s", "the speed of light is C"
238
+ ])
239
+ print(a) # the speed of light is C
240
+ ```
241
+
242
+ #### BM25EvidenceSolverPlugin
243
+
244
+ BM25EvidenceSolverPlugin is designed to extract the most relevant sentence from a text passage that answers a given question. This plugin uses the BM25 algorithm to evaluate and rank sentences based on their relevance to the query.
245
+
246
+ In text extraction and machine comprehension tasks, BM25EvidenceSolverPlugin enables the identification of specific sentences within a larger body of text that directly address a user's query.
247
+
248
+ For example, in a scenario where a user queries about the number of rovers exploring Mars, BM25EvidenceSolverPlugin scans the provided text passage, ranks sentences based on their relevance, and extracts the most informative sentence.
249
+
250
+ ```python
251
+ from ovos_bm25_solver import BM25EvidenceSolverPlugin
252
+
253
+ config = {
254
+ "lang": "en-us",
255
+ "min_conf": 0.4,
256
+ "n_answer": 1
257
+ }
258
+ solver = BM25EvidenceSolverPlugin(config)
259
+
260
+ text = """Mars is the fourth planet from the Sun. It is a dusty, cold, desert world with a very thin atmosphere.
261
+ Mars is also a dynamic planet with seasons, polar ice caps, canyons, extinct volcanoes, and evidence that it was even more active in the past.
262
+ Mars is one of the most explored bodies in our solar system, and it's the only planet where we've sent rovers to roam the alien landscape.
263
+ NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
264
+ """
265
+ query = "how many rovers are currently exploring Mars"
266
+ answer = solver.get_best_passage(evidence=text, question=query)
267
+ print("Query:", query)
268
+ print("Answer:", answer)
269
+ # 2024-07-22 15:05:14.209 - OVOS - __main__:load_corpus:61 - DEBUG - indexed 5 documents
270
+ # 2024-07-22 15:05:14.209 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 1 (score: 1.39238703250885): NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
271
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 2 (score: 0.38667747378349304): Mars is one of the most explored bodies in our solar system, and it's the only planet where we've sent rovers to roam the alien landscape.
272
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 3 (score: 0.15732118487358093): Mars is the fourth planet from the Sun.
273
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 4 (score: 0.10177625715732574): Mars is also a dynamic planet with seasons, polar ice caps, canyons, extinct volcanoes, and evidence that it was even more active in the past.
274
+ # 2024-07-22 15:05:14.210 - OVOS - __main__:retrieve_from_corpus:70 - DEBUG - Rank 5 (score: 0.0): It is a dusty, cold, desert world with a very thin atmosphere.
275
+ # Query: how many rovers are currently exploring Mars
276
+ # Answer: NASA currently has two rovers (Curiosity and Perseverance), one lander (InSight), and one helicopter (Ingenuity) exploring the surface of Mars.
277
+
278
+ ```
279
+
280
+ In this example, `BM25EvidenceSolverPlugin` effectively identifies and retrieves the most relevant sentence from the provided text that answers the query about the number of rovers exploring Mars.
281
+ This capability is essential for applications requiring information extraction from extensive textual content, such as automated research assistants or content summarizers.
282
+
283
+ ## Embeddings Store
284
+
285
+ A fake embeddings store is provided using only text search
286
+
287
+ > NOTE: this does not scale to large datasets
288
+
289
+ ```python
290
+ from ovos_bm25_solver.embed import JsonEmbeddingsDB, BM25TextEmbeddingsStore
291
+ db = JsonEmbeddingsDB("bm25_index")
292
+ # Initialize the BM25 text embeddings store
293
+ index = BM25TextEmbeddingsStore(db=db)
294
+
295
+ # Add documents to the database
296
+ text = "hello world"
297
+ text2 = "goodbye cruel world"
298
+ index.add_document(text)
299
+ index.add_document(text2)
300
+
301
+ # Querying with fuzzy match
302
+ results = db.query("the world", top_k=2)
303
+ print("Fuzzy Match Results:", results)
304
+
305
+ # Querying with BM25
306
+ results = index.query("the world", top_k=2)
307
+ print("BM25 Query Results:", results)
308
+
309
+ # Comparing strings using fuzzy match
310
+ distance = index.distance(text, text2)
311
+ print("Distance between strings:", distance)
312
+ ```
313
+
314
+ ## Integrating with Persona Framework
315
+
316
+ To use the `SquadQASolver` and `FreebaseQASolver` in the persona framework, you can define a persona configuration file and specify the solvers to be used.
317
+
318
+ Here's an example of how to define a persona that uses the `SquadQASolver` and `FreebaseQASolver`:
319
+
320
+ 1. Create a persona configuration file, e.g., `qa_persona.json`:
321
+
322
+ ```json
323
+ {
324
+ "name": "QAPersona",
325
+ "solvers": [
326
+ "ovos-solver-squadqa-plugin",
327
+ "ovos-solver-freebaseqa-plugin",
328
+ "ovos-solver-failure-plugin"
329
+ ]
330
+ }
331
+ ```
332
+
333
+ 2. Run [ovos-persona-server](https://github.com/OpenVoiceOS/ovos-persona-server) with the defined persona:
334
+
335
+ ```bash
336
+ $ ovos-persona-server --persona qa_persona.json
337
+ ```
338
+
339
+ In this example, the persona named "QAPersona" will first use the `SquadQASolver` to answer questions. If it cannot find an answer, it will fall back to the `FreebaseQASolver`. Finally, it will use the `ovos-solver-failure-plugin` to ensure it always responds with something, even if the previous solvers fail.
340
+
341
+
342
+ Check setup.py for reference in how to package your own corpus backed solvers
343
+
344
+ ```python
345
+ PLUGIN_ENTRY_POINTS = [
346
+ 'ovos-solver-bm25-squad-plugin=ovos_bm25_solver:SquadQASolver',
347
+ 'ovos-solver-bm25-freebase-plugin=ovos_bm25_solver:FreebaseQASolver'
348
+ ]
349
+ ```
350
+
351
+
@@ -0,0 +1,12 @@
1
+ ovos_bm25_solver/__init__.py,sha256=U9Qw5Q3f-jG3fKIatniLiPF78oA9nEH-eqO8Ub0je24,309
2
+ ovos_bm25_solver/base.py,sha256=dt5FPp1V_dVVlwmz_3xYRSX1l5BUU8jI9mgH6sARqGs,9175
3
+ ovos_bm25_solver/corpus.py,sha256=MDIj10AkLolYVZfw2pYlHsHZjWAOiytujO1Zpp1N8x0,5507
4
+ ovos_bm25_solver/demo.py,sha256=1adK3M345CYYGXKHUUPZiz0VJ_XSJy6_qWKqXBeLy2I,4696
5
+ ovos_bm25_solver/embed.py,sha256=zsWyr27q86os0pq9JIo-9AySrEYthlv6Vc7CK9B9i7k,6656
6
+ ovos_bm25_solver/version.py,sha256=peYHqopxd1W3l3PTIN2GzerAUX8Su-_tfbPZbZ4og3o,114
7
+ ovos_solver_bm25_plugin-0.0.0.dist-info/METADATA,sha256=IHPHrZ2NEQSNM7cm2tBCFi5JMqrVafelkZxgr15ngj0,17107
8
+ ovos_solver_bm25_plugin-0.0.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
9
+ ovos_solver_bm25_plugin-0.0.0.dist-info/entry_points.txt,sha256=FKbCt9E6gO06WPMtZuUq9GOJunYk3NGEvkaCUS_itcM,558
10
+ ovos_solver_bm25_plugin-0.0.0.dist-info/top_level.txt,sha256=AVGCOfJCv2a9Vyo7dbgszHSjM_hLnwXaUcX9Ox1srrI,17
11
+ ovos_solver_bm25_plugin-0.0.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
12
+ ovos_solver_bm25_plugin-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.44.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,16 @@
1
+ [neon.plugin.solver]
2
+ ovos-solver-bm25-freebase-plugin = ovos_bm25_solver.demo:BM25FreebaseQASolver
3
+ ovos-solver-bm25-squad-plugin = ovos_bm25_solver.demo:BM25SquadQASolver
4
+
5
+ [opm.embeddings]
6
+ ovos-json-embeddings-plugin=ovos_bm25_solver.embed:JsonEmbeddingsDB
7
+
8
+ [opm.embeddings.text]
9
+ ovos-bm25-embeddings-plugin=ovos_bm25_solver.embed:BM25TextEmbeddingsStore
10
+
11
+ [opm.solver.multiple_choice]
12
+ ovos-choice-solver-bm25=ovos_bm25_solver.base:BM25MultipleChoiceSolver
13
+
14
+ [opm.solver.reading_comprehension]
15
+ ovos-evidence-solver-bm25=ovos_bm25_solver.base:BM25SolverPlugin
16
+
@@ -0,0 +1 @@
1
+ ovos_bm25_solver