from __future__ import annotations import json from typing import Any, List, Optional, Tuple def _pick_json_like_text(texts: List[str]) -> str: candidates = [t.strip() for t in texts if isinstance(t, str) and t.strip()] for t in reversed(candidates): s = t.lstrip() if s.startswith("{") or s.startswith("["): return t return candidates[-1] if candidates else "" def extract_json_string_from_response(resp: Any) -> Tuple[str, Optional[str]]: """ Try to extract a JSON object either from structured 'json' content parts, or from text content parts. Returns: (raw_json_string, error_message_or_None) """ out_text = getattr(resp, "output_text", None) if isinstance(out_text, str) and out_text.strip(): s = out_text.lstrip() if s.startswith("{") or s.startswith("["): return out_text, None outputs = getattr(resp, "output", None) or [] texts: List[str] = [] for item in outputs: content = getattr(item, "content", None) if content is None and isinstance(item, dict): content = item.get("content") if not content: continue for part in content: ptype = getattr(part, "type", None) if ptype is None and isinstance(part, dict): ptype = part.get("type") if ptype == "refusal": refusal_msg = getattr(part, "refusal", None) if refusal_msg is None and isinstance(part, dict): refusal_msg = part.get("refusal") return "", f"Model refusal: {refusal_msg}" j = getattr(part, "json", None) if j is None and isinstance(part, dict): j = part.get("json") if j is not None: try: return json.dumps(j, ensure_ascii=False), None except Exception as e: return "", f"Failed to serialize json content part: {e}" t = getattr(part, "text", None) if t is None and isinstance(part, dict): t = part.get("text") if isinstance(t, str) and t.strip(): texts.append(t) raw = _pick_json_like_text(texts) if raw: return raw, None return "", "Empty output (no json/text content parts found)"