Multilingual Book Recommendation Chatbot

Build a chatbot with Amazon Lex, Lambda, and Translate to recommend books.

Introduction

30 Second Summary

Think about the last time you asked a friend for a book recommendation. They probably asked follow-up questions like "what genre?" and "what mood are you in?" before suggesting something perfect.

In this project, you will build a chatbot that does exactly that. Your bot uses three AWS services: Amazon Lex for conversation, AWS Lambda for logic, and Amazon Translate for delivering recommendations in any language.

What You'll Build

You type "recommend me a book" in the test console, answer questions about your genre and mood, and receive a personalized book recommendation translated into Spanish, French, or whichever language you choose.

By the end of this project, you'll have:

  • A conversational chatbot that asks follow-up questions about your genre, mood, and preferred language before making a recommendation.
  • A serverless function that processes your preferences and picks the perfect book from a curated collection based on your combination of genre and mood.
  • Instant multilingual translations of your recommendation into Spanish, French, German, Japanese, or other languages you choose.
  • Secret Mission: Add a second intent that lets users ask for a translated book summary, stretching into multi-intent bot architecture.

Are there any prerequisites?

You need an AWS account to complete this project. If you don't have one yet, you can create a free-tier-eligible account at aws.amazon.com. Everything in this project stays within the AWS Free Tier.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Create Your Amazon Lex V2 Bot

Every chatbot needs a foundation before it can hold a conversation. Amazon Lex V2 is AWS's service for building conversational AI, and your book recommendation bot starts here.

In this step, you'll create a blank bot in the Lex console. Starting from a blank slate gives you full control over every intent and slot rather than inheriting a pre-built template that does not match your use case.

In this step, get ready to:
  • Navigate to the Amazon Lex V2 console and create a blank bot.
  • Configure English (US) as the bot's language.
  • Verify the bot appears with its default FallbackIntent.
Create a blank bot in the Lex console

Amazon Lex V2 offers templates, but a blank bot lets you define every piece of the conversation yourself. You'll fill out a short configuration form to get your bot started.

  • Navigate to the Amazon Lex V2 console.
  • Click Create bot.
  • Under Creation method, select Traditional method.
  • Select Create a blank bot.
  • In the Bot name field, enter BookRecommendationBot.
  • In the Description field, enter A chatbot that recommends books based on user preferences and translates them.
  • Under IAM permissions, select Create a role with basic Amazon Lex permissions.
  • For Children's Online Privacy Protection Act (COPPA), select No.

What is Amazon Lex?

Amazon Lex is the same technology that powers Amazon Alexa. It handles the hard parts of conversational AI: understanding what a user means (natural language understanding) and managing multi-turn conversations.

The IAM role you just created gives Lex the permissions it needs to operate within your AWS account. AWS created it automatically so you do not have to configure permissions manually.

  • Leave the Idle session timeout at the default (5 minutes).
  • Click Next.
Configure the language

The next page asks which language your bot should understand. You'll use English (US) as the base language, and your Lambda function will handle translation later.

  • Confirm that English (US) is selected as the language.
  • Under Voice interaction, select None. This keeps the bot text-only for this project.
  • Click Done.
Verify bot creation

After clicking Done, Lex takes you straight into the bot's intent editor. Before you move on, take a moment to predict what you expect to see on this page.

  • Look at the left navigation panel. You should see Intents listed with a FallbackIntent already created.
  • Check the top navigation breadcrumb. You should see BookRecommendationBot displayed as the bot name.
  • Confirm that the version shows as Draft and the language is listed as English (US).

What is the FallbackIntent?

Every Lex bot comes with a FallbackIntent by default. This is the safety net that catches anything the bot does not understand. If a user types something that does not match any of your custom intents, the FallbackIntent responds with a generic "I didn't understand" message.

Don't see the intent editor?

If you landed on a different page after clicking Done, navigate to the Amazon Lex V2 console and click on BookRecommendationBot in the bot list. Then select Intents in the left sidebar.

If you do not see your bot in the list, check that you are in the same AWS region where you created it. The region selector is in the top-right corner of the console.

Help me find my BookRecommendationBot in the Lex console.

Your bot exists, but it is completely blank. It does not know what a book recommendation is or how to ask users about their preferences. In the next step, you'll teach it to understand book requests by creating a custom intent with slots that collect genre, mood, and language preferences.

Build Your Book Recommendation Intent

Your bot exists, but right now it has no idea what users want. If you typed "recommend me a book" right now, the bot would trigger its FallbackIntent and shrug.

You need to teach the bot what a book recommendation request looks like and what information to collect. In this step, you will create an intent with slots that guide users through a multi-turn conversation about their reading preferences.

