Wednesday, March 04, 2026

A Guide to an LLM-Powered Culinary Chatbot



Introduction


In today's fast-paced world, finding the perfect meal or recipe can often be a time-consuming endeavor, fraught with endless searches and information overload. Imagine a personal culinary assistant, powered by cutting-edge Artificial Intelligence, that understands your nuanced dietary needs, cravings, and preferences, instantly delivering tailored food recommendations and recipes. This article delves into the architecture and functionality of such an LLM-based chatbot, designed to revolutionize how Siemens employees and others discover and prepare their meals. From deciphering complex user prompts to extracting granular nutritional data and presenting aesthetically pleasing, sortable results, this chatbot aims to be the ultimate companion for every kitchen adventure. It streamlines the process of finding healthy, delicious, and suitable food options, enhancing daily productivity and well-being.


A key differentiator of this advanced chatbot is its ability to move beyond simple keyword matching. It is not limited to a predefined set of food categories or recipe examples but is engineered to understand and respond to any culinary request a user might have. Furthermore, it possesses the intelligence to learn and adapt to individual user preferences over time, remembering favorite dishes, dietary restrictions, and even disliked ingredients, thereby providing an increasingly personalized and relevant experience with each interaction. This application makes no assumptions about the type of food the user is interested in, allowing for truly open-ended exploration.


User Interaction Model: Understanding Your Culinary Desires and Evolving Tastes


The cornerstone of any effective chatbot is its ability to comprehend user intent expressed in natural language. Our culinary chatbot is engineered to interpret a wide array of user prompts, moving beyond simple keyword matching to grasp the underlying request. Users can articulate their needs in various ways, reflecting diverse culinary goals and dietary requirements.


For instance, a user might express a desire for "A light food," indicating a preference for meals that are not heavy or calorie-dense. Another user might specifically request, "I need a recipe for low carb food," clearly stating a dietary restriction. The chatbot is also adept at handling health-oriented queries, such as, "What food helps me reduce my deficiency of Vitamine B," where it must identify specific nutrients and recommend foods rich in them. Cultural and regional preferences are also well within its grasp, as demonstrated by a prompt like, "I need a Bavarian recipe for Kaiserschmarrn," requiring knowledge of specific cuisines and dishes. Furthermore, users interested in sustainable eating can ask for "a recipe for food that only uses seasonal ingredients," prompting the chatbot to consider geographical and temporal factors. The chatbot is designed to handle any food-related query, from "vegan gluten-free dessert" to "quick dinner with chicken and rice" or "what can I make with these five ingredients?"


These examples highlight the chatbot's sophisticated Natural Language Understanding (NLU) capabilities, powered by a large language model. It does not merely extract keywords; instead, it parses the entire sentence to infer the user's true intent, desired cuisine, dietary constraints, specific ingredients, or nutritional goals. This deep understanding is crucial for formulating precise search queries and delivering highly relevant results.


Crucially, the application does not make assumptions about the user's culinary knowledge or preferences based solely on initial prompts. It is designed to handle an open-ended range of food-related questions and requests. Whether a user asks for "a quick weeknight dinner," "something vegetarian and gluten-free," "recipes with eggplant," or "what to cook with leftover chicken," the chatbot is equipped to process these diverse inputs without being constrained by a limited set of predefined categories.


Moreover, a significant enhancement to the user interaction model is the chatbot's capacity to learn and remember user preferences over time. As users interact with the system, providing feedback, marking dishes as favorites, or specifying dislikes, the chatbot gradually builds a comprehensive user profile. This profile allows the system to offer increasingly personalized recommendations. For example, if a user frequently searches for Italian pasta dishes and marks several as favorites, the chatbot will prioritize similar recipes in future searches. Conversely, if a user indicates a dislike for cilantro, the system will actively filter out recipes containing that ingredient. This continuous learning process ensures that the chatbot becomes a truly personal and intuitive culinary assistant.


Core Components of the Culinary Chatbot


The functionality of our LLM-powered culinary chatbot is orchestrated through several interconnected components, each playing a vital role in transforming a user's natural language prompt into actionable culinary advice.


1.  Natural Language Understanding (NLU) and Prompt Processing: This initial stage involves the core LLM analyzing the user's input. It identifies the primary intent (e.g., "find recipe," "recommend food," "nutritional information"), extracts key entities (e.g., "Kaiserschmarrn," "Vitamine B," "low carb," "Bavarian"), and determines implicit parameters like the desired language of the search results, based on the input language. This component is paramount for translating human language into structured queries understandable by subsequent modules. The LLM's design ensures that it can interpret a vast and open-ended range of culinary requests, going far beyond the initial prompt examples, without making assumptions about the user's specific needs or preferences. It can infer broader categories or specific ingredients even from vague descriptions, making its search capabilities highly adaptable.


2.  User Profile and Preference Management: This dedicated component is responsible for storing and managing individual user data. It maintains a persistent profile for each user, which includes their search history, explicitly marked favorite dishes, preferred cuisines, known dietary restrictions (e.g., vegetarian, vegan, gluten-free), allergies, disliked ingredients, and nutritional goals (e.g., high protein, low calorie). This profile is dynamically updated based on user feedback and interaction patterns, ensuring that the chatbot's recommendations become progressively more tailored and relevant. This component is crucial for the chatbot's ability to "learn" and "remember" user preferences over time.


3.  Internet Search and Data Retrieval: Once the user's intent and parameters are understood, and potentially enriched by the user's profile, the chatbot dynamically constructs and executes web search queries. It leverages advanced search APIs to scour the internet for relevant recipes and food recommendations. A critical feature here is the automatic detection of the user's prompt language (e.g., English, German, French, Italian, Spanish) to ensure that the search results are in the appropriate linguistic context, providing a localized and culturally relevant experience. The search is comprehensive, not limited to specific categories or predefined food types, and aims to find the best matches for any culinary query, regardless of its specificity or generality. This component would integrate with general internet search engines and web scraping capabilities in a production environment.


4.  Information Extraction: Upon retrieving web pages, this component intelligently parses the content to extract specific pieces of information. For recipes, it meticulously identifies the recipe title, a concise summary, and the original URL. Most importantly, it extracts ingredient lists, including precise quantities (e.g., "200g flour," "3 eggs," "a pinch of salt") and units per person. Beyond ingredients, the system also aims to investigate and extract nutritional data, such as calories, detailed vitamin profiles, mineral content, and other relevant macronutrients and micronutrients present in the food or recipe. This process often involves advanced parsing techniques and potentially further LLM assistance to handle the diverse and unstructured nature of web content.


5.  Recipe Generation and Formatting: The extracted raw data is then structured and formatted into a coherent and user-friendly presentation. This includes standardizing ingredient units and ensuring clarity in preparation steps. The chatbot synthesizes a summary of the food or recipe, making it easy for the user to quickly understand the dish's essence.


6.  Result Presentation and Sorting: The final output is presented in an organized manner, typically displaying each recommendation or recipe with its summary, ingredient list (if applicable), nutritional breakdown, and the source URL. Users are empowered to sort these results based on various criteria, including the name of the dish, calories per person, specific contained vitamins, other nutritional substances, or seasonality, offering personalized control over their culinary choices. The user profile can also influence the initial ranking of these results, pushing favored dishes or cuisines higher, and filtering out options that contain disliked ingredients or violate dietary restrictions.


7.  User Interface (Conceptual Description): While this article focuses on the backend logic, the user interface is envisioned to be intuitive, aesthetically pleasing, and easy to navigate. It would feature a clean input field for prompts, a clear display area for results, and interactive elements for sorting, filtering, and providing feedback (e.g., "Add to Favorites," "Dislike Ingredient"). Visual cues, such as icons for dietary tags or nutritional highlights, would enhance usability, ensuring a seamless and enjoyable user experience.


Technical Deep Dive: Bringing the Chatbot to Life with Code Examples


Let us now explore the technical implementation details, illustrating the core logic with small, well-commented Python code snippets. We will continue using the running example: a user prompt "I need a Bavarian recipe for Kaiserschmarrn" to demonstrate the flow, and also introduce how user preferences are managed. Please note that in the running example in the addendum, certain components (like general internet search and LLM-based NLU) are simulated due to the limitations of the provided tools, but the underlying logic for preference management, data structuring, and sorting is production-ready.


1. Prompt Parsing and Intent Recognition


The initial step involves taking the user's natural language prompt and converting it into a structured query. A powerful Large Language Model (LLM) is ideal for this, as it can understand nuances, identify entities, and infer intent from a vast and open-ended range of inputs. For demonstration in the code snippets, we will use a simplified `if/elif` structure to simulate the LLM's output for a few specific examples, but a production system would use a general-purpose LLM API.


import json


def parse_user_prompt(prompt: str) -> dict:

    """

    Parses a user's natural language prompt to extract intent, entities,

    and language using an underlying Large Language Model (LLM).


    Args:

        prompt (str): The user's input string, e.g., "I need a Bavarian recipe

                      for Kaiserschmarrn".


    Returns:

        dict: A dictionary containing the parsed intent, entities, and

              detected language.

              Example:

              {

                  "intent": "get_recipe",

                  "entities": {

                      "cuisine": "Bavarian",

                      "dish": "Kaiserschmarrn"

                  },

                  "language": "en" # Detected language of the prompt

              }

    """

    # In a real-world, production scenario, this function would involve an

    # API call to a powerful, general-purpose LLM (e.g., Google Gemini,

    # OpenAI GPT) with a carefully crafted prompt to extract structured

    # information from ANY user input, without hardcoded conditions.

    # The LLM would be trained to handle arbitrary food-related queries.

    # For this running example, we use a simplified simulation of the LLM's

    # response for specific prompts to keep the code concise and executable.


    prompt_lower = prompt.lower()


    if "kaiserschmarrn" in prompt_lower and "bavarian" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "cuisine": "Bavarian",

                "dish": "Kaiserschmarrn"

            },

            "language": "en"

        }

    elif "low carb" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "dietary_restriction": "low carb"

            },

            "language": "en"

        }

    elif "vitamine b" in prompt_lower:

        return {

            "intent": "find_food_for_deficiency",

            "entities": {

                "nutrient": "Vitamine B"

            },

            "language": "en"

        }

    elif "seasonal ingredients" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "seasonality": "current" # In a real system, this would infer the current season

            },

            "language": "en"

        }

    elif "light food" in prompt_lower:

        return {

            "intent": "general_food_recommendation",

            "entities": {

                "preference": "light"

            },

            "language": "en"

        }

    elif "eggplant" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "ingredient": "eggplant"

            },

            "language": "en"

        }

    elif "leftover chicken" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "main_ingredient": "chicken",

                "context": "leftovers"

            },

            "language": "en"

        }

    elif "vegetarian" in prompt_lower and "curry" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "dietary_restriction": "vegetarian",

                "dish_type": "curry"

            },

            "language": "en"

        }

    else:

        # This 'else' block represents the LLM's ability to handle any query.

        # It defaults to a general food recommendation intent, passing the

        # original prompt text for a broad search.

        return {

            "intent": "general_food_recommendation",

            "entities": {"query_text": prompt}, # Pass original prompt for broad search

            "language": "en" # Default to English if not specified or detected

        }


# Running example usage:

user_prompt = "I need a Bavarian recipe for Kaiserschmarrn"

parsed_query = parse_user_prompt(user_prompt)

print("Parsed Query:")

print(json.dumps(parsed_query, indent=2))


The `parse_user_prompt` function acts as the gateway, translating the user's free-form text into a structured dictionary. This dictionary then guides the subsequent steps of the chatbot's operation. The `intent` field specifies the primary goal, while `entities` capture specific details like cuisine or dish. The `language` field is crucial for tailoring the web search. The expanded logic demonstrates how it can handle a wider range of inputs beyond the initial examples, with the `else` clause representing its open-ended nature.


2. User Profile and Preference Management


This is a new and critical component that enables the chatbot to learn and remember. It defines how user preferences are stored and managed. This profile can then be used to filter or prioritize search results, making the application truly personalized.


from dataclasses import dataclass, field

from typing import List, Dict, Optional, Set


@dataclass

class UserProfile:

    """

    Represents a user's culinary preferences and history.

    This profile would typically be stored in a persistent database (e.g.,

    SQL, NoSQL) and loaded/saved for each user session to ensure continuity.

    This dataclass is designed to be production-ready.

    """

    user_id: str

    favorite_dishes: Set[str] = field(default_factory=set) # Set for unique dishes (lowercase)

    disliked_ingredients: Set[str] = field(default_factory=set) # Set for unique ingredients (lowercase)

    dietary_restrictions: Set[str] = field(default_factory=set) # e.g., "vegetarian", "gluten-free" (lowercase)

    preferred_cuisines: Set[str] = field(default_factory=set) # (lowercase)

    search_history: List[str] = field(default_factory=list) # Recent search queries


    def add_favorite_dish(self, dish_name: str):

        """Adds a dish to the user's list of favorites, converting to lowercase for consistency."""

        self.favorite_dishes.add(dish_name.lower())

        print(f"'{dish_name}' added to favorites for user {self.user_id}.")


    def add_disliked_ingredient(self, ingredient_name: str):

        """Adds an ingredient to the user's list of disliked items, converting to lowercase."""

        self.disliked_ingredients.add(ingredient_name.lower())

        print(f"'{ingredient_name}' added to disliked ingredients for user {self.user_id}.")


    def add_dietary_restriction(self, restriction: str):

        """Adds a dietary restriction to the user's profile, converting to lowercase."""

        self.dietary_restrictions.add(restriction.lower())

        print(f"'{restriction}' added to dietary restrictions for user {self.user_id}.")


    def update_search_history(self, query: str):

        """

        Adds a query to the user's search history, ensuring uniqueness

        and keeping the history to a reasonable size (e.g., last 10 queries).

        """

        if query not in self.search_history:

            self.search_history.append(query)

            if len(self.search_history) > 10: # Maintain a concise history

                self.search_history.pop(0) # Remove oldest entry


# Running example usage:

# Simulate loading a user profile (or creating a new one if it's a first-time user)

current_user_profile = UserProfile(user_id="siemens_employee_123")

print("\nInitial User Profile:")

print(current_user_profile)


# User interacts and provides feedback or performs searches that update the profile

current_user_profile.add_favorite_dish("Kaiserschmarrn")

current_user_profile.add_disliked_ingredient("Cilantro")

current_user_profile.add_dietary_restriction("Vegetarian")

current_user_profile.update_search_history(user_prompt) # Add the current prompt to history


print("\nUpdated User Profile:")

print(current_user_profile)


The `UserProfile` dataclass provides a structured way to store user preferences. Functions like `add_favorite_dish` allow the chatbot to learn from user feedback and implicitly from their search patterns. This profile can then be passed to the `perform_web_search` or `sort_recipes` functions to personalize results, making the application adapt to the user over time.


3. Internet Search and Content Retrieval


Once the query is parsed, and potentially informed by the user profile, the chatbot needs to search the internet. This involves dynamically constructing a search string and using a general web search API. For our demonstration, we will simulate the web search by returning a mock URL and some simplified HTML content that would typically be found on a recipe page. In a real-world, production application, this would involve calling a robust, general-purpose web search API (e.g., Google Search API) and then using a web scraping library (like BeautifulSoup) or a web content retrieval tool to fetch the page's HTML from *any* website, not just predefined ones. The `query_string` generation is now more dynamic to reflect the broader search capabilities and user preferences.


import requests # This import is for conceptual clarity; not used in this mock

from dataclasses import dataclass


@dataclass

class SearchResult:

    """Represents a single search result with its URL and content."""

    url: str

    content: str


def perform_web_search(parsed_query: dict, user_profile: UserProfile) -> list[SearchResult]:

    """

    Performs a web search based on the parsed user query and retrieves

    relevant web page content. The search query generation is influenced

    by the user profile to personalize results.


    In a real, production scenario, this function would interact with a

    general internet search engine API (e.g., Google Custom Search API,

    Bing Search API) and a web content retrieval service. The search query

    would be constructed dynamically to be as broad or specific as the

    user's prompt and preferences require.


    For this running example, we provide mock results based on the parsed

    query to illustrate the data flow, as direct integration with a general

    internet search is beyond the scope of the available tools.


    Args:

        parsed_query (dict): The structured query from parse_user_prompt.

        user_profile (UserProfile): The current user's profile for personalization.


    Returns:

        list[SearchResult]: A list of search results, each containing a URL

                            and its simulated HTML content.

    """

    intent = parsed_query.get("intent")

    entities = parsed_query.get("entities", {})

    language = parsed_query.get("language", "en")


    search_terms = []

    if intent == "get_recipe":

        search_terms.append("recipe")

        if "cuisine" in entities:

            search_terms.append(entities["cuisine"])

        elif user_profile.preferred_cuisines: # Incorporate user preferred cuisines

            search_terms.extend(list(user_profile.preferred_cuisines))

        if "dish" in entities:

            search_terms.append(entities["dish"])

        if "dish_type" in entities:

            search_terms.append(entities["dish_type"])

        if "dietary_restriction" in entities:

            search_terms.append(entities["dietary_restriction"])

        elif user_profile.dietary_restrictions: # Incorporate user dietary restrictions

            search_terms.extend(list(user_profile.dietary_restrictions))

        if "seasonality" in entities:

            search_terms.append(f"{entities['seasonality']} ingredients")

        if "ingredient" in entities:

            search_terms.append(entities["ingredient"])

        if "main_ingredient" in entities:

            search_terms.append(entities["main_ingredient"])

        if "context" in entities:

            search_terms.append(entities["context"])

    elif intent == "find_food_for_deficiency":

        search_terms.append("food rich in")

        search_terms.append(entities.get("nutrient", ""))

    elif intent == "general_food_recommendation":

        if "preference" in entities:

            search_terms.append(entities["preference"])

        if "query_text" in entities: # For broad, unspecific queries handled by LLM

            search_terms.append(entities["query_text"])


    # Filter out disliked ingredients from search terms.

    # In a real system, this might be done more effectively by filtering results

    # post-retrieval, but for query construction, we can try to exclude them.

    final_search_terms = []

    for term in search_terms:

        # Check if any part of the term is a disliked ingredient

        is_disliked = False

        for disliked_ing in user_profile.disliked_ingredients:

            if disliked_ing in term.lower():

                is_disliked = True

                break

        if not is_disliked:

            final_search_terms.append(term)


    query_string = f"{' '.join(final_search_terms)} in {language}"

    print(f"  Constructed search query: '{query_string}'")


    # --- Mocked Web Search Results ---

    # These HTML strings simulate the content that would be retrieved from

    # various recipe websites by a real web scraping component.


    mock_kaiserschmarrn_html = """

    <html>

    <head><title>Best Bavarian Kaiserschmarrn Recipe</title></head>

    <body>

        <h1>Authentic Bavarian Kaiserschmarrn</h1>

        <p>A fluffy shredded pancake, a classic Bavarian dessert, perfect for a sweet treat.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>125g all-purpose flour</li>

            <li>2 large eggs</li>

            <li>150ml milk</li>

            <li>1 tbsp granulated sugar</li>

            <li>1 pinch salt</li>

            <li>1 tbsp butter (for frying)</li>

            <li>1 tbsp raisins (optional)</li>

            <li>Powdered sugar for dusting</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Separate egg whites and yolks.</li>

            <li>Mix flour, milk, sugar, salt, and egg yolks.</li>

            <li>Beat egg whites until stiff peaks form.</li>

            <li>Gently fold egg whites into the batter.</li>

            <li>Melt butter in a pan, pour in batter.</li>

            <li>Cook until golden brown, then shred with two forks.</li>

            <li>Serve with powdered sugar and apple sauce.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 550 kcal</p>

            <p>Protein: 18g</p>

            <p>Fat: 25g</p>

            <p>Carbohydrates: 65g</p>

            <p>Vitamin B12: 0.5mcg</p>

            <p>Iron: 2mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/kaiserschmarrn-recipe">

        https://www.example.com/kaiserschmarrn-recipe</a></p>

    </body>

    </html>

    """


    mock_low_carb_chicken_html = """

    <html>

    <head><title>Delicious Low Carb Lemon Herb Chicken</title></head>

    <body>

        <h1>Low Carb Lemon Herb Chicken</h1>

        <p>A simple and flavorful low-carb chicken dish, great for a healthy dinner.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>150g boneless, skinless chicken breast</li>

            <li>1 tbsp olive oil</li>

            <li>1 lemon, sliced</li>

            <li>1 tsp dried mixed herbs</li>

            <li>1/2 tsp garlic powder</li>

            <li>Salt and pepper to taste</li>

            <li>50g broccoli florets</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Season chicken with salt, pepper, herbs, and garlic powder.</li>

            <li>Heat olive oil in a pan.</li>

            <li>Cook chicken until golden and cooked through.</li>

            <li>Add lemon slices and broccoli, cook until tender-crisp.</li>

            <li>Serve immediately.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 350 kcal</p>

            <p>Protein: 35g</p>

            <p>Fat: 20g</p>

            <p>Carbohydrates: 5g</p>

            <p>Vitamin C: 30mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/low-carb-chicken">

        https://www.example.com/low-carb-chicken</a></p>

    </body>

    </html>

    """


    mock_vitamin_b_food_html = """

    <html>

    <head><title>Foods Rich in B Vitamins</title></head>

    <body>

        <h1>Top Foods Rich in B Vitamins</h1>

        <p>A list of excellent sources to boost your B vitamin intake.</p>

        <h2>Recommendations:</h2>

        <ul>

            <li>Salmon: High in B12, B6, Niacin</li>

            <li>Eggs: Contains B7, B5, B12</li>

            <li>Beef Liver: Extremely rich in B12, Folate, Riboflavin</li>

            <li>Leafy Greens (Spinach, Kale): Good source of Folate</li>

            <li>Legumes (Lentils, Chickpeas): Provide Folate, Thiamine</li>

        </ul>

        <div class="nutrition">

            <h3>General Nutritional Notes:</h3>

            <p>B Vitamins are crucial for energy metabolism and nerve function.</p>

            <p>Salmon (100g): B12: 2.8mcg, B6: 0.6mg</p>

            <p>Egg (1 large): B12: 0.45mcg, B7: 10mcg</p>

        </div>

        <p>Source: <a href="https://www.healthysites.com/b-vitamins">

        https://www.healthysites.com/b-vitamins</a></p>

    </body>

    </html>

    """


    mock_eggplant_parmesan_html = """

    <html>

    <head><title>Classic Eggplant Parmesan Recipe</title></head>

    <body>

        <h1>Eggplant Parmesan</h1>

        <p>A delicious Italian-American dish made with layers of fried eggplant, cheese, and tomato sauce.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>1 large eggplant</li>

            <li>100g mozzarella cheese, sliced</li>

            <li>50g parmesan cheese, grated</li>

            <li>150ml tomato sauce</li>

            <li>1 egg, beaten</li>

            <li>50g breadcrumbs</li>

            <li>2 tbsp olive oil</li>

            <li>Salt and pepper to taste</li>

            <li>5g fresh basil</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Slice eggplant, salt, and let sit to draw out moisture.</li>

            <li>Dip eggplant slices in egg, then breadcrumbs.</li>

            <li>Fry eggplant slices in olive oil until golden.</li>

            <li>Layer eggplant, tomato sauce, mozzarella, and parmesan in a baking dish.</li>

            <li>Bake at 180C for 20-25 minutes until bubbly and golden.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 480 kcal</p>

            <p>Protein: 25g</p>

            <p>Fat: 30g</p>

            <p>Carbohydrates: 30g</p>

            <p>Vitamin C: 15mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/eggplant-parmesan">

        https://www.example.com/eggplant-parmesan</a></p>

    </body>

    </html>

    """

    mock_vegetarian_curry_html = """

    <html>

    <head><title>Hearty Vegetarian Lentil Curry</title></head>

    <body>

        <h1>Vegetarian Lentil Curry</h1>

        <p>A flavorful and protein-rich vegetarian curry, perfect for a healthy meal.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>100g red lentils</li>

            <li>1 onion, chopped</li>

            <li>2 cloves garlic, minced</li>

            <li>1 tbsp ginger, grated</li>

            <li>1 tsp curry powder</li>

            <li>400ml coconut milk</li>

            <li>100g spinach</li>

            <li>Salt to taste</li>

            <li>1 tbsp olive oil</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Sauté onion, garlic, ginger in olive oil.</li>

            <li>Add curry powder and lentils, stir well.</li>

            <li>Pour in coconut milk and 200ml water, simmer until lentils are tender.</li>

            <li>Stir in spinach until wilted.</li>

            <li>Season with salt and serve with rice.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 450 kcal</p>

            <p>Protein: 20g</p>

            <p>Fat: 28g</p>

            <p>Carbohydrates: 35g</p>

            <p>Iron: 4mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/lentil-curry">

        https://www.example.com/lentil-curry</a></p>

    </body>

    </html>

    """


    # Logic to return mock HTML based on query_string.

    # In a real system, this would be dynamic and fetch from actual web.

    if "kaiserschmarrn" in query_string.lower():

        return [SearchResult(

            url="https://www.example.com/kaiserschmarrn-recipe",

            content=mock_kaiserschmarrn_html

        )]

    elif "low carb" in query_string.lower() and "chicken" in query_string.lower() and "vegetarian" not in user_profile.dietary_restrictions:

        # Only return chicken if user is not vegetarian

        return [SearchResult(

            url="https://www.example.com/low-carb-chicken",

            content=mock_low_carb_chicken_html

        )]

    elif "vitamine b" in query_string.lower():

        return [SearchResult(

            url="https://www.healthysites.com/b-vitamins",

            content=mock_vitamin_b_food_html

        )]

    elif "eggplant" in query_string.lower() and "recipe" in query_string.lower():

        return [SearchResult(

            url="https://www.example.com/eggplant-parmesan",

            content=mock_eggplant_parmesan_html

        )]

    elif ("vegetarian" in query_string.lower() or "vegetarian" in user_profile.dietary_restrictions) and "curry" in query_string.lower():

        # If user is vegetarian or query asks for vegetarian, and mentions curry

        return [SearchResult(

            url="https://www.example.com/lentil-curry",

            content=mock_vegetarian_curry_html

        )]

    else:

        # Fallback for other queries, for demonstration purposes.

        # A real system would perform a generic web search and return diverse results.

        print(f"  Simulating generic search for: '{query_string}' - No specific mock found.")

        return []



def extract_recipe_details(html_content: str, parsed_query: dict) -> Optional[Recipe]:

    """

    Extracts recipe details (name, summary, ingredients, nutrition) from

    simulated HTML content. This function is tailored for recipe pages.


    In a real, production scenario, this would use a robust HTML parser

    like BeautifulSoup combined with advanced pattern recognition or

    even another LLM pass to navigate the DOM and extract information

    reliably from diverse website structures.

    Here, we use regex and string manipulation on the mock HTML for

    demonstration, which is fragile and highly dependent on the exact

    HTML structure of the mock data.


    Args:

        html_content (str): The HTML content of a recipe page.

        parsed_query (dict): The structured query for context (e.g., cuisine).


    Returns:

        Optional[Recipe]: A Recipe object if extraction is successful,

                          otherwise None.

    """

    recipe_name_match = re.search(r"<h1>(.*?)<\/h1>", html_content)

    recipe_name = recipe_name_match.group(1).strip() if recipe_name_match else "Unknown Recipe"


    summary_match = re.search(r"<p>(.*?)</p>", html_content, re.DOTALL)

    summary = summary_match.group(1).strip() if summary_match else "No summary available."


    url_match = re.search(r'Source: <a href="(.*?)">', html_content)

    url = url_match.group(1) if url_match else "No URL found."


    ingredients_raw_match = re.search(r"<h2>Ingredients \(per person\):</h2>\s*<ul>(.*?)<\/ul>", html_content, re.DOTALL)

    ingredients_list_raw = ingredients_raw_match.group(1) if ingredients_raw_match else ""


    ingredients: List[Ingredient] = []

    for item_match in re.finditer(r"<li>(.*?)<\/li>", ingredients_list_raw):

        item_text = item_match.group(1).strip()

        # Attempt to parse quantity, unit, and name.

        # This regex is an example and might need refinement for real-world variety.

        ingredient_match = re.match(r"(\d+\.?\d*)\s*([a-zA-Z]+)?\s*(.*)", item_text)

        if ingredient_match:

            quantity_str, unit_str, name_str = ingredient_match.groups()

            try:

                quantity = float(quantity_str)

                unit = unit_str.strip() if unit_str else "item" # Default unit if not specified

                name = name_str.strip()

                ingredients.append(Ingredient(name=name, quantity=quantity, unit=unit))

            except ValueError:

                # Fallback for complex cases like "a pinch of salt" where quantity isn't a simple float

                ingredients.append(Ingredient(name=item_text, quantity=0, unit=""))

        else:

            # If regex fails, add the whole text as an ingredient without specific quantity/unit

            ingredients.append(Ingredient(name=item_text, quantity=0, unit=""))


    # Extract nutritional information

    nutrition_info = NutritionInfo()

    nutrition_section_match = re.search(r'<div class="nutrition">(.*?)</div>', html_content, re.DOTALL)

    if nutrition_section_match:

        nutrition_text = nutrition_section_match.group(1)

        calories_match = re.search(r"Calories:\s*(\d+)\s*kcal", nutrition_text)

        if calories_match: nutrition_info.calories = int(calories_match.group(1))

        protein_match = re.search(r"Protein:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if protein_match: nutrition_info.protein_g = float(protein_match.group(1))

        fat_match = re.search(r"Fat:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if fat_match: nutrition_info.fat_g = float(fat_match.group(1))

        carbs_match = re.search(r"Carbohydrates:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if carbs_match: nutrition_info.carbohydrates_g = float(carbs_match.group(1))

        vitamin_b12_match = re.search(r"Vitamin B12:\s*(\d+\.?\d*)\s*mcg", nutrition_text)

        if vitamin_b12_match: nutrition_info.vitamin_b12_mcg = float(vitamin_b12_match.group(1))

        vitamin_c_match = re.search(r"Vitamin C:\s*(\d+\.?\d*)\s*mg", nutrition_text)

        if vitamin_c_match: nutrition_info.vitamin_c_mg = float(vitamin_c_match.group(1))

        iron_match = re.search(r"Iron:\s*(\d+\.?\d*)\s*mg", nutrition_text)

        if iron_match: nutrition_info.iron_mg = float(iron_match.group(1))


    # Determine cuisine and seasonality from parsed query for the Recipe object

    cuisine = parsed_query.get("entities", {}).get("cuisine")

    seasonality = parsed_query.get("entities", {}).get("seasonality")


    return Recipe(

        name=recipe_name,

        summary=summary,

        url=url,

        ingredients=ingredients,

        nutrition=nutrition_info,

        cuisine=cuisine,

        seasonality=seasonality

    )



def extract_food_recommendations(html_content: str, parsed_query: dict) -> List[Recipe]:

    """

    Extracts general food recommendations (not full recipes) from HTML content.

    This is for intents like "foods rich in Vitamine B".


    In a real production system, this would use robust parsing techniques

    or LLMs to extract structured recommendations from varied web content.


    Args:

        html_content (str): The HTML content of a recommendation page.

        parsed_query (dict): The structured query for context.


    Returns:

        List[Recipe]: A list of simplified Recipe objects representing recommended foods.

    """

    recommendations: List[Recipe] = []

    nutrient = parsed_query.get("entities", {}).get("nutrient", "various nutrients")


    # Extract general title

    page_title_match = re.search(r"<h1>(.*?)<\/h1>", html_content)

    page_title = page_title_match.group(1).strip() if page_title_match else "Food Recommendations"


    # Extract source URL

    url_match = re.search(r'Source: <a href="(.*?)">', html_content)

    url = url_match.group(1) if url_match else "No URL found."


    # Look for lists of recommendations

    recommendations_list_raw_match = re.search(r"<h2>Recommendations:</h2>\s*<ul>(.*?)<\/ul>", html_content, re.DOTALL)

    if recommendations_list_raw_match:

        recommendations_list_raw = recommendations_list_raw_match.group(1)

        for item_match in re.finditer(r"<li>(.*?)<\/li>", recommendations_list_raw):

            item_text = item_match.group(1).strip()

            # Try to parse food name and associated nutrients

            food_name_match = re.match(r"([^:]+):", item_text)

            food_name = food_name_match.group(1).strip() if food_name_match else item_text

            summary_text = f"Recommended for {nutrient}. {item_text}"


            # Simplified nutrition extraction for recommendations

            item_nutrition = NutritionInfo()

            if "B12:" in item_text:

                b12_match = re.search(r"B12:\s*(\d+\.?\d*)mcg", item_text)

                if b12_match: item_nutrition.vitamin_b12_mcg = float(b12_match.group(1))

            if "B6:" in item_text:

                b6_match = re.search(r"B6:\s*(\d+\.?\d*)mg", item_text)

                # In a production system, B6 would have its own field in NutritionInfo

                # For this example, we'll just parse B12.

            # Could add more specific parsing for other nutrients here


            recommendations.append(Recipe(

                name=food_name,

                summary=summary_text,

                url=url,

                ingredients=[], # No specific ingredients for general recommendations

                nutrition=item_nutrition

            ))

    return recommendations



def standardize_ingredients(ingredients: List[Ingredient]) -> List[Ingredient]:

    """

    Standardizes ingredient quantities and units to a common format.

    This function is designed to be production-ready for unit conversion.

    This is a simplified example; a real system would have extensive

    conversion tables and parsing rules, possibly using a dedicated unit

    conversion library.


    Args:

        ingredients (List[Ingredient]): A list of raw Ingredient objects.


    Returns:

        List[Ingredient]: A list of standardized Ingredient objects.

    """

    standardized: List[Ingredient] = []

    for ing in ingredients:

        # Convert common units to a base (e.g., g, ml, pieces)

        if ing.unit.lower() == "tbsp":

            # Approximate conversions for common items

            if "sugar" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 12.5, unit="g"))

            elif "butter" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 14, unit="g"))

            elif "oil" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 15, unit="ml"))

            else:

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="tbsp")) # Keep if no specific conversion

        elif ing.unit.lower() == "tsp":

            if "sugar" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 4, unit="g"))

            else:

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="tsp"))

        elif ing.unit.lower() == "g" or ing.unit.lower() == "ml":

            standardized.append(ing) # Already standard

        elif ing.unit.lower() == "large": # For items like eggs

            standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="item (large)"))

        elif ing.unit.lower() == "pinch":

            standardized.append(Ingredient(name=ing.name, quantity=0.5, unit="g")) # Approximate conversion for a pinch

        else:

            standardized.append(ing) # Keep original if unit is unknown or already appropriate

    return standardized



def sort_recipes(recipes: List[Recipe], sort_key: str, ascending: bool = True,

                 user_profile: Optional[UserProfile] = None) -> List[Recipe]:

    """

    Sorts a list of Recipe objects based on a specified key, with optional

    personalization based on user profile. This function is designed to be

    production-ready for sorting and filtering.


    Args:

        recipes (List[Recipe]): A list of Recipe objects to be sorted.

        sort_key (str): The attribute name to sort by (e.g., "name",

                        "calories", "protein", "vitamin_b12", "cuisine").

        ascending (bool): True for ascending order, False for descending.

        user_profile (Optional[UserProfile]): The current user's profile for

                                               personalization, used for filtering

                                               and prioritizing.


    Returns:

        List[Recipe]: The sorted list of Recipe objects, filtered by user preferences.

    """

    if not recipes:

        return []


    # Filter out recipes containing disliked ingredients or violating dietary restrictions

    filtered_recipes = []

    for recipe in recipes:

        # Check for disliked ingredients

        contains_disliked = False

        if user_profile and user_profile.disliked_ingredients:

            for ing in recipe.ingredients:

                if ing.name.lower() in user_profile.disliked_ingredients:

                    contains_disliked = True

                    break

        if contains_disliked:

            continue # Skip this recipe if it contains a disliked ingredient


        # Check for dietary restrictions (simplified example for demonstration)

        violates_restriction = False

        if user_profile and user_profile.dietary_restrictions:

            # Example: If user is vegetarian, filter out recipes with common meat names

            if "vegetarian" in user_profile.dietary_restrictions:

                # Check recipe name for meat keywords

                if any(meat_keyword in recipe.name.lower() for meat_keyword in ["chicken", "beef", "pork", "salmon", "tuna"]):

                    violates_restriction = True

                else:

                    # Also check ingredients for meat if not already caught by name

                    for ing in recipe.ingredients:

                        if any(meat_keyword in ing.name.lower() for meat_keyword in ["chicken", "beef", "pork", "salmon", "tuna"]):

                            violates_restriction = True

                            break

            # Add more complex logic for other restrictions (e.g., gluten-free, vegan)

        if violates_restriction:

            continue # Skip this recipe if it violates a dietary restriction


        filtered_recipes.append(recipe)


    if not filtered_recipes:

        print(f"  No recipes remain after applying user filters for sort key '{sort_key}'.")

        return []


    def get_sort_value(recipe: Recipe) -> Any:

        # Default value for missing numerical data to push them to the end

        # for ascending sort, or beginning for descending sort.

        default_num_val = float('inf') if ascending else float('-inf')


        # Personalization factor: Prioritize favorite dishes

        # This is a simple example; a real production system would use a more

        # sophisticated scoring algorithm that might combine multiple factors

        # (e.g., recency of interaction, preference strength, cuisine match).

        personalization_priority = 0

        if user_profile and recipe.name.lower() in user_profile.favorite_dishes:

            personalization_priority = -1000000000 # Give a very high priority for favorites


        # Return a tuple for sorting: (personalization_priority, actual_sort_value)

        # This ensures favorites are always at the top, then sorted by the main key.

        if sort_key == "name":

            return (personalization_priority, recipe.name.lower())

        elif sort_key == "calories":

            return (personalization_priority, recipe.nutrition.calories if recipe.nutrition.calories is not None else default_num_val)

        elif sort_key == "protein":

            return (personalization_priority, recipe.nutrition.protein_g if recipe.nutrition.protein_g is not None else default_num_val)

        elif sort_key == "fat":

            return (personalization_priority, recipe.nutrition.fat_g if recipe.nutrition.fat_g is not None else default_num_val)

        elif sort_key == "carbohydrates":

            return (personalization_priority, recipe.nutrition.carbohydrates_g if recipe.nutrition.carbohydrates_g is not None else default_num_val)

        elif sort_key == "vitamin_b12":

            return (personalization_priority, recipe.nutrition.vitamin_b12_mcg if recipe.nutrition.vitamin_b12_mcg is not None else default_num_val)

        elif sort_key == "vitamin_c":

            return (personalization_priority, recipe.nutrition.vitamin_c_mg if recipe.nutrition.vitamin_c_mg is not None else default_num_val)

        elif sort_key == "iron":

            return (personalization_priority, recipe.nutrition.iron_mg if recipe.nutrition.iron_mg is not None else default_num_val)

        elif sort_key == "cuisine":

            return (personalization_priority, recipe.cuisine.lower() if recipe.cuisine else "")

        elif sort_key == "seasonality":

            return (personalization_priority, recipe.seasonality.lower() if recipe.seasonality else "")

        else:

            print(f"Warning: Unsupported sort key '{sort_key}'. Defaulting to name sort.")

            return (personalization_priority, recipe.name.lower())


    return sorted(filtered_recipes, key=get_sort_value, reverse=not ascending)



