Skip to content

Commit e14466e

Browse files
JarbasAlclaude
andcommitted
feat: emit INTENT-4 template registrations in the intent runner
The runner's _register_templates() only emitted the legacy padatious:register_intent topic. kw-template-matcher (from 0.1.6a1) also binds the OVOS-INTENT-4 §6 spec topic ovos.intent.register.template and keys templates learned from it as "skill_id:intent_name" — the same skill-prefixed form m2v-style fighters return in IntentHandlerMatch.match_type. The runner never emitted that topic, so its own transformer slot lookup (always keyed by the bare, normalised intent id) could only ever hit the legacy-topic registration. Emit ovos.intent.register.template alongside the legacy topic (mirrored onto the transformer bus, matching ovos-workshop's own dual-emit in Skill.register_template()), and make _transform_slots try both the bare and the "arena:<intent_id>" skill-prefixed lookup keys so a transformer that only recognises the spec-form key still fills slots. Both registration messages carry context={"skill_id": "arena"}: OVOS-INTENT-4 §3.1 engines key registration on message.context, not message.data. Adds the floor pin keyword-template-matcher>=0.1.6a1 to the test extra. Fail-before: with only the source change reverted, test_register_templates_emits_spec_topic and test_transform_slots_falls_back_to_skill_prefixed_key fail (2 failed, 23 passed in tests/test_intent_pipeline.py); with only the context argument reverted, test_register_templates_emits_spec_topic fails with KeyError: 'skill_id' on seen[0].context. All 25 pass with the fix. The 22 failures elsewhere in the suite fail identically on origin/dev in the same venv (missing `datasets` and similar), unchanged by this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent e633a7b commit e14466e

3 files changed

Lines changed: 104 additions & 20 deletions

File tree

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ test = [
9090
# test_intent_pipeline.py exercises the typed-slot stripping choke
9191
# point directly, so the test extra needs it self-sufficiently too.
9292
"ovos-spec-tools>=1.11.0a1",
93+
# The intent-transformer tests (tests/test_intent_pipeline.py::
94+
# TestIntentTransformers) exercise the real kw-template-matcher plugin
95+
# against both the legacy padatious:register_intent topic and the
96+
# OVOS-INTENT-4 ovos.intent.register.template spec topic; 0.1.6a1 is
97+
# the floor that binds the spec topic.
98+
"keyword-template-matcher>=0.1.6a1",
9399
]
94100
lint = [
95101
"ruff>=0.6",

runner/intent_pipeline.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -306,15 +306,38 @@ def _register_templates(self, bus, rows: list[dict], extra_bus=None) -> None:
306306
"samples": samples,
307307
"lang": self.lang,
308308
"skill_id": "arena",
309-
})
309+
}, {"skill_id": "arena"})
310310
bus.emit(msg)
311+
# OVOS-INTENT-4 §6 spec topic — ovos-workshop's Skill.register_
312+
# template() dual-emits this alongside the legacy padatious
313+
# topic (see ovos_workshop.intents.PadatiousIntentContainer.
314+
# register_template), and kw-template-matcher >=0.1.6a1 keys
315+
# templates registered from it as "skill_id:intent_name"
316+
# (kw_template_matcher.opm.KeywordTemplateMatcher.
317+
# handle_register_template) rather than the bare name the
318+
# legacy topic uses. Emitting both lets the transformer serve
319+
# both a legacy-form and a spec-form (m2v-style, skill-
320+
# prefixed) match_type; kw-template-matcher de-duplicates a
321+
# template set registered twice under different topics for
322+
# the same (lang, key), so this is not a double-count.
323+
spec_msg = Message("ovos.intent.register.template", {
324+
"skill_id": "arena",
325+
"intent_name": intent_id,
326+
"samples": samples,
327+
"lang": self.lang,
328+
"blacklist": [],
329+
"slot_blacklist": {},
330+
}, {"skill_id": "arena"})
331+
bus.emit(spec_msg)
311332
# Mirror onto the intent-transformer bus (if configured) so
312333
# transformers that learn from registration traffic — e.g.
313-
# kw-template-matcher's ``padatious:register_intent`` listener —
314-
# see the same templates every pipeline plugin trains on,
315-
# regardless of which stage ends up firing at predict() time.
334+
# kw-template-matcher's ``padatious:register_intent`` and
335+
# ``ovos.intent.register.template`` listeners — see the same
336+
# templates every pipeline plugin trains on, regardless of
337+
# which stage ends up firing at predict() time.
316338
if extra_bus is not None:
317339
extra_bus.emit(msg)
340+
extra_bus.emit(spec_msg)
318341

