Module 3: LM Studio - Introduction

Mini-Project: 100% Local Chatbot

Project overview

Module 3's final project: a fully local chatbot (cost $0, 100% privacy) using LM Studio.

You'll take the Module 2 chatbot and adapt it to run 100% offline with local Mistral 7B.

Time: 30-45 minutes
Difficulty: Medium


🎯 Goal

Build a local CLI chatbot with:

  • ✅ Conversations with context
  • ✅ Cost tracking (confirm $0)
  • ✅ Performance metrics (latency)
  • ✅ Persistence (JSON)
  • ✅ 100% offline (no cloud API calls)

💻 Full Code

#!/usr/bin/env python3
"""
100% Local Chatbot with LM Studio
Module 3 - Final Project
"""

import json
import time
from datetime import datetime
from pathlib import Path
from openai import OpenAI

# ============================================================================
# CONFIGURATION
# ============================================================================

# LM Studio client (local)
client = OpenAI(
    base_url="http://localhost:1234/v1",
    api_key="not-needed"
)

MODEL = "mistral-7b-instruct"  # Adjust to your model

Path("conversations_local").mkdir(exist_ok=True)

# ============================================================================
# LOCAL CHATBOT CLASS
# ============================================================================

class LocalChatBot:
    """100% local chatbot with LM Studio."""
    
    def __init__(self):
        self.messages = [
            {"role": "system", "content": "You are a helpful and friendly assistant."}
        ]
        self.total_time = 0.0
        self.query_count = 0
        self.conversation_id = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    def chat(self, user_message: str) -> str:
        """Send a message and return the response."""
        
        self.messages.append({"role": "user", "content": user_message})
        
        # Measure latency
        start = time.time()
        
        try:
            response = client.chat.completions.create(
                model=MODEL,
                messages=self.messages,
                temperature=0.7,
                max_tokens=150
            )
            
            latency = time.time() - start
            self.total_time += latency
            self.query_count += 1
            
            assistant_message = response.choices[0].message.content
            self.messages.append({"role": "assistant", "content": assistant_message})
            
            print(f"[Latency: {latency:.2f}s]")
            
            return assistant_message
            
        except Exception as e:
            print(f"❌ Error: {e}")
            return None
    
    def show_stats(self):
        """Show statistics."""
        if self.query_count == 0:
            print("\n📊 No queries yet")
            return
        
        avg_latency = self.total_time / self.query_count
        
        print("\n" + "="*50)
        print("📊 STATISTICS")
        print("="*50)
        print(f"Queries:        {self.query_count}")
        print(f"Total time:     {self.total_time:.2f}s")
        print(f"Avg latency:    {avg_latency:.2f}s")
        print(f"💰 Cost:        $0.00 (100% local!)")
        print("="*50 + "\n")
    
    def show_history(self):
        """Show the history."""
        print("\n" + "="*50)
        print("HISTORY")
        print("="*50)
        
        for i, msg in enumerate(self.messages[1:], 1):
            role = "YOU" if msg["role"] == "user" else "BOT"
            print(f"\n[{i}] {role}: {msg['content']}")
        
        print("\n" + "="*50 + "\n")
    
    def save_conversation(self):
        """Save the conversation."""
        filename = f"conversations_local/conversation_{self.conversation_id}.json"
        
        data = {
            "conversation_id": self.conversation_id,
            "timestamp": datetime.now().isoformat(),
            "model": MODEL,
            "messages": self.messages[1:],
            "query_count": self.query_count,
            "total_time": self.total_time,
            "avg_latency": self.total_time / self.query_count if self.query_count > 0 else 0,
            "cost": 0.0  # $0 local!
        }
        
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        
        print(f"💾 Conversation saved: {filename}")

# ============================================================================
# MAIN
# ============================================================================