In this step, get ready to:
  • Create a custom slot type for book genres.
  • Build an intent with sample utterances, slots, and a confirmation prompt.
  • Test the multi-turn conversation flow in the Lex test console.
Create a custom slot type for genre

Before you create the intent itself, you need a custom slot type that lists the genres your chatbot supports. This tells Lex exactly which values to accept when a user says what genre they want.

  • In the left navigation panel of your bot editor, click Slot types.
  • Click Add slot type.
  • Select Add blank slot type.
  • Enter GenreType as the slot type name.
  • Add the following slot values one by one:
  • mystery (add synonyms: mysteries, detective).
  • romance.
  • science fiction (add synonyms: sci-fi, scifi).
  • fantasy.
  • thriller.
  • biography.
  • self-help.
  • history.
  • Click Save slot type.

What is a slot type?

A slot type defines the set of values Lex can recognize for a particular piece of information. Your custom GenreType teaches Lex that "mystery", "sci-fi", and "detective" all mean the same thing.

Synonyms are important because users rarely say things the exact same way. Someone might type "sci-fi" instead of "science fiction" and Lex will still understand.

Adding synonyms

To add synonyms for a value, click the value row after adding it, then type each synonym and press Enter. You only need synonyms where users would say the same genre differently.

Create the GetBookRecommendation intent with slots

Now you will create the intent that fires when a user asks for a book recommendation. An intent needs two things: sample utterances (phrases that trigger it) and slots (the information it collects).

  • In the left navigation, click Intents.
  • Click Add intent, then select Add empty intent.
  • Enter GetBookRecommendation as the intent name.

Scroll down to the Sample utterances section. Add each of the following utterances one at a time by typing each phrase and pressing Enter:

  • recommend me a book.
  • I want a book recommendation.
  • suggest a book.
  • what should I read.
  • I need a book to read.
  • can you recommend something to read.
  • give me a book suggestion.
  • I'm looking for a book.

Why so many utterances?

More sample utterances help Lex understand the many different ways a user might phrase the same request. Ten utterances is a solid starting point for a custom intent.

  • Continue adding these final utterances:
  • help me find a book.
  • what book should I read next.

Now scroll down to the Slots section. This is where you define the three pieces of information your bot collects during the conversation.

  • Click Add slot to add the first slot.
  • Enter Genre as the slot name.
  • For Slot type, select GenreType (the custom type you just created).
  • In the Prompts field, enter:
What genre are you interested in? I have mystery, romance, science fiction, fantasy, thriller, biography, self-help, and history.
  • Click Add slot to add the second slot.
  • Enter Mood as the slot name.
  • For Slot type, select AMAZON.AlphaNumeric.
  • In the Prompts field, enter:
What mood are you in? For example: light, intense, thought-provoking, or adventurous.
  • Click Add slot to add the third slot.
  • Enter Language as the slot name.
  • For Slot type, select AMAZON.AlphaNumeric.
  • In the Prompts field, enter:
What language would you like your recommendation in? For example: Spanish, French, German, or English.

Why use AMAZON.AlphaNumeric for Mood and Language?

A custom slot type requires you to list every valid value up front. AMAZON.AlphaNumeric accepts free-form text input, which keeps setup simple.

Your Lambda function (built in the next step) will handle mapping whatever the user types to a supported value. This pattern lets the slot collect raw input while your code does the validation.

Now scroll down to the Confirmation prompts section. This is what the bot says to confirm the user's choices before processing them.

  • Toggle the Confirmation section to active.
  • In the Confirm prompt field, enter:
Got it! You want a {Genre} book that feels {Mood}, translated into {Language}. Should I find a recommendation?
  • In the Decline response field, enter:
No problem! Let me know if you'd like to try different preferences.
  • Click Save intent at the bottom of the page.

What do the curly braces mean?

The {Genre}, {Mood}, and {Language} placeholders in your confirmation prompt get replaced with the actual values the user provided. So if someone says "mystery", "intense", and "Spanish", the bot confirms: "You want a mystery book that feels intense, translated into Spanish."

Build and test the conversation flow

Your intent is configured. Time to compile everything and see if the bot actually carries on a conversation.

  • Click Build at the top of the page.
  • Wait for the build to complete. This takes 1-2 minutes.

Why do I need to build?

Amazon Lex compiles your intents, utterances, and slots into a language model each time you build. Changes you make in the editor are not live until you click Build.

Before you test, think about what you expect to happen. You gave the bot sample utterances like "recommend me a book" and three slots to fill. What do you think it will say after you confirm your preferences?

  • Click Test in the top right corner to open the test panel.
  • Type I want a book recommendation and press Enter.
  • The bot asks "What genre are you interested in?" with your full list of genres.
  • Type mystery and press Enter.
  • The bot asks about your mood.
  • Type intense and press Enter.
  • The bot asks about your preferred language.
  • Type Spanish and press Enter.