# ------------------------------------------------------------------------------

# 3. Main Chatbot Orchestration Logic

# ------------------------------------------------------------------------------


def run_culinary_chatbot(user_prompt: str, user_profile: UserProfile) -> List[Recipe]:

    """

    Orchestrates the entire chatbot process from user prompt to sorted results.

    This function is designed to be production-ready, integrating the various

    components.


    Args:

        user_prompt (str): The initial query from the user.

        user_profile (UserProfile): The current user's profile for personalization.


    Returns:

        List[Recipe]: A list of processed and potentially sorted Recipe objects.

    """

    print(f"\n--- Processing User Prompt: '{user_prompt}' ---")

    user_profile.update_search_history(user_prompt)


    # Step 1: Parse the user's prompt using the (simulated) LLM

    parsed_query = parse_user_prompt(user_prompt)

    print("Parsed Query:")

    print(json.dumps(parsed_query, indent=2))


    # Step 2: Perform web search based on the parsed query and user profile

    search_results = perform_web_search(parsed_query, user_profile)


    if not search_results:

        print("\nNo relevant search results found.")

        return []


    processed_recipes: List[Recipe] = []

    for result in search_results:

        print(f"\n--- Processing Search Result from: {result.url} ---")

        if parsed_query["intent"] == "get_recipe":

            # Step 3: Extract recipe details from the web content

            extracted_recipe = extract_recipe_details(result.content, parsed_query)

            if extracted_recipe:

                # Step 4: Standardize ingredients

                extracted_recipe.ingredients = standardize_ingredients(extracted_recipe.ingredients)

                processed_recipes.append(extracted_recipe)

                print("  Successfully extracted and standardized recipe.")

            else:

                print("  Failed to extract specific recipe details from this source.")

        elif parsed_query["intent"] == "find_food_for_deficiency" or \

             parsed_query["intent"] == "general_food_recommendation":

            # Step 3 (alternative): Extract general food recommendations

            extracted_recommendations = extract_food_recommendations(result.content, parsed_query)

            if extracted_recommendations:

                processed_recipes.extend(extracted_recommendations)

                print(f"  Successfully extracted {len(extracted_recommendations)} food recommendations.")

            else:

                print("  Failed to extract general food recommendations from this source.")

        else:

            print(f"  Unsupported intent for extraction: {parsed_query['intent']}")


    return processed_recipes



def display_recipes(recipes: List[Recipe], title: str):

    """Helper function to display a list of recipes in a readable format."""

    print(f"\n{title}:")

    if not recipes:

        print("  No recipes to display.")

        return


    for i, recipe in enumerate(recipes):

        print(f"\n--- {i+1}. {recipe.name} ---")

        print(f"  Summary: {recipe.summary}")

        print(f"  URL: {recipe.url}")

        if recipe.cuisine:

            print(f"  Cuisine: {recipe.cuisine.capitalize()}")

        if recipe.seasonality:

            print(f"  Seasonality: {recipe.seasonality.capitalize()}")


        if recipe.ingredients:

            print("  Ingredients (per person):")

            for ing in recipe.ingredients:

                # Format quantity to avoid .0 if it's a whole number

                qty_str = str(ing.quantity)

                if qty_str.endswith('.0'):

                    qty_str = qty_str[:-2]

                print(f"    - {qty_str}{ing.unit} {ing.name}")

        else:

            print("  No specific ingredients listed (general recommendation).")



        print("  Nutritional Information (Approx. per serving):")

        if recipe.nutrition.calories is not None:

            print(f"    Calories: {recipe.nutrition.calories} kcal")

        if recipe.nutrition.protein_g is not None:

            print(f"    Protein: {recipe.nutrition.protein_g} g")

        if recipe.nutrition.fat_g is not None:

            print(f"    Fat: {recipe.nutrition.fat_g} g")

        if recipe.nutrition.carbohydrates_g is not None:

            print(f"    Carbohydrates: {recipe.nutrition.carbohydrates_g} g")

        if recipe.nutrition.vitamin_b12_mcg is not None:

            print(f"    Vitamin B12: {recipe.nutrition.vitamin_b12_mcg} mcg")

        if recipe.nutrition.vitamin_c_mg is not None:

            print(f"    Vitamin C: {recipe.nutrition.vitamin_c_mg} mg")

        if recipe.nutrition.iron_mg is not None:

            print(f"    Iron: {recipe.nutrition.iron_mg} mg")

        if all(v is None for v in [recipe.nutrition.calories, recipe.nutrition.protein_g,

                                   recipe.nutrition.fat_g, recipe.nutrition.carbohydrates_g,

                                   recipe.nutrition.vitamin_b12_mcg, recipe.nutrition.vitamin_c_mg,

                                   recipe.nutrition.iron_mg]):

            print("    No detailed nutritional information available.")



# ------------------------------------------------------------------------------

# 4. Running Example Execution

# ------------------------------------------------------------------------------


if __name__ == "__main__":

    # Initialize a user profile. In a real application, this would be loaded

    # from a database based on the authenticated user.

    my_user_profile = UserProfile(user_id="siemens_employee_123")

    my_user_profile.add_favorite_dish("Kaiserschmarrn")

    my_user_profile.add_disliked_ingredient("Cilantro")

    my_user_profile.add_dietary_restriction("vegetarian")

    my_user_profile.preferred_cuisines.add("italian") # User prefers Italian cuisine


    print("\n--- Initial User Profile ---")

    print(my_user_profile)


    # Example 1: Bavarian Kaiserschmarrn recipe (should be prioritized due to favorites)

    user_prompt_1 = "I need a Bavarian recipe for Kaiserschmarrn"

    recipes_1 = run_culinary_chatbot(user_prompt_1, my_user_profile)

    display_recipes(recipes_1, "Results for 'Bavarian Kaiserschmarrn'")


    # Example 2: Low carb food (should be filtered for vegetarian)

    user_prompt_2 = "I need a recipe for low carb food"

    recipes_2 = run_culinary_chatbot(user_prompt_2, my_user_profile)

    # The mock perform_web_search will not return the chicken recipe if vegetarian

    # is in dietary_restrictions, demonstrating filtering at search level.

    display_recipes(recipes_2, "Results for 'Low Carb Food' (should be empty if vegetarian)")


    # Example 3: Food for Vitamin B deficiency

    user_prompt_3 = "What food helps me reduce my deficiency of Vitamine B"

    recipes_3 = run_culinary_chatbot(user_prompt_3, my_user_profile)

    display_recipes(recipes_3, "Results for 'Food for Vitamin B Deficiency'")


    # Example 4: Recipe with a disliked ingredient (Apfelstrudel contains Cilantro in dummy data, but

    # the mock web search might not return it directly. Let's test filtering in sort_recipes).

    # We'll use a generic query to get a broader set of recipes including the dummy ones.

    user_prompt_4 = "Show me some food ideas"

    recipes_4 = run_culinary_chatbot(user_prompt_4, my_user_profile)

    display_recipes(recipes_4, "Results for 'Show me some food ideas' (Generic Search)")


    # Example 5: Vegetarian Curry (should match user's dietary restriction)

    user_prompt_5 = "I want a vegetarian curry recipe"

    recipes_5 = run_culinary_chatbot(user_prompt_5, my_user_profile)

    display_recipes(recipes_5, "Results for 'Vegetarian Curry'")



    # Combine all found recipes for a comprehensive sorting demonstration

    all_recipes_for_sorting = []

    if recipes_1: all_recipes_for_sorting.extend(recipes_1)

    if recipes_2: all_recipes_for_sorting.extend(recipes_2)

    if recipes_3: all_recipes_for_sorting.extend(recipes_3)

    # Add some dummy recipes to ensure filtering by disliked ingredients is shown

    all_recipes_for_sorting.extend([

        Recipe(

            name="Apfelstrudel", summary="Apple pastry", url="http://example.com/apfel",

            ingredients=[Ingredient(name="Cilantro", quantity=10, unit="g")], # Contains disliked ingredient

            nutrition=NutritionInfo(calories=400, protein_g=5, vitamin_b12_mcg=0.1),

            cuisine="Austrian"

        ),

        Recipe(

            name="Goulash", summary="Hearty stew", url="http://example.com/goulash",

            ingredients=[Ingredient(name="Beef", quantity=200, unit="g")], # Violates vegetarian restriction

            nutrition=NutritionInfo(calories=600, protein_g=30, vitamin_b12_mcg=1.2),

            cuisine="Hungarian"

        ),

        Recipe(

            name="Chicken Stir-fry", summary="Quick chicken and veggie stir-fry", url="http://example.com/stirfry",

            ingredients=[Ingredient(name="Chicken breast", quantity=150, unit="g")], # Violates vegetarian restriction

            nutrition=NutritionInfo(calories=380, protein_g=35, vitamin_c_mg=20),

            cuisine="Asian"

        ),

        Recipe(

            name="Vegetable Lasagna", summary="Classic Italian-style vegetable lasagna", url="http://example.com/lasagna",

            ingredients=[Ingredient(name="Zucchini", quantity=100, unit="g")],

            nutrition=NutritionInfo(calories=420, protein_g=15, vitamin_c_mg=10),

            cuisine="Italian"

        )

    ])

    if recipes_4: all_recipes_for_sorting.extend(recipes_4)

    if recipes_5: all_recipes_for_sorting.extend(recipes_5)


    # Remove duplicates from the combined list for cleaner sorting demo

    unique_recipes = {}

    for recipe in all_recipes_for_sorting:

        unique_recipes[recipe.name.lower()] = recipe

    all_recipes_for_sorting = list(unique_recipes.values())



    if all_recipes_for_sorting:

        print("\n--- Sorting All Collected Recipes with User Profile ---")


        # Sort by calories ascending, showing personalization (Kaiserschmarrn first, then calories)

        # and filtering (no Cilantro, no meat for vegetarian)

        sorted_by_calories_asc = sort_recipes(all_recipes_for_sorting, "calories", ascending=True, user_profile=my_user_profile)

        display_recipes(sorted_by_calories_asc, "All Results Sorted by Calories (Ascending, with personalization)")


        # Sort by protein descending, showing personalization and filtering

        sorted_by_protein_desc = sort_recipes(all_recipes_for_sorting, "protein", ascending=False, user_profile=my_user_profile)

        display_recipes(sorted_by_protein_desc, "All Results Sorted by Protein (Descending, with personalization)")


        # Sort by name ascending, showing personalization and filtering

        sorted_by_name_asc = sort_recipes(all_recipes_for_sorting, "name", ascending=True, user_profile=my_user_profile)

        display_recipes(sorted_by_name_asc, "All Results Sorted by Name (Ascending, with personalization)")


    print("\n--- Final User Profile State ---")

    print(my_user_profile)



