Skip to content

feat(generation): add support for custom structured output schema#634

Open
lukehinds wants to merge 1 commit into
mainfrom
custom-schema
Open

feat(generation): add support for custom structured output schema#634
lukehinds wants to merge 1 commit into
mainfrom
custom-schema

Conversation

@lukehinds
Copy link
Copy Markdown
Collaborator

  • Introduce generation.output_schema and output.format parameters to configure direct structured output based on a user-defined JSON schema.
  • When output_schema is provided, the generator now bypasses the standard conversation format and uses constrained decoding to generate output that adheres directly to the specified schema.
  • Implement a dynamic model creation utility (make_dynamic_model) to convert a raw JSON schema dictionary into a Pydantic-compatible class, enabling its use with constrained decoding.
  • Update the outlines and transformers dependencies to their latest versions to support these new capabilities.

- Introduce `generation.output_schema` and `output.format` parameters to configure direct structured output based on a user-defined JSON schema.
- When `output_schema` is provided, the generator now bypasses the standard conversation format and uses constrained decoding to generate output that adheres directly to the specified schema.
- Implement a dynamic model creation utility (`make_dynamic_model`) to convert a raw JSON schema dictionary into a Pydantic-compatible class, enabling its use with constrained decoding.
- Update the `outlines` and `transformers` dependencies to their latest versions to support these new capabilities.

Signed-off-by: Luke Hinds <lukehinds@gmail.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for custom structured output schemas, enabling constrained decoding directly into user-defined JSON formats. Key updates include new configuration parameters, a dynamic model factory to bridge raw schemas with the generation pipeline, and a dedicated generation path that bypasses the standard conversation builder. Review feedback highlights that the dynamic model implementation lacks actual schema validation and robust serialization for complex structures. Additionally, the reviewer recommends unifying the retry logic in the generator to eliminate duplication and ensure consistent error handling across all generation modes.

Comment thread deepfabric/llm/client.py
Comment on lines +1246 to +1251
def model_validate_json(cls, json_data: str) -> "DynamicModel":
try:
data = json.loads(json_data)
except json.JSONDecodeError as exc:
raise ValueError(f"Invalid JSON from model: {exc}") from exc
return cls(data)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The model_validate_json method only performs a basic json.loads() check and does not verify that the resulting data conforms to the provided schema_dict. Since this class is intended to replace a Pydantic model in the structured output pipeline, it should ideally perform schema validation to ensure downstream components receive data in the expected format. If a full validation library like jsonschema is not available, consider at least documenting this limitation.

Comment thread deepfabric/llm/client.py
Comment on lines +1253 to +1256
def model_dump(self, exclude_none: bool = False, **_kwargs: Any) -> dict:
if exclude_none:
return {k: v for k, v in self._data.items() if v is not None}
return dict(self._data)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The model_dump implementation is shallow and its exclude_none logic is not recursive. Additionally, it assumes self._data is a dictionary (by calling .items()), which will fail if the JSON schema defines a top-level array. Pydantic's model_dump typically handles nested structures and various JSON types. Consider making this implementation more robust to match the expected Pydantic interface.

Comment thread deepfabric/generator.py
Comment on lines +1051 to +1067
if config.output_schema:
schema_model = self._get_custom_schema_model()
for attempt in range(max_attempts):
try:
result = await self.llm_client.generate_async(
prompt,
schema_model,
max_tokens=config.max_tokens,
)
return True, result
except Exception as e: # noqa: BLE001
last_error = e
if is_validation_error(e) and attempt < self.config.sample_retries:
self._emit_retry(sample_idx, attempt, max_attempts, e)
continue
return False, last_error
return False, last_error or Exception("Custom schema generation failed")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block introduces a separate retry loop for custom schema generation that duplicates logic from the standard path. Furthermore, the except block at line 1066 returns immediately for any error that is not a validation error (e.g., transient network issues or rate limits), whereas the standard path likely retries such cases. It is recommended to unify the retry logic to ensure consistent error handling and reduce maintenance overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant