from __future__ import annotations from .shared import * class LLMStructuredCandidatesMixin: def _iter_structured_candidates( self, *, message: Any, choice: Any, response: Any, ) -> list[tuple[str, Any]]: candidates: list[tuple[str, Any]] = [] if isinstance(message, dict): tool_calls = message.get("tool_calls") if isinstance(tool_calls, list) and tool_calls: first = tool_calls[0] if isinstance(tool_calls[0], dict) else {} fn = first.get("function") if isinstance(first, dict) else {} candidates.append(("tool_calls.function.arguments", fn.get("arguments") if isinstance(fn, dict) else None)) candidates.append(("message.content", message.get("content"))) if isinstance(choice, dict): candidates.append(("choice.text", choice.get("text"))) if isinstance(response, dict): candidates.append(("response.output_text", response.get("output_text"))) if isinstance(message, dict): candidates.append(("message.reasoning_content", message.get("reasoning_content"))) candidates.append(("message.reasoning_content", message.get("reasoning"))) if isinstance(choice, dict): candidates.append(("choice.reasoning_content", choice.get("reasoning_content"))) candidates.append(("choice.reasoning_content", choice.get("reasoning"))) if isinstance(response, dict): candidates.append(("response.reasoning_content", response.get("reasoning_content"))) candidates.append(("response.reasoning_content", response.get("reasoning"))) return candidates @classmethod def _unwrap_structured_payload_once(cls, payload: Any) -> Any: if not isinstance(payload, dict): return payload for key in ("json", "data", "result", "arguments"): if key not in payload: continue inner = payload.get(key) if isinstance(inner, str): candidate = cls._extract_json_candidate(cls._strip_code_fence(inner)) if not candidate: continue try: return json.loads(candidate) except Exception: return inner if isinstance(inner, (dict, list)): return inner return payload @staticmethod def _matches_expected_type(value: Any, expected: str) -> bool: if expected == "bool": return isinstance(value, bool) if expected == "str": return isinstance(value, str) if expected == "list": return isinstance(value, list) if expected == "number": return isinstance(value, (int, float)) and not isinstance(value, bool) if expected == "number_or_none": return value is None or (isinstance(value, (int, float)) and not isinstance(value, bool)) if expected == "str_or_none": return value is None or isinstance(value, str) if expected == "dict_or_none": return value is None or isinstance(value, dict) return True def _validate_structured_payload(self, *, function_name: str, payload: Any) -> Optional[str]: fn = str(function_name or "").strip().lower() schema = STRUCTURED_OBJECT_SCHEMAS.get(fn) if schema is None: return None if isinstance(payload, (dict, list)) else "structured_json_invalid" if not isinstance(payload, dict): return "structured_schema_invalid" required_keys = schema.get("required_keys") or () for key in required_keys: if key not in payload: return "structured_schema_invalid" type_checks = schema.get("type_checks") or {} for key, expected in type_checks.items(): if not self._matches_expected_type(payload.get(key), str(expected)): return "structured_schema_invalid" optional_type_checks = schema.get("optional_type_checks") or {} for key, expected_values in optional_type_checks.items(): if key not in payload: continue if not any(self._matches_expected_type(payload.get(key), str(expected)) for expected in expected_values): return "structured_schema_invalid" return None