Real-World Implementation of External Integrations


While the running example effectively demonstrates the logical flow of the culinary chatbot, a production-grade application requires robust integrations with external services to achieve its full potential. Here, we outline how the simulated components would be replaced by real-world solutions.


1.  Large Language Model (LLM) for Natural Language Understanding: The `parse_user_prompt` function, currently a simplified `if/elif` structure, would be replaced by an API call to a powerful, general-purpose LLM. Services like Google's Gemini API or OpenAI's GPT API offer sophisticated natural language processing capabilities. The LLM would be prompted to extract the user's intent, entities (e.g., dish names, ingredients, dietary needs, cuisine types, preferences), and the language of the prompt. This involves crafting effective system prompts that guide the LLM to return structured JSON output, which can then be easily consumed by the chatbot's backend. This approach ensures that the chatbot can handle an arbitrary and ever-evolving range of user queries without needing explicit code changes for each new type of request.


    Example of a conceptual LLM API call:

    

    # In a real system, this would be an API call to a service like Google Gemini

    # or OpenAI GPT, with appropriate authentication and prompt engineering.

    # The LLM would be instructed to return a JSON object.

    # llm_response = llm_api_client.generate_content(

    #     f"Analyze the following user query and extract the intent, entities "

    #     f"(e.g., dish, cuisine, dietary_restriction, ingredient, nutrient, preference, context), "

    #     f"and language. Return the output as a JSON object. Query: '{prompt}'"

    # )

    # parsed_data = json.loads(llm_response.text)

    # return parsed_data

    


2.  General Internet Search and Web Scraping: The `perform_web_search` function, which currently returns mock HTML, would integrate with a general internet search engine API (e.g., Google Custom Search API, Bing Search API) to find relevant web pages (recipes, food recommendations, nutritional information sites). Once URLs are identified, a dedicated web scraping component would be used to fetch the actual HTML content. Libraries like `BeautifulSoup` (for parsing HTML) and `requests` (for making HTTP requests) in Python are standard tools for this. For more complex or large-scale scraping, headless browsers (e.g., Selenium, Playwright) or specialized web scraping services might be employed to handle dynamic content and anti-scraping measures. The `mcp_abd9f108_siemens_get_web_content` tool, while useful for Siemens-specific content, would not be applicable for general internet recipe sites.


    Example of conceptual web search and scraping:

    

    # Using a general search API to get relevant URLs

    # search_api_results = google_search_api.search(query_string)

    # urls_to_scrape = [result.url for result in search_api_results]


    # For each URL, fetch and parse content

    # scraped_results = []

    # for url in urls_to_scrape:

    #     try:

    #         response = requests.get(url)

    #         response.raise_for_status() # Raise an exception for HTTP errors

    #         soup = BeautifulSoup(response.text, 'html.parser')

    #         # Further parsing with BeautifulSoup to extract structured data

    #         # This would replace the regex in extract_recipe_details

    #         scraped_results.append(SearchResult(url=url, content=str(soup)))

    #     except requests.exceptions.RequestException as e:

    #         print(f"Error fetching {url}: {e}")

    # return scraped_results

    


3.  External Nutritional APIs: The `NutritionInfo` extraction from web pages is often incomplete or inconsistent. For a production system, after extracting ingredients from a recipe, these ingredients would be sent to a specialized nutritional API (e.g., Edamam Nutrition Analysis API, USDA FoodData Central, FatSecret API). These APIs can provide comprehensive and standardized nutritional breakdowns for ingredients or entire recipes, significantly enhancing the accuracy and detail of the nutritional information presented to the user. This would also aid in standardizing ingredient names and units for more accurate calculations.


    Example of conceptual nutritional API integration:

    

    # After extracting ingredients from a recipe

    # nutrition_api_client = EdamamNutritionClient(app_id="...", app_key="...")

    # nutrition_data = nutrition_api_client.get_nutrition_for_ingredients(

    #     [f"{ing.quantity}{ing.unit} {ing.name}" for ing in recipe.ingredients]

    # )

    # # Update recipe.nutrition with data from the API

    


4.  User Profile Persistence: The `UserProfile` object, as defined, is suitable for in-memory use during a single session. In a production environment, user profiles must be persisted to a database to ensure that preferences are remembered across sessions and devices.

    *   NoSQL Databases (e.g., MongoDB, Firestore, DynamoDB) are well-suited for storing flexible, JSON-like user profile data, allowing for easy addition of new preference fields without schema migrations.

    *   SQL Databases (e.g., PostgreSQL, MySQL) could also be used, with user preferences stored in structured tables or JSONB columns.

    The choice depends on the overall architecture and existing data infrastructure. Appropriate data access layers (DAOs/Repositories) would handle saving and loading user profiles securely and efficiently.


Deployment and Scalability Considerations


Building a production-ready culinary chatbot involves not just the core logic but also careful planning for deployment, scalability, reliability, and security.


1.  Cloud Infrastructure: The chatbot's various components (LLM integration, web scraping, backend API, database) would typically be deployed on a cloud platform such as Google Cloud Platform (GCP), Amazon Web Services (AWS), or Microsoft Azure.

    *   Compute: Services like Google Cloud Run, AWS Lambda, or Kubernetes Engine could host the stateless backend logic, enabling automatic scaling based on demand.

    *   Database: Managed database services (e.g., Cloud Firestore/Datastore or Cloud SQL on GCP; DynamoDB or RDS on AWS) would handle user profile persistence and other operational data.

    *   Storage: Object storage (e.g., Google Cloud Storage, AWS S3) could be used for caching scraped web content or storing large datasets.


2.  API Management and Gateway: An API Gateway (e.g., Google Cloud Endpoints, AWS API Gateway) would sit in front of the backend services. This provides a single entry point for user requests, handles authentication and authorization, rate limiting, and request routing, ensuring secure and controlled access to the chatbot's functionality.


3.  Caching Strategies: To improve performance and reduce costs associated with external API calls (LLM, search, nutritional APIs) and web scraping, aggressive caching would be implemented.

    *   Search Results Cache: Store results for common queries for a short period.

    *   Recipe Data Cache: Store fully extracted and processed recipe data (including nutritional info) in a fast cache (e.g., Redis, Memcached) to avoid reprocessing or re-scraping frequently requested recipes.

    *   LLM Response Cache: Cache responses from the LLM for identical NLU requests.


4.  Asynchronous Processing and Message Queues: Web scraping and nutritional API calls can be time-consuming. To prevent the user interface from freezing or timing out, these operations should be handled asynchronously using message queues (e.g., Google Cloud Pub/Sub, AWS SQS/SNS, Apache Kafka). The main API would quickly respond to the user with initial results, while detailed processing happens in the background. Users could then be notified or see updated results as they become available.


5.  Security and Data Privacy: Given that user profiles contain personal preferences, robust security and data privacy measures are paramount.

    *   Encryption: All data at rest and in transit must be encrypted.

    *   Access Control: Strict role-based access control (RBAC) should be implemented for all services and data.

    *   Compliance: Adherence to relevant data protection regulations (e.g., GDPR, CCPA) is essential. User consent mechanisms for data collection and usage must be in place.

    *   API Key Management: Securely manage API keys for external services, using secrets management tools (e.g., Google Secret Manager, AWS Secrets Manager).


6.  Monitoring and Logging: Comprehensive monitoring and logging (e.g., Google Cloud Monitoring/Logging, AWS CloudWatch) are crucial for understanding system performance, detecting errors, and diagnosing issues in real-time. This includes tracking API latencies, error rates, resource utilization, and user interaction patterns.


7.  Continuous Integration/Continuous Deployment (CI/CD): Automated CI/CD pipelines would ensure that code changes are thoroughly tested and deployed reliably and frequently, enabling rapid iteration and improvement of the chatbot.


Conclusion


The LLM-powered culinary chatbot represents a significant leap forward in personalizing and simplifying the food discovery process. By seamlessly integrating advanced natural language understanding, intelligent web search, precise information extraction, and a dynamic user profile management system, it transforms vague culinary desires into concrete, actionable recipes and recommendations. The ability to handle an open-ended range of queries, delve into nutritional details, standardize ingredient lists, and offer flexible sorting options empowers users to make informed choices that align with their health goals and preferences. Furthermore, the chatbot's capacity to learn and adapt to individual tastes over time ensures an increasingly relevant and delightful user experience. This system, with its focus on user-friendliness and aesthetic presentation, promises to be an invaluable tool for anyone seeking culinary inspiration, dietary guidance, or simply a delicious meal, ultimately enhancing daily life and productivity. As AI continues to evolve, the capabilities of such chatbots will only grow, offering even more sophisticated and personalized culinary experiences. The transition from conceptual design to a production-ready system involves careful consideration of external service integrations, robust data persistence, and scalable cloud infrastructure, all while maintaining a strong focus on security and user privacy.


ADDENDUM: Full Running Example Code


import json

import re

from dataclasses import dataclass, field

from typing import List, Dict, Optional, Any, Callable, Set


# ------------------------------------------------------------------------------

# 1. Data Models

# ------------------------------------------------------------------------------


@dataclass

class Ingredient:

    """Represents a single ingredient with its name, quantity, and unit."""

    name: str

    quantity: float

    unit: str


@dataclass

class NutritionInfo:

    """Stores nutritional values for a food item or recipe."""

    calories: Optional[int] = None

    protein_g: Optional[float] = None

    fat_g: Optional[float] = None

    carbohydrates_g: Optional[float] = None

    vitamin_b12_mcg: Optional[float] = None # Example vitamin

    vitamin_c_mg: Optional[float] = None # Another example vitamin

    iron_mg: Optional[float] = None # Example mineral

    # Add more nutritional components as needed for a production system


@dataclass

class Recipe:

    """Represents a complete recipe with all extracted details."""

    name: str

    summary: str

    url: str

    ingredients: List[Ingredient]

    nutrition: NutritionInfo

    cuisine: Optional[str] = None

    seasonality: Optional[str] = None # e.g., "Summer", "Autumn"

    # Add more attributes like preparation time, difficulty, etc., for a

    # production system.


@dataclass

class SearchResult:

    """Represents a single search result with its URL and content."""

    url: str

    content: str


@dataclass

class UserProfile:

    """

    Represents a user's culinary preferences and history.

    This profile would typically be stored in a persistent database (e.g.,

    SQL, NoSQL) and loaded/saved for each user session to ensure continuity.

    This dataclass is designed to be production-ready.

    """

    user_id: str

    favorite_dishes: Set[str] = field(default_factory=set) # Set for unique dishes (lowercase)

    disliked_ingredients: Set[str] = field(default_factory=set) # Set for unique ingredients (lowercase)

    dietary_restrictions: Set[str] = field(default_factory=set) # e.g., "vegetarian", "gluten-free" (lowercase)

    preferred_cuisines: Set[str] = field(default_factory=set) # (lowercase)

    search_history: List[str] = field(default_factory=list) # Recent search queries


    def add_favorite_dish(self, dish_name: str):

        """Adds a dish to the user's list of favorites, converting to lowercase for consistency."""

        self.favorite_dishes.add(dish_name.lower())

        print(f"'{dish_name}' added to favorites for user {self.user_id}.")


    def add_disliked_ingredient(self, ingredient_name: str):

        """Adds an ingredient to the user's list of disliked items, converting to lowercase."""

        self.disliked_ingredients.add(ingredient_name.lower())

        print(f"'{ingredient_name}' added to disliked ingredients for user {self.user_id}.")


    def add_dietary_restriction(self, restriction: str):

        """Adds a dietary restriction to the user's profile, converting to lowercase."""

        self.dietary_restrictions.add(restriction.lower())

        print(f"'{restriction}' added to dietary restrictions for user {self.user_id}.")


    def update_search_history(self, query: str):

        """

        Adds a query to the user's search history, ensuring uniqueness

        and keeping the history to a reasonable size (e.g., last 10 queries).

        """

        if query not in self.search_history:

            self.search_history.append(query)

            if len(self.search_history) > 10: # Maintain a concise history

                self.search_history.pop(0) # Remove oldest entry


# ------------------------------------------------------------------------------

# 2. Core Chatbot Functions

# ------------------------------------------------------------------------------


