ics-simlab-config-gen-claude/services/generation.py

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,
)