319342
# Entities registered once per name — merged example values across
320343
# intents (some engines raise on re-registration)
@@ -405,26 +428,35 @@ def _transform_slots(
405428
"""Run the winning match through the configured intent-transformer
406429
chain and return any slot keys it added.
407430
408-
Constructed with the normalised ``intent_id`` (not the engine's raw,
409-
possibly skill-prefixed ``match_type``) so it lines up with the name
410-
transformers like kw-template-matcher learned from
411-
``padatious:register_intent`` (see ``_register_templates``).
431+
Transformers like kw-template-matcher key what they learned by the
432+
topic the templates arrived on: the legacy ``padatious:
433+
register_intent`` topic keys by the bare intent name, while the
434+
spec ``ovos.intent.register.template`` topic keys by the skill-
435+
prefixed ``"arena:<intent_id>"`` form used by ``IntentHandlerMatch.
436+
match_type`` for m2v-style fighters (see ``_register_templates``).
437+
Try both forms and keep whichever one the transformer actually
438+
recognised.
412439
"""
413440
from ovos_plugin_manager.templates.pipeline import IntentHandlerMatch
414441

415442
before = set(match_data.keys())
416-
handler_match = IntentHandlerMatch(
417-
match_type=intent_id,
418-
match_data=dict(match_data),
419-
skill_id="arena",
420-
utterance=utterance,
421-
)
422-
transformed = self._xformers.transform(handler_match)
423-
tdata = transformed.match_data if isinstance(transformed.match_data, dict) else {}
424-
return {
425-
k: v for k, v in tdata.items()
426-
if k not in before and k not in _META_KEYS and isinstance(v, str)
427-
}
443+
for match_type in (intent_id, f"arena:{intent_id}"):
444+
handler_match = IntentHandlerMatch(
445+
match_type=match_type,
446+
match_data=dict(match_data),
447+
skill_id="arena",
448+
utterance=utterance,
449+
)
450+
transformed = self._xformers.transform(handler_match)
451+
tdata = (transformed.match_data
452+
if isinstance(transformed.match_data, dict) else {})
453+
new_slots = {
454+
k: v for k, v in tdata.items()
455+
if k not in before and k not in _META_KEYS and isinstance(v, str)
456+
}
457+
if new_slots:
458+
return new_slots
459+
return {}
428460

429461
@staticmethod
430462
def _normalise(match_type: str) -> str:

tests/test_intent_pipeline.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,52 @@ def test_slots_filled_with_transformer_configured(self):
260260
assert stage == "toy-label-pipeline-high"
261261
assert slots == {"song": "africa"}
262262

263+
def test_register_templates_emits_spec_topic(self):
264+
# OVOS-INTENT-4 §6: ovos-workshop's Skill.register_template()
265+
# dual-emits padatious:register_intent (legacy) and
266+
# ovos.intent.register.template (spec) — the runner's own
267+
# registration must do the same so transformers that only bind
268+
# the spec topic (or key templates by its skill-prefixed form,
269+
# like kw-template-matcher) still see the training data.
270+
pipeline = _toy_pipeline(with_transformer=True)
271+
seen = []
272+
pipeline.xformer_bus.on(
273+
"ovos.intent.register.template", lambda m: seen.append(m)
274+
)
275+
276+
pipeline.train(TRAIN_ROWS)
277+
278+
assert len(seen) == 1
279+
assert seen[0].context["skill_id"] == "arena"
280+
assert seen[0].data["skill_id"] == "arena"
281+
assert seen[0].data["intent_name"] == "play_song"
282+
assert seen[0].data["lang"] == "en-us"
283+
assert "play africa" in seen[0].data["samples"]
284+
285+
def test_transform_slots_falls_back_to_skill_prefixed_key(self):
286+
# kw-template-matcher keys templates it learned from the spec
287+
# topic as "skill_id:intent_name" (m2v's IntentHandlerMatch.
288+
# match_type convention) rather than the bare name the legacy
289+
# padatious topic uses. Register a template ONLY via the spec
290+
# topic (bypassing train()'s legacy emission) and confirm the
291+
# runner's lookup still finds it by trying the skill-prefixed
292+
# form after the bare form misses.
293+
from ovos_bus_client.message import Message
294+
295+
pipeline = _toy_pipeline(with_transformer=True)
296+
pipeline.xformer_bus.emit(Message("ovos.intent.register.template", {
297+
"skill_id": "arena",
298+
"intent_name": "spec_only_intent",
299+
"samples": ["play {song}"],
300+
"lang": "en-us",
301+
}))
302+
303+
slots = pipeline._transform_slots(
304+
"spec_only_intent", {"conf": 1.0}, "play africa"
305+
)
306+
307+
assert slots == {"song": "africa"}
308+
263309
def test_no_slots_without_transformer_configured(self):
264310
pipeline = _toy_pipeline(with_transformer=False)
265311
pipeline.train(TRAIN_ROWS)

0 commit comments

Comments
 (0)