Uh oh!
There was an error while loading. Please reload this page.
refactor(providers): extract shared output schema builders into _schema.py - #311
Conversation
Jason Robert (jrob5756)
left a comment
There was a problem hiding this comment.
Nice extraction overall, and the golden tests actually pin real pre-refactor output (I checked). Two things I'd want fixed before this merges: the depth check on Hermes array items now fires unconditionally, which tightens the depth limit for non-object items and breaks the zero-behavior-change goal, and there are a few leftover methods in claude.py and copilot.py that nothing calls anymore. Smaller doc nits below.
| Raises: | ||
| SchemaDepthError: When the depth limit is exceeded. | ||
| """ | ||
| _check_depth(depth, max_depth) |
There was a problem hiding this comment.
This check now runs for every array item, but the pre-refactor Hermes code only checked depth when it actually recursed into object properties. Array items of type array or a scalar never hit a depth check before.
I reproduced the regression: a schema with 10 nested objects ending in an array-of-strings field succeeds on main but raises ValidationError("Schema nesting depth exceeds maximum of 10 levels") here. That's a real behavior change for valid schemas right at the boundary.
| _check_depth(depth, max_depth) | |
| iffield.type=="object"andfield.properties: | |
| _check_depth(depth, max_depth) |
Move the check inside the object branch below so it only fires when recursing, same as before.
There was a problem hiding this comment.
Fixed in 7a6d1f5 — moved _check_depth inside the object branch per your suggestion, so it only fires when recursing, exactly matching pre-refactor semantics. Verified against main: your repro (10 nested objects ending in array-of-strings) now passes, and 11 object levels still raises. Added a regression test test_build_prompt_schema_non_object_array_item_at_boundary_accepted pinning this boundary alongside the existing array-item parity test.
| return properties | ||
| ) from exc | ||
| def _build_single_field_schema(self, field: OutputField, depth: int = 0) -> dict[str, Any]: |
There was a problem hiding this comment.
Nothing calls this anymore. _build_json_schema_properties delegates fully to build_json_schema_properties in _schema.py now, which handles array items internally through build_json_schema_field instead of coming back through this method. Checked src/ and tests/, no references outside the definition. Safe to delete.
There was a problem hiding this comment.
Deleted in e1cc0cd, together with the now-unused build_json_schema_field import. Confirmed no references left in src/ or tests/.
| } | ||
| ) from exc | ||
| def _build_prompt_field_schema( |
There was a problem hiding this comment.
_build_prompt_field_schema and _build_prompt_item_schema (line 1569) are both unreachable now. _build_prompt_schema calls build_prompt_schema_properties from _schema.py directly, which recurses on its own instead of going through these wrappers. The docstrings claim tests call them directly, but none do. Delete both, or if you want to keep them as an extension point, add tests and fix the docstring.
There was a problem hiding this comment.
Deleted both in e1cc0cd. The docstrings claiming tests called them directly were indeed wrong — nothing referenced them, so removal was the right call over keeping them as an extension point.
| return schema | ||
| Kept as a thin wrapper for tests that call it directly. | ||
| """ | ||
| from conductor.providers._schema import build_prompt_schema_field |
There was a problem hiding this comment.
This duplicates the module-level import of build_prompt_schema_properties/SchemaDepthError from the same module at line 26 (and it's repeated again at line 1574). No circular-import issue here, might as well hoist build_prompt_schema_field into that existing import.
There was a problem hiding this comment.
Resolved in e1cc0cd — both local imports went away with the methods themselves, since nothing else used build_prompt_schema_field here.
| A JSON-Schema fragment dictionary. | ||
| Raises: | ||
| _SchemaDepthError: When the depth limit is exceeded. |
There was a problem hiding this comment.
The Raises: section names _SchemaDepthError with a leading underscore, but the class is just SchemaDepthError (defined at line 16, correctly referenced at lines 39 and 272). Same typo at lines 97, 135, 188, and 225.
There was a problem hiding this comment.
Fixed in 7a6d1f5 — all five occurrences now read SchemaDepthError (lines 62, 97, 135, 188, 225).
| This matches the legacy Hermes provider behavior: descriptions fall back | ||
| to ``"The {field_name} field"`` at the top level, but array items do not | ||
| receive a fallback description. Unlike the generic prompt builder, object | ||
| items inside arrays are emitted with ``properties`` but no ``required`` |
There was a problem hiding this comment.
This says array-of-array items collapse "without further recursion or description," but _build_hermes_legacy_item_schema keeps the description when the item has one (line 277). Only the recursion into nested items actually gets dropped. Worth tightening the wording so it matches what's actually pinned.
There was a problem hiding this comment.
Tightened in 7a6d1f5 — both docstrings now say the collapse drops recursion but keeps an explicit item description, matching what's pinned.
| """Golden regression tests for provider output-schema wrappers. | ||
| These tests pin the exact full-wrapper output of each provider's schema builder | ||
| against pre-refactor literals captured in Task 0. Any behavioral change in the |
There was a problem hiding this comment.
This points at "Task 0" and a .omo/scripts/capture_schema_baseline.py script, neither of which exists in this repo or its history. I regenerated the baselines from main myself and they check out, so the values are fine, there's just no way for anyone else to reproduce them from what's actually checked in. Worth either committing the capture script somewhere or rewording this to explain how the values were verified instead of pointing at a script that isn't there.
There was a problem hiding this comment.
Reworded in e1cc0cd — the module docstring no longer references "Task 0", and the schema comment no longer points at .omo/scripts/capture_schema_baseline.py. The values themselves stand as pinned literals verified against main (you independently regenerated them too); the docstring now states that directly instead of pointing at a script that isn't checked in.
| def test_build_prompt_schema_depth_limit_enforced(self) -> None: | ||
| """Excessively nested schemas raise ValidationError with the pinned message. | ||
| This is a targeted regression guard for PR-2a: the Copilot provider |
There was a problem hiding this comment.
"PR-2a" reads like an internal task label from planning rather than anything tied to this repo's PR numbers, so it won't mean much to anyone reading this later. The rest of the docstring already explains the invariant fine without it.
There was a problem hiding this comment.
Removed in e1cc0cd — "PR-2a" was indeed an internal planning label, the docstring now stands on the invariant alone.
…rray items The shared _build_hermes_legacy_item_schema checked depth for every array item, but the pre-refactor Hermes builder only checked depth when recursing into object properties. A schema with 10 nested objects ending in an array-of-strings (or array-of-arrays) field passed on main but raised ValidationError here. Move the depth check inside the object-item branch so it fires only on recursion, matching legacy semantics exactly. Adds a regression test pinning the boundary: 10 nested objects with a non-object array leaf is accepted, 11 object levels still raises. Also fixes the _SchemaDepthError docstring typo (the class is SchemaDepthError) and tightens the array-of-array collapse wording: recursion is dropped, but an explicit item description is kept. Addresses PR microsoft#311 review comments by jrob5756.
…nces Delete ClaudeProvider._build_single_field_schema and CopilotProvider ._build_prompt_field_schema/_build_prompt_item_schema: after the shared builder wiring nothing calls them (checked src/ and tests/). This also removes the duplicated local imports of build_prompt_schema_field inside the two Copilot methods and the now-unused build_json_schema_field import in claude.py. Reword test docstrings that pointed at non-existent artifacts: the "Task 0" baseline reference and the .omo/scripts/capture_schema_baseline.py comment in test_output_schema.py, and the internal "PR-2a" label in test_copilot.py. Addresses PR microsoft#311 review comments by jrob5756.
hertznsk
commented
Jul 18, 2026
All review comments addressed in 7a6d1f5 (Hermes depth boundary + docstring fixes) and e1cc0cd (dead code removal + stale references). Full provider suite: 800 passed; One open question for Jason Robert (@jrob5756): the golden tests in |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #311 +/- ##
=======================================
Coverage ? 89.80% =======================================
Files ? 73 Lines ? 13059 Branches ? 0 =======================================
Hits ? 11728 Misses ? 1331 Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Jason Robert (jrob5756)
left a comment
There was a problem hiding this comment.
LGTM. Approved!
Summary
Four providers (Claude, Copilot, Hermes, Claude Agent SDK) contained near-duplicate logic for building provider output schemas. This PR extracts the shared logic into a new private module
src/conductor/providers/_schema.py; each provider keeps a thin wrapper preserving its exact API contract, error types, and messages.What changed
src/conductor/providers/_schema.py): Implementsbuild_json_schema_field/properties,build_prompt_schema_field/properties,build_hermes_legacy_prompt_schema, and custom exceptionSchemaDepthError.tests/test_providers/test_output_schema.pycontaining 9 golden tests.Behavior preservation
tests/test_providers/test_output_schema.pypins full wrapper outputs against literals captured from the pre-refactor code to ensure byte-for-byte correctness and prevent regressions.requiredfield inside array-item objects, collapsed array-of-array items) in a dedicated legacy builder, including exact depth counting for array object items (test_build_prompt_schema_array_item_depth_parity).Explicitly out of scope
tool_choicechanges.additionalProperties: falseadditions.OutputFieldschema extensions.validate_outputchanges.Test plan
make test: 4146 passed (1 pre-existing failure intest_large_create_tool_call_does_not_truncatecaused by a local Copilot SDK authorization/enterprise-policy error, which also fails on themainbranch).make check: clean execution with only 3 pre-existingtywarnings.make validate-examples: all validated successfully.