DEV Community

Cover image for I Built an AI-Powered Pokedex with Python & Streamlit (And It's Free!) 🚀
Hoang Manh Cam
Hoang Manh Cam

Posted on

I Built an AI-Powered Pokedex with Python & Streamlit (And It's Free!) 🚀

🔴 Introduction

Remember the original Pokedex? It was a handheld encyclopedia that gave you information about every Pokemon you encountered. Fast forward to 2025, and I thought: "What if the Pokedex could actually talk back?"

In this article, I'll take you on a deep technical dive into how I built a "Minimal Pokedex" web app. This isn't just a "hello world" tutorial; I'll show you how I handled real-world challenges like API caching, complex UI states, recursive data structures, and finally, integrating a Large Language Model (LLM) for real-time chat.

Best of all? It's built entirely in Python, hosted for free, and the AI costs $0 to run!

🔗 Live Demo: https://pokedex-ai.streamlit.app/

👨‍💻 GitHub Repo: cam-hm/pokedex-ai

Demo


🛠️ The Tech Stack

I chose this stack for speed and simplicity:

  • Frontend: Streamlit - Turns Python scripts into shareable web apps in minutes.
  • Data: PokeAPI - The gold standard for Pokemon data.
  • AI: Groq API - Running Llama-3.3-70b, which is incredibly fast and free for developers.
  • Hosting: Streamlit Cloud - One-click deployment from GitHub.

✨ What We're Building (Feature Overview)

Before we dive into the code, let's look at what this app actually does. It's not just a list of names; it's a full-featured companion.

1. 🏠 The Animated Grid

Instead of static images, the home page features a responsive grid of Animated Sprites (using Showdown GIFs). It feels alive and nostalgic.

2. 📊 Deep Detail View

Clicking a Pokemon reveals:

  • Official Artwork (High-res)
  • Base Stats visualized with colored progress bars.
  • Evolution Chain navigation.
  • Type Effectiveness calculator.

3. 🤖 The AI Expert

The killer feature. You can chat with the Pokedex!

  • "How do I evolve Eevee into Umbreon?"
  • "Is this Pokemon good against Water types?" The AI answers using real game data, not just generic knowledge.

📱 Phase 1: The "Monolith" (Building the MVP)

I started exactly how you should start a prototype: One single file (app.py).

My goal was to get something on the screen fast. I didn't worry about folder structures or architecture. I just wanted to fetch Pokemon and show them.

1. Fetching Data (The Naive Way)

I wrote a simple function right inside app.py to hit the PokeAPI. To prevent the app from being slow, I used Streamlit's caching.

# app.py  import streamlit as st import requests @st.cache_data def get_pokemon_list(limit=100): url = f"https://pokeapi.co/api/v2/pokemon?limit={limit}" return requests.get(url).json()['results'] pokemon_list = get_pokemon_list() 
Enter fullscreen mode Exit fullscreen mode

2. The Grid Layout

I iterated through the list and displayed them. I wanted animated sprites, so I added some logic to check for the "Showdown" GIF first.

# app.py (continued)  cols = st.columns(4) for i, pokemon in enumerate(pokemon_list): with cols[i % 4]: # Logic to find the best sprite URL  sprite_url = get_sprite_url(pokemon['id']) st.image(sprite_url) 
Enter fullscreen mode Exit fullscreen mode

3. Adding Complexity (The Detail View)

Then I added the detail view. This is where things got tricky.

The Tricky Part: Evolution Chains (Recursion!) 🤯

Displaying "Charmander → Charmeleon → Charizard" sounds easy, but the API returns a deeply nested JSON tree (chain -> evolves_to -> evolves_to...).

I had to write a recursive function inside app.py to flatten this tree:

# app.py (still the same file!)  def parse_evolution_chain(chain_link, evo_list=None): if evo_list is None: evo_list = [] species_name = chain_link['species']['name'] evo_list.append(species_name) # Recursion magic happens here  for evolution in chain_link['evolves_to']: parse_evolution_chain(evolution, evo_list) return evo_list 
Enter fullscreen mode Exit fullscreen mode

Visualizing Stats

I also wanted those cool colored progress bars for stats (Green for high, Red for low). Streamlit's default bar wasn't enough, so I injected custom HTML/CSS directly into the Python script.

# app.py  st.markdown(f""" <div style="width: {percentage}%; background-color: {color};"></div> """, unsafe_allow_html=True) 
Enter fullscreen mode Exit fullscreen mode

All of this logic—API calls, recursion, HTML generation—was stuffed into app.py.

By the time I finished the core features, app.py had grown to 500+ lines. It was a mix of everything.

It worked, but it was becoming unmanageable.

🏗️ Phase 2: Refactoring for Scale (The "Spaghetti" Problem)

As I added more features—Shiny Toggle, Type Effectiveness Modal, Audio Cries—my app.py started to look like a disaster zone. It was a 500-line monolithic script where UI code was mixed with API logic and state management.

The Problem:

  • Readability: I couldn't find anything.
  • State Management: Variables were being overwritten.
  • Scalability: Adding AI to this mess would be a nightmare.

The Solution: Modular Architecture
I stopped coding features and spent an hour refactoring. I split the app into 4 distinct layers:

pokedex_app/ ├── app.py # Entry point (Main router - only 20 lines!) ├── src/ │ ├── config/ # Constants (Type colors, Gen limits) │ ├── api/ # API Client (Network layer, Caching) │ ├── services/ # Business Logic (Data processing, AI) │ └── ui/ # Presentation Layer │ ├── components/ # Reusable widgets (Modals, Cards) │ ├── home.py # Home Page Logic │ └── detail.py # Detail Page Logic 
Enter fullscreen mode Exit fullscreen mode

Why this matters:
By moving the Pokemon logic to src/services/pokemon_service.py and the UI to src/ui/, I created a clean slot for the AI. When I was ready to add the chatbot, I didn't have to touch the existing UI code—I just created a new src/services/ai_service.py.


🤖 Phase 3: The AI Brain (Groq + Llama 3)

Now, the killer feature: Conversational AI.

I didn't want a generic chatbot that says "I am an AI model." I wanted a Pokemon Expert that knows exactly which Pokemon you are looking at.

1. The Challenge: Hallucinations

If you ask ChatGPT "How do I beat Charizard?", it gives a generic answer. But what if you're playing a specific ROM hack where Charizard is a Water type? The AI wouldn't know.

2. The Solution: Context Injection (RAG-lite)

I used a technique similar to RAG (Retrieval Augmented Generation). Instead of searching a vector database, I inject the Ground Truth (the exact stats of the current Pokemon) directly into the system prompt.

Here is the exact prompt engineering strategy:

# src/services/ai_service.py  class PokemonChatbot: def chat(self, pokemon_name, pokemon_data, user_message): # 1. Extract relevant data  stats = pokemon_data['stats'] types = [t['type']['name'] for t in pokemon_data['types']] abilities = [a['ability']['name'] for a in pokemon_data['abilities']] # 2. Construct the Context String  # This is what the AI "sees" before it answers  context = f""" You are analyzing {pokemon_name}. Type: {', '.join(types)} Abilities: {', '.join(abilities)} Base Stats: {stats} Height: {pokemon_data['height']} | Weight: {pokemon_data['weight']} """ # 3. System Prompt  system_prompt = f""" You are a Pokemon Expert. Use the data below to answer the user's question. DATA CONTEXT: {context} GUIDELINES: - Be concise, strategic, and friendly. - Use emojis (🔥, ⚡, 🛡️). - If asked about battles, refer to the specific Stats provided. - Do NOT make up information not in the data. """ # 4. Call Groq API  return self.client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": system_prompt}, ...] ) 
Enter fullscreen mode Exit fullscreen mode

3. Why Groq? (Speed is a Feature)

For a chat interface, latency is everything.

  • OpenAI GPT-4: Great quality, but can be slow (2-5s) and expensive.
  • Groq Llama 3: Instant (<0.5s) and Free for developers.

Groq's LPU (Language Processing Unit) inference engine delivers responses so fast it feels like the AI is pre-generating them. For a real-time companion app, this speed difference is the difference between "cool" and "usable."


🚀 Deployment

directly with GitHub.

The Secret Sauce:
To keep my API keys safe, I used Streamlit's Secrets management.

  1. Local: .streamlit/secrets.toml (Git ignored)
  2. Production: Streamlit Dashboard -> App Settings -> Secrets
# .streamlit/secrets.toml GROQ_API_KEY = "gsk_..." 
Enter fullscreen mode Exit fullscreen mode

💡 Key Takeaways

  1. UX Matters: Small details like animated sprites, colored stat bars, and loading states make a "toy app" feel professional.
  2. Architecture Saves Time: Refactoring early allowed me to add the AI feature in just 30 minutes.
  3. AI Needs Context: The chatbot is only smart because I programmatically feed it the correct data. Without context, it's just ChatGPT; with context, it's a Pokedex Expert.

👋 Try it out!

Go ahead, ask the AI for a competitive moveset for Magikarp (spoiler: it's Flail).

👉 App: pokedex-ai.streamlit.app

👉 Code: github.com/cam-hm/pokedex-ai

If you enjoyed this write-up, drop a star on the repo! ⭐

python #streamlit #ai #coding #pokemon

Top comments (8)

Collapse
 
ben profile image
Ben Halpern

This is fun

Collapse
 
cammanhhoang profile image
Hoang Manh Cam

Thank you so much!

Collapse
 
datawranglerai profile image
datawrangler.AI

This is SO good and detailed. Love the inclusion of EVs and enjoyed talking to it about my boy Heracross.

Collapse
 
cammanhhoang profile image
Hoang Manh Cam

Thank you so much! Happy you liked it — I’ll keep optimizing for better response.

Collapse
 
njvelazquez215 profile image
Nicolás Velazquez

Spectacular work and an even better explanation!

Collapse
 
r_gholston_48d3644743c7a5 profile image
R Gholston

I would not mind something like that for my Pokemon Pocket collection, very nice work.

Collapse
 
cammanhhoang profile image
Hoang Manh Cam

Thank you for your kind words!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.