From f033ecf904c63831e3aeeebb93e6d8d8cea31724 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:35:29 -0700 Subject: [PATCH] fix: parse skill selection JSON with trailing braces Greedy \{.*\} matching dropped valid skill-selection objects when the LLM appended prose that contained braces. Use raw_decode from the first brace instead. --- .gitignore | 1 + openspace/skill_engine/registry.py | 10 +++++----- .../test_registry_skill_selection_json.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/skill_engine/test_registry_skill_selection_json.py diff --git a/.gitignore b/.gitignore index 27e8dfb0..6c717eef 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ tests/skill_engine/* !tests/skill_engine/test_evolver_length_recovery.py !tests/skill_engine/test_evolution_retry_idempotency.py !tests/skill_engine/test_analyzer_length_recovery.py +!tests/skill_engine/test_registry_skill_selection_json.py !tests/skill_engine/decision/ tests/skill_engine/decision/* !tests/skill_engine/decision/test_analysis_adapter.py diff --git a/openspace/skill_engine/registry.py b/openspace/skill_engine/registry.py index 1ffddd4a..9f7be000 100644 --- a/openspace/skill_engine/registry.py +++ b/openspace/skill_engine/registry.py @@ -1888,13 +1888,13 @@ def _parse_skill_selection_response(content: str) -> tuple[List[str], str]: if code_block: content = code_block.group(1).strip() else: - # Try to find a raw JSON object - json_match = re.search(r"\{.*\}", content, re.DOTALL) - if json_match: - content = json_match.group() + # Prefer the first object start; raw_decode ignores trailing prose. + brace = content.find("{") + if brace >= 0: + content = content[brace:] try: - data = json.loads(content) + data, _ = json.JSONDecoder().raw_decode(content) except json.JSONDecodeError: logger.warning(f"Failed to parse LLM skill selection JSON: {content[:200]}") return [], "" diff --git a/tests/skill_engine/test_registry_skill_selection_json.py b/tests/skill_engine/test_registry_skill_selection_json.py new file mode 100644 index 00000000..0c0fb342 --- /dev/null +++ b/tests/skill_engine/test_registry_skill_selection_json.py @@ -0,0 +1,12 @@ +"""Regression: trailing prose with braces must not drop skill-selection JSON.""" + +from __future__ import annotations + +from openspace.skill_engine.registry import SkillRegistry + + +def test_parse_skill_selection_tolerates_trailing_prose_braces(): + text = '{"brief_plan": "ok", "skills": ["a"]}\n\nnote {x}' + ids, plan = SkillRegistry._parse_skill_selection_response(text) + assert ids == ["a"] + assert plan == "ok"