from __future__ import annotations from pathlib import Path from typing import Any, List, Optional from openai import OpenAI from services.agent_call import agent_call_req from helpers.helper import dump_response_debug, log from services.response_extract import extract_json_string_from_response def generate_json_with_llm( client: OpenAI, model: str, full_prompt: str, schema: Optional[dict[str, Any]], max_output_tokens: int, ) -> str: """ Uses Responses API request shape: text.format. Robust extraction + debug dump + fallback to json_object if schema path fails. """ if schema is not None: text_format: dict[str, Any] = { "type": "json_schema", "name": "ics_simlab_config", "strict": True, "schema": schema, } else: text_format = {"type": "json_object"} req: dict[str, Any] = { "model": model, "input": full_prompt, "max_output_tokens": max_output_tokens, "text": { "format": text_format, "verbosity": "low", }, } # GPT-5 models: no temperature/top_p/logprobs if model.startswith("gpt-5"): req["reasoning"] = {"effort": "minimal"} else: req["temperature"] = 0 resp = agent_call_req(client, req) raw, err = extract_json_string_from_response(resp) if err is None and raw: return raw dump_response_debug(resp, Path("outputs/last_response_debug.json")) # Fallback if we used schema if schema is not None: log( "Structured Outputs produced no extractable JSON/text. " "Fallback -> JSON mode. (See outputs/last_response_debug.json)" ) req["text"]["format"] = {"type": "json_object"} resp2 = agent_call_req(client, req) raw2, err2 = extract_json_string_from_response(resp2) dump_response_debug(resp2, Path("outputs/last_response_debug_fallback.json")) if err2 is None and raw2: return raw2 raise RuntimeError(f"Fallback JSON mode failed: {err2}") raise RuntimeError(err or "Unknown extraction error") def repair_with_llm( client: OpenAI, model: str, schema: Optional[dict[str, Any]], repair_template: str, user_input: str, current_raw: str, errors: List[str], max_output_tokens: int, ) -> str: repair_prompt = ( repair_template .replace("{{USER_INPUT}}", user_input) .replace("{{ERRORS}}", "\n".join(f"- {e}" for e in errors)) .replace("{{CURRENT_JSON}}", current_raw) ) return generate_json_with_llm( client=client, model=model, full_prompt=repair_prompt, schema=schema, max_output_tokens=max_output_tokens, )