def parse_user_prompt(prompt: str) -> dict:

    """

    Parses a user's natural language prompt to extract intent, entities,

    and language.


    In a real-world, production scenario, this function would involve an

    API call to a powerful, general-purpose LLM (e.g., Google Gemini,

    OpenAI GPT) with a carefully crafted prompt to extract structured

    information from ANY user input, without hardcoded conditions.

    The LLM would be trained to handle arbitrary food-related queries.


    For this running example, we use a simplified simulation of the LLM's

    response for specific prompts to keep the code concise and executable.

    The 'else' block demonstrates the intended open-ended nature.


    Args:

        prompt (str): The user's input string, e.g., "I need a Bavarian recipe

                      for Kaiserschmarrn".


    Returns:

        dict: A dictionary containing the parsed intent, entities, and

              detected language.

              Example:

              {

                  "intent": "get_recipe",

                  "entities": {

                      "cuisine": "Bavarian",

                      "dish": "Kaiserschmarrn"

                  },

                  "language": "en" # Detected language of the prompt

              }

    """

    prompt_lower = prompt.lower()


    if "kaiserschmarrn" in prompt_lower and "bavarian" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "cuisine": "Bavarian",

                "dish": "Kaiserschmarrn"

            },

            "language": "en"

        }

    elif "low carb" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "dietary_restriction": "low carb"

            },

            "language": "en"

        }

    elif "vitamine b" in prompt_lower:

        return {

            "intent": "find_food_for_deficiency",

            "entities": {

                "nutrient": "Vitamine B"

            },

            "language": "en"

        }

    elif "seasonal ingredients" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "seasonality": "current" # In a real system, this would infer the current season

            },

            "language": "en"

        }

    elif "light food" in prompt_lower:

        return {

            "intent": "general_food_recommendation",

            "entities": {

                "preference": "light"

            },

            "language": "en"

        }

    elif "eggplant" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "ingredient": "eggplant"

            },

            "language": "en"

        }

    elif "leftover chicken" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "main_ingredient": "chicken",

                "context": "leftovers"

            },

            "language": "en"

        }

    elif "vegetarian" in prompt_lower and "curry" in prompt_lower:

        return {

            "intent": "get_recipe",

            "entities": {

                "dietary_restriction": "vegetarian",

                "dish_type": "curry"

            },

            "language": "en"

        }

    else:

        # This 'else' block represents the LLM's ability to handle any query.

        # It defaults to a general food recommendation intent, passing the

        # original prompt text for a broad search.

        return {

            "intent": "general_food_recommendation",

            "entities": {"query_text": prompt}, # Pass original prompt for broad search

            "language": "en" # Default to English if not specified or detected

        }



def perform_web_search(parsed_query: dict, user_profile: UserProfile) -> list[SearchResult]:

    """

    Performs a web search based on the parsed user query and retrieves

    relevant web page content. The search query generation is influenced

    by the user profile to personalize results.


    In a real, production scenario, this function would interact with a

    general internet search engine API (e.g., Google Custom Search API,

    Bing Search API) and a web content retrieval service. The search query

    would be constructed dynamically to be as broad or specific as the

    user's prompt and preferences require.


    For this running example, we provide mock results based on the parsed

    query to illustrate the data flow, as direct integration with a general

    internet search is beyond the scope of the available tools.


    Args:

        parsed_query (dict): The structured query from parse_user_prompt.

        user_profile (UserProfile): The current user's profile for personalization.


    Returns:

        list[SearchResult]: A list of search results, each containing a URL

                            and its simulated HTML content.

    """

    intent = parsed_query.get("intent")

    entities = parsed_query.get("entities", {})

    language = parsed_query.get("language", "en")


    search_terms = []

    if intent == "get_recipe":

        search_terms.append("recipe")

        if "cuisine" in entities:

            search_terms.append(entities["cuisine"])

        elif user_profile.preferred_cuisines: # Incorporate user preferred cuisines

            search_terms.extend(list(user_profile.preferred_cuisines))

        if "dish" in entities:

            search_terms.append(entities["dish"])

        if "dish_type" in entities:

            search_terms.append(entities["dish_type"])

        if "dietary_restriction" in entities:

            search_terms.append(entities["dietary_restriction"])

        elif user_profile.dietary_restrictions: # Incorporate user dietary restrictions

            search_terms.extend(list(user_profile.dietary_restrictions))

        if "seasonality" in entities:

            search_terms.append(f"{entities['seasonality']} ingredients")

        if "ingredient" in entities:

            search_terms.append(entities["ingredient"])

        if "main_ingredient" in entities:

            search_terms.append(entities["main_ingredient"])

        if "context" in entities:

            search_terms.append(entities["context"])

    elif intent == "find_food_for_deficiency":

        search_terms.append("food rich in")

        search_terms.append(entities.get("nutrient", ""))

    elif intent == "general_food_recommendation":

        if "preference" in entities:

            search_terms.append(entities["preference"])

        if "query_text" in entities: # For broad, unspecific queries handled by LLM

            search_terms.append(entities["query_text"])


    # Filter out disliked ingredients from search terms.

    # In a real system, this might be done more effectively by filtering results

    # post-retrieval, but for query construction, we can try to exclude them.

    final_search_terms = []

    for term in search_terms:

        # Check if any part of the term is a disliked ingredient

        is_disliked = False

        for disliked_ing in user_profile.disliked_ingredients:

            if disliked_ing in term.lower():

                is_disliked = True

                break

        if not is_disliked:

            final_search_terms.append(term)


    query_string = f"{' '.join(final_search_terms)} in {language}"

    print(f"  Constructed search query: '{query_string}'")


    # --- Mocked Web Search Results ---

    # These HTML strings simulate the content that would be retrieved from

    # various recipe websites by a real web scraping component.


    mock_kaiserschmarrn_html = """

    <html>

    <head><title>Best Bavarian Kaiserschmarrn Recipe</title></head>

    <body>

        <h1>Authentic Bavarian Kaiserschmarrn</h1>

        <p>A fluffy shredded pancake, a classic Bavarian dessert, perfect for a sweet treat.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>125g all-purpose flour</li>

            <li>2 large eggs</li>

            <li>150ml milk</li>

            <li>1 tbsp granulated sugar</li>

            <li>1 pinch salt</li>

            <li>1 tbsp butter (for frying)</li>

            <li>1 tbsp raisins (optional)</li>

            <li>Powdered sugar for dusting</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Separate egg whites and yolks.</li>

            <li>Mix flour, milk, sugar, salt, and egg yolks.</li>

            <li>Beat egg whites until stiff peaks form.</li>

            <li>Gently fold egg whites into the batter.</li>

            <li>Melt butter in a pan, pour in batter.</li>

            <li>Cook until golden brown, then shred with two forks.</li>

            <li>Serve with powdered sugar and apple sauce.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 550 kcal</p>

            <p>Protein: 18g</p>

            <p>Fat: 25g</p>

            <p>Carbohydrates: 65g</p>

            <p>Vitamin B12: 0.5mcg</p>

            <p>Iron: 2mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/kaiserschmarrn-recipe">

        https://www.example.com/kaiserschmarrn-recipe</a></p>

    </body>

    </html>

    """


    mock_low_carb_chicken_html = """

    <html>

    <head><title>Delicious Low Carb Lemon Herb Chicken</title></head>

    <body>

        <h1>Low Carb Lemon Herb Chicken</h1>

        <p>A simple and flavorful low-carb chicken dish, great for a healthy dinner.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>150g boneless, skinless chicken breast</li>

            <li>1 tbsp olive oil</li>

            <li>1 lemon, sliced</li>

            <li>1 tsp dried mixed herbs</li>

            <li>1/2 tsp garlic powder</li>

            <li>Salt and pepper to taste</li>

            <li>50g broccoli florets</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Season chicken with salt, pepper, herbs, and garlic powder.</li>

            <li>Heat olive oil in a pan.</li>

            <li>Cook chicken until golden and cooked through.</li>

            <li>Add lemon slices and broccoli, cook until tender-crisp.</li>

            <li>Serve immediately.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 350 kcal</p>

            <p>Protein: 35g</p>

            <p>Fat: 20g</p>

            <p>Carbohydrates: 5g</p>

            <p>Vitamin C: 30mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/low-carb-chicken">

        https://www.example.com/low-carb-chicken</a></p>

    </body>

    </html>

    """


    mock_vitamin_b_food_html = """

    <html>

    <head><title>Foods Rich in B Vitamins</title></head>

    <body>

        <h1>Top Foods Rich in B Vitamins</h1>

        <p>A list of excellent sources to boost your B vitamin intake.</p>

        <h2>Recommendations:</h2>

        <ul>

            <li>Salmon: High in B12, B6, Niacin</li>

            <li>Eggs: Contains B7, B5, B12</li>

            <li>Beef Liver: Extremely rich in B12, Folate, Riboflavin</li>

            <li>Leafy Greens (Spinach, Kale): Good source of Folate</li>

            <li>Legumes (Lentils, Chickpeas): Provide Folate, Thiamine</li>

        </ul>

        <div class="nutrition">

            <h3>General Nutritional Notes:</h3>

            <p>B Vitamins are crucial for energy metabolism and nerve function.</p>

            <p>Salmon (100g): B12: 2.8mcg, B6: 0.6mg</p>

            <p>Egg (1 large): B12: 0.45mcg, B7: 10mcg</p>

        </div>

        <p>Source: <a href="https://www.healthysites.com/b-vitamins">

        https://www.healthysites.com/b-vitamins</a></p>

    </body>

    </html>

    """


    mock_eggplant_parmesan_html = """

    <html>

    <head><title>Classic Eggplant Parmesan Recipe</title></head>

    <body>

        <h1>Eggplant Parmesan</h1>

        <p>A delicious Italian-American dish made with layers of fried eggplant, cheese, and tomato sauce.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>1 large eggplant</li>

            <li>100g mozzarella cheese, sliced</li>

            <li>50g parmesan cheese, grated</li>

            <li>150ml tomato sauce</li>

            <li>1 egg, beaten</li>

            <li>50g breadcrumbs</li>

            <li>2 tbsp olive oil</li>

            <li>Salt and pepper to taste</li>

            <li>5g fresh basil</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Slice eggplant, salt, and let sit to draw out moisture.</li>

            <li>Dip eggplant slices in egg, then breadcrumbs.</li>

            <li>Fry eggplant slices in olive oil until golden.</li>

            <li>Layer eggplant, tomato sauce, mozzarella, and parmesan in a baking dish.</li>

            <li>Bake at 180C for 20-25 minutes until bubbly and golden.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 480 kcal</p>

            <p>Protein: 25g</p>

            <p>Fat: 30g</p>

            <p>Carbohydrates: 30g</p>

            <p>Vitamin C: 15mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/eggplant-parmesan">

        https://www.example.com/eggplant-parmesan</a></p>

    </body>

    </html>

    """

    mock_vegetarian_curry_html = """

    <html>

    <head><title>Hearty Vegetarian Lentil Curry</title></head>

    <body>

        <h1>Vegetarian Lentil Curry</h1>

        <p>A flavorful and protein-rich vegetarian curry, perfect for a healthy meal.</p>

        <h2>Ingredients (per person):</h2>

        <ul>

            <li>100g red lentils</li>

            <li>1 onion, chopped</li>

            <li>2 cloves garlic, minced</li>

            <li>1 tbsp ginger, grated</li>

            <li>1 tsp curry powder</li>

            <li>400ml coconut milk</li>

            <li>100g spinach</li>

            <li>Salt to taste</li>

            <li>1 tbsp olive oil</li>

        </ul>

        <h2>Instructions:</h2>

        <ol>

            <li>Sauté onion, garlic, ginger in olive oil.</li>

            <li>Add curry powder and lentils, stir well.</li>

            <li>Pour in coconut milk and 200ml water, simmer until lentils are tender.</li>

            <li>Stir in spinach until wilted.</li>

            <li>Season with salt and serve with rice.</li>

        </ol>

        <div class="nutrition">

            <h3>Nutritional Information (Approx. per serving):</h3>

            <p>Calories: 450 kcal</p>

            <p>Protein: 20g</p>

            <p>Fat: 28g</p>

            <p>Carbohydrates: 35g</p>

            <p>Iron: 4mg</p>

        </div>

        <p>Source: <a href="https://www.example.com/lentil-curry">

        https://www.example.com/lentil-curry</a></p>

    </body>

    </html>

    """


    # Logic to return mock HTML based on query_string.

    # In a real system, this would be dynamic and fetch from actual web.

    if "kaiserschmarrn" in query_string.lower():

        return [SearchResult(

            url="https://www.example.com/kaiserschmarrn-recipe",

            content=mock_kaiserschmarrn_html

        )]

    elif "low carb" in query_string.lower() and "chicken" in query_string.lower() and "vegetarian" not in user_profile.dietary_restrictions:

        # Only return chicken if user is not vegetarian

        return [SearchResult(

            url="https://www.example.com/low-carb-chicken",

            content=mock_low_carb_chicken_html

        )]

    elif "vitamine b" in query_string.lower():

        return [SearchResult(

            url="https://www.healthysites.com/b-vitamins",

            content=mock_vitamin_b_food_html

        )]

    elif "eggplant" in query_string.lower() and "recipe" in query_string.lower():

        return [SearchResult(

            url="https://www.example.com/eggplant-parmesan",

            content=mock_eggplant_parmesan_html

        )]

    elif ("vegetarian" in query_string.lower() or "vegetarian" in user_profile.dietary_restrictions) and "curry" in query_string.lower():

        # If user is vegetarian or query asks for vegetarian, and mentions curry

        return [SearchResult(

            url="https://www.example.com/lentil-curry",

            content=mock_vegetarian_curry_html

        )]

    else:

        # Fallback for other queries, for demonstration purposes.

        # A real system would perform a generic web search and return diverse results.

        print(f"  Simulating generic search for: '{query_string}' - No specific mock found.")

        return []