The bot repeats your choices in the confirmation prompt. Type yes to confirm.

After confirming, the bot responds with a generic fulfillment message or stays silent. That is expected. There is no Lambda function connected yet to actually recommend a book. That is what you will build next.

Bot not asking for genre?

If the bot triggers the FallbackIntent instead of your genre prompt, check that you typed at least one of your sample utterances exactly as listed. Also confirm you clicked Build after saving your intent. Unsaved or unbuilt changes are invisible to the test console.

If the bot asks for genre but skips mood or language, check that all three slots are marked as Required in the Slots section of your intent.

Help me debug why my Lex bot is not collecting all three slots.

Your bot carries on a real conversation now. It collects preferences, confirms them, and waits for something to happen. Next up, you will build the Lambda function that takes those preferences and returns an actual book recommendation.

Create the Lambda Function

Your bot can now ask users about their genre, mood, and language preferences. But after the user confirms their choices, nothing useful happens.

You need a brain behind the bot. An AWS Lambda function will receive the slot values from Lex, look up a matching book, and send back a real recommendation.

In this step, get ready to:
  • Create a Lambda function in the AWS console with a Python 3.12 runtime.
  • Write the recommendation logic that maps genres and moods to books.
  • Test the function with a sample Lex event to confirm it returns a recommendation.
Create the Lambda function in the console
  • Open the AWS Lambda console at https://console.aws.amazon.com/lambda/.
  • Choose Create function.
  • Select Author from scratch.
  • For Function name, enter BookRecommendationFunction.
  • For Runtime, choose Python 3.12.
  • For Architecture, choose x86_64.
  • Expand Change default execution role and select Create a new role with basic Lambda permissions.
  • Choose Create function.

You should now see the function's Code source pane with a default lambda_function.py file open in the editor.

What is AWS Lambda?

Lambda lets you run code without managing servers. You upload a function, and AWS runs it every time a trigger fires. In this project, Amazon Lex triggers your function whenever a user confirms their book preferences.

Write the book data and imports

The function needs a dictionary of book recommendations organized by genre and mood. It also needs a map of language names to translation codes for later use.

  • In the Code source editor, select all the existing code and delete it.
  • Paste the following code to set up the imports, Translate client, and the first three genres of book data:
import json
import boto3

# Create a Translate client for later use
translate_client = boto3.client('translate')

