Accumulates a schema subagent's ``StructuredOutput`` emissions. The injected tool calls :meth:`offer` for each emission. A valid object is captured and ends the run; an invalid one returns the validation error (fed back to the model as a tool error so it retries) until the retry cap is
| 39 | |
| 40 | @dataclass |
| 41 | class StructuredOutputCollector: |
| 42 | """Accumulates a schema subagent's ``StructuredOutput`` emissions. |
| 43 | |
| 44 | The injected tool calls :meth:`offer` for each emission. A valid object is |
| 45 | captured and ends the run; an invalid one returns the validation error (fed |
| 46 | back to the model as a tool error so it retries) until the retry cap is hit. |
| 47 | """ |
| 48 | |
| 49 | schema: Mapping[str, Any] |
| 50 | max_retries: int = MAX_STRUCTURED_OUTPUT_RETRIES |
| 51 | attempts: int = 0 |
| 52 | value: Any = None |
| 53 | succeeded: bool = False |
| 54 | last_error: Optional[str] = None |
| 55 | |
| 56 | def offer(self, obj: Any) -> tuple[bool, Optional[str]]: |
| 57 | """Validate one emission. Returns ``(accepted, error_message)``.""" |
| 58 | if self.succeeded: |
| 59 | return True, None |
| 60 | self.attempts += 1 |
| 61 | ok, error = validate_structured(obj, self.schema) |
| 62 | if ok: |
| 63 | self.value = obj |
| 64 | self.succeeded = True |
| 65 | return True, None |
| 66 | self.last_error = error |
| 67 | return False, error |
| 68 | |
| 69 | @property |
| 70 | def exhausted(self) -> bool: |
| 71 | """True once retries are spent without a valid object.""" |
| 72 | return not self.succeeded and self.attempts >= self.max_retries |
| 73 | |
| 74 | |
| 75 | def make_structured_output_tool(collector: StructuredOutputCollector) -> Tool: |
no outgoing calls