def extract_recipe_details(html_content: str, parsed_query: dict) -> Optional[Recipe]:

    """

    Extracts recipe details (name, summary, ingredients, nutrition) from

    simulated HTML content. This function is tailored for recipe pages.


    In a real, production scenario, this would use a robust HTML parser

    like BeautifulSoup combined with advanced pattern recognition or

    even another LLM pass to navigate the DOM and extract information

    reliably from diverse website structures.

    Here, we use regex and string manipulation on the mock HTML for

    demonstration, which is fragile and highly dependent on the exact

    HTML structure of the mock data.


    Args:

        html_content (str): The HTML content of a recipe page.

        parsed_query (dict): The structured query for context (e.g., cuisine).


    Returns:

        Optional[Recipe]: A Recipe object if extraction is successful,

                          otherwise None.

    """

    recipe_name_match = re.search(r"<h1>(.*?)<\/h1>", html_content)

    recipe_name = recipe_name_match.group(1).strip() if recipe_name_match else "Unknown Recipe"


    summary_match = re.search(r"<p>(.*?)</p>", html_content, re.DOTALL)

    summary = summary_match.group(1).strip() if summary_match else "No summary available."


    url_match = re.search(r'Source: <a href="(.*?)">', html_content)

    url = url_match.group(1) if url_match else "No URL found."


    ingredients_raw_match = re.search(r"<h2>Ingredients \(per person\):</h2>\s*<ul>(.*?)<\/ul>", html_content, re.DOTALL)

    ingredients_list_raw = ingredients_raw_match.group(1) if ingredients_raw_match else ""


    ingredients: List[Ingredient] = []

    for item_match in re.finditer(r"<li>(.*?)<\/li>", ingredients_list_raw):

        item_text = item_match.group(1).strip()

        # Attempt to parse quantity, unit, and name.

        # This regex is an example and might need refinement for real-world variety.

        ingredient_match = re.match(r"(\d+\.?\d*)\s*([a-zA-Z]+)?\s*(.*)", item_text)

        if ingredient_match:

            quantity_str, unit_str, name_str = ingredient_match.groups()

            try:

                quantity = float(quantity_str)

                unit = unit_str.strip() if unit_str else "item" # Default unit if not specified

                name = name_str.strip()

                ingredients.append(Ingredient(name=name, quantity=quantity, unit=unit))

            except ValueError:

                # Fallback for complex cases like "a pinch of salt" where quantity isn't a simple float

                ingredients.append(Ingredient(name=item_text, quantity=0, unit=""))

        else:

            # If regex fails, add the whole text as an ingredient without specific quantity/unit

            ingredients.append(Ingredient(name=item_text, quantity=0, unit=""))


    # Extract nutritional information

    nutrition_info = NutritionInfo()

    nutrition_section_match = re.search(r'<div class="nutrition">(.*?)</div>', html_content, re.DOTALL)

    if nutrition_section_match:

        nutrition_text = nutrition_section_match.group(1)

        calories_match = re.search(r"Calories:\s*(\d+)\s*kcal", nutrition_text)

        if calories_match: nutrition_info.calories = int(calories_match.group(1))

        protein_match = re.search(r"Protein:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if protein_match: nutrition_info.protein_g = float(protein_match.group(1))

        fat_match = re.search(r"Fat:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if fat_match: nutrition_info.fat_g = float(fat_match.group(1))

        carbs_match = re.search(r"Carbohydrates:\s*(\d+\.?\d*)\s*g", nutrition_text)

        if carbs_match: nutrition_info.carbohydrates_g = float(carbs_match.group(1))

        vitamin_b12_match = re.search(r"Vitamin B12:\s*(\d+\.?\d*)\s*mcg", nutrition_text)

        if vitamin_b12_match: nutrition_info.vitamin_b12_mcg = float(vitamin_b12_match.group(1))

        vitamin_c_match = re.search(r"Vitamin C:\s*(\d+\.?\d*)\s*mg", nutrition_text)

        if vitamin_c_match: nutrition_info.vitamin_c_mg = float(vitamin_c_match.group(1))

        iron_match = re.search(r"Iron:\s*(\d+\.?\d*)\s*mg", nutrition_text)

        if iron_match: nutrition_info.iron_mg = float(iron_match.group(1))


    # Determine cuisine and seasonality from parsed query for the Recipe object

    cuisine = parsed_query.get("entities", {}).get("cuisine")

    seasonality = parsed_query.get("entities", {}).get("seasonality")


    return Recipe(

        name=recipe_name,

        summary=summary,

        url=url,

        ingredients=ingredients,

        nutrition=nutrition_info,

        cuisine=cuisine,

        seasonality=seasonality

    )



def extract_food_recommendations(html_content: str, parsed_query: dict) -> List[Recipe]:

    """

    Extracts general food recommendations (not full recipes) from HTML content.

    This is for intents like "foods rich in Vitamine B".


    In a real production system, this would use robust parsing techniques

    or LLMs to extract structured recommendations from varied web content.


    Args:

        html_content (str): The HTML content of a recommendation page.

        parsed_query (dict): The structured query for context.


    Returns:

        List[Recipe]: A list of simplified Recipe objects representing recommended foods.

    """

    recommendations: List[Recipe] = []

    nutrient = parsed_query.get("entities", {}).get("nutrient", "various nutrients")


    # Extract general title

    page_title_match = re.search(r"<h1>(.*?)<\/h1>", html_content)

    page_title = page_title_match.group(1).strip() if page_title_match else "Food Recommendations"


    # Extract source URL

    url_match = re.search(r'Source: <a href="(.*?)">', html_content)

    url = url_match.group(1) if url_match else "No URL found."


    # Look for lists of recommendations

    recommendations_list_raw_match = re.search(r"<h2>Recommendations:</h2>\s*<ul>(.*?)<\/ul>", html_content, re.DOTALL)

    if recommendations_list_raw_match:

        recommendations_list_raw = recommendations_list_raw_match.group(1)

        for item_match in re.finditer(r"<li>(.*?)<\/li>", recommendations_list_raw):

            item_text = item_match.group(1).strip()

            # Try to parse food name and associated nutrients

            food_name_match = re.match(r"([^:]+):", item_text)

            food_name = food_name_match.group(1).strip() if food_name_match else item_text

            summary_text = f"Recommended for {nutrient}. {item_text}"


            # Simplified nutrition extraction for recommendations

            item_nutrition = NutritionInfo()

            if "B12:" in item_text:

                b12_match = re.search(r"B12:\s*(\d+\.?\d*)mcg", item_text)

                if b12_match: item_nutrition.vitamin_b12_mcg = float(b12_match.group(1))

            if "B6:" in item_text:

                b6_match = re.search(r"B6:\s*(\d+\.?\d*)mg", item_text)

                # In a production system, B6 would have its own field in NutritionInfo

                # For this example, we'll just parse B12.

            # Could add more specific parsing for other nutrients here


            recommendations.append(Recipe(

                name=food_name,

                summary=summary_text,

                url=url,

                ingredients=[], # No specific ingredients for general recommendations

                nutrition=item_nutrition

            ))

    return recommendations



def standardize_ingredients(ingredients: List[Ingredient]) -> List[Ingredient]:

    """

    Standardizes ingredient quantities and units to a common format.

    This function is designed to be production-ready for unit conversion.

    This is a simplified example; a real system would have extensive

    conversion tables and parsing rules, possibly using a dedicated unit

    conversion library.


    Args:

        ingredients (List[Ingredient]): A list of raw Ingredient objects.


    Returns:

        List[Ingredient]: A list of standardized Ingredient objects.

    """

    standardized: List[Ingredient] = []

    for ing in ingredients:

        # Convert common units to a base (e.g., g, ml, pieces)

        if ing.unit.lower() == "tbsp":

            # Approximate conversions for common items

            if "sugar" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 12.5, unit="g"))

            elif "butter" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 14, unit="g"))

            elif "oil" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 15, unit="ml"))

            else:

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="tbsp")) # Keep if no specific conversion

        elif ing.unit.lower() == "tsp":

            if "sugar" in ing.name.lower():

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity * 4, unit="g"))

            else:

                standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="tsp"))

        elif ing.unit.lower() == "g" or ing.unit.lower() == "ml":

            standardized.append(ing) # Already standard

        elif ing.unit.lower() == "large": # For items like eggs

            standardized.append(Ingredient(name=ing.name, quantity=ing.quantity, unit="item (large)"))

        elif ing.unit.lower() == "pinch":

            standardized.append(Ingredient(name=ing.name, quantity=0.5, unit="g")) # Approximate conversion for a pinch

        else:

            standardized.append(ing) # Keep original if unit is unknown or already appropriate

    return standardized



def sort_recipes(recipes: List[Recipe], sort_key: str, ascending: bool = True,

                 user_profile: Optional[UserProfile] = None) -> List[Recipe]:

    """

    Sorts a list of Recipe objects based on a specified key, with optional

    personalization based on user profile. This function is designed to be

    production-ready for sorting and filtering.


    Args:

        recipes (List[Recipe]): A list of Recipe objects to be sorted.

        sort_key (str): The attribute name to sort by (e.g., "name",

                        "calories", "protein", "vitamin_b12", "cuisine").

        ascending (bool): True for ascending order, False for descending.

        user_profile (Optional[UserProfile]): The current user's profile for

                                               personalization, used for filtering

                                               and prioritizing.


    Returns:

        List[Recipe]: The sorted list of Recipe objects, filtered by user preferences.

    """

    if not recipes:

        return []


    # Filter out recipes containing disliked ingredients or violating dietary restrictions

    filtered_recipes = []

    for recipe in recipes:

        # Check for disliked ingredients

        contains_disliked = False

        if user_profile and user_profile.disliked_ingredients:

            for ing in recipe.ingredients:

                if ing.name.lower() in user_profile.disliked_ingredients:

                    contains_disliked = True

                    break

        if contains_disliked:

            continue # Skip this recipe if it contains a disliked ingredient


        # Check for dietary restrictions (simplified example for demonstration)

        violates_restriction = False

        if user_profile and user_profile.dietary_restrictions:

            # Example: If user is vegetarian, filter out recipes with common meat names

            if "vegetarian" in user_profile.dietary_restrictions:

                # Check recipe name for meat keywords

                if any(meat_keyword in recipe.name.lower() for meat_keyword in ["chicken", "beef", "pork", "salmon", "tuna"]):

                    violates_restriction = True

                else:

                    # Also check ingredients for meat if not already caught by name

                    for ing in recipe.ingredients:

                        if any(meat_keyword in ing.name.lower() for meat_keyword in ["chicken", "beef", "pork", "salmon", "tuna"]):

                            violates_restriction = True

                            break

            # Add more complex logic for other restrictions (e.g., gluten-free, vegan)

        if violates_restriction:

            continue # Skip this recipe if it violates a dietary restriction


        filtered_recipes.append(recipe)


    if not filtered_recipes:

        print(f"  No recipes remain after applying user filters for sort key '{sort_key}'.")

        return []


    def get_sort_value(recipe: Recipe) -> Any:

        # Default value for missing numerical data to push them to the end

        # for ascending sort, or beginning for descending sort.

        default_num_val = float('inf') if ascending else float('-inf')


        # Personalization factor: Prioritize favorite dishes

        # This is a simple example; a real production system would use a more

        # sophisticated scoring algorithm that might combine multiple factors

        # (e.g., recency of interaction, preference strength, cuisine match).

        personalization_priority = 0

        if user_profile and recipe.name.lower() in user_profile.favorite_dishes:

            personalization_priority = -1000000000 # Give a very high priority for favorites


        # Return a tuple for sorting: (personalization_priority, actual_sort_value)

        # This ensures favorites are always at the top, then sorted by the main key.

        if sort_key == "name":

            return (personalization_priority, recipe.name.lower())

        elif sort_key == "calories":

            return (personalization_priority, recipe.nutrition.calories if recipe.nutrition.calories is not None else default_num_val)

        elif sort_key == "protein":

            return (personalization_priority, recipe.nutrition.protein_g if recipe.nutrition.protein_g is not None else default_num_val)

        elif sort_key == "fat":

            return (personalization_priority, recipe.nutrition.fat_g if recipe.nutrition.fat_g is not None else default_num_val)

        elif sort_key == "carbohydrates":

            return (personalization_priority, recipe.nutrition.carbohydrates_g if recipe.nutrition.carbohydrates_g is not None else default_num_val)

        elif sort_key == "vitamin_b12":

            return (personalization_priority, recipe.nutrition.vitamin_b12_mcg if recipe.nutrition.vitamin_b12_mcg is not None else default_num_val)

        elif sort_key == "vitamin_c":

            return (personalization_priority, recipe.nutrition.vitamin_c_mg if recipe.nutrition.vitamin_c_mg is not None else default_num_val)

        elif sort_key == "iron":

            return (personalization_priority, recipe.nutrition.iron_mg if recipe.nutrition.iron_mg is not None else default_num_val)

        elif sort_key == "cuisine":

            return (personalization_priority, recipe.cuisine.lower() if recipe.cuisine else "")

        elif sort_key == "seasonality":

            return (personalization_priority, recipe.seasonality.lower() if recipe.seasonality else "")

        else:

            print(f"Warning: Unsupported sort key '{sort_key}'. Defaulting to name sort.")

            return (personalization_priority, recipe.name.lower())


    return sorted(filtered_recipes, key=get_sort_value, reverse=not ascending)