# Book recommendations organized by genre and mood
BOOK_RECOMMENDATIONS = {
    "mystery": {
        "light": {"title": "The Thursday Murder Club", "author": "Richard Osman", "description": "A cozy mystery about four retirees who meet weekly to investigate cold cases, until a real murder lands on their doorstep."},
        "intense": {"title": "Gone Girl", "author": "Gillian Flynn", "description": "A dark psychological thriller about a husband suspected of his wife's disappearance, with shocking twists at every turn."},
        "thought-provoking": {"title": "The Name of the Rose", "author": "Umberto Eco", "description": "A medieval monastery mystery that weaves philosophy, theology, and detective work into one intricate puzzle."},
        "adventurous": {"title": "The Da Vinci Code", "author": "Dan Brown", "description": "A symbologist races across Europe solving ancient puzzles to uncover a secret that could shake the foundations of Christianity."}
    },
    "romance": {
        "light": {"title": "Beach Read", "author": "Emily Henry", "description": "Two writers with opposite styles swap genres for the summer, and sparks fly in this witty and heartfelt love story."},
        "intense": {"title": "Wuthering Heights", "author": "Emily Bronte", "description": "A passionate and destructive love story set on the wild English moors that spans generations."},
        "thought-provoking": {"title": "Normal People", "author": "Sally Rooney", "description": "Two Irish students navigate their complicated connection from school through university, exploring class and intimacy."},
        "adventurous": {"title": "The Time Traveler's Wife", "author": "Audrey Niffenegger", "description": "A love story complicated by involuntary time travel, exploring how love endures across scattered moments in time."}
    },
    "science fiction": {
        "light": {"title": "The Hitchhiker's Guide to the Galaxy", "author": "Douglas Adams", "description": "A hilarious cosmic adventure following an ordinary man swept off Earth moments before its demolition."},
        "intense": {"title": "Dune", "author": "Frank Herbert", "description": "An epic saga of politics, religion, and survival on a desert planet that holds the key to the universe's most valuable resource."},
        "thought-provoking": {"title": "Brave New World", "author": "Aldous Huxley", "description": "A disturbing vision of a future society engineered for stability through genetic modification and conditioning."},
        "adventurous": {"title": "Ender's Game", "author": "Orson Scott Card", "description": "A child genius is recruited to battle school to prepare for an alien invasion, pushing the boundaries of strategy and morality."}
    },

What is this dictionary structure?

Each genre maps to four moods (light, intense, thought-provoking, adventurous). Each mood maps to a book with a title, author, and description. When Lex sends the user's genre and mood, the function looks up the matching book.

  • Directly below (no blank line between), paste the next three genres:
    "fantasy": {
        "light": {"title": "The House in the Cerulean Sea", "author": "TJ Klune", "description": "A heartwarming story about a caseworker who discovers magic and family in the most unexpected place."},
        "intense": {"title": "The Name of the Wind", "author": "Patrick Rothfuss", "description": "A legendary figure recounts his extraordinary life, from orphaned street kid to the most notorious wizard alive."},
        "thought-provoking": {"title": "The Left Hand of Darkness", "author": "Ursula K. Le Guin", "description": "An envoy visits a planet where people have no fixed gender, challenging everything he knows about society and identity."},
        "adventurous": {"title": "The Hobbit", "author": "J.R.R. Tolkien", "description": "A comfort-loving hobbit is swept into an epic quest to reclaim a dwarven kingdom from a fearsome dragon."}
    },
    "thriller": {
        "light": {"title": "The Firm", "author": "John Grisham", "description": "A young lawyer discovers his dream job at a prestigious firm hides a deadly secret, and now he must outsmart everyone to survive."},
        "intense": {"title": "The Girl with the Dragon Tattoo", "author": "Stieg Larsson", "description": "A disgraced journalist and a brilliant hacker team up to investigate a decades-old disappearance in a wealthy family."},
        "thought-provoking": {"title": "Shutter Island", "author": "Dennis Lehane", "description": "A U.S. Marshal investigates a disappearance at a remote psychiatric facility, where nothing is what it seems."},
        "adventurous": {"title": "The Bourne Identity", "author": "Robert Ludlum", "description": "A man with no memory discovers he possesses deadly skills and must unravel his own identity while being hunted."}
    },
    "biography": {
        "light": {"title": "Bossypants", "author": "Tina Fey", "description": "A hilarious and honest memoir from one of comedy's sharpest minds about her journey from awkward kid to SNL head writer."},
        "intense": {"title": "Educated", "author": "Tara Westover", "description": "A woman raised in a survivalist family with no formal schooling fights her way to education and a new understanding of the world."},
        "thought-provoking": {"title": "Sapiens", "author": "Yuval Noah Harari", "description": "A sweeping history of humankind from the Stone Age to the present, challenging how we think about progress and civilization."},
        "adventurous": {"title": "Into the Wild", "author": "Jon Krakauer", "description": "The true story of a young man who abandoned everything to live in the Alaskan wilderness, with tragic consequences."}
    },
  • Paste the final two genres and the closing brace, followed by the language map:
    "self-help": {
        "light": {"title": "Atomic Habits", "author": "James Clear", "description": "A practical guide to building good habits and breaking bad ones through small, incremental changes."},
        "intense": {"title": "Can't Hurt Me", "author": "David Goggins", "description": "A raw memoir of mental toughness from a man who transformed himself from a broken childhood to a Navy SEAL and ultramarathon runner."},
        "thought-provoking": {"title": "Thinking, Fast and Slow", "author": "Daniel Kahneman", "description": "A Nobel laureate reveals the two systems that drive the way we think and the biases that shape our decisions."},
        "adventurous": {"title": "The 4-Hour Workweek", "author": "Timothy Ferriss", "description": "A blueprint for escaping the 9-to-5 grind by automating income and designing a life of freedom and adventure."}
    },
    "history": {
        "light": {"title": "A Short History of Nearly Everything", "author": "Bill Bryson", "description": "A funny and accessible journey through science and history, explaining how we got from nothing at all to here."},
        "intense": {"title": "The Guns of August", "author": "Barbara W. Tuchman", "description": "A gripping account of the first month of World War I that reads like a thriller and reveals how catastrophe unfolded."},
        "thought-provoking": {"title": "Guns, Germs, and Steel", "author": "Jared Diamond", "description": "An exploration of why some civilizations conquered others, arguing geography and environment shaped human destiny."},
        "adventurous": {"title": "Endurance", "author": "Alfred Lansing", "description": "The incredible true story of Shackleton's Antarctic expedition and his crew's fight for survival after their ship was crushed by ice."}
    }
}

# Map language names to Amazon Translate language codes
LANGUAGE_MAP = {
    "spanish": "es",
    "french": "fr",
    "german": "de",
    "portuguese": "pt",
    "japanese": "ja",
    "italian": "it",
    "chinese": "zh",
    "korean": "ko",
    "arabic": "ar",
    "hindi": "hi",
    "english": "en"
}

