fix: JSON truncation recovery + 2min timeout on AI template generation

This commit is contained in:
root
2026-07-16 08:01:51 +00:00
parent 70735502f2
commit 8d9506efcf
3 changed files with 73 additions and 10 deletions

View File

@@ -37,12 +37,30 @@ def llm_chat(messages, temperature=0.3, max_tokens=4000, response_format=None):
def extract_json(text):
"""Extract JSON from LLM response, handling markdown code blocks."""
"""Extract JSON from LLM response, handling markdown code blocks and truncation."""
json_str = text.strip()
if json_str.startswith("```"):
json_str = re.sub(r'^```(?:json)?\s*', '', json_str)
json_str = re.sub(r'\s*```$', '', json_str)
try:
return json.loads(json_str)
except json.JSONDecodeError:
# Try to fix truncated JSON by closing open braces/brackets
open_b = json_str.count('{')
close_b = json_str.count('}')
open_arr = json_str.count('[')
close_arr = json_str.count(']')
if open_b > close_b or open_arr > close_arr:
# Remove any trailing partial content after last complete element
# then close the structures
json_str = json_str.rstrip()
# Remove trailing incomplete key-value (e.g. cut off mid-string)
json_str = re.sub(r'[\s,]*"[^"]*":\s*"?[^",}\]]*$', '', json_str)
json_str += '}' * max(0, open_b - json_str.count('}'))
json_str += ']' * max(0, open_arr - json_str.count(']'))
return json.loads(json_str)
raise
# ============================================================
@@ -333,8 +351,34 @@ def generate_template(description: str) -> dict:
{"role": "system", "content": "You are a CV template designer that outputs only valid JSON."},
{"role": "user", "content": prompt}
]
resp = llm_chat(messages, temperature=0.5, max_tokens=2000)
# Retry with increasing max_tokens if JSON parsing fails
for attempt, max_tok in enumerate([2000, 3000, 4000]):
try:
resp = llm_chat(messages, temperature=0.5, max_tokens=max_tok)
return extract_json(resp)
except json.JSONDecodeError as e:
if attempt < 2:
# Retry with more tokens
continue
# Last attempt failed — try to salvage partial JSON
try:
# Attempt to fix truncated JSON by closing braces
json_str = resp.strip()
if json_str.startswith("```"):
json_str = re.sub(r'^```(?:json)?\s*', '', json_str)
json_str = re.sub(r'\s*```$', '', json_str)
# Count open vs close braces
open_b = json_str.count('{')
close_b = json_str.count('}')
open_arr = json_str.count('[')
close_arr = json_str.count(']')
# Append missing closers
json_str += '}' * (open_b - close_b)
json_str += ']' * (open_arr - close_arr)
return json.loads(json_str)
except:
raise ValueError(f"AI returned invalid JSON (truncated at {max_tok} tokens). Please try again with a shorter description.")
# ============================================================

View File

@@ -286,20 +286,39 @@ async function createTemplate() {
async function generateTemplate() {
const desc = document.getElementById('tmpl-gen-desc').value;
if (!desc) { toast('Description required', 'error'); return; }
document.querySelector('.modal button').textContent = 'Generating...';
document.querySelector('.modal button').disabled = true;
const btn = document.querySelector('.modal button');
btn.textContent = 'Generating...';
btn.disabled = true;
try {
await api('/api/templates/generate', 'POST', {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120000); // 2 min timeout
const resp = await fetch(API + '/api/templates/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: document.getElementById('tmpl-gen-name').value || 'AI Generated Template',
description: desc
}),
signal: controller.signal
});
clearTimeout(timeout);
if (!resp.ok) {
const err = await resp.text();
throw new Error(err);
}
closeModal();
toast('Template generated with AI');
loadTemplates();
} catch(e) {
btn.textContent = 'Generate with AI';
btn.disabled = false;
if (e.name === 'AbortError') {
toast('Generation timed out after 2 minutes. Try a shorter description.', 'error');
} else {
toast('Generation failed: ' + e.message, 'error');
}
}
}
async function deleteTemplate(id) {
if (!confirm('Delete this template?')) return;