# ------------------------------------------------------------------------------

# 3. Main Chatbot Orchestration Logic

# ------------------------------------------------------------------------------


def run_culinary_chatbot(user_prompt: str, user_profile: UserProfile) -> List[Recipe]:

    """

    Orchestrates the entire chatbot process from user prompt to sorted results.

    This function is designed to be production-ready, integrating the various

    components.


    Args:

        user_prompt (str): The initial query from the user.

        user_profile (UserProfile): The current user's profile for personalization.


    Returns:

        List[Recipe]: A list of processed and potentially sorted Recipe objects.

    """

    print(f"\n--- Processing User Prompt: '{user_prompt}' ---")

    user_profile.update_search_history(user_prompt)


    # Step 1: Parse the user's prompt using the (simulated) LLM

    parsed_query = parse_user_prompt(user_prompt)

    print("Parsed Query:")

    print(json.dumps(parsed_query, indent=2))


    # Step 2: Perform web search based on the parsed query and user profile

    search_results = perform_web_search(parsed_query, user_profile)


    if not search_results:

        print("\nNo relevant search results found.")

        return []


    processed_recipes: List[Recipe] = []

    for result in search_results:

        print(f"\n--- Processing Search Result from: {result.url} ---")

        if parsed_query["intent"] == "get_recipe":

            # Step 3: Extract recipe details from the web content

            extracted_recipe = extract_recipe_details(result.content, parsed_query)

            if extracted_recipe:

                # Step 4: Standardize ingredients

                extracted_recipe.ingredients = standardize_ingredients(extracted_recipe.ingredients)

                processed_recipes.append(extracted_recipe)

                print("  Successfully extracted and standardized recipe.")

            else:

                print("  Failed to extract specific recipe details from this source.")

        elif parsed_query["intent"] == "find_food_for_deficiency" or \

             parsed_query["intent"] == "general_food_recommendation":

            # Step 3 (alternative): Extract general food recommendations

            extracted_recommendations = extract_food_recommendations(result.content, parsed_query)

            if extracted_recommendations:

                processed_recipes.extend(extracted_recommendations)

                print(f"  Successfully extracted {len(extracted_recommendations)} food recommendations.")

            else:

                print("  Failed to extract general food recommendations from this source.")

        else:

            print(f"  Unsupported intent for extraction: {parsed_query['intent']}")


    return processed_recipes



def display_recipes(recipes: List[Recipe], title: str):

    """Helper function to display a list of recipes in a readable format."""

    print(f"\n{title}:")

    if not recipes:

        print("  No recipes to display.")

        return


    for i, recipe in enumerate(recipes):

        print(f"\n--- {i+1}. {recipe.name} ---")

        print(f"  Summary: {recipe.summary}")

        print(f"  URL: {recipe.url}")

        if recipe.cuisine:

            print(f"  Cuisine: {recipe.cuisine.capitalize()}")

        if recipe.seasonality:

            print(f"  Seasonality: {recipe.seasonality.capitalize()}")


        if recipe.ingredients:

            print("  Ingredients (per person):")

            for ing in recipe.ingredients:

                # Format quantity to avoid .0 if it's a whole number

                qty_str = str(ing.quantity)

                if qty_str.endswith('.0'):

                    qty_str = qty_str[:-2]

                print(f"    - {qty_str}{ing.unit} {ing.name}")

        else:

            print("  No specific ingredients listed (general recommendation).")



        print("  Nutritional Information (Approx. per serving):")

        if recipe.nutrition.calories is not None:

            print(f"    Calories: {recipe.nutrition.calories} kcal")

        if recipe.nutrition.protein_g is not None:

            print(f"    Protein: {recipe.nutrition.protein_g} g")

        if recipe.nutrition.fat_g is not None:

            print(f"    Fat: {recipe.nutrition.fat_g} g")

        if recipe.nutrition.carbohydrates_g is not None:

            print(f"    Carbohydrates: {recipe.nutrition.carbohydrates_g} g")

        if recipe.nutrition.vitamin_b12_mcg is not None:

            print(f"    Vitamin B12: {recipe.nutrition.vitamin_b12_mcg} mcg")

        if recipe.nutrition.vitamin_c_mg is not None:

            print(f"    Vitamin C: {recipe.nutrition.vitamin_c_mg} mg")

        if recipe.nutrition.iron_mg is not None:

            print(f"    Iron: {recipe.nutrition.iron_mg} mg")

        if all(v is None for v in [recipe.nutrition.calories, recipe.nutrition.protein_g,

                                   recipe.nutrition.fat_g, recipe.nutrition.carbohydrates_g,

                                   recipe.nutrition.vitamin_b12_mcg, recipe.nutrition.vitamin_c_mg,

                                   recipe.nutrition.iron_mg]):

            print("    No detailed nutritional information available.")



# ------------------------------------------------------------------------------

# 4. Running Example Execution

# ------------------------------------------------------------------------------


if __name__ == "__main__":

    # Initialize a user profile. In a real application, this would be loaded

    # from a database based on the authenticated user.

    my_user_profile = UserProfile(user_id="siemens_employee_123")

    my_user_profile.add_favorite_dish("Kaiserschmarrn")

    my_user_profile.add_disliked_ingredient("Cilantro")

    my_user_profile.add_dietary_restriction("vegetarian")

    my_user_profile.preferred_cuisines.add("italian") # User prefers Italian cuisine


    print("\n--- Initial User Profile ---")

    print(my_user_profile)


    # Example 1: Bavarian Kaiserschmarrn recipe (should be prioritized due to favorites)

    user_prompt_1 = "I need a Bavarian recipe for Kaiserschmarrn"

    recipes_1 = run_culinary_chatbot(user_prompt_1, my_user_profile)

    display_recipes(recipes_1, "Results for 'Bavarian Kaiserschmarrn'")


    # Example 2: Low carb food (should be filtered for vegetarian)

    user_prompt_2 = "I need a recipe for low carb food"

    recipes_2 = run_culinary_chatbot(user_prompt_2, my_user_profile)

    # The mock perform_web_search will not return the chicken recipe if vegetarian

    # is in dietary_restrictions, demonstrating filtering at search level.

    display_recipes(recipes_2, "Results for 'Low Carb Food' (should be empty if vegetarian)")


    # Example 3: Food for Vitamin B deficiency

    user_prompt_3 = "What food helps me reduce my deficiency of Vitamine B"

    recipes_3 = run_culinary_chatbot(user_prompt_3, my_user_profile)

    display_recipes(recipes_3, "Results for 'Food for Vitamin B Deficiency'")


    # Example 4: Recipe with a disliked ingredient (Apfelstrudel contains Cilantro in dummy data, but

    # the mock web search might not return it directly. Let's test filtering in sort_recipes).

    # We'll use a generic query to get a broader set of recipes including the dummy ones.

    user_prompt_4 = "Show me some food ideas"

    recipes_4 = run_culinary_chatbot(user_prompt_4, my_user_profile)

    display_recipes(recipes_4, "Results for 'Show me some food ideas' (Generic Search)")


    # Example 5: Vegetarian Curry (should match user's dietary restriction)

    user_prompt_5 = "I want a vegetarian curry recipe"

    recipes_5 = run_culinary_chatbot(user_prompt_5, my_user_profile)

    display_recipes(recipes_5, "Results for 'Vegetarian Curry'")



    # Combine all found recipes for a comprehensive sorting demonstration

    all_recipes_for_sorting = []

    if recipes_1: all_recipes_for_sorting.extend(recipes_1)

    if recipes_2: all_recipes_for_sorting.extend(recipes_2)

    if recipes_3: all_recipes_for_sorting.extend(recipes_3)

    # Add some dummy recipes to ensure filtering by disliked ingredients is shown

    all_recipes_for_sorting.extend([

        Recipe(

            name="Apfelstrudel", summary="Apple pastry", url="http://example.com/apfel",

            ingredients=[Ingredient(name="Cilantro", quantity=10, unit="g")], # Contains disliked ingredient

            nutrition=NutritionInfo(calories=400, protein_g=5, vitamin_b12_mcg=0.1),

            cuisine="Austrian"

        ),

        Recipe(

            name="Goulash", summary="Hearty stew", url="http://example.com/goulash",

            ingredients=[Ingredient(name="Beef", quantity=200, unit="g")], # Violates vegetarian restriction

            nutrition=NutritionInfo(calories=600, protein_g=30, vitamin_b12_mcg=1.2),

            cuisine="Hungarian"

        ),

        Recipe(

            name="Chicken Stir-fry", summary="Quick chicken and veggie stir-fry", url="http://example.com/stirfry",

            ingredients=[Ingredient(name="Chicken breast", quantity=150, unit="g")], # Violates vegetarian restriction

            nutrition=NutritionInfo(calories=380, protein_g=35, vitamin_c_mg=20),

            cuisine="Asian"

        ),

        Recipe(

            name="Vegetable Lasagna", summary="Classic Italian-style vegetable lasagna", url="http://example.com/lasagna",

            ingredients=[Ingredient(name="Zucchini", quantity=100, unit="g")],

            nutrition=NutritionInfo(calories=420, protein_g=15, vitamin_c_mg=10),

            cuisine="Italian"

        )

    ])

    if recipes_4: all_recipes_for_sorting.extend(recipes_4)

    if recipes_5: all_recipes_for_sorting.extend(recipes_5)


    # Remove duplicates from the combined list for cleaner sorting demo

    unique_recipes = {}

    for recipe in all_recipes_for_sorting:

        unique_recipes[recipe.name.lower()] = recipe

    all_recipes_for_sorting = list(unique_recipes.values())



    if all_recipes_for_sorting:

        print("\n--- Sorting All Collected Recipes with User Profile ---")


        # Sort by calories ascending, showing personalization (Kaiserschmarrn first, then calories)

        # and filtering (no Cilantro, no meat for vegetarian)

        sorted_by_calories_asc = sort_recipes(all_recipes_for_sorting, "calories", ascending=True, user_profile=my_user_profile)

        display_recipes(sorted_by_calories_asc, "All Results Sorted by Calories (Ascending, with personalization)")


        # Sort by protein descending, showing personalization and filtering

        sorted_by_protein_desc = sort_recipes(all_recipes_for_sorting, "protein", ascending=False, user_profile=my_user_profile)

        display_recipes(sorted_by_protein_desc, "All Results Sorted by Protein (Descending, with personalization)")


        # Sort by name ascending, showing personalization and filtering

        sorted_by_name_asc = sort_recipes(all_recipes_for_sorting, "name", ascending=True, user_profile=my_user_profile)

        display_recipes(sorted_by_name_asc, "All Results Sorted by Name (Ascending, with personalization)")


    print("\n--- Final User Profile State ---")

    print(my_user_profile)