Why a language map?

Amazon Translate uses short language codes like es for Spanish and fr for French. Your users will type the full language name, so this dictionary translates between the two formats.

Write the handler functions

Now you need the logic that reads the Lex event, picks the right book, and sends back a response in the format Lex expects.

  • Below the LANGUAGE_MAP dictionary, paste the helper functions:


def get_slot_value(slots, slot_name):
    # Safely extract the interpreted value from a Lex slot
    if slots and slot_name in slots and slots[slot_name] is not None:
        return slots[slot_name]['value']['interpretedValue']
    return None


def translate_text(text, target_language_code):
    # Skip translation if target language is English
    if target_language_code == "en":
        return text
    # Call Amazon Translate to convert the text
    result = translate_client.translate_text(
        Text=text,
        SourceLanguageCode="en",
        TargetLanguageCode=target_language_code
    )
    return result['TranslatedText']

What do these helpers do?

The get_slot_value() function safely navigates the nested Lex event structure to pull out a slot's value without crashing if the slot is empty.

The translate_text() function calls Amazon Translate to convert English text into the target language. It skips the API call entirely if the user wants English.

  • Paste the recommendation handler that processes the user's choices:


def handle_get_book_recommendation(slots):
    # Extract slot values from the Lex event
    genre = get_slot_value(slots, 'Genre')
    mood = get_slot_value(slots, 'Mood')
    language = get_slot_value(slots, 'Language')

    # Normalize to lowercase for dictionary lookup
    genre_lower = genre.lower() if genre else "mystery"
    mood_lower = mood.lower() if mood else "light"
    language_lower = language.lower() if language else "english"

    # Map the language name to a translation code
    target_lang_code = LANGUAGE_MAP.get(language_lower, "en")

    # Look up the matching book
    genre_books = BOOK_RECOMMENDATIONS.get(genre_lower, BOOK_RECOMMENDATIONS["mystery"])
    book = genre_books.get(mood_lower, genre_books["light"])

    # Build the recommendation message
    recommendation = (
        f"I recommend '{book['title']}' by {book['author']}. "
        f"{book['description']}"
    )

    # Translate if the user did not ask for English
    if target_lang_code != "en":
        recommendation = translate_text(recommendation, target_lang_code)

    return recommendation

How does the lookup work?

The function normalizes user input to lowercase, then uses it as a key into the nested dictionary. If the genre or mood does not match (for example, if the user typed a creative answer), it falls back to the mystery genre or light mood.

  • Paste the main Lambda handler that Lex calls directly:


def lambda_handler(event, context):
    # Get the intent name and slots from the Lex event
    intent_name = event['sessionState']['intent']['name']
    slots = event['sessionState']['intent']['slots']

    # Route to the correct handler based on intent
    if intent_name == 'GetBookRecommendation':
        message = handle_get_book_recommendation(slots)
    else:
        message = "I'm sorry, I can only help with book recommendations right now."

    # Build the Lex V2 response format
    response = {
        "sessionState": {
            "dialogAction": {
                "type": "Close"
            },
            "intent": {
                "name": intent_name,
                "state": "Fulfilled"
            }
        },
        "messages": [
            {
                "contentType": "PlainText",
                "content": message
            }
        ]
    }

    return response

Why route on intent name?

Amazon Lex V2 allows only one Lambda function per bot alias. If you add more intents later, this routing pattern lets the same function handle all of them by checking which intent triggered the call.

  • Choose Deploy in the Code source pane to save the function.

You should see a green banner confirming the function was successfully updated.

Seeing a syntax error on deploy?

Double check that the BOOK_RECOMMENDATIONS dictionary has matching opening and closing braces for every genre. Each genre block ends with }, except the last one (history) which ends with just }.

Also confirm that the outermost } closing the BOOK_RECOMMENDATIONS dictionary appears on its own line before LANGUAGE_MAP.

Help me fix a syntax error in my Lambda function code.

✔️ Awesome, I've got everything!

Great. Make sure you see the green success banner before moving on.

ⓧ I'd like to double check the full code

Scroll through your code editor and confirm you have these sections in order:

  • The import json and import boto3 lines at the top, followed by translate_client = boto3.client('translate').
  • The BOOK_RECOMMENDATIONS dictionary with 8 genres (mystery, romance, science fiction, fantasy, thriller, biography, self-help, history), each containing 4 moods.
  • The LANGUAGE_MAP dictionary mapping 11 language names to codes.
  • The get_slot_value() helper function.
  • The translate_text() helper function.
  • The handle_get_book_recommendation() function.
  • The lambda_handler() function at the bottom.
Test the Lambda function

