Lifecycle
Overview¶
As Instructor processes your request, it moves through a well-defined series of stages. Understanding this lifecycle helps when debugging unexpected output or building custom extensions.
Request Lifecycle Steps¶
1. Build the Request¶
The StructuredOutput facade collects your messages, response model, system prompt,
examples, model overrides, and options into an immutable StructuredOutputRequest.
2. Analyze the Response Model¶
The ResponseModelFactory inspects the responseModel parameter and determines how
to build a schema. Depending on the type of input, it follows one of several
paths (class string, object instance, raw JSON Schema array, schema provider, etc.).
The result is a ResponseModel containing the target class, a Schema, and the
rendered JSON Schema for the provider.
3. Translate to Provider Schema¶
The schema is rendered into the format required by the selected output mode:
| Output Mode | Schema Delivery |
|---|---|
Tools |
Wrapped in a tool-call function definition |
Json |
Included in the system/user prompt as text |
JsonSchema |
Sent via the provider's response_format parameter |
MdJson |
Included in the prompt, response expected in a json codeblock |
Text |
Schema included in the prompt; JSON extracted from unstructured text |
Unrestricted |
No output constraints; extraction is best-effort |
4. Execute the Inference Request¶
The PendingStructuredOutput delegates to the configured CanCreateInference
implementation (via the Polyglot inference layer). For streaming requests, the
response arrives as a series of chunks.
5. Extract Structured Data¶
The CanExtractResponse extractor pulls JSON data from the raw inference response.
The extraction strategy depends on the output mode -- for Tools mode it reads
tool-call arguments; for Json/JsonSchema it parses the content directly; for
MdJson it extracts from a fenced code block.
6. Materialize the Target Shape¶
The extracted data is materialized according to the resolved output format. Plain JSON
Schemas and intoArray() return an associative array; class-backed schemas hydrate the
target PHP class; intoStdClass() returns stdClass. Classes implementing
CanDeserializeSelf can control their own materialization.
7. Validate the Result¶
The extracted data is checked against its schema. Materialized objects are then
validated with Symfony Validator constraints declared on the response model class.
Classes implementing CanValidateSelf can provide their own validation logic.
8. Transform (Optional)¶
Configured transformations and CanTransformSelf run once after validation. A
transformation failure fails the attempt and can enter the retry policy. This is how
helpers like Scalar unwrap a wrapper class into a plain PHP scalar.
9. Return the Result¶
The final value is wrapped in a StructuredOutputResponse (or yielded as a stream
of partial responses) and returned to the caller.
Retry Loop¶
Steps 4 through 8 run inside a retry loop. When validation or deserialization fails:
- The error message is formatted as feedback for the LLM.
- The feedback is appended to the conversation as a retry message.
- The LLM is called again with the updated context.
- The cycle repeats until the response passes or
maxRetriesis exhausted.
The retry budget is configured via StructuredOutputConfig::maxRetries. A value of
0 (the default) means a single attempt with no retries. The total number of
attempts is always maxRetries + 1.
When the retry limit is reached without a valid response, a
StructuredOutputRecoveryException is thrown.
Streaming Lifecycle¶
When streaming is enabled, the lifecycle diverges after step 4:
- Chunks arrive incrementally from the provider.
- Each chunk is accumulated into a partial JSON string.
- The partial JSON is deserialized into a partial object (best-effort).
- A
StructuredOutputResponsesnapshot (marked as partial) is emitted. - When the stream completes, the final response goes through the full validation and transformation pipeline (steps 7-8).
- The finalized response is emitted as a non-partial
StructuredOutputResponse.
Partial updates are available through StructuredOutputStream::partials(),
sequence(), or responses().