Skip to content

Error handling #46

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

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions ovos_padatious/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def train(self, train_data):
tokens = set([token for sent in train_data.my_sents(self.name)
for token in sent if token.startswith('{')])
self.pos_intents = [PosIntent(i, self.name) for i in tokens]

self.simple_intent.train(train_data)
success = self.simple_intent.train(train_data)
for i in self.pos_intents:
i.train(train_data)
return success
8 changes: 8 additions & 0 deletions ovos_padatious/intent_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ def calc_intents(self, query: str, entity_manager) -> List[MatchData]:
List[MatchData]: A list of matches sorted by confidence.
"""
sent = tokenize(query)
if not self.objects:
return []
if len(self.objects) == 1:
try:
match = self.objects[0].match(sent, entity_manager).detokenize()
return [match]
except:
return []
Comment on lines +53 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Specify the exception type instead of using bare except.

The bare except clause could mask important errors. Consider catching specific exceptions that you expect might occur during matching.

Apply this diff to improve error handling:

     if len(self.objects) == 1:
         try:
             match = self.objects[0].match(sent, entity_manager).detokenize()
             return [match]
-        except:
+        except Exception as e:
+            LOG.error(f"Error matching single intent: {e}")
             return []

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.8.2)

57-57: Do not use bare except

(E722)


def match_intent(intent):
start_time = time.monotonic()
Expand Down
6 changes: 3 additions & 3 deletions ovos_padatious/opm.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class PadatiousPipeline(ConfidenceMatcherPipeline):

def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None,
config: Optional[Dict] = None,
engine_class: Optional[PadatiousEngine] = IntentContainer):
engine_class: Optional[PadatiousEngine] = None):

super().__init__(bus, config)
self.lock = RLock()
Expand All @@ -273,8 +273,8 @@ def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None,
self.conf_med = self.config.get("conf_med") or 0.8
self.conf_low = self.config.get("conf_low") or 0.5

if engine_class is None and self.config.get("domain_engine"):
engine_class = DomainIntentContainer
engine_class = engine_class or DomainIntentContainer if self.config.get("domain_engine") else IntentContainer
LOG.info(f"Padatious class: {engine_class.__name__}")

self.remove_punct = self.config.get("cast_to_ascii", False)
use_stemmer = self.config.get("stem", False)
Expand Down
3 changes: 2 additions & 1 deletion ovos_padatious/pos_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def from_file(cls, prefix, token):
i.load(prefix)
return self

def train(self, train_data):
def train(self, train_data) -> bool:
for i in self.edges:
i.train(train_data)
return True
18 changes: 15 additions & 3 deletions ovos_padatious/simple_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os.path

from fann2 import libfann as fann
from ovos_utils.log import LOG
Expand Down Expand Up @@ -62,7 +63,7 @@ def configure_net(self):
self.net.set_train_stop_function(fann.STOPFUNC_BIT)
self.net.set_bit_fail_limit(0.1)

def train(self, train_data):
def train(self, train_data) -> bool:
for sent in train_data.my_sents(self.name):
self.ids.add_sent(sent)

Expand All @@ -72,6 +73,10 @@ def train(self, train_data):
n_pos = len(list(train_data.my_sents(self.name)))
n_neg = len(list(train_data.other_sents(self.name)))

if not n_neg or not n_pos:
LOG.error(f"not enough samples to learn intent: pos {n_pos} / neg {n_neg}")
return False

def add(vec, out):
inputs.append(self.vectorize(vec))
outputs.append([out])
Expand Down Expand Up @@ -126,15 +131,22 @@ def calc_weight(w): return pow(len(w), 3.0)
if self.net.get_bit_fail() == 0:
break
LOG.debug(f"Training {self.name} finished!")
return True

def save(self, prefix):
prefix += '.intent'
if not self.net:
raise RuntimeError(f"intent not yet trained! '{prefix}.net'")
if not prefix.endswith(".intent"):
prefix += '.intent'
self.net.save(str(prefix + '.net')) # Must have str()
self.ids.save(prefix)

@classmethod
def from_file(cls, name, prefix):
prefix += '.intent'
if not prefix.endswith(".intent"):
prefix += '.intent'
if not os.path.isfile(str(prefix + '.net')):
raise FileNotFoundError(f"intent not yet trained! '{prefix}.net'")
self = cls(name)
self.net = fann.neural_net()
if not self.net.create_from_file(str(prefix + '.net')): # Must have str()
Expand Down
2 changes: 1 addition & 1 deletion ovos_padatious/trainable.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def save_hash(self, prefix):
f.write(self.hash)

@abstractmethod
def train(self, data):
def train(self, data) -> bool:
pass

@abstractmethod
Expand Down
70 changes: 39 additions & 31 deletions ovos_padatious/training_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ def _train_and_save(obj: Trainable, cache: str, data: TrainData, print_updates:
data (TrainData): Training data.
print_updates (bool): Whether to print updates during training.
"""
obj.train(data)
obj.save(cache)
if print_updates:
LOG.debug(f'Saving {obj.name} to cache ({cache})')
if obj.train(data):
obj.save(cache)
if print_updates:
LOG.debug(f'Saving {obj.name} to cache ({cache})')
else:
LOG.debug(f'Failed to train {obj.name}')