Before connecting this to Lex, you can test the function directly in the Lambda console with a sample event that mimics what Lex would send.

  • Choose the Test tab in the Lambda console.
  • For Event name, enter TestBookRec.
  • Replace the default event JSON with the following sample Lex V2 event:
{
  "sessionState": {
    "intent": {
      "name": "GetBookRecommendation",
      "slots": {
        "Genre": {
          "value": {
            "interpretedValue": "mystery"
          }
        },
        "Mood": {
          "value": {
            "interpretedValue": "intense"
          }
        },
        "Language": {
          "value": {
            "interpretedValue": "english"
          }
        }
      }
    }
  }
}

What is this test event?

This JSON mimics the structure Lex V2 sends to your Lambda function when a user completes the conversation. It provides slot values for Genre (mystery), Mood (intense), and Language (english) so your function can process them.

  • Choose Save to save the test event.

Before you run the test, what do you think the response will contain? Think about which book matches mystery + intense in your dictionary.

  • Choose Test.

You should see Executing function: succeeded with a response containing the Gone Girl recommendation. The dialogAction type is Close and the intent state is Fulfilled.

Seeing an error instead of a successful execution?

If you see a KeyError mentioning sessionState, make sure your test event JSON exactly matches the structure shown above. The nested path event['sessionState']['intent']['slots'] must exist.

If you see a TranslateException or access denied error, that is expected for now. The test uses Language set to english, which skips the Translate call entirely. If you accidentally set Language to something else, change it back to english for this test.

Help me debug my Lambda test event error.

Your Lambda function works in isolation. Next up, you will wire it to your Lex bot so the chatbot calls this function when a user confirms their preferences.

Connect Lambda to Lex and Hit the Translation Wall

Your Lambda function works perfectly in isolation. You tested it, it returned a book recommendation, and all the logic is solid.

The problem is that Amazon Lex has no idea your function exists. When a user confirms their preferences, the bot has nowhere to send them. In this step, you will wire Lambda to Lex so the bot can deliver real recommendations.

In this step, get ready to:
  • Add Amazon Translate permissions to your Lambda role.
  • Attach your Lambda function to the Lex bot alias.
  • Enable the fulfillment code hook and test the full conversation flow.
Add Translate permissions to the Lambda role

Your Lambda function's code already references Amazon Translate, but the IAM role attached to the function does not have permission to call Translate yet. Without this permission, any translation attempt would fail.

  • Switch back to the Lambda console at https://console.aws.amazon.com/lambda/.
  • Click on BookRecommendationFunction to open it.
  • Choose the Configuration tab.
  • Select Permissions in the left sidebar.
  • Under Execution role, click the role name link. This opens the IAM console in a new tab.

You are now in the IAM console looking at your Lambda function's execution role.

  • Click Add permissions, then select Attach policies.
  • In the search bar, type TranslateReadOnly.
  • Select the checkbox next to TranslateReadOnly.
  • Click Add permissions.

You should see TranslateReadOnly now listed alongside the basic Lambda permissions policy in the role's permissions table.

What is TranslateReadOnly?

This is a managed IAM policy that grants read-only access to Amazon Translate. It includes the translate:TranslateText permission your Lambda function needs to convert text between languages.

Don't see TranslateReadOnly in search?

Make sure you selected Attach policies (not "Create policy"). Also check that you are searching in the filter box on the permissions page, not in the main IAM search bar at the top.

Help me find and attach the TranslateReadOnly policy to my Lambda role.

Attach Lambda to the Lex bot alias

Amazon Lex V2 uses a bot alias to manage which version of your bot is active. You need to tell the alias which Lambda function to call when fulfillment is needed.

  • Return to the Amazon Lex console and open BookRecommendationBot.
  • In the left navigation, choose Aliases.
  • Click on TestBotAlias.
  • Under Languages, choose English (US).
  • In the Lambda function source dropdown, select BookRecommendationFunction.
  • Click Save.

You should see BookRecommendationFunction displayed as the Lambda function source for English (US).

Why one Lambda per alias?

Amazon Lex V2 allows one Lambda function per bot alias per language. If your bot has multiple intents, they all share the same Lambda function. The function uses the intent name from the event to decide what logic to run.

Don't see your function in the dropdown?

Make sure you are in the same AWS region for both Lambda and Lex. Check the region selector in the top-right corner of the console. Your Lambda function and your Lex bot must be in the same region.

Help me connect my Lambda function to my Lex bot alias.

Enable the fulfillment code hook and test

