← Back to DevBytes

How to Implement Retry Logic for Failed Structured Outputs

Introduction to Retry Logic for Structured Outputs

When integrating Large Language Models (LLMs) into applications, developers frequently require responses in a specific format, such as JSON, XML, or YAML. These are known as structured outputs. While modern LLMs are highly capable of generating structured data, they are not infallible. They can occasionally hallucinate, include markdown formatting blocks (like json), or omit required fields, causing your application's parser to fail.

Retry logic is the programmatic mechanism of catching these parsing failures and automatically requesting the LLM to generate the output again. Implementing a robust retry strategy is critical for building resilient AI applications that do not break in production due to occasional formatting hiccups.

Why Retry Logic Matters

Structured outputs are the bridge between unstructured AI generation and deterministic software logic. If an LLM returns a malformed JSON object, your database insertion or API call will fail. Relying on a single attempt is a recipe for fragile applications. By implementing retry logic, you achieve several key benefits:

How to Implement Retry Logic

Implementing retry logic involves wrapping your LLM API call and parsing logic in a loop. If parsing succeeds, you return the data. If it fails, you catch the exception and try again, up to a predefined maximum number of attempts.

Basic Retry Mechanism

The simplest form of retry logic uses a for loop and a try-except block. This approach is useful for quick scripts or low-stakes applications where immediate retries are acceptable.

import json
import time

def mock_llm_call(prompt):
    # Simulating an LLM response that might be malformed
    return "{'name': 'John', 'age': 30}" # Note: single quotes are invalid JSON

def get_structured_output(prompt, max_retries=3):
    for attempt in range(max_retries):
        print(f"Attempt {attempt + 1}...")
        response = mock_llm_call(prompt)
        
        try:
            # Attempt to parse the response as JSON
            parsed_data = json.loads(response)
            return parsed_data
        except json.JSONDecodeError as e:
            print(f"Failed to parse JSON: {e}")
            if attempt < max_retries - 1:
                time.sleep(1) # Brief pause before retrying
                
    raise Exception("Failed to generate valid structured output after maximum retries.")

# Usage
try:
    data = get_structured_output("Generate a user profile in JSON.")
    print(data)
except Exception as e:
    print(e)

Advanced Retry with Exponential Backoff and Error Feedback

A basic loop is a good start, but production environments require more sophisticated handling. If the LLM is failing due to rate limits or server load, retrying immediately will likely fail again. Furthermore, if the LLM made a formatting mistake, simply asking it to try again without context might result in the exact same mistake.

The advanced approach incorporates two improvements: exponential backoff (waiting longer between each retry) and error feedback (telling the LLM exactly why its previous attempt failed).

import json
import time

def mock_llm_call(prompt):
    # Simulating an LLM that eventually returns valid JSON
    if "Previous attempt failed" in prompt:
        return '{"name": "Jane", "age": 28}'
    return "Here is the data: json\n{'name': 'Jane', 'age': 28}\n"

def get_structured_output_advanced(prompt, max_retries=4):
    current_prompt = prompt
    
    for attempt in range(max_retries):
        print(f"Attempt {attempt + 1}...")
        response = mock_llm_call(current_prompt)
        
        # Clean up common LLM markdown formatting
        if "json" in response:
            response = response.split("json")[1].split("")[0].strip()
        elif "" in response:
            response = response.split("")[1].split("")[0].strip()
            
        try:
            return json.loads(response)
        except json.JSONDecodeError as e:
            print(f"Parse error: {e}")
            
            # Feed the error back to the LLM
            current_prompt = f"""
            Your previous response failed to parse as valid JSON.
            Error encountered: {e}
            Your previous response: {response}
            
            Please return ONLY valid JSON, without any markdown formatting or conversational text.
            Original request: {prompt}
            """
            
            # Exponential backoff: wait 2^attempt seconds
            if attempt < max_retries - 1:
                sleep_time = 2 ** attempt
                print(f"Waiting {sleep_time} seconds before retrying...")
                time.sleep(sleep_time)
                
    raise Exception("Failed to generate valid structured output after maximum retries.")

# Usage
try:
    data = get_structured_output_advanced("Generate a user profile in JSON.")
    print("Success:", data)
except Exception as e:
    print(e)

Best Practices for Retry Logic

To ensure your retry logic is effective and does not introduce new problems, follow these best practices:

Conclusion

Implementing retry logic for failed structured outputs is an essential step in moving AI features from proof-of-concept to production-ready. By combining basic parsing loops with advanced techniques like exponential backoff and error feedback, developers can build resilient applications that gracefully handle the inherent unpredictability of LLMs. While retry logic significantly improves reliability, remember that it works best in tandem with strong prompt engineering and, where available, native structured output features provided by modern LLM APIs.

— Ad —

Google AdSense will appear here after approval

← Back to all articles