class TrainingManager:
Expand Down Expand Up @@ -72,37 +74,43 @@ def add(self, name: str, lines: List[str], reload_cache: bool = False, must_trai
reload_cache (bool): Whether to force reload of cache if it exists.
must_train (bool): Whether training is required for the new intent/entity.
"""
hash_fn = join(self.cache, name + '.hash')
min_ver = splitext(ovos_padatious.__version__)[0]
if not isfile(hash_fn):
must_train = True

if not must_train:
LOG.debug(f"Loading {name} from intent cache")
self.objects.append(self.cls.from_file(name=name, folder=self.cache))
try: # .net file renamed/deleted for some reason
LOG.debug(f"Loading '{name}' from intent cache")
self.objects.append(self.cls.from_file(name=name, folder=self.cache))
except:
LOG.debug(f"Regenerating cache for intent: {name}")

# general case: load resource (entity or intent) to training queue
# or if no change occurred to memory data structures
old_hsh = None
new_hsh = lines_hash([min_ver] + lines)

if isfile(hash_fn):
with open(hash_fn, 'rb') as g:
old_hsh = g.read()
if old_hsh != new_hsh:
LOG.debug(f"{name} training data changed! retraining")
else:
hash_fn = join(self.cache, name + '.hash')
old_hsh = None
min_ver = splitext(ovos_padatious.__version__)[0]
new_hsh = lines_hash([min_ver] + lines)

if isfile(hash_fn):
with open(hash_fn, 'rb') as g:
old_hsh = g.read()
if old_hsh != new_hsh:
LOG.debug(f"{name} training data changed! retraining")
else:
LOG.debug(f"First time training '{name}")

retrain = reload_cache or old_hsh != new_hsh
if not retrain:
try:
LOG.debug(f"Loading {name} from intent cache")
self.objects.append(self.cls.from_file(name=name, folder=self.cache))
except Exception as e:
LOG.error(f"Failed to load intent from cache: {name} - {str(e)}")
retrain = True
if retrain:
LOG.debug(f"Queuing {name} for training")
self.objects_to_train.append(self.cls(name=name, hsh=new_hsh))
self.train_data.add_lines(name, lines)
LOG.debug(f"First time training '{name}'")

retrain = reload_cache or old_hsh != new_hsh
if not retrain:
try:
LOG.debug(f"Loading {name} from intent cache")
self.objects.append(self.cls.from_file(name=name, folder=self.cache))
except Exception as e:
LOG.error(f"Failed to load intent from cache: {name} - {str(e)}")
retrain = True
if retrain:
LOG.debug(f"Queuing {name} for training")
self.objects_to_train.append(self.cls(name=name, hsh=new_hsh))
self.train_data.add_lines(name, lines)

def load(self, name: str, file_name: str, reload_cache: bool = False) -> None:
"""
Expand Down
Loading