The alias knows about your Lambda function, but the intent itself has not been told to use it. You need to enable the fulfillment code hook on the GetBookRecommendation intent so Lex actually calls Lambda after the user confirms.

  • In the left navigation, choose Intents.
  • Click on GetBookRecommendation.
  • Scroll down to the Fulfillment section.
  • Toggle the Active button to active (it turns blue).
  • Expand On successful fulfillment, then choose Advanced options.
  • Select the checkbox labelled Use a Lambda function for fulfillment.
  • Click Update options to close the advanced panel.
  • Click Save intent.

Now rebuild the bot so Lex picks up the new fulfillment configuration.

  • Click Build at the top of the page.
  • Wait for the build to complete (1-2 minutes).

Before you test, think about this: you told the bot to call your Lambda function, and your Lambda function has a language mapping dictionary. If you ask for a recommendation in Spanish, what do you think the bot will respond with?

  • Open the Test panel on the right side of the page.
  • Type recommend me a book and press Enter.
  • When the bot asks for genre, type mystery.
  • When the bot asks for mood, type intense.
  • When the bot asks for language, type Spanish.
  • When the bot shows the confirmation prompt, type yes.

The bot returns a book recommendation in English, even though you asked for Spanish. You should see something like "I recommend 'Gone Girl' by Gillian Flynn" followed by the description, all in English.

Why is this still in English?

This is the translation wall. Your Lambda function is connected and returning real book recommendations based on the genre and mood slots. That part works perfectly.

But the translation is not working yet. The function has the code structure for translation, but the language slot value coming through Lex is not being processed into a successful Translate API call. You will fix this in the next step by ensuring the Amazon Translate integration works end-to-end.

Not seeing any recommendation at all?

If the bot responds with a fallback message or an error instead of a book recommendation, check these things:

  • Confirm the fulfillment code hook is toggled to Active and you checked the "Use a Lambda function for fulfillment" box in Advanced options.
  • Make sure you saved the intent and rebuilt the bot after enabling fulfillment.
  • Verify that BookRecommendationFunction is selected under your TestBotAlias for English (US).

Help me get my Lex bot to call my Lambda function for fulfillment.

Your Lambda function is now wired to Lex and delivering real book recommendations. The missing piece is obvious: the user asked for Spanish and got English. Next up, you will make Amazon Translate deliver on that language promise.

Add Amazon Translate for Multilingual Recommendations

Your bot recommends books, but it ignores the language preference entirely. Users who asked for Spanish or French still get an English response.

In this step, you will confirm that Amazon Translate is wired into your Lambda function to convert recommendations into the user's chosen language. Then you will rebuild the bot and test the full multilingual pipeline end-to-end.

In this step, get ready to:
  • Verify the Lambda code calls Amazon Translate with the correct language mapping.
  • Rebuild the Lex bot so it picks up the latest Lambda logic.
  • Test the chatbot with Spanish and French to confirm multilingual recommendations work.
Verify the Lambda translation code

Your Lambda function already contains the translation logic from when you deployed it. The issue is that the bot has not been rebuilt since you connected the Lambda, so the full flow has not been tested with a non-English language input.

  • Return to the Lambda console at https://console.aws.amazon.com/lambda/.
  • Open BookRecommendationFunction.
  • In the code editor, find the LANGUAGE_MAP dictionary near the top of the file. Confirm it contains entries mapping language names to codes:
LANGUAGE_MAP = {
    "spanish": "es",
    "french": "fr",
    "german": "de",
    "portuguese": "pt",
    "japanese": "ja",
    "italian": "it",
    "chinese": "zh",
    "korean": "ko",
    "arabic": "ar",
    "hindi": "hi",
    "english": "en"
}

What does this dictionary do?

Amazon Translate expects ISO language codes like es or fr, not full words like "Spanish". This dictionary translates the user's plain-English answer into the code Amazon Translate needs.

  • Now find the translate_text function. Confirm it matches this:
def translate_text(text, target_language_code):
    if target_language_code == "en":
        return text
    result = translate_client.translate_text(
        Text=text,
        SourceLanguageCode="en",
        TargetLanguageCode=target_language_code
    )
    return result['TranslatedText']

How does this function work?

If the target language is English, it skips the API call entirely and returns the text unchanged. For any other language, it calls translate_client.translate_text() with the source set to en and the target set to the user's chosen code.

  • Finally, find the handle_get_book_recommendation function. Confirm it ends with the translation call:
    if target_lang_code != "en":
        recommendation = translate_text(recommendation, target_lang_code)

    return recommendation

What triggers the translation?

The function reads the Language slot, converts it to lowercase, and looks it up in LANGUAGE_MAP. If the result is anything other than en, the recommendation text is sent through Amazon Translate before being returned to the user.

  • Click Deploy to confirm the function is saved with the latest code.

You should see a green banner confirming the function was successfully updated.

Don't see the green banner?

Make sure you are on the Code tab of your Lambda function. The Deploy button is above the code editor.

