Introduction: AI-Powered Recipe Generation

Welcome to this lesson on generating new recipes with AI! So far, you have learned how to interact with large language models (LLMs), structure prompts, and manage LLM calls in your application. Now, you will see how these skills come together to create a feature that generates unique cooking recipes based on a list of ingredients provided by the user.

Imagine you have a few ingredients in your fridge and want to know what you can cook. With AI-powered recipe generation, your app can suggest creative, step-by-step recipes instantly. This not only makes your app more helpful but also demonstrates the power of combining AI with backend development.

In this lesson, you will learn how to connect your backend logic to the AI, send ingredients lists, and return structured recipes to your users. By the end, you will understand the full flow of generating a recipe with AI and preparing it for use in your application.

Quick Recall: Prompts and LLM Manager

Before we dive in, let’s briefly recall two important concepts from previous lessons:

  • Prompt Templates: You learned how to use template files to create prompts for the AI. These templates include placeholders (like {{ingredients}}) that are filled in with real values before being sent to the AI.

  • LLM Manager: The LLM Manager (typically a function like generate_response) is a helper that handles all communication with the language model. It loads the right prompts, fills in variables, sends the request, and returns the AI’s response.

These tools are the foundation for generating recipes with AI. In this lesson, you will see how they are used together in a real API view.

How Recipe Generation Works

Let’s walk through how your app generates a recipe using AI, step by step.

1. The API View

Your app provides a specific function to handle the recipe generation request. Since this function receives data from the user, it is set up to handle POST requests.

from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from .utils import _parse_json_body, _json_error

@csrf_exempt
@require_http_methods(["POST"])
def generate_recipe(request):
    payload = _parse_json_body(request)
    if payload is None:
        return _json_error("Invalid JSON body", 400)

    ingredients = payload.get("ingredients")
    if not ingredients:
        return _json_error("No ingredients provided", 400)

    ingredient_str = ", ".join(str(item) for item in ingredients)
    # ... (rest of the code)
  • Decorators: @require_http_methods(["POST"]) ensures the endpoint only accepts POST requests, and @csrf_exempt allows the request to pass without a CSRF token (common for API endpoints).
  • Body Parsing: We use a helper function _parse_json_body to manually extract the JSON data from the request.
  • Validation: If the body is malformed or ingredients are missing, we return a structured error using _json_error.

Example request body:

{
  "ingredients": ["chicken", "rice", "broccoli"]
}
2. Preparing the Prompts

The view uses two prompt templates: a system prompt and a user prompt. These are loaded and filled with the user’s ingredients using our LLM Manager.

response = generate_response(
    system_prompt_name="recipe_gen_system",
    user_prompt_name="recipe_gen_user",
    variables={"ingredients": ingredient_str},
)
  • generate_response loads the template files and fills in the {{ingredients}} variable.
  • The system prompt defines the AI’s persona (a creative cooking assistant) and specifies the output format.
  • The user prompt provides the actual list of ingredients.
3. Getting the AI’s Response

The AI returns a recipe as a formatted string. It follows the structure we requested in the system prompt:

Name: Chicken and Broccoli Rice Bowl
Ingredients:
- chicken
- rice
- broccoli
Steps:
1. Cook the rice.
2. Sauté the chicken.
3. Add broccoli and mix everything together.
Parsing and Structuring the AI’s Recipe

After receiving the AI’s response, your app needs to extract the recipe name, ingredients, and steps so they can be returned as structured data (JSON).

1. Splitting the Response

The response is a single block of text. To process it, we split it into individual lines:

lines = response.strip().splitlines()
section = None
name = None
parsed_ingredients = []
steps = []
2. Extracting Each Section

We iterate through the lines and check for keywords to determine which section of the recipe we are currently reading:

for line in lines:
    low = line.lower()
    if low.startswith("name:"):
        name = line.split(":", 1)[1].strip()
        section = None
    elif low.startswith("ingredients:"):
        section = "ingredients"
    elif low.startswith("steps:"):
        section = "steps"
    elif section == "ingredients" and line.strip().startswith("-"):
        parsed_ingredients.append(line.strip()[1:].strip())
    elif section == "steps":
        stripped = line.strip()
        # Clean the prefix (e.g., "1." or "2)") to check if it's a step
        prefix = stripped[:2].replace(".", "").replace(")", "")
        if prefix.isdigit():
            steps.append(stripped)
  • Name: When a line starts with name:, we grab the text following the colon.
  • Section Switching: When we see ingredients: or steps:, we update the section variable so the code knows how to handle the following lines.
  • Steps logic: We look at the first two characters of a line, remove dots or parentheses, and check if what remains is a number. This identifies lines like 1. Step one.
3. Returning the Structured Recipe

Finally, the view returns the data using JsonResponse:

return JsonResponse({
    "name": name, 
    "ingredients": parsed_ingredients, 
    "steps": steps
})

Example output:

{
  "name": "Chicken and Broccoli Rice Bowl",
  "ingredients": ["chicken", "rice", "broccoli"],
  "steps": [
    "1. Cook the rice.",
    "2. Sauté the chicken.",
    "3. Add broccoli and mix everything together."
  ]
}
Putting It All Together: Full Implementation

Here is the complete implementation of the generate_recipe view. This function handles the request, interacts with the LLM, parses the response, and manages potential errors.

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from typing import List, Optional

from .llm import generate_response

@csrf_exempt
@require_http_methods(["POST"])
def generate_recipe(request):
    # Parse the incoming JSON body
    payload = _parse_json_body(request)
    if payload is None:
        return _json_error("Invalid JSON body", 400)

    ingredients = payload.get("ingredients")
    if not ingredients:
        return _json_error("No ingredients provided", 400)

    # Prepare ingredients for the prompt
    ingredient_str = ", ".join(str(item) for item in ingredients)

    # Call the LLM Manager
    response = generate_response(
        system_prompt_name="recipe_gen_system",
        user_prompt_name="recipe_gen_user",
        variables={"ingredients": ingredient_str},
    )

    if response is None:
        return _json_error("Failed to generate recipe", 500)

    try:
        name: Optional[str] = None
        parsed_ingredients: List[str] = []
        steps: List[str] = []

        lines = response.strip().splitlines()
        section: Optional[str] = None

        for line in lines:
            low = line.lower()
            if low.startswith("name:"):
                name = line.split(":", 1)[1].strip()
                section = None
            elif low.startswith("ingredients:"):
                section = "ingredients"
            elif low.startswith("steps:"):
                section = "steps"
            elif section == "ingredients" and line.strip().startswith("-"):
                parsed_ingredients.append(line.strip()[1:].strip())
            elif section == "steps":
                stripped = line.strip()
                # Remove common list markers like "1." or "1)"
                prefix = stripped[:2].replace(".", "").replace(")", "")
                if prefix.isdigit():
                    steps.append(stripped)

        return JsonResponse({"name": name, "ingredients": parsed_ingredients, "steps": steps})
    except Exception:
        return _json_error("Invalid recipe format from AI", 500)
Summary and Practice Preview

In this lesson, you learned how your app generates new recipes using AI. You saw how the app:

  • Receives a list of ingredients through a POST request.
  • Uses _parse_json_body to handle the incoming data.
  • Prepares and sends dynamic prompts to the AI using the generate_response helper.
  • Parses a raw string response into a structured format by iterating through lines and detecting sections.
  • Returns the final recipe as a structured JsonResponse object.

You are now ready to practice building and testing this feature yourself. In the upcoming exercises, you will implement this logic, experiment with different ingredients lists, and ensure the AI's output is correctly transformed for your users.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal