98 lines
2.7 KiB
Python
98 lines
2.7 KiB
Python
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,
|
|
)
|