def main():
    """Main CLI."""
    
    print("\n" + "="*50)
    print("🤖 100% LOCAL CHATBOT (LM Studio)")
    print("="*50)
    print(f"\nModel: {MODEL}")
    print("Cost: $0.00 (offline)")
    print("\nCommands:")
    print("  - 'salir'       → Quit")
    print("  - 'historial'   → See history")
    print("  - 'stats'       → See stats")
    print("\n" + "="*50 + "\n")
    
    # Check the LM Studio server
    print("Checking the LM Studio server...")
    try:
        models = client.models.list()
        print(f"✅ Server active ({len(models.data)} models available)\n")
    except Exception as e:
        print(f"❌ Error: {e}")
        print("\n⚠️ Make sure LM Studio is running with the server active")
        print("   Settings → Local Server → Start Server\n")
        return
    
    bot = LocalChatBot()
    
    # Main loop
    while True:
        try:
            user_input = input("You: ").strip()
            
            if user_input.lower() in ["salir", "exit", "quit"]:
                print("\n👋 See you soon!\n")
                bot.show_stats()
                bot.save_conversation()
                break
            
            if user_input.lower() == "historial":
                bot.show_history()
                continue
            
            if user_input.lower() == "stats":
                bot.show_stats()
                continue
            
            if not user_input:
                print("⚠️ Type a message.\n")
                continue
            
            response = bot.chat(user_input)
            
            if response:
                print(f"\nBot: {response}\n")
        
        except KeyboardInterrupt:
            print("\n\n👋 Interrupted by the user\n")
            bot.show_stats()
            bot.save_conversation()
            break
        
        except Exception as e:
            print(f"\n❌ Unexpected error: {e}\n")
            break

if __name__ == "__main__":
    main()

🚀 Usage

1. Start the LM Studio server:

  • LM Studio → Settings → Start Server
  • Check for 🟢 "Server running on port 1234"

2. Load a model:

  • Home → My Models → Load (Mistral 7B)

3. Run the chatbot:

python chatbot_local.py

📊 Example Conversation

==================================================
🤖 100% LOCAL CHATBOT (LM Studio)
==================================================

Model: mistral-7b-instruct
Cost: $0.00 (offline)

Commands:
  - 'salir'       → Quit
  - 'historial'   → See history
  - 'stats'       → See stats

==================================================

Checking the LM Studio server...
✅ Server active (1 models available)

You: Hi, what model are you?
[Latency: 4.2s]

Bot: I'm Mistral 7B, a local language model running on your machine.

You: What is the capital of France?
[Latency: 3.8s]

Bot: The capital of France is Paris.

You: stats

==================================================
📊 STATISTICS
==================================================
Queries:        2
Total time:     8.00s
Avg latency:    4.00s
💰 Cost:        $0.00 (100% local!)
==================================================

You: salir

👋 See you soon!

==================================================
📊 STATISTICS
==================================================
Queries:        2
Total time:     8.00s
Avg latency:    4.00s
💰 Cost:        $0.00 (100% local!)
==================================================

💾 Conversation saved: conversations_local/conversation_20240215_143022.json

✅ Self-Assessment Rubric

Functionality (40 pts):

  • (10) The chatbot answers coherently
  • (10) Maintains context
  • (10) Commands work (historial, stats, salir)
  • (10) Conversations saved

Performance (30 pts):

  • (10) Latency tracking works
  • (10) Accurate stats
  • (10) Cost confirmed at $0

Local (30 pts):

  • (15) Works without the OpenAI API
  • (15) Works offline (disconnect WiFi and test)

Total: ___/100


🎯 Comparison with Module 2

MetricModule 2 (OpenAI)Module 3 (LM Studio)
Latency1.5s4.0s (2.6x slower)
Cost (100 queries)$0.10$0.00
PrivacyCloud100% local ✅
Offline
Setup5 min30 min

🔗 Optional Extensions

1. Side-by-side comparator:

# Send the query to both (OpenAI + LM Studio)
# Compare responses and latency

2. Auto-switch by latency:

# If LM Studio >10s → fall back to OpenAI

3. Hybrid mode:

# Simple queries → LM Studio ($0)
# Complex queries → GPT-4 ($$$)

✅ Module 3 Summary

What you mastered:

  • ✅ Complete LM Studio setup
  • ✅ Model management (download, quantization)
  • ✅ OpenAI-compatible local API
  • ✅ Trivial code migration
  • ✅ 100% local chatbot working

Trade-offs understood:

  • Cost $0 vs 3-5x slower latency
  • Maximum privacy vs more complex setup
  • Offline vs 10% lower quality

➡️ Next step

Next module: Module 4 - Ollama (Local CLI)

You'll learn Ollama, an alternative to LM Studio but:

  • CLI instead of GUI (more pro)
  • Docker support (production)
  • Multi-node clustering (scalability)

Time: 2-3 hours


Congratulations! You completed Module 3. 🎉

Total module time: 2-3 hours
Next: Module 4 - Ollama