Build an AI Web App
with Python
& Free APIs.
A complete, beginner-friendly tutorial to build a multi-tool AI web application in Python using Streamlit and the genuinely-free Google Gemini API tier. Five tools in one app: summarizer, sentiment analyzer, Q&A, content generator, and translator. Full source code, .env setup, error handling, and free deployment on Streamlit Community Cloud β no credit card, no trial expiry. If you’re looking for practical Python projects with source code, this one is end-to-end.
# AI Content Studio β Streamlit + Gemini import streamlit as st from src.ai_client import summarize_text text = st.text_area("Paste text") if st.button("Summarize"): out = summarize_text(text, n=5) st.markdown(out)
What we’re building
and why it matters.
In this tutorial you’ll build AI Content Studio β a single Python web application that bundles five AI tools behind a clean browser UI: a text summarizer, a sentiment analyzer, a question answerer, a content generator, and a translator. The whole app runs on Streamlit (a Python web framework that needs zero HTML/CSS) and calls the Google Gemini API free tier for the actual AI work.
Most “AI web app” tutorials online either (a) call paid APIs like OpenAI without mentioning the cost, or (b) show pseudocode that doesn’t actually run. This one is different on three counts:
- Genuinely free end-to-end. The Gemini 1.5 Flash free tier gives you 15 requests/minute and 1,500 requests/day with no credit card. Streamlit Community Cloud hosts the app for $0 from a public GitHub repo. You can build, run, and ship this without spending anything.
- Real working code, not snippets. Every file β
app.py,src/ai_client.py,src/prompts.py,requirements.txt,.env.example,.streamlit/config.toml,README.mdβ is provided in full. You can clone the structure, paste the code, and runstreamlit run app.py. - Production-grade error handling. The wrapper catches the four Gemini errors beginners actually hit β rate-limit (429), invalid key (403), invalid argument (400), and generic API failure β and surfaces friendly messages instead of stack traces.
If you’ve worked through our broader Python projects with source code guide, this is the natural next step: it’s the first project that puts a real AI model behind a real web UI you can share with non-technical users.
By the end you’ll have a deployed web app at a URL like https://your-username-ai-content-studio.streamlit.app that anyone can open in a browser. The same architecture β Streamlit frontend, a thin Python wrapper around an LLM API, and free cloud hosting β works for chatbots, code explainers, document Q&A tools, and most other single-user AI apps you’ll want to prototype.
What is an AI-powered
web application?
An AI-powered web application is any web app where the core business logic β what happens when the user clicks a button β is delegated to a machine-learning model instead of being hand-coded with rules. In a traditional web app, the server runs deterministic code: if user.age > 18: show_premium_page(). In an AI web app, the server forwards the user’s input to a model and renders whatever the model returns.
There are two architectural patterns:
Self-hosted model
vs. hosted API.
Most beginners should start with the API approach β it’s what this tutorial uses.
Self-hosted model
You download a model (e.g. Llama 3 8B, ~5 GB) and run it inside your Python process with transformers or llama-cpp-python. Pros: free, private, no rate limits. Cons: needs a GPU or 16+ GB RAM, slow on CPU, complex deployment.
Hosted API (this tutorial)
You call a remote model via HTTP. The provider (Google, OpenAI, Anthropic, Hugging Face) runs the model on their GPUs and charges per request β but most offer a free tier generous enough for prototyping. Pros: fast, no GPU needed, simple deployment. Cons: rate limits, network latency, data leaves your machine.
If you’ve already built something like our Python sentiment analysis project using a locally-trained model, this tutorial shows the complementary pattern: instead of training the model yourself, you consume one as a service. Both are valid; the API approach is faster to ship and easier to maintain.
What the app
actually does.
A sidebar radio
switches between
five AI tools.
All tools share the same layout β input on the left, output on the right β so users learn the pattern once.
Text Summarizer
Paste any long text (article, meeting notes, transcript) and get a 3β10 bullet-point summary. The number of bullets is adjustable with a slider.
Sentiment Analyzer
Returns overall sentiment (Positive/Negative/Neutral), a confidence score, the key emotions detected, and a one-sentence explanation. Useful for product reviews, social media monitoring, and customer feedback.
Question Answerer
Give the model a context paragraph and a question. It answers only from the context β and says “I cannot answer this from the given context” when the answer isn’t there, instead of hallucinating.
Content Generator
Generate 50β500 words on any topic in five tones: informative, casual, professional, funny, or persuasive. Useful for blog drafts, social posts, and email outlines.
Language Translator
Translate text between any two languages you type in (e.g. “English” β “Spanish”, “Japanese” β “French”). The model handles 100+ languages without you needing to maintain a list.
Technologies and APIs
we use.
Each choice is justified β no arbitrary picks.
| Layer | Choice | Free-tier facts | Official docs |
|---|---|---|---|
| Language | Python 3.10+ | Free forever | docs.python.org |
| Web framework | Streamlit 1.39 | Open source, MIT | docs.streamlit.io |
| AI API | Google Gemini 1.5 Flash | 15 RPM Β· 1,500/day Β· 1M TPM, no card | ai.google.dev |
| Python SDK | google-generativeai 0.8.3 | Free PyPI package | GitHub repo |
| Secrets | python-dotenv 1.0.1 | Free PyPI package | pypi.org |
| Hosting | Streamlit Community Cloud | 1 GB RAM Β· 5 apps Β· sleeps after 12h | Deploy docs |
| Version control | Git + GitHub | Free for public repos | docs.github.com |
Why Streamlit + Gemini,
not Flask + OpenAI.
Each choice
removes friction
for beginners.
If you disagree with any of these, the architecture is modular β swap one layer without touching the others.
Streamlit over Flask/Django
Flask forces you to write HTML templates, route handlers, and static asset folders β three new skills. Streamlit renders a polished UI from pure Python: st.text_area() becomes an HTML textarea, st.button() becomes a button, st.markdown() renders rich text. You write 80% less boilerplate. Trade-off: Streamlit is single-page and Python-only; if you need a public multi-user SaaS with custom auth, switch to Flask later.
Gemini over OpenAI
OpenAI’s API requires a credit card on signup and bills per token. There is no permanent free tier β only $5 of trial credit that expires in 3 months. Gemini’s free tier is permanent: 15 RPM, 1,500 RPD, no card, no expiry. For a tutorial aimed at students, that’s the difference between “I can build this for free forever” and “I need to set up billing first.”
Hosted API over self-hosted model
You could run Llama 3 8B locally with transformers β but you’d need 8+ GB VRAM, a GPU, and 15 minutes of model loading per session. The Gemini API returns answers in 1β3 seconds from any laptop, including a Chromebook. Trade-off: your data leaves your machine. Don’t paste trade secrets into any hosted AI API, free or paid.
src/ai_client.py needs to change.What you need
before starting.
- Python 3.10 or newer. Verify with
python --versionin your terminal. Streamlit andgoogle-generativeaiboth require 3.9+; 3.10+ is recommended for the modern type-hint syntax used inai_client.py. - A code editor. VS Code, PyCharm, Sublime, or even
nanoβ anything that edits text files. We recommend VS Code with the official Python extension. - A terminal. On Windows: PowerShell or Git Bash. On macOS/Linux: the default Terminal.
- A free Google account. Used to get the Gemini API key from Google AI Studio. No credit card.
- A free GitHub account. Only needed for deployment (Step 17). You can build and run the whole app locally without one.
- Basic Python. You should know what a function, a list, and an
importstatement are. If you’ve completed our Python projects guide, you’re ready.
No prior web development experience needed. No prior machine-learning experience needed. No prior API-integration experience needed β that’s literally what this tutorial teaches.
The complete project
file tree.
Recreate this exact structure β Streamlit expects app.py at the root, and the src/ package keeps the AI logic separate from the UI.
You could put everything in one big app.py file. We don’t, for three reasons:
- Testability. You can unit-test
ai_client.pywithout launching Streamlit. - Swap-ability. If you later switch from Gemini to OpenAI or a local Llama model, you only edit
ai_client.pyβ the UI code inapp.pystays untouched. - Readability.
app.pystays under 120 lines, focused purely on UI. The AI logic lives where it belongs.
Set up the Python
virtual environment.
Always use a virtual environment so this project’s dependencies don’t pollute your system Python.
Step 8.1 β Create the project folder and enter it
mkdir ai-content-studio cd ai-content-studio
Step 8.2 β Create and activate a virtual environment
# macOS / Linux python3 -m venv venv source venv/bin/activate # Windows (PowerShell) python -m venv venv venv\Scripts\Activate.ps1 # Windows (Git Bash) python -m venv venv source venv/Scripts/activate
You should now see (venv) at the start of your terminal prompt. If you don’t, the activation failed β check that you’re in the right folder and that Python is on your PATH. (If PowerShell blocks the script with an execution-policy error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once and retry.)
Step 8.3 β Install the dependencies
pip install streamlit==1.39.0 google-generativeai==0.8.3 python-dotenv==1.0.1
Step 8.4 β Save the dependencies to requirements.txt
streamlit==1.39.0 google-generativeai==0.8.3 python-dotenv==1.0.1
Locking the exact versions is what lets Streamlit Community Cloud reproduce your environment reliably. Without == pinning, a future breaking change in Streamlit 1.40+ could silently break your deployed app a month from now.
Get your free
Gemini API key.
This takes 90 seconds and requires no credit card. The free tier is permanent β it does not expire.
- Open https://aistudio.google.com/app/apikey in your browser.
- Sign in with any Google account (Gmail, Workspace, school Google account β all work).
- Read and accept the terms of service. (The free tier is for non-production use; that’s fine for a tutorial.)
- Click the Create API key button. If prompted, choose “Create API key in new project”.
- Copy the string that appears. It will look like
AIzaSyA1B2C3D4E5F6G7H8I9J0K_L_M_N_O_P_Q(39 characters, starts withAIza). - Paste it into your
.envfile (created in Step 10) asGEMINI_API_KEY=AIza....
app.py or any other committed file. (2) Never commit .env to git. (3) Never share the key in screenshots, Discord, or Slack. (4) If you accidentally leak it, delete it from Google AI Studio and create a new one β Google automatically scans public GitHub for leaked keys and revokes them within minutes.The free tier limits are generous for personal use: 15 requests per minute, 1,500 requests per day, and 1 million tokens per minute. For comparison, a typical summarization request uses ~500 input tokens and ~200 output tokens β so you can summarize about 2,000 articles per day before hitting the daily cap. Full pricing details are on the official Gemini pricing page.
The full pipeline,
7 mini-steps.
Each mini-step below corresponds to one file. Create them in order; later files import from earlier ones.
.env.example + .env
Template for the API key (committed) + the real key file (gitignored).
.gitignore
Prevents .env, venv/, and __pycache__/ from being pushed to GitHub.
src/prompts.py
Five prompt templates (one per tool) β keeps prompts out of the code.
src/ai_client.py
The Gemini API wrapper with full error handling. This is the heart of the app.
src/utils.py
Three small Streamlit helpers (show_error, show_success, render_markdown).
app.py
The Streamlit UI β sidebar tool selector + 5 tool layouts + footer.
.streamlit/config.toml
Dark theme + upload-size limit. Optional but matches the Darekdari aesthetic.
Every file, fully listed.
Copy and run.
Each block has a copy button. Recreate the folder structure from Section 7, then paste each file’s contents.
File 1 β .env.example (commit this) + .env (never commit)
Create .env.example with the template below. Then copy it to .env and replace the placeholder with your real key from Section 9.
# Copy this file to .env and paste your real key from # https://aistudio.google.com/app/apikey GEMINI_API_KEY=your_api_key_here
Then in your terminal:
cp .env.example .env # Then open .env in your editor and replace "your_api_key_here" # with your real key starting with AIza...
File 2 β .gitignore
# Virtual environment venv/ .env # Python cache __pycache__/ *.pyc # Streamlit secrets (used in cloud deploy) .streamlit/secrets.toml
File 3 β src/prompts.py
Prompt engineering is half the work. The five templates below are battle-tested β they reliably produce structured output for each tool. Save them in src/prompts.py.
SUMMARIZE_PROMPT = """Summarize the following text in {n} bullet points. Be concise and factual. Do not add information that is not in the source. TEXT: {text} SUMMARY:""" SENTIMENT_PROMPT = """Analyze the sentiment of the following text. Return your answer in this exact format: - Overall sentiment: Positive / Negative / Neutral - Confidence: 0-100% - Key emotions: (comma-separated list) - One-sentence explanation TEXT: {text} ANALYSIS:""" QA_PROMPT = """Use only the context below to answer the question. If the answer is not contained in the context, reply exactly: "I cannot answer this from the given context." CONTEXT: {context} QUESTION: {question} ANSWER:""" GENERATE_PROMPT = """You are a helpful writing assistant. Write {n} words of {tone} content about: {topic} Do not add a title or headings unless asked. CONTENT:""" TRANSLATE_PROMPT = """Translate the following text from {source_lang} to {target_lang}. Preserve the original tone and formatting. Return only the translation. TEXT: {text} TRANSLATION:"""
File 4 β src/ai_client.py (the AI wrapper)
This is the most important file. It loads the Gemini model once at import time, exposes five functions (one per tool), and catches every common Gemini error with a friendly message.
"""Thin wrapper around the Google Gemini API with friendly error handling.""" import os import google.generativeai as genai from google.api_core.exceptions import ( ResourceExhausted, PermissionDenied, GoogleAPICallError, InvalidArgument, ) from .prompts import ( SUMMARIZE_PROMPT, SENTIMENT_PROMPT, QA_PROMPT, GENERATE_PROMPT, TRANSLATE_PROMPT, ) # Load the API key from the environment API_KEY = os.getenv("GEMINI_API_KEY") if not API_KEY: raise RuntimeError( "GEMINI_API_KEY is missing. Copy .env.example to .env " "and paste your key from https://aistudio.google.com/app/apikey" ) genai.configure(api_key=API_KEY) _MODEL = genai.GenerativeModel("gemini-1.5-flash") class AIClientError(Exception): """Raised when the Gemini API call fails. The message is user-facing.""" def _safe_generate(prompt: str) -> str: try: response = _MODEL.generate_content(prompt) return response.text.strip() except ResourceExhausted: raise AIClientError( "Rate limit reached. The free tier allows 15 requests/minute " "and 1,500/day. Wait 60 seconds and try again." ) except PermissionDenied: raise AIClientError( "API key rejected. Check that GEMINI_API_KEY in your .env " "matches a key from https://aistudio.google.com/app/apikey" ) except InvalidArgument as e: raise AIClientError(f"Invalid request: {e}") except GoogleAPICallError as e: raise AIClientError(f"Gemini API error: {e}") def summarize_text(text: str, n: int = 5) -> str: if len(text.strip()) < 30: raise AIClientError("Please paste at least 30 characters to summarize.") return _safe_generate(SUMMARIZE_PROMPT.format(n=n, text=text)) def analyze_sentiment(text: str) -> str: if len(text.strip()) < 10: raise AIClientError("Please paste at least 10 characters to analyze.") return _safe_generate(SENTIMENT_PROMPT.format(text=text)) def answer_question(context: str, question: str) -> str: if len(context.strip()) < 30: raise AIClientError("Context must be at least 30 characters.") if len(question.strip()) < 3: raise AIClientError("Question must be at least 3 characters.") return _safe_generate(QA_PROMPT.format(context=context, question=question)) def generate_content(topic: str, tone: str = "informative", n: int = 200) -> str: if len(topic.strip()) < 3: raise AIClientError("Please enter a topic of at least 3 characters.") return _safe_generate(GENERATE_PROMPT.format(topic=topic, tone=tone, n=n)) def translate_text(text: str, source_lang: str, target_lang: str) -> str: if len(text.strip()) < 5: raise AIClientError("Please enter at least 5 characters to translate.") return _safe_generate( TRANSLATE_PROMPT.format( text=text, source_lang=source_lang, target_lang=target_lang ) )
os.getenv("GEMINI_API_KEY")_MODEL = genai.GenerativeModel("gemini-1.5-flash")_safe_generate(prompt)AIClientError.summarize_text(text, n=5)_safe_generate, returns the model’s text.File 5 β src/utils.py
Three small Streamlit helpers that keep app.py readable.
"""Small helpers shared across the Streamlit UI.""" import streamlit as st def show_error(message: str) -> None: st.error(f"β {message}") def show_success(message: str) -> None: st.success(f"β {message}") def render_markdown(content: str) -> None: st.markdown(content)
File 6 β app.py (the Streamlit UI)
This is the file you launch with streamlit run app.py. It defines the sidebar tool selector, the five tool layouts, and the footer. Each tool calls the corresponding function from src/ai_client.py and renders the result with st.markdown.
"""AI Content Studio β a Streamlit web app powered by Google Gemini.""" import os from dotenv import load_dotenv import streamlit as st # Load .env before importing ai_client (which reads the key at import time) load_dotenv() from src.ai_client import ( AIClientError, summarize_text, analyze_sentiment, answer_question, generate_content, translate_text, ) from src.utils import show_error, show_success, render_markdown st.set_page_config( page_title="AI Content Studio", page_icon="π€", layout="wide", initial_sidebar_state="expanded", ) # ---------- Header ---------- st.title("π€ AI Content Studio") st.caption("Powered by Google Gemini 1.5 Flash Β· Free tier Β· No credit card required") # ---------- Sidebar ---------- with st.sidebar: st.header("Choose a tool") tool = st.radio( "Available tools", ["π Summarize", "π Sentiment", "β Q&A", "βοΈ Generate", "π Translate"], label_visibility="collapsed", ) st.divider() st.markdown( "**Free tier limits**\n" "- 15 requests / minute\n" "- 1,500 requests / day\n" "- 1 million tokens / minute\n\n" "_No credit card required. Get a key at " "[Google AI Studio](https://aistudio.google.com/app/apikey)._\"" ) # ---------- Tool layouts ---------- left, right = st.columns([1, 1.2]) if tool == "π Summarize": with left: st.subheader("Paste your text") text = st.text_area("Text to summarize", height=300, key="sum_text") n = st.slider("Number of bullet points", 3, 10, 5) if st.button("Summarize", type="primary"): try: with st.spinner("Summarizing..."): out = summarize_text(text, n=n) show_success("Done!") render_markdown(out) except AIClientError as e: show_error(str(e)) elif tool == "π Sentiment": with left: st.subheader("Paste text to analyze") text = st.text_area("Text", height=300, key="sent_text") if st.button("Analyze", type="primary"): try: with st.spinner("Analyzing..."): out = analyze_sentiment(text) show_success("Done!") render_markdown(out) except AIClientError as e: show_error(str(e)) elif tool == "β Q&A": with left: st.subheader("Provide context and a question") context = st.text_area("Context (the source text)", height=220, key="qa_ctx") question = st.text_input("Your question", key="qa_q") if st.button("Answer", type="primary"): try: with st.spinner("Answering..."): out = answer_question(context, question) show_success("Done!") render_markdown(out) except AIClientError as e: show_error(str(e)) elif tool == "βοΈ Generate": with left: st.subheader("Generate new content") topic = st.text_input("Topic", key="gen_topic") tone = st.selectbox("Tone", ["informative", "casual", "professional", "funny", "persuasive"]) n = st.slider("Word count", 50, 500, 200, step=50) if st.button("Generate", type="primary"): try: with st.spinner("Generating..."): out = generate_content(topic, tone=tone, n=n) show_success("Done!") render_markdown(out) except AIClientError as e: show_error(str(e)) elif tool == "π Translate": with left: st.subheader("Translate text") text = st.text_area("Text to translate", height=220, key="tr_text") col1, col2 = st.columns(2) with col1: source_lang = st.text_input("From", value="English") with col2: target_lang = st.text_input("To", value="Spanish") if st.button("Translate", type="primary"): try: with st.spinner("Translating..."): out = translate_text(text, source_lang, target_lang) show_success("Done!") render_markdown(out) except AIClientError as e: show_error(str(e)) with right: st.subheader("Result") st.info("Your AI output will appear on the left after you click the action button.") # ---------- Footer ---------- st.divider() st.caption( "Built with [Streamlit](https://streamlit.io) + " "[Google Gemini](https://ai.google.dev). " "Source: [Darekdari.com tutorial](https://darekdari.com/build-ai-web-app-python-free-apis/)" )
File 7 β .streamlit/config.toml (optional, dark theme)
Create the .streamlit/ folder inside your project root, then create config.toml inside it. This makes Streamlit use a dark theme matching the Darekdari aesthetic and limits uploads to 5 MB.
[server] maxUploadSize = 5 [theme] base = "dark" primaryColor = "#22d3ee" backgroundColor = "#04060d" secondaryBackgroundColor = "#0a1128" textColor = "#f5f8ff"
src/__init__.py β create an empty file at src/__init__.py. Without it, Python won’t recognize src/ as a package and your from src.ai_client import ... will fail with ModuleNotFoundError.How the Streamlit UI
actually works.
Streamlit’s mental model is unique: every time the user interacts with a widget (clicks a button, types in a text area, moves a slider), Streamlit re-runs your entire app.py script from top to bottom. State is preserved only inside special widgets via the key parameter. This sounds inefficient, but it’s what lets you write a stateful UI without ever touching React or JavaScript.
st.set_page_config(...)st.write or st.title.st.sidebar.radio(...)tool and use to switch the main layout.st.columns([1, 1.2])[1, 1.2] gives the result panel slightly more space.st.text_area(..., key="...")key keeps the text preserved across re-runs.st.button("...", type="primary")True only on the run triggered by clicking it β so the if st.button(...) block executes the AI call exactly once per click.st.spinner("...")If you’ve built a Python social media bot before, you’re used to writing CLI scripts that run once and exit. Streamlit is different: your script runs continuously, re-executing on every widget interaction. Once you internalize that, the framework becomes very intuitive.
How ai_client.py
talks to Gemini.
The flow for every tool is identical: UI collects input β app.py validates β ai_client.py formats the prompt β google-generativeai SDK sends HTTP POST to Gemini β response.text is returned β app.py renders it. The whole round-trip takes 1β3 seconds.
User clicks "Summarize" | v app.py (line 30) ai_client.py Gemini API app.py (line 32) +-----------------+ +-------------------+ +---------------+ +-----------------+ | st.button( |--->| summarize_text( |--->| HTTPS POST |--->| st.markdown( | | "Summarize") | | text, n=5) | | /v1beta/models/ | | out) | +-----------------+ +-------------------+ +---------------+ +-----------------+ | | gemini-1.5-flash| | <---| response.text |<---| | +---------------+ |
Two architectural choices matter here:
- The model is loaded once at import time (
_MODEL = genai.GenerativeModel("gemini-1.5-flash")). Loading per request would add ~200ms of overhead each call. - The
_safe_generate()wrapper catches every common error and converts it to anAIClientErrorwith a friendly message. Without this, a rate-limit hit would crash the app with a 50-line stack trace.
Launch the app
on your machine.
Step 14.1 β Verify your folder structure
From inside ai-content-studio/, run ls -la (macOS/Linux) or dir (Windows). You should see all 7 files from Section 7 plus the venv/ folder. If anything is missing, go back to Section 11.
Step 14.2 β Activate the virtual environment
# macOS / Linux source venv/bin/activate # Windows (PowerShell) venv\Scripts\Activate.ps1
Step 14.3 β Launch Streamlit
streamlit run app.py
Your browser should auto-open at http://localhost:8501. If it doesn’t, open it manually. You should see the π€ AI Content Studio title and the 5-tool sidebar.
Step 14.4 β Test each tool
- Summarize: Paste any 200+ word paragraph (e.g. from a Wikipedia article). Click Summarize. You should get a 5-bullet summary in 2-3 seconds.
- Sentiment: Paste a strong product review (“This product is amazing! Best purchase I’ve made all year.”). Click Analyze. You should get “Overall sentiment: Positive, Confidence: 95%”.
- Q&A: Paste a paragraph about any topic, ask a question whose answer is in the text. Click Answer.
- Generate: Type “the future of remote work” as topic, pick “informative” tone, 200 words. Click Generate.
- Translate: Paste “Hello, how are you today?” β From: English, To: Spanish. Click Translate. You should get “Hola, ΒΏcΓ³mo estΓ‘s hoy?”.
Step 14.5 β Test error handling (important)
- Empty input: Click Summarize without typing anything. You should see “β Please paste at least 30 characters to summarize.” β not a stack trace.
- Invalid API key: Temporarily rename your
.envfile to.env.bak, restart Streamlit, and try any tool. You should see the “API key rejected” error. Rename it back when done. - Rate limit (optional): Click Summarize 16 times within 60 seconds. On the 16th, you should see “β Rate limit reached. The free tier allows 15 requests/minute…” instead of a crash.
Sample inputs
and outputs.
Real outputs from running the app on the Gemini 1.5 Flash free tier.
π Summarize β example
// INPUT (paste into Summarize tool) "Python is a high-level, general-purpose programming language. Its design philosophy emphasizes code readability with the use of significant indentation. Python is dynamically typed and garbage-collected. It supports multiple programming paradigms, including structured, object-oriented and functional programming. First released in 1991 by Guido van Rossum, Python has become one of the most popular programming languages in the world, especially for data science, machine learning, and web development. The Python Software Foundation maintains the language and curates PyPI, a repository of over 500,000 third-party packages." // OUTPUT (5 bullets) - Python is a high-level, general-purpose language emphasizing code readability. - It is dynamically typed, garbage-collected, and supports multiple paradigms. - Created by Guido van Rossum in 1991, it's now among the world's most popular languages. - Python dominates data science, machine learning, and web development. - The Python Software Foundation maintains the language and PyPI (500k+ packages).
π Sentiment β example
// INPUT "I've been using this laptop for three months now and I'm extremely disappointed. The battery dies after 2 hours, the screen flickers, and customer support keeps transferring me between departments. Save your money." // OUTPUT - Overall sentiment: Negative - Confidence: 96% - Key emotions: frustration, disappointment, regret, anger - Explanation: The user describes multiple product failures and unhelpful support, ending with an explicit warning to others β strong negative sentiment.
π Translate β example
// INPUT (From: English β To: Spanish) "The quick brown fox jumps over the lazy dog." // OUTPUT "El rΓ‘pido zorro marrΓ³n salta sobre el perro perezoso."
The 5 errors you’ll
actually hit.
Each one has a 1-line fix.
Error 1 β ModuleNotFoundError: No module named 'src'
Cause: You forgot to create src/__init__.py, or you ran streamlit run app.py from inside the src/ folder instead of the project root.
Fix: Create an empty file at src/__init__.py. Then cd back to ai-content-studio/ (where app.py lives) and run streamlit run app.py again.
Error 2 β RuntimeError: GEMINI_API_KEY is missing
Cause: The .env file doesn’t exist, or you forgot the load_dotenv() call before importing ai_client, or the key value has a typo.
Fix: Run cp .env.example .env, open .env in your editor, paste your real key (starts with AIza), save. Confirm app.py line 6 calls load_dotenv() before the from src.ai_client import line.
Error 3 β 429 Resource has been exhausted
Cause: You exceeded the free-tier rate limit (15 requests/minute or 1,500/day).
Fix: Wait 60 seconds (for the per-minute limit) or until midnight Pacific time (for the daily limit). The _safe_generate wrapper converts this into a friendly message β you should never see the raw 429 unless you’re calling the SDK directly.
Error 4 β 403 PermissionDenied
Cause: Wrong API key, expired key, or Google revoked the key (often because it was committed to a public GitHub repo).
Fix: Visit Google AI Studio, delete the old key, create a new one, paste it into .env. If you committed the old key to GitHub, force-push to remove it from history (git filter-branch or BFG Repo-Cleaner) β Google scans public repos.
Error 5 β StreamlitAPIException: files need a unique key
Cause: Two Streamlit widgets share the same key= parameter. Streamlit uses keys to track widget state across re-runs; duplicates break it.
Fix: Open app.py and verify every st.text_area and st.text_input has a unique key value. The 5 tools use: sum_text, sent_text, qa_ctx, qa_q, gen_topic, tr_text.
Deploy to Streamlit
Community Cloud.
3-minute deploy from a public GitHub repo. The hosting is genuinely free for public apps.
- Push to GitHub. Create a public repo (e.g.
ai-content-studio) and push your project. Double-check.envis in.gitignoreβ verify withgit statusthat.envdoes NOT appear as a tracked file. - Open share.streamlit.io. Sign in with the same GitHub account.
- Click “New app”. Pick your repo, the
mainbranch, and the file pathapp.py. - Open Advanced Settings β Secrets. Paste this TOML:
GEMINI_API_KEY = "AIza...your-real-key-here"
This is how Streamlit Community Cloud injects your API key without you committing it to git. - Click Deploy. The first build takes 1-2 minutes (Streamlit installs your
requirements.txtin a fresh container). When done, you’ll get a URL likehttps://your-username-ai-content-studio.streamlit.app. - Verify the deployed app by opening the URL in an incognito window and running the same smoke test from Section 14.
Every git push to your main branch triggers an automatic redeploy within ~60 seconds. There’s no CI/CD to configure β it just works. This is the fastest iteration loop of any free Python hosting option we’ve found.
Where to take this
from here.
Pick one,
implement it,
add to your portfolio.
Each enhancement below is a 2-4 hour project on top of the existing codebase.
Add chat memory
Swap the one-shot Q&A tool for a conversational chat using genai.GenerativeModel.start_chat(). The Gemini SDK handles message history automatically β you just append user turns and render the response.
Switch to a stronger model
Change "gemini-1.5-flash" to "gemini-1.5-pro" in ai_client.py. The Pro model is smarter but has a tighter free tier (2 RPM, 50 RPD). Use it for the Q&A tool only.
Add file upload + PDF parsing
Use st.file_uploader + pypdf2 to let users upload a PDF and summarize or Q&A it. The text-extraction logic is ~15 lines.
Add usage analytics
Log every API call to a SQLite database with timestamp, tool name, and token count. Render a weekly chart with st.bar_chart. Useful for tracking your own usage against the 1,500/day limit.
Multi-language UI
Add a language dropdown in the sidebar and translate the UI labels. The Gemini API can translate the UI strings on first launch β store the result in st.session_state.
For more Python project ideas that pair well with this one β including a full Python activation and license validation control system if you ever want to commercialize an AI app β browse our Computer Engineering project archive.
Skills gained from
this project.
Each skill
transfers to your
next AI project.
Streamlit fundamentals
Widgets, layout, sidebar, columns, session state, page config. Enough to build any data app you can imagine.
LLM API integration
Loading a model, formatting prompts, parsing responses, handling errors. The same pattern works for OpenAI, Anthropic, Cohere, and Hugging Face.
Secrets management
Using python-dotenv locally and Streamlit Secrets in production. Never committing .env to git.
Prompt engineering
How to write templates that produce structured output (bullet lists, sentiment reports). The skill that separates good AI apps from broken ones.
Python packaging
The src/ package pattern, __init__.py, relative imports (from .prompts import ...).
Free cloud deployment
Git β GitHub β Streamlit Cloud. The same workflow applies to Render, Fly.io, and Hugging Face Spaces.
You built an AI web app.
Now ship more.
You now have a deployed AI web application at a public URL, built entirely with free tools, with production-grade error handling and a clean modular codebase. That’s more than most “AI tutorials” deliver β most stop at “here’s how to call the API” and skip deployment, secrets, and error handling entirely.
The architecture you just learned β Streamlit UI + thin Python wrapper + hosted LLM API + free cloud deploy β is the same pattern behind most single-user AI tools you see on Product Hunt, indie hacker portfolios, and university final-year projects. Once you internalize it, building the next idea (chatbot, code explainer, document Q&A, content rewriter) takes hours, not weeks.
If you want to keep building, two natural next projects: (1) our Python sentiment analysis project shows you how to train your own sentiment model locally β useful when you don’t want to depend on a paid API; (2) our Python social media bot project shows you how to schedule automated posts β combine it with the content generator from this tutorial and you have an AI content pipeline.
Frequently asked
questions.
Can I really build an AI web app for free with Python? +
Yes. This tutorial uses two genuinely free services: the Google Gemini API free tier (Gemini 1.5 Flash at 15 requests/minute and 1,500 requests/day, no credit card required) and Streamlit Community Cloud (free hosting for public apps from public GitHub repos, 1 GB RAM, 5-app limit). You can build, run, and deploy the entire application for $0.
Is the Google Gemini API really free, or is it just a free trial? +
The Gemini API free tier is a permanent free tier, not a trial. It requires a Google account and an API key from Google AI Studio, but no credit card. The free tier of Gemini 1.5 Flash allows 15 requests per minute, 1,500 requests per day, and 1 million tokens per minute. If you exceed these limits, the API returns a 429 error and you simply wait β there are no charges.
Why use Streamlit instead of Flask or Django for an AI web app? +
Streamlit is purpose-built for data and AI apps: it handles HTML/CSS/JS automatically, so you write pure Python and get a polished, responsive web UI in 50 lines. Flask requires you to write templates, route handlers, and static assets separately. For a beginner AI project where the focus is on the AI logic, Streamlit cuts boilerplate by 80%. Flask is better for production APIs; Streamlit is better for interactive demos and internal tools. See the official Streamlit docs for more.
How do I securely store the Gemini API key in a Python web app? +
Store the API key in a .env file at the project root, load it with the python-dotenv library, and never commit .env to git. Add .env to your .gitignore. In Streamlit Community Cloud, paste the key into the Secrets manager (Settings β Secrets) instead of using a .env file. Never hard-code API keys directly in app.py β if you push them to a public GitHub repo, Google will detect the leak and revoke the key automatically.
What happens when the Gemini free-tier rate limit is exceeded? +
The API returns HTTP 429 Too Many Requests. Our ai_client.py wraps every call in a try/except that catches google.api_core.exceptions.ResourceExhausted and returns a friendly error message to the user. The user can wait 60 seconds (for the 15-RPM limit) or until midnight Pacific time (for the 1,500-RPD limit) and retry.
Can I use Hugging Face Inference API instead of Gemini for this project? +
You can, but as of late 2025 Hugging Face requires free users to purchase credits to use serverless Inference Providers β it is no longer strictly free. The Google Gemini free tier is currently the most generous no-cost option for generative AI. If you want to swap providers, replace the ai_client.py module with a Hugging Face InferenceClient wrapper; the rest of the Streamlit app stays the same.
How long does it take to deploy a Streamlit app to Streamlit Community Cloud? +
About 3 minutes from a clean GitHub repo. You log in with GitHub at share.streamlit.io, pick the repo/branch/file, paste your GEMINI_API_KEY into the Secrets manager, and click Deploy. The first build takes 1-2 minutes; subsequent pushes redeploy automatically. The free tier sleeps the app after 12 hours of inactivity, with a 5-10 second cold start when a user revisits.
Build & deploy
your AI app today.
Get a free Gemini API key, clone the source code, and ship your AI Content Studio to a public URL in under 2 hours. Or click “Copy Full Project Code” at the top of this page to grab every file in one click.
Share this Tutorial
If this tutorial helped you build your first AI web app, share it with fellow students and developers.
