Build a Construction Spanish Tutor CLI
Let's build a terminal AI tutor that teaches construction Spanish locally.
Introduction
30 Second Summary
If you work in construction, clear communication on the job site is everything.
Generic language apps teach you how to order coffee, but they do not teach you the Spanish word for angle grinder or how to explain a brake problem to a mechanic.
In this project, you will build a terminal-based AI Spanish tutor that:
- Specializes in construction, automotive, and heavy equipment vocabulary.
- Runs locally on your machine using Ollama and corrects your grammar.
- Teaches you the right technical terms, and adapts to your skill level.
What You'll Build
You will have a live conversation with an AI tutor in your terminal, type something in broken Spanish, and watch it respond with the correct construction term and a grammar fix.
By the end of this project, you'll have:
- A streaming chat loop where you type in Spanish and the tutor responds word-by-word, correcting mistakes and teaching you terms like el taladro or la amoladora angular.
- Three scenario modes that drop you into realistic situations: buying tools at a hardware store, receiving directions from a foreman, or describing a truck problem to a mechanic.
- An adaptive difficulty system that starts with short sentences and English translations, then ramps up to full-speed Spanish with regional slang as you improve.
- Secret Mission: Wire up Google Gemini as a cloud backend so the tutor works both offline and online with a single environment variable swap.
Are there any prerequisites?
You'll need a macOS, Windows, or Linux computer with at least 8 GB of RAM and roughly 6 GB of free disk space for the model.
Windows learners use Git Bash from Git for Windows for every terminal command, while basic Python familiarity helps with the code.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Ollama and Python Environment
Before you can build a tutor on top of an LLM, you need a local model actually running on your machine.
Ollama makes this painless. It lets you download and run open-source models locally with no GPU configuration. In this step, you will install Ollama, pull the Llama 3.2 model, and set up the Python environment for your project.
In this step, get ready to:
- Prepare Terminal on macOS or Linux, or Git Bash on Windows.
- Install Ollama and test the Llama 3.2 model.
- Create a Python virtual environment with the required package.
Set up your terminal and install Ollama
The system-level Ollama app and CLI run the tutor's model on your computer. Choose your operating system below to install them and confirm the ollama command works before you create a Python environment.
macOS
- Press Cmd+Space to open Spotlight search.
- Type Terminal and press Enter to open it.
- Check whether Ollama is installed by running this command:
ollama --version
✔️ I see a version number
You should see an Ollama version number. Your terminal can reach the local Ollama service.
ⓧ Command not found
- Open Ollama's download page in your browser.
- Click Download for macOS.
- Open the downloaded .dmg file.
- Drag Ollama into the Applications folder.
- Press Cmd+Space and search for Ollama.
- Press Enter to start Ollama.
- Return to Terminal and check the installation by running:
ollama --version
You should now see an Ollama version number.
Windows
Git for Windows includes Git Bash. You use Git Bash for every terminal command in this project.
- Press the Windows key to open Search.
- Type Git Bash.
✔️ Git Bash opens
- Open Git Bash from the search results.
- Confirm Git works by running:
git --version
You should see git version followed by a version number. Keep Git Bash open for the rest of this project.
ⓧ Git Bash is missing
Git for Windows is a permanent development tool on your computer. This project does not ask you to remove it during cleanup.
- Press the Windows key to open Search.
- Type PowerShell and press Enter.
- Install Git for Windows by running this command:
winget install --id Git.Git -e --source winget
Windows may ask you to accept source terms or approve the installer. Accept the prompts so the installation can finish.
- Close PowerShell after the installation completes.
- Press the Windows key and search for Git Bash.
- Open Git Bash from the search results.
- Confirm Git works by running:
git --version
You should see git version followed by a version number. Keep Git Bash open for the rest of this project.
- Check whether Ollama is installed from Git Bash by running:
ollama --version
✔️ I see a version number
You should see an Ollama version number. Git Bash can reach the local Ollama service.
ⓧ Command not found
- Open Ollama's download page in your browser.
- Click Download for Windows.
- Open OllamaSetup.exe from your Downloads folder.
- Approve the Windows installation prompt.
- Close Git Bash after the installer finishes.
- Open Git Bash again from Windows Search.
- Check the installation by running:
ollama --version
You should now see an Ollama version number.
Linux
- Open Terminal from your desktop's application launcher.
- Check whether Git is installed by running:
git --version
✔️ I see a version number
You should see git version followed by a version number.
ⓧ Command not found
- Install Git on Debian or Ubuntu by running:
sudo apt update
sudo apt install git
- Install Git on Fedora by running this command instead:
sudo dnf install git
- Confirm Git works by running:
git --version
You should now see git version followed by a version number.
- Check whether Ollama is installed by running:
ollama --version
✔️ I see a version number
You should see an Ollama version number. Your terminal can reach the local Ollama service.
ⓧ Command not found
- Install Ollama by running the official Linux installation script:
curl -fsSL https://ollama.com/install.sh | sh
The installer can take a few minutes while it downloads Ollama. Leave the terminal open until it completes.
- Check the installation by running:
ollama --version
You should now see an Ollama version number.
Your terminal now reports an Ollama version. That confirms the app and command-line tool are ready on your operating system.
Ollama command still missing?
On macOS or Windows, close and reopen your terminal after installing Ollama so it reloads the command path.
On Linux, rerun the official installation script and check its final output for a failed download or permission issue.
Help me troubleshoot the missing Ollama command.
Pull and test the Llama 3.2 model
Ollama is running, but it does not have any models downloaded yet. You need to pull Llama 3.2, a 2GB model that officially supports Spanish.
- Continue in Terminal on macOS or Linux, or Git Bash on Windows.
Stay in the same terminal
Keep using Terminal on macOS or Linux, or Git Bash on Windows. The remaining commands use the same Bash syntax on all three platforms.
- Download the Llama 3.2 model by running this command:
ollama pull llama3.2
This downloads about 2GB of model weights. You will see a progress bar as each layer downloads. When it finishes, you will see success.
Download stalling or failing?
Confirm Ollama is running before retrying the download. On macOS or Windows, reopen the Ollama app. On Linux, check that the Ollama service is active.
The model download is about 2 GB, so a slower connection can take several minutes. Leave the terminal open while the progress bar moves.
Help me fix the model download.
Now for the exciting part. Before you run the next command, what do you think will happen when you send a Spanish greeting to the model?
- Start a live chat session with the model by running:
ollama run llama3.2
- Type Hola and press Enter.
The model responds in Spanish. You now have a local LLM running entirely on your machine with no API key, no cloud account, and no internet required for inference.
- Press Ctrl+D to exit the chat session and return to your normal terminal prompt.
Why Llama 3.2?
Llama 3.2 (3B) officially supports Spanish as one of its eight languages. It has a 128K context window (great for long conversations) and is only a 2GB download.
Larger models produce better output, but the 3B parameter size hits the sweet spot of quality and speed for a local tutor that needs to respond quickly in conversation.
Create and verify the Python venv
With the system-level Ollama app running, prepare a Python 3.8 or newer project inside a virtual environment. The ollama package you install there is a client library that lets your code call the local Ollama server.
macOS
- Check your Python version by running:
python3 --version
✔️ I see Python 3.8 or newer
Continue if you see Python 3.8 or newer.
ⓧ I see an older version
- Open Python's macOS downloads page.
- Download the macOS 64-bit universal installer for a current Python release.
- Open the downloaded .pkg file.
- Complete the installer with its default options.
- Close and reopen Terminal.
- Check the new version by running:
python3 --version
You should now see Python 3.8 or newer.
ⓧ Command not found
- Open Python's macOS downloads page.
- Download the macOS 64-bit universal installer for a current Python release.
- Open the downloaded .pkg file.
- Complete the installer with its default options.
- Close and reopen Terminal.
- Check the installation by running:
python3 --version
You should now see Python 3.8 or newer.
Windows
- Check your Python version from Git Bash by running:
python --version
✔️ I see Python 3.8 or newer
Continue if you see Python 3.8 or newer. Keep using Git Bash.
ⓧ I see an older version
- Open Python's Windows downloads page.
- Download the Windows installer for a current 64-bit Python release.
- Open the downloaded installer.
- Select Add Python to PATH on the first installer screen.
- Click Install Now.
- Close and reopen Git Bash after installation.
- Check the new version by running:
python --version
You should now see Python 3.8 or newer.
ⓧ Command not found
- Open Python's Windows downloads page.
- Download the Windows installer for a current 64-bit Python release.
- Open the downloaded installer.
- Select Add Python to PATH on the first installer screen.
- Click Install Now.
- Close and reopen Git Bash after installation.
- Check the installation by running:
python --version
You should now see Python 3.8 or newer.
Linux
- Check your Python version by running:
python3 --version
✔️ I see Python 3.8 or newer
Continue if you see Python 3.8 or newer.
ⓧ I see an older version
- Upgrade Python on Debian or Ubuntu by running:
sudo apt update
sudo apt install python3 python3-venv
- Upgrade Python on Fedora by running this command instead:
sudo dnf install python3
- Check the new version by running:
python3 --version
You should now see Python 3.8 or newer. If your distribution still provides an older version, update the distribution before continuing.
ⓧ Command not found
- Install Python on Debian or Ubuntu by running:
sudo apt update
sudo apt install python3 python3-venv
- Install Python on Fedora by running this command instead:
sudo dnf install python3
- Check the installation by running:
python3 --version
You should now see Python 3.8 or newer.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir construction-spanish-tutor && cd construction-spanish-tutor
Your terminal prompt now shows construction-spanish-tutor as your current directory.
- Create and activate the project's venv environment by running the command for your operating system:
macOS
python3 -m venv venv && source venv/bin/activate
Windows
python -m venv venv && source venv/Scripts/activate
Linux
python3 -m venv venv && source venv/bin/activate
Your terminal prompt now shows (venv) at the beginning. This confirms the virtual environment is active.
- Install the Ollama Python client library inside venv by running:
python -m pip install ollama==0.6.2
You should see pip download and install the package, ending with a Successfully installed message that includes ollama-0.6.2.
Package installation failing?
Confirm your prompt starts with (venv). On Windows, reactivate it from Git Bash with source venv/Scripts/activate.
On macOS or Linux, reactivate it with source venv/bin/activate. Then rerun the package installation command.
Help me fix the package installation.
- Confirm the Ollama package is installed inside the active virtual environment by running:
python -m pip show ollama
You should see Name: ollama and Version: 0.6.2. The package location should be inside the construction-spanish-tutor/venv folder.
Your environment is ready. Next up, you will build your first chat loop and have a live back-and-forth conversation with Llama 3.2 in your terminal.
Get a Basic Chat Loop Working
Your Ollama server is running and Llama 3.2 is ready to go. Now you need something to talk to it with.
Before making the tutor smart with construction vocabulary, you need a working conversation loop where you type and the model responds. This is your early visible win: a live back-and-forth with an AI in your terminal.
In this step, get ready to:
- Build a streaming response function that prints words as they arrive.
- Create a chat loop that maintains conversation history across turns.
- Test a multi-turn conversation to confirm the model remembers context.
Create the streaming response function
The ollama Python package gives you a chat function that sends messages to a model and gets responses back. With stream=True, the response arrives word-by-word instead of all at once.
- Create a new file called tutor.py in your construction-spanish-tutor folder by running:
touch tutor.py
- Confirm the file exists by running:
ls
You should see tutor.py listed alongside your venv folder.
- Open tutor.py in your editor and add the imports and response function:
import os
from ollama import chat
def get_ollama_response(messages, stream=True):
# Stream mode prints each word as it arrives
if stream:
response_text = ""
stream_response = chat(
model="llama3.2",
messages=messages,
stream=True,
)
for chunk in stream_response:
content = chunk["message"]["content"]
print(content, end="", flush=True)
response_text += content
print()
return response_text
else:
# Non-streaming fallback waits for the full response
response = chat(
model="llama3.2",
messages=messages,
)
result = response.message.content
print(result)
return result
What does this code do?
The get_ollama_response function wraps the Ollama chat call so you have one place to control how responses come back.
- stream=True makes the model send text in small chunks as it generates them, giving you that word-by-word typing effect.
- flush=True forces each chunk to print immediately instead of waiting for a full line.
- The function collects all chunks into response_text and returns the full response so you can store it in the conversation history.
Build the chat loop
The response function handles talking to the model. Now you need a loop that accepts your input, passes it to the model, and remembers the full conversation.
- Add the main() function below get_ollama_response in tutor.py:
def main():
# Keep all messages for multi-turn context
messages = []
print("=== Construction Spanish Tutor ===")
print("Type 'exit' to quit.\n")
while True:
user_input = input("Tu: ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
print("\nHasta luego! Keep practicing!")
break
# Add user message and get AI response
messages.append({"role": "user", "content": user_input})
print("Tutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
if __name__ == "__main__":
main()
What does this code do?
The messages list is the key to multi-turn conversation. Every message (yours and the tutor's) gets appended to this list and sent to the model on every turn.
This means the model sees the full conversation history each time it responds. It can reference what you said three messages ago, correct recurring mistakes, and build on earlier topics.
- Save tutor.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before running it.
ⓧ I'd like to double check the full code
Your complete tutor.py should look like this:
import os
from ollama import chat
def get_ollama_response(messages, stream=True):
if stream:
response_text = ""
stream_response = chat(
model="llama3.2",
messages=messages,
stream=True,
)
for chunk in stream_response:
content = chunk["message"]["content"]
print(content, end="", flush=True)
response_text += content
print()
return response_text
else:
response = chat(
model="llama3.2",
messages=messages,
)
result = response.message.content
print(result)
return result
def main():
messages = []
print("=== Construction Spanish Tutor ===")
print("Type 'exit' to quit.\n")
while True:
user_input = input("Tu: ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
print("\nHasta luego! Keep practicing!")
break
messages.append({"role": "user", "content": user_input})
print("Tutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
if __name__ == "__main__":
main()
Test your tutor
Your script is complete. Time to have your first conversation with it.
Before you run this, what do you expect to happen when you type a message and press Enter?
- Run the script by entering:
python tutor.py
You should see the header print, then a Tu: prompt waiting for your input.
- Type Hola, como estas? and press Enter.
Watch the response appear word-by-word in real time. That is the streaming in action.
- Send 2-3 more messages and reference something from an earlier message to confirm the model remembers context.
- Type exit to quit cleanly.
You should see Hasta luego! Keep practicing! and the script ends.
Not seeing any response?
- Make sure Ollama is running. Open a new terminal and run ollama list to confirm it responds.
- Check that your virtual environment is activated. Your prompt should show (venv) at the beginning.
- If you see ModuleNotFoundError, run pip install ollama==0.6.2 again inside the activated environment.
Still stuck? Help me debug why my ollama chat script is not getting a response from the model.
You now have a live AI conversation running in your terminal. Next up, you will turn this generic chatbot into a real construction Spanish tutor by engineering a system prompt that corrects your grammar and teaches trade vocabulary.
Add System Prompting to Create the Tutor Personality
Your chat loop works. You can type messages and the model responds. But try typing a sentence with a grammar mistake or a vague word, and you will notice something: the model just chats back without correcting you or teaching you anything.
In this step, you will experience that gap firsthand. Then you will fix it by engineering a system prompt that transforms the generic model into a patient Spanish tutor who corrects grammar and teaches construction vocabulary.
In this step, get ready to:
- Discover what happens when the model has no instructions.
- Engineer a system prompt that shapes the tutor personality.
- Verify the tutor now corrects mistakes and teaches construction terms.
Run the tutor and notice what's missing
Before making any changes, run your tutor as it currently exists. You are going to deliberately type a sentence with a vague description and a grammar issue to see how the model handles it.
Before you run this, what do you think the model will do if you type a grammatically incorrect sentence in Spanish? Will it correct you, or just continue the conversation?
- Run your tutor script:
python tutor.py
- Type this sentence (a vague description for a drill): Yo necesito la cosa para hacer hoyos en la pared
- Press Enter and read the model's response.
- Type exit to quit.
The model responds conversationally. It does not correct your Spanish. It does not teach you that "la cosa para hacer hoyos en la pared" is actually called "el taladro" (the drill). It just keeps chatting.
This is the gap. Without explicit instructions, the model has no idea it is supposed to be a tutor. It is just a general-purpose chatbot.
Add a system prompt to shape the tutor's behavior
A system prompt is a hidden message at the start of the conversation that tells the model how to behave. The model never shows it to the user, but it follows those instructions for every response.
- In tutor.py, find the line def get_ollama_response(messages, stream=True): and add this code above it:
BASE_SYSTEM_PROMPT = (
"You are a Spanish language tutor specializing in construction, automotive, "
"and heavy equipment terminology. Your role is to help the user practice Spanish "
"in realistic construction scenarios.\n\n"
"Rules:\n"
"1. Always respond in Spanish.\n"
"2. When the user makes a grammar mistake, correct it briefly.\n"
"3. When the user uses a vague description (like 'la cosa para cortar'), "
"teach them the correct technical term.\n"
"4. Keep responses concise: 2-3 sentences maximum.\n"
"5. After corrections, continue the conversation naturally.\n"
"6. If the user writes in English, respond in Spanish but include a brief "
"English explanation of new vocabulary.\n"
)
def build_system_prompt(scenario=None, difficulty_level=None):
prompt = BASE_SYSTEM_PROMPT
return prompt
What does this code do?
BASE_SYSTEM_PROMPT is a multi-line string containing the tutor's personality and rules. It tells the model to always respond in Spanish, correct grammar, and teach construction terms when the user uses vague descriptions.
build_system_prompt() is a wrapper function that returns the base prompt. Right now it just returns the string directly. In later steps, you will expand it to add scenario context and difficulty settings.
Now you need to tell the model to follow this prompt. The system prompt goes as the first message in the conversation's messages list.
- In the main() function, find this line:
messages = []
- Replace it with these two lines:
system_prompt = build_system_prompt()
messages = [{"role": "system", "content": system_prompt}]
What changed?
Instead of starting with an empty messages list, you now build the system prompt and place it as the first message with the role system. Every user message and assistant response will come after this hidden instruction, so the model always knows it is a construction Spanish tutor.
- Save tutor.py.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your file before moving on to the test.
ⓧ I'd like to double check the full code
Your file is over 30 lines now, so scroll through and confirm these sections are present in order:
- The import os and from ollama import chat lines at the top.
- The BASE_SYSTEM_PROMPT string constant with all six rules.
- The build_system_prompt() function that returns the base prompt.
- The get_ollama_response() function (unchanged from before).
- The main() function starting with system_prompt = build_system_prompt() and messages = [{"role": "system", "content": system_prompt}].
Test the tutor with the same sentence
Time to see if the system prompt made a difference. You will type the same vague sentence as before and compare the response.
Before you run this, what do you expect the tutor to do differently now that it has instructions to correct grammar and teach vocabulary?
- Run the tutor again:
python tutor.py
- Type the same sentence: Yo necesito la cosa para hacer hoyos en la pared
- Press Enter and read the response.
This time, the tutor responds in Spanish and teaches you the correct construction term. It should mention "el taladro" (the drill) and may also correct any grammar issues in your sentence. The response stays concise at 2-3 sentences, just like the rules specify.
- Try one more sentence to confirm multi-turn context works. Type: Y tambien quiero la cosa que corta madera
- The tutor should teach you "la sierra circular" (circular saw) or a similar cutting tool term.
- Type exit to quit.
Tutor still not correcting you?
- Check that messages in main() is initialized with the system prompt dict, not an empty list [].
- Make sure BASE_SYSTEM_PROMPT is defined above get_ollama_response(), not inside a function.
- Confirm the role in the system message dict is "system", not "user" or "assistant".
Help me debug my system prompt setup
Your tutor now has a personality. It corrects grammar, teaches real construction terms, and stays concise. Next up, you will add scenario modes that drop you into specific situations like a hardware store or a job site, so you practice vocabulary that clusters around real tasks.
Add Construction Scenario Modes
Your tutor corrects grammar and teaches construction terms, but it just waits passively for you to type something. Real language learning happens in context.
Scenario modes drop you into a specific situation so you practice vocabulary that clusters around real tasks. In this step, you will add three scenarios that transform the tutor into a hardware store clerk, a job site foreman, or a diesel mechanic.
In this step, get ready to:
- Create scenario definitions that seed the model with domain-specific vocabulary.
- Build a startup menu so you can choose your practice context.
- Wire scenarios into the chat flow so the tutor opens each conversation in character.
Add the SCENARIOS dictionary
Each scenario is a chunk of text that gets appended to the system prompt. It tells the model to stay in character and lists the specific vocabulary to use.
- In tutor.py, find the line BASE_SYSTEM_PROMPT = ( and paste the following code directly above it:
SCENARIOS = {
"hardware_store": (
"You are a hardware store clerk in a Latin American country. "
"The user is a construction worker who needs to buy materials and tools. "
"Stay in character as the clerk. Use terminology for: power tools (amoladora angular, "
"taladro percutor, sierra circular), hand tools (llave inglesa, llave de tubo, "
"destornillador, martillo), building materials (cemento, varilla de acero, tablaroca, "
"madera contrachapada), and fasteners (tornillos, clavos, taquetes, anclas)."
),
"job_site": (
"You are a foreman on a construction site in a Latin American country. "
"The user is a worker who needs directions on what to do next. "
"Stay in character as the foreman. Use terminology for: heavy equipment "
"(retroexcavadora, excavadora, bulldozer, grua, montacargas), site tasks "
"(excavar, nivelar, compactar, colar concreto, levantar muro), "
"safety equipment (casco, chaleco reflectante, arnes, guantes, lentes de seguridad), "
"and measurements (metros, nivel, plomada, escuadra)."
),
"truck_diagnostics": (
"You are a diesel mechanic in a Latin American country. "
"The user is a truck driver describing a problem with their vehicle. "
"Stay in character as the mechanic. Use terminology for: engine parts "
"(motor, radiador, alternador, bomba de agua, turbo, inyectores), "
"transmission (transmision, clutch/embrague, caja de velocidades, diferencial), "
"brakes (frenos, balatas, discos, tambores, compresor de aire), "
"and electrical (bateria, marcha, fusibles, luces, alternador)."
),
}
What does this code do?
Each key in the dictionary maps to a role-play context. The strings tell the model who it is, who the user is, and what specific vocabulary to use.
By listing real terms (like "amoladora angular" for angle grinder or "retroexcavadora" for backhoe), you seed the model with domain vocabulary it might not surface on its own.
- Save tutor.py.
- Confirm your syntax is correct by running:
python tutor.py
- The tutor should start as before (no menu yet). Type exit to quit.
Seeing a SyntaxError?
Check that every opening parenthesis in the SCENARIOS dictionary has a matching close. Each scenario string uses implicit string concatenation (adjacent strings with no comma between them). Make sure there is no stray comma between the string fragments inside each parenthesized group.
Help me debug a SyntaxError in my SCENARIOS dictionary
Build the scenario menu and wire it into the prompt
You need a way for the user to pick a scenario at startup. The menu maps numbered choices to dictionary keys, then build_system_prompt() appends the chosen scenario context to the base prompt.
- In tutor.py, find the get_ollama_response function. Paste the following new function directly above it:
def display_menu():
print("\n=== Construction Spanish Tutor ===")
print("\nChoose a scenario:")
print(" 1. Hardware Store (Ferreteria)")
print(" 2. Job Site (Obra)")
print(" 3. Truck Diagnostics (Taller Mecanico)")
print(" 4. Free Conversation (Conversacion Libre)")
print()
# Loop until the user enters a valid choice
while True:
choice = input("Enter 1-4: ").strip()
if choice == "1":
return "hardware_store"
elif choice == "2":
return "job_site"
elif choice == "3":
return "truck_diagnostics"
elif choice == "4":
return None
else:
print("Please enter 1, 2, 3, or 4.")
What does this code do?
The function prints numbered options and loops until the user enters a valid choice. It returns the matching dictionary key (like "hardware_store") or None for free conversation.
Now you need build_system_prompt() to actually use the scenario. Right now it ignores the scenario parameter.
- In build_system_prompt(), find the line:
return prompt
- Add these two lines directly above return prompt, so the function now looks like this:
def build_system_prompt(scenario=None, difficulty_level=None):
prompt = BASE_SYSTEM_PROMPT
# Append scenario-specific context if one was chosen
if scenario and scenario in SCENARIOS:
prompt += f"\nScenario context: {SCENARIOS[scenario]}\n"
return prompt
Finally, replace the main() function so it calls the menu at startup and kicks off the conversation in character when a scenario is selected.
- Select the entire def main(): function (from def main(): down to the last line of the while True loop) and replace it with:
def main():
# Show the scenario menu and get the user's choice
scenario = display_menu()
system_prompt = build_system_prompt(scenario)
messages = [{"role": "system", "content": system_prompt}]
# Display the active scenario name
scenario_name = scenario.replace("_", " ").title() if scenario else "Free Conversation"
print(f"\n--- {scenario_name} ---")
print("Type 'exit' to quit.\n")
# If a scenario was chosen, start the conversation in character
if scenario:
opening = {"role": "user", "content": "Hola, empecemos."}
messages.append(opening)
print("Tu: Hola, empecemos.")
print(f"\nTutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
# Main chat loop
while True:
user_input = input("\nTu: ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
print("\nHasta luego! Keep practicing!")
break
messages.append({"role": "user", "content": user_input})
print("Tutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
What's new in this main() function?
- display_menu() runs first and returns the scenario key (or None for free conversation).
- If a scenario is active, the tutor sends an opening message ("Hola, empecemos.") on the user's behalf so the model responds in character immediately.
- The prompt prefix now shows the scenario name so you always know which context you are practicing in.
- Save tutor.py.
Not sure your file is structured correctly?
Make sure the if __name__ == "__main__": block at the very bottom of your file is unchanged. It should still just call main().
Help me fix my tutor.py file structure after adding scenarios
Test each scenario mode
Before you run this, what do you expect to see when you pick "Hardware Store"? Think about what role the tutor should take on.
- Run the tutor:
python tutor.py
- You should see the scenario menu with four numbered options.
- Enter 1 to pick Hardware Store.
The tutor should respond in character as a hardware store clerk. You will see something like "Buenas tardes, que necesita?" or a similar greeting that stays on-topic for buying tools and materials.
- Try asking for something using a vague description, like Necesito la cosa para cortar madera. The tutor should teach you the proper term ("sierra circular").
- Type exit to quit.
- Run the script again and enter 3 to try Truck Diagnostics. Describe an engine problem like Mi troca no quiere arrancar and confirm the tutor responds as a mechanic using engine terminology.
Tutor not staying in character?
Make sure build_system_prompt() has the if scenario and scenario in SCENARIOS: check. Without it, the scenario context never gets appended to the prompt.
Also check that main() passes the scenario variable to build_system_prompt(scenario) instead of calling it with no arguments.
Help me debug why my tutor is not staying in character for the chosen scenario
✔️ Awesome, I've got everything!
Great. Double check you have saved tutor.py.
ⓧ I'd like to double check the full code
Here is the full tutor.py at this point. Scroll up and confirm each section is present in this order:
- Imports (import os and from ollama import chat).
- SCENARIOS dictionary with three keys: hardware_store, job_site, truck_diagnostics.
- BASE_SYSTEM_PROMPT string (unchanged from last step).
- build_system_prompt() with the new if scenario check.
- display_menu() function.
- get_ollama_response() function (unchanged from last step).
- main() with scenario selection, opening message, and the chat loop.
- if __name__ == "__main__": main() at the bottom.
Your tutor now drops you into realistic practice contexts. Next up, you will add difficulty tracking so the tutor adapts to your level and ramps up as you improve.
Add Difficulty Tracking
Your tutor can now correct grammar and stay in character across three different scenarios. There is one problem: it always responds at the same complexity level, regardless of how well you are doing.
If the tutor speaks full-speed advanced Spanish to a beginner, they get overwhelmed. If it uses simple vocab with an advanced speaker, they get bored. In this step, you will add a difficulty tracking system that monitors how you are performing and adapts the tutor's responses in real time.
In this step, get ready to:
- Define three difficulty levels with tailored instructions for each.
- Add progression logic that evaluates your performance every 5 messages.
- Display your current level in the prompt and watch it change as you improve.
Add difficulty levels and update the system prompt
Each difficulty level needs its own set of instructions telling the model how to behave. A beginner gets short sentences with English translations. An advanced speaker gets full-speed Spanish with regional slang.
- In tutor.py, find the closing } of the SCENARIOS dictionary. Add the following code below it, replacing the existing build_system_prompt function:
DIFFICULTY_LEVELS = ["principiante", "intermedio", "avanzado"]
DIFFICULTY_INSTRUCTIONS = {
"principiante": (
"The user is a BEGINNER. Use short, simple sentences (5-8 words). "
"After each response, translate key vocabulary words to English in parentheses. "
"Speak slowly and clearly. If they make a mistake, correct it gently with "
"the English translation of the correct word."
),
"intermedio": (
"The user is INTERMEDIATE. Use medium-length sentences (8-15 words). "
"Only translate advanced or technical terms to English. "
"Correct mistakes by showing the correct form without over-explaining."
),
"avanzado": (
"The user is ADVANCED. Use natural, full-speed Spanish with longer sentences. "
"Use regional slang and informal construction terminology. "
"Do NOT translate to English unless explicitly asked. "
"Correct only significant errors, ignore minor ones."
),
}
def build_system_prompt(scenario, difficulty_level):
prompt = BASE_SYSTEM_PROMPT
prompt += f"\nDifficulty level: {DIFFICULTY_INSTRUCTIONS[difficulty_level]}\n"
if scenario and scenario in SCENARIOS:
prompt += f"\nScenario context: {SCENARIOS[scenario]}\n"
return prompt
What does this code do?
DIFFICULTY_LEVELS is the ordered list the tutor progresses through: beginner, intermediate, advanced.
DIFFICULTY_INSTRUCTIONS maps each level to specific instructions for the model. At principiante, the model uses 5-8 word sentences and translates key terms. At avanzado, it speaks full-speed with regional slang and no English.
The updated build_system_prompt() now injects the difficulty instructions into the system prompt before the scenario context. This means the model adjusts its behavior based on the current level.
The main() function still calls the old version of build_system_prompt with just one argument. You need to update it to initialize a difficulty level and pass it through.
- Replace the entire main() function (from def main(): all the way to the end of the file) with this updated setup. This is the first half. The while loop comes next:
def main():
scenario = display_menu()
# Start at the easiest difficulty
difficulty_index = 0
difficulty = DIFFICULTY_LEVELS[difficulty_index]
# Build the system prompt with both scenario and difficulty
system_prompt = build_system_prompt(scenario, difficulty)
messages = [{"role": "system", "content": system_prompt}]
scenario_name = scenario.replace("_", " ").title() if scenario else "Free Conversation"
print(f"\n--- {scenario_name} | Level: {difficulty} ---")
print("Type 'exit' to quit.\n")
if scenario:
opening = {"role": "user", "content": "Hola, empecemos."}
messages.append(opening)
print("Tu: Hola, empecemos.")
print(f"\nTutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
# Track how many messages the user has sent
message_count = 0
What changed in main()?
- difficulty_index tracks the position in the DIFFICULTY_LEVELS list. It starts at 0 (principiante).
- build_system_prompt(scenario, difficulty) now passes the current difficulty level to shape the model's behavior.
- The header now prints the current level so you can see it immediately.
- message_count will be used by the level-up logic you add next.
You still need the while loop. For now, add this temporary version so the script runs. You will replace it in the next substep:
- Add the following while loop immediately after message_count = 0, still inside the main() function. Keep the if __name__ == "__main__": block at the very bottom:
while True:
user_input = input(f"\n[{difficulty}] Tu: ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
print("\nHasta luego! Keep practicing!")
break
messages.append({"role": "user", "content": user_input})
message_count += 1
print(f"Tutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
if __name__ == "__main__":
main()
- Save tutor.py.
- Run the script to confirm the difficulty system is working at the beginner level:
python tutor.py
- Pick any scenario, then type a sentence like Necesito comprar cemento.
You should see shorter, simpler responses with English translations in parentheses. The header also shows Level: principiante and the input prompt shows [principiante] Tu:. Type exit to quit.
Seeing a TypeError about difficulty_level?
- Make sure you deleted the old build_system_prompt function entirely. Having two definitions causes Python to use the last one.
- Check that DIFFICULTY_LEVELS and DIFFICULTY_INSTRUCTIONS are defined BEFORE build_system_prompt and AFTER BASE_SYSTEM_PROMPT.
- Confirm that main() now calls build_system_prompt(scenario, difficulty) with two arguments, not one.
Help me debug my difficulty level setup in tutor.py
Add level progression logic
The tutor now starts at beginner level, but it stays there forever. You need a function that checks whether the learner is ready to advance. The heuristic is simple: every 5 messages, look at how often the user asked for help. If they rarely asked, they are keeping up and should level up.
- In tutor.py, add the following function after get_ollama_response() and before main():
def should_level_up(messages, current_level_index, message_count):
# Check every 5 messages if the user is ready to advance
if message_count > 0 and message_count % 5 == 0:
if current_level_index < len(DIFFICULTY_LEVELS) - 1:
recent_user_messages = [
m["content"]
for m in messages[-10:]
if m["role"] == "user"
]
# Count how often the user asked for help
help_phrases = ["what does", "que significa", "no entiendo", "translate", "english"]
help_count = sum(
1
for msg in recent_user_messages
if any(phrase in msg.lower() for phrase in help_phrases)
)
# Level up only if the user rarely asked for help
if help_count <= 1:
return True
return False
What does this function do?
- It only triggers on every 5th message (message_count % 5 == 0), giving the user enough time to demonstrate their skill.
- It scans the last 10 messages for help-seeking phrases like "what does", "no entiendo", or "translate".
- If the user asked for help at most once, they are keeping up and the function returns True to signal a level-up.
- It will not advance past avanzado (the check current_level_index < len(DIFFICULTY_LEVELS) - 1 prevents overflow).
Wire up level progression in the chat loop
The function exists but nothing calls it yet. You need to update the while loop so it checks for a level-up after each message and rebuilds the system prompt when the level changes.
- Replace the while True: loop you added earlier (everything from while True: through the end of main()) with this updated version:
while True:
user_input = input(f"\n[{difficulty}] Tu: ").strip()
if not user_input:
continue
if user_input.lower() == "exit":
print("\nHasta luego! Keep practicing!")
break
messages.append({"role": "user", "content": user_input})
message_count += 1
# Check if the user is ready to advance
if should_level_up(messages, difficulty_index, message_count):
difficulty_index += 1
difficulty = DIFFICULTY_LEVELS[difficulty_index]
system_prompt = build_system_prompt(scenario, difficulty)
messages[0] = {"role": "system", "content": system_prompt}
print(f"\n [Level up! Now at: {difficulty}]\n")
print(f"Tutor: ", end="")
assistant_response = get_ollama_response(messages)
messages.append({"role": "assistant", "content": assistant_response})
What does the level-up block do?
- After each user message, it calls should_level_up() to see if 5 messages have passed and the user is performing well.
- When it returns True, the code bumps difficulty_index, rebuilds the system prompt with the new level, and swaps messages[0] (the system message) in place.
- This hot-swaps the model's instructions mid-conversation without losing any chat history.
- Save tutor.py.
Before you run this, think: how many messages do you think it will take before the level changes from principiante to intermedio?
- Run the tutor and have a conversation of at least 5 messages. Write correct Spanish sentences without asking for help:
python tutor.py
- Pick a scenario, then type 5 correct sentences in a row (e.g. Necesito cemento, Cuanto cuesta?, etc.).
After your 5th message, you should see [Level up! Now at: intermedio] printed in the terminal. The input prompt changes from [principiante] Tu: to [intermedio] Tu:. The tutor's responses also shift: longer sentences, fewer English translations.
Level not changing after 5 messages?
- Make sure you are not typing any of the help phrases ("what does", "no entiendo", "translate") in your messages. The function counts those as signs of struggle.
- Verify that message_count += 1 appears inside the while loop, right after messages.append({"role": "user", ...}).
- Check that should_level_up is defined before main() in the file. Python reads top to bottom, so the function must exist before it is called.
Help me debug why my tutor level is not advancing
✔️ Awesome, I've got everything!
Great. Double check you have saved tutor.py.
ⓧ I'd like to double check the full code
Scroll up and confirm your tutor.py has these sections in order:
- Imports (import os and from ollama import chat).
- SCENARIOS dictionary with hardware_store, job_site, and truck_diagnostics.
- DIFFICULTY_LEVELS list and DIFFICULTY_INSTRUCTIONS dictionary.
- BASE_SYSTEM_PROMPT string.
- build_system_prompt(scenario, difficulty_level) that appends both difficulty instructions and scenario context.
- display_menu() function.
- get_ollama_response(messages, stream=True) function.
- should_level_up(messages, current_level_index, message_count) function.
- main() with difficulty initialization, level-up check in the while loop, and [difficulty] Tu: prompt format.
- if __name__ == "__main__": main() at the very bottom.
Your tutor now adapts to your skill level in real time. It starts gentle with translations, then ramps up to full-speed construction Spanish as you prove you can keep up. Next up: you will wire in a cloud backend so this tutor works even without Ollama installed.
Secret mission
Wire Up Gemini as a Cloud Backend
Your tutor works great offline with Ollama. But what if you want a more powerful model, or you are on a machine without Ollama installed? In this secret mission, you will wire up Google Gemini as a swappable cloud backend. One environment variable switches between local and cloud, giving you the best of both worlds.
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 runs entirely locally, so there are no ongoing costs.
Resources you used:
- Ollama installed locally with the Llama 3.2 model (~2GB disk space).
- The construction-spanish-tutor project folder on your Desktop, including the Python virtual environment and installed packages.
Keep everything running
No action is needed. Choose this if you are still building or want to keep practicing.
- Keep the Llama 3.2 model ready for future sessions.
- Keep the construction-spanish-tutor folder and virtual environment on your Desktop.
- Keep Git for Windows installed if you use Windows. It remains useful for other development projects.
Pause - I'll come back to this later
Unload the model to free memory while keeping the model and project files on disk.
- Stop the active Llama 3.2 model from Terminal or Git Bash by running:
ollama stop llama3.2
The command returns you to the prompt. Your downloaded model remains ready for the next session.
Delete - I don't want to use this again
Deleting these resources is permanent, so copy any tutor code you want to keep first. Git for Windows stays installed because it is a reusable development tool.
- Remove the Llama 3.2 model by running:
ollama rm llama3.2
Choose your operating system to remove the project folder and optionally uninstall Ollama.
macOS
- Delete the project folder from Terminal by running:
rm -rf ~/Desktop/construction-spanish-tutor
- Optionally uninstall Ollama and its local data by running the official macOS cleanup commands:
sudo rm -rf /Applications/Ollama.app
sudo rm /usr/local/bin/ollama
rm -rf "~/Library/Application Support/Ollama"
rm -rf "~/Library/Saved Application State/com.electron.ollama.savedState"
rm -rf ~/Library/Caches/com.electron.ollama/
rm -rf ~/Library/Caches/ollama
rm -rf ~/Library/WebKit/com.electron.ollama
rm -rf ~/.ollama
Windows
- Delete the project folder from Git Bash by running:
rm -rf ~/Desktop/construction-spanish-tutor
Keep Git for Windows installed. It is separate from this project and remains your Bash terminal for future development work.
- Press the Windows key to open Search.
- Type Add or remove programs and press Enter.
- Find Ollama in the installed apps list.
- Select Ollama.
- Click Uninstall if you want to remove the app.
Linux
- Delete the project folder from Terminal by running:
rm -rf ~/Desktop/construction-spanish-tutor
- Optionally stop and remove the Ollama system service by running:
sudo systemctl stop ollama
sudo systemctl disable ollama
sudo rm /etc/systemd/system/ollama.service
- Remove the Ollama libraries and executable by running:
sudo rm -r $(which ollama | tr 'bin' 'lib')
sudo rm $(which ollama)
- Remove the Ollama service account and downloaded data by running:
sudo userdel ollama
sudo groupdel ollama
sudo rm -r /usr/share/ollama
Nice Work!
Nice Work!
Nice work! You just built a fully functional construction Spanish tutor that runs in your terminal, powered by a local LLM.
You've learned how to:
- Build a terminal-based AI tutor using Ollama and Llama 3.2, with system prompt engineering that transforms a generic LLM into a specialized construction language teacher.
- Implement scenario-based practice modes that simulate real conversations at the hardware store, job site, and mechanic shop.
- Add adaptive difficulty tracking that adjusts vocabulary complexity, sentence length, and English explanations based on the learner's proficiency level.
- Secret Mission: Create a provider-agnostic architecture supporting both local (Ollama) and cloud (Gemini) backends.
Ready to quiz yourself?