help me deploy my Lambda function

Test the full multilingual flow

The Lambda code is ready. Now you need to rebuild the Lex bot so it picks up any changes, then test the conversation with a non-English language.

  • Return to the Amazon Lex console at https://console.aws.amazon.com/lexv2/.
  • Open BookRecommendationBot.
  • Click Build at the top of the page.
  • Wait for the build to complete. This takes 1-2 minutes.

Before you test, think about what you expect to happen. If you type "Spanish" as your language preference, do you think the bot will respond in Spanish this time?

  • Open the Test panel on the right side of the screen.
  • Type suggest a book and press Enter.
  • When the bot asks for a genre, type mystery.
  • When the bot asks for a mood, type intense.
  • When the bot asks for a language, type Spanish.
  • When the bot shows the confirmation prompt, type yes.

The bot responds with a book recommendation translated into Spanish. You should see something like "Recomiendo 'Gone Girl' de Gillian Flynn..." instead of the English version you saw in the previous step.

What just happened?

Lex collected your three slot values and passed them to Lambda. Lambda looked up "mystery" + "intense" to find Gone Girl, then passed the recommendation text to Amazon Translate with TargetLanguageCode set to es. Translate returned the Spanish text, and Lambda sent it back to Lex.

  • Test again with a different language. Type I want a book recommendation to start a new conversation.
  • This time, choose any genre and mood you like, but type French for the language.
  • Confirm the intent when prompted.

The bot responds with a recommendation translated into French, confirming that Amazon Translate works across multiple languages.

Still seeing English responses?

Make sure you typed the language name exactly as it appears in the LANGUAGE_MAP dictionary (e.g., "Spanish" not "Espanol"). The code converts your input to lowercase before looking it up.

Check that the TranslateReadOnly policy is still attached to your Lambda role. In the Lambda console, go to Configuration then Permissions and click the role name to verify.

help me debug why my Lex bot returns English instead of the translated language

Your multilingual chatbot pipeline is fully working. You have connected three AWS services together: Amazon Lex collects preferences, AWS Lambda picks the right book, and Amazon Translate delivers it in the user's chosen language. Next up, a Secret Mission that extends your bot with a brand new intent.

Secret mission

Add a Book Summary Intent

Your chatbot recommends books, but what if someone wants to know more about a book before reading it? In this secret mission, you'll add a second intent that lets users ask for a translated book summary. This tests your ability to build multi-intent bots independently.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses only AWS Free Tier services, so there are no ongoing costs as long as your account is within its first 12 months and you stay below the free tier limits.

Free tier limits for this project

Amazon Lex V2 gives you 10,000 text requests per month for 12 months. AWS Lambda gives you 1 million requests per month (always free, no 12-month limit). Amazon Translate gives you 2 million characters per month for 12 months.

After your 12-month free tier expires, Lex charges $0.00075 per text request and Translate charges $15.00 per million characters. If you are no longer experimenting, delete the bot to avoid accidental charges.

Resources you used:

  • Amazon Lex V2 bot (BookRecommendationBot).
  • AWS Lambda function (BookRecommendationFunction).
  • IAM role for the Lambda function (auto-created during setup).

Keep everything running

No action needed. Choose this if you are still actively testing your chatbot or plan to extend it further.

  • All resources stay within the free tier for normal testing volumes.
  • The bot only incurs charges when it receives requests. If nobody sends it messages, it costs nothing.
  • The Lambda function costs nothing unless invoked.

Pause - I'll come back to this later

No special pause action is needed for these resources. The bot and Lambda function sit idle at zero cost when not receiving requests.

  • Your Lex bot remains in Draft status and does not serve traffic unless you publish it to a production alias.
  • The Lambda function only runs when triggered by Lex, so it generates no cost while idle.
  • You can return to the project at any time and pick up where you left off.

Delete - I don't want to use this again

Remove all project resources from your AWS account. Follow each step below.

  • Delete the Lex bot:
  • Delete the Lambda function:
  • Delete the IAM role:

Nice Work!

Nice Work!

You just built a multilingual book recommendation chatbot that carries on a real conversation, picks a book based on your preferences, and translates it into your chosen language.

Here's what you learned:

  • Built an Amazon Lex V2 chatbot from scratch with custom intents, slots, and slot types that collect user preferences through a multi-turn conversation.
  • Created an AWS Lambda function in Python that processes conversational input and returns dynamic book recommendations.
  • Connected three AWS services (Lex, Lambda, Translate) through IAM roles and fulfillment hooks to deliver recommendations in multiple languages.
  • Secret Mission: Added a multi-intent bot architecture with routing logic in Lambda to serve both book recommendations and translated summaries.

Ready to quiz yourself?