Files
kunail0804-ComfyUI-Scene-Co…/compiler/analyzer/response_parser.py
T
5948b20741 feat(analyzer): parse and validate Analyzer responses (#17) (#61)
Add compiler/analyzer/response_parser.py: parse_scene_response(text, logger=None)
-> CompilerResult, per MASTER_SPEC §12.2, §12.8.

- Strictly json.loads the model response; JSON is never repaired heuristically
  (§12.9), so malformed or markdown-fenced output fails here (feeding the retry
  path in #18).
- Validates the parsed document against the Scene schema; any failure (malformed
  JSON, non-object, missing field, wrong type) is reported as SC0011 and stops
  the compilation -- invalid responses are never silently accepted.
- Handles both object and string concept forms via the Scene model; confidence
  stays metadata-only.
- Stamps a default schema_version onto compiler-owned metadata (the model emits
  `"metadata": {}`) before validation.

Tests: valid response -> Scene, metadata stamping, object/string concepts +
confidence, and rejection (SC0011) of malformed JSON, non-object JSON, missing
fields, wrong types, and markdown-fenced (unrepaired) output.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:54:39 +02:00

72 lines
2.7 KiB
Python

"""Parsing and validation of the Analyzer's model response (MASTER_SPEC §12.2, §12.8).
Turns the raw model response text into a validated :class:`Scene`. The response
MUST be a single valid JSON document conforming to the Scene schema; invalid
output MUST NOT continue through the compiler.
JSON is never repaired heuristically (§12.9): malformed or markdown-wrapped output
fails here, which the retry orchestration (issue #18) uses to request a corrected
response. Any parse or validation failure is reported as SC0011.
"""
from __future__ import annotations
import json
from typing import Any
from compiler.common.log import StructuredLogger
from compiler.common.message_codes import message
from compiler.common.result import CompilerResult
from schemas.models import Scene
from schemas.validation import validate_document
# The Scene schema version stamped onto compiler-owned metadata when the model
# leaves it empty (the model is instructed to emit `"metadata": {}`).
SCENE_SCHEMA_VERSION = "1.0"
def parse_scene_response(text: str, logger: StructuredLogger | None = None) -> CompilerResult:
"""Parse and validate an Analyzer response into a Scene.
Returns:
A CompilerResult whose ``data`` is the validated :class:`Scene` on
success, or ``None`` with an SC0011 error when the response is not a
valid Scene JSON document.
"""
try:
data = json.loads(text)
except json.JSONDecodeError as exc:
return _reject("json_decode", f"Analyzer response is not valid JSON: {exc}", logger)
if not isinstance(data, dict):
return _reject(
"not_object",
f"Analyzer response must be a JSON object, got {type(data).__name__}.",
logger,
)
_stamp_metadata(data)
issues = validate_document(data, "scene")
if issues:
detail = "; ".join(f"{issue.path or '<root>'}: {issue.message}" for issue in issues)
return _reject("schema", f"Analyzer response failed Scene validation: {detail}", logger)
scene = Scene.from_json(data)
if logger is not None:
logger.verbose("analyzer_response_parsed", characters=len(scene.characters))
return CompilerResult(data=scene)
def _stamp_metadata(data: dict[str, Any]) -> None:
"""Ensure compiler-owned metadata carries a schema_version before validation."""
metadata = data.get("metadata")
if isinstance(metadata, dict):
metadata.setdefault("schema_version", SCENE_SCHEMA_VERSION)
def _reject(kind: str, description: str, logger: StructuredLogger | None) -> CompilerResult:
if logger is not None:
logger.basic("analyzer_response_rejected", kind=kind)
return CompilerResult().add_error(message("SC0011", description, kind=kind))