def do_intents_to_graph(intents, slot_names, targets): sentences, replacements = ini_jsgf.split_rules(intents) # Load slot values for slot_name in slot_names: slot_path = slots_dir / slot_name assert slot_path.is_file(), f"Missing slot file at {slot_path}" # Parse each non-empty line as a JSGF sentence slot_values = [] with open(slot_path, "r") as slot_file: for line in slot_file: line = line.strip() if line: sentence = jsgf.Sentence.parse(line) slot_values.append(sentence) # Replace $slot with sentences replacements[f"${slot_name}"] = slot_values if profile.get("intent.replace_numbers", True): # Replace numbers in parsed sentences for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, number_transform, replacements) # Convert to directed graph graph = intents_to_graph(intents, replacements) # Write graph to JSON file json_graph = graph_to_json(graph) with open(targets[0], "w") as graph_file: json.dump(json_graph, graph_file)
def task_ini_graph(): """sentences.ini -> intent.json""" sentences, replacements = ini_jsgf.split_rules(intents) if profile.get("intent.replace_numbers", True): # Replace number ranges with rhasspy/number for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, number_range_transform, replacements) # Gather used slot names slot_names = set() for intent_name in intents: for item in intents[intent_name]: for slot_name in get_slot_names(item): slot_names.add(slot_name) # Load slot values has_slot_program = False for slot_key in slot_names: slot_info = find_slot(slot_key) if isinstance(slot_info, StaticSlotInfo): # Parse each non-empty line as a JSGF sentence slot_values = [] with open(slot_info.path, "r") as slot_file: for line in slot_file: line = line.strip() if line: sentence = jsgf.Sentence.parse(line) if word_transform: jsgf.walk_expression(sentence, fix_word_case) slot_values.append(sentence) elif isinstance(slot_info, SlotProgramInfo): # Program that will generate values has_slot_program = True slot_values = SlotProgram(slot_info.path, command_args=slot_info.args) # Replace $slot with sentences replacements[f"${slot_key}"] = slot_values # Add slot files as dependencies deps = [find_slot(slot_key).path for slot_key in slot_names] # Add profile itself as a dependency profile_json_path = profile_dir / "profile.json" if profile_json_path.is_file(): deps.append(profile_json_path) return { "file_dep": ini_paths + deps, "targets": [intent_graph], "actions": [(do_intents_to_graph, [sentences, slot_names, replacements])], "uptodate": [False if has_slot_program else None], }
def do_intents_to_graph(sentences, slot_names, replacements, targets): # Replace actual numbers if profile.get("intent.replace_numbers", True): # Replace numbers in parsed sentences for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, number_transform, replacements) # Determine whether word casing has to be fixed transform = None if word_casing == "upper": transform = str.upper elif word_casing == "lower": transform = str.lower if transform: def fix_case(word): if isinstance(word, jsgf.Word): word.text = transform(word.text) return word # Fix casing for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, fix_case, replacements) # Convert to directed graph graph = intents_to_graph(sentences, replacements) # Write graph to JSON file json_graph = graph_to_json(graph) with open(targets[0], "w") as graph_file: json.dump(json_graph, graph_file)
def test_walk(self): """Test Expression.walk with rule and slot reference.""" ini_text = """ [SetAlarm] minutes = $minute minutes set alarm for <minutes> """ intents = parse_ini(ini_text) sentences, replacements = split_rules(intents) replacements["$minute"] = [Sentence.parse("2 | 3")] def num2words(word): if not isinstance(word, Word): return try: n = int(word.text) if n == 2: word.text = "two" word.substitution = "2" elif n == 3: word.text = "three" word.substitution = "3" except ValueError: pass for s in sentences["SetAlarm"]: walk_expression(s, num2words, replacements) # Verify minute digits were replaced minute = replacements["$minute"][0] self.assertEqual( minute, Sentence( text="2 | 3", type=SequenceType.GROUP, items=[ Sequence( text="2 | 3", type=SequenceType.ALTERNATIVE, items=[ Word("two", substitution="2"), Word("three", substitution="3"), ], ) ], ), )
def test_walk(self): """Test walk_expression.""" s = Sentence.parse("set alarm for (2 | 3) minutes") def num2words(word): if not isinstance(word, Word): return try: n = int(word.text) if n == 2: word.text = "two" word.substitution = "2" elif n == 3: word.text = "three" word.substitution = "3" except ValueError: pass walk_expression(s, num2words) self.assertEqual( s.items, [ Word("set"), Word("alarm"), Word("for"), Sequence( text="2 | 3", type=SequenceType.GROUP, items=[ Sequence( text="2 | 3", type=SequenceType.ALTERNATIVE, items=[ Word("two", substitution="2"), Word("three", substitution="3"), ], ) ], ), Word("minutes"), ], )
def test_rule_with_same_name(self): """Ensure referenced rule names don't conflict with local names.""" ini_text = """ [Test1] rule = is a test this <rule> [Test2] rule = <Test1.rule> this <rule> """ _intents = parse_ini(ini_text) intents, replacements = split_rules(_intents) # Walk all sentences to make sure there's no infinite recursion for intent, sentences in intents.items(): for sentence in sentences: walk_expression(sentence, lambda x: x, replacements)
def do_intents_to_graph(sentences, slot_names, replacements, targets): # Replace actual numbers if profile.get("intent.replace_numbers", True): # Replace numbers in parsed sentences for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, number_transform, replacements) if word_transform: # Fix casing for intent_sentences in sentences.values(): for sentence in intent_sentences: jsgf.walk_expression(sentence, fix_word_case, replacements) # Convert to directed graph graph = intents_to_graph(sentences, replacements) # Write graph to JSON file json_graph = graph_to_json(graph) with open(targets[0], "w") as graph_file: json.dump(json_graph, graph_file)
def test_walk_multiple_words(self): """Test walk_expression with a multi-word replacement.""" s = Sentence.parse("set alarm for 23 minutes") def num2words(word): if not isinstance(word, Word): return try: n = int(word.text) if n == 23: return Sequence( text="23", type=SequenceType.GROUP, items=[Word("twenty"), Word("three")], substitution="23", ) except ValueError: pass walk_expression(s, num2words) self.assertEqual( s.items, [ Word("set"), Word("alarm"), Word("for"), Sequence( text="23", type=SequenceType.GROUP, items=[Word("twenty"), Word("three")], substitution="23", ), Word("minutes"), ], )