All articles
Build Guides

Build a Study Flashcard Generator That Turns Lecture Notes into Anki Decks with Groq's Free LLM

FDE Coach EditorialJuly 16, 202611 min read

What We're Building

We're building a single Python script that consumes a raw lecture PDF (or Markdown file), intelligently chunks the text, and fires each chunk at Groq's free-tier Llama-3.1-70B model to extract question-answer pairs. The output is a valid Anki CSV file you can import in two clicks.

Feature list

  • PDF and Markdown ingestion – drag-and-drop a lecture slide deck or exported Obsidian notes.
  • Semantic chunking – splits text on logical boundaries (headings, paragraphs) to avoid butchering a concept mid-sentence.
  • High-signal Q&A generation – Llama-3.1-70B is prompted to produce conceptual, recall-oriented flashcards, not trivial definitions.
  • Anki-ready CSV output – columns are Front, Back, and optional Tags. No manual reformatting.
  • Free-tier rate limiting – built-in retry logic respects Groq's RPM limits so you don't get 429'd.

If you've ever spent three hours manually typing flashcards from a 60-slide deck, this script will feel like cheating. If you've already built a local RAG chatbot over your PDFs, think of this as the structured extraction cousin.

Architecture Overview

The pipeline is linear: ingest → chunk → prompt → parse → output. No vector database, no orchestration framework. Just Python and an HTTP call.

Why Groq's free tier? Llama-3.1-70B via Groq runs at ~250 tokens/second on the free tier. That's fast enough to process a 50-chunk document in under a minute, and the model is strong enough to follow a structured output format without a constrained grammar. No credit card required.

Prerequisites (All Free Tier)

You need three things, all zero-cost:

ToolPurposeSetup Link
Python 3.10+Runtimepython.org/downloads
Groq API keyFree LLM accessconsole.groq.com/keys
Anki (optional)Flashcard reviewapps.ankiweb.net

Create a Groq account, generate an API key, and export it:

export GROQ_API_KEY="gsk_your_key_here"

No GPU, no Docker, no cloud billing. This runs on a Raspberry Pi if you're patient.

Step 1: Project Setup and Dependencies

Create a project directory and a virtual environment:

mkdir flashcard-generator && cd flashcard-generator
python -m venv venv && source venv/bin/activate  # Windows: venv\Scripts\activate

Install the only three dependencies:

pip install groq pypdf2 tiktoken
  • groq – official Python SDK for Groq's API.
  • pypdf2 – extracts text from PDFs. No OCR, so scanned handwritten notes won't work.
  • tiktoken – OpenAI's tokenizer. We use it to count tokens before sending to the model, because Llama-3.1's context window is 128K tokens but we want each chunk small enough to produce focused flashcards.

Create a file called generate.py. All code from here on goes into that single file.

Step 2: Ingesting and Chunking Notes

First, the ingestion layer. We'll handle PDF and Markdown with a single dispatcher.

import sys
from pathlib import Path
from PyPDF2 import PdfReader

def extract_text(file_path: str) -> str:
    path = Path(file_path)
    if path.suffix.lower() == '.pdf':
        reader = PdfReader(file_path)
        return "\n".join(page.extract_text() or "" for page in reader.pages)
    elif path.suffix.lower() in ('.md', '.txt'):
        return path.read_text(encoding='utf-8')
    else:
        raise ValueError(f"Unsupported file type: {path.suffix}")

Now chunking. Naive fixed-size chunking cuts paragraphs in half and produces garbage flashcards. Instead, we split on double-newlines (paragraph boundaries) and merge small paragraphs until we hit a target token count.

import tiktoken

def chunk_text(text: str, target_tokens: int = 600, overlap_tokens: int = 100) -> list[str]:
    enc = tiktoken.get_encoding("cl100k_base")  # Good enough for Llama-3.1 token estimation
    paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
    chunks = []
    current_chunk = ""
    current_tokens = 0

    for para in paragraphs:
        para_tokens = len(enc.encode(para))
        if current_tokens + para_tokens > target_tokens and current_chunk:
            chunks.append(current_chunk.strip())
            # Overlap: carry last paragraph forward
            overlap_text = current_chunk.split("\n\n")[-1] if "\n\n" in current_chunk else ""
            current_chunk = overlap_text + "\n\n" + para if overlap_text else para
            current_tokens = len(enc.encode(current_chunk))
        else:
            current_chunk = current_chunk + "\n\n" + para if current_chunk else para
            current_tokens += para_tokens

    if current_chunk.strip():
        chunks.append(current_chunk.strip())
    return chunks

The overlap ensures a concept that straddles a chunk boundary still gets captured. Target 600 tokens per chunk: small enough for focused flashcards, large enough for context.

Step 3: Crafting the LLM Prompt for Anki CSV

The prompt is the intellectual property of this build. We need the model to output a specific format every time without hallucinating extra commentary.

SYSTEM_PROMPT = """You are an expert study coach. Given a chunk of lecture notes, generate 3-6 high-quality Anki flashcards.

Rules:
- Each flashcard must test a single, atomic concept.
- Front: a question or cloze-style prompt. Back: a concise, accurate answer.
- Prefer conceptual understanding questions over rote memorization.
- If the chunk contains a definition, generate a recall question.
- If it contains a process, generate a "what happens next" or "why" question.
- Output ONLY a valid CSV with columns: Front, Back, Tags
- Do NOT include a header row.
- Do NOT wrap the CSV in markdown fences or any other text.
- Escape any internal double quotes by doubling them (CSV standard).

Example output format:
"What is the primary function of mitochondria?","ATP production through oxidative phosphorylation","biology::cell"
"Why does water have a high specific heat capacity?","Hydrogen bonds between water molecules require significant energy to break, absorbing heat without large temperature changes","chemistry::thermodynamics"
"""

The prompt enforces a strict output contract. No JSON, no markdown fences, just raw CSV lines. This is critical for the parsing step. If you've worked on DSL-driven LLM reliability, you'll recognize this as a lightweight output grammar enforced through prompting alone.

Step 4: Calling Groq's Free Llama-3.1-70B

Now the API call. We'll add retry logic for rate limits and a token budget check.

import os
import time
from groq import Groq

client = Groq(api_key=os.environ["GROQ_API_KEY"])

def generate_flashcards(chunk: str, model: str = "llama-3.1-70b-versatile") -> str:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"Generate flashcards from these lecture notes:\n\n{chunk}"}
    ]

    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model=model,
                messages=messages,
                temperature=0.3,  # Low temp for consistent formatting
                max_tokens=1024,
            )
            return response.choices[0].message.content.strip()
        except Exception as e:
            if "rate_limit" in str(e).lower() and attempt < max_retries - 1:
                wait = 10 * (attempt + 1)
                print(f"Rate limited. Waiting {wait}s...")
                time.sleep(wait)
            else:
                raise
    return ""

Why llama-3.1-70b-versatile? On Groq's free tier, this model offers the best balance of instruction-following and speed. The 8B variant is faster but frequently breaks the CSV format. The 70B follows the output contract reliably.

Step 5: Assembling and Running the Pipeline

Wire everything together with a main function that handles the full file-to-CSV pipeline.

import csv
import io

def parse_csv_response(response: str) -> list[dict]:
    """Parse the model's CSV output into a list of dicts."""
    cards = []
    reader = csv.reader(io.StringIO(response))
    for row in reader:
        if len(row) >= 2:
            cards.append({
                "Front": row[0].strip(),
                "Back": row[1].strip(),
                "Tags": row[2].strip() if len(row) > 2 else ""
            })
    return cards

def write_anki_csv(cards: list[dict], output_path: str):
    with open(output_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        for card in cards:
            writer.writerow([card["Front"], card["Back"], card["Tags"]])

def main():
    if len(sys.argv) < 2:
        print("Usage: python generate.py <lecture.pdf|notes.md>")
        sys.exit(1)

    file_path = sys.argv[1]
    print(f"Extracting text from {file_path}...")
    text = extract_text(file_path)

    print(f"Chunking {len(text)} characters...")
    chunks = chunk_text(text)
    print(f"Created {len(chunks)} chunks.")

    all_cards = []
    for i, chunk in enumerate(chunks):
        print(f"Processing chunk {i+1}/{len(chunks)}...")
        response = generate_flashcards(chunk)
        cards = parse_csv_response(response)
        print(f"  Generated {len(cards)} cards.")
        all_cards.extend(cards)

    output_path = Path(file_path).stem + "_flashcards.csv"
    write_anki_csv(all_cards, output_path)
    print(f"Done! {len(all_cards)} flashcards written to {output_path}")

if __name__ == "__main__":
    main()

Run it:

python generate.py lecture_slides.pdf

You'll see progress per chunk. A 30-page slide deck (~15 chunks) finishes in about 45 seconds on the free tier.

How to Import into Anki

  1. Open Anki, click File → Import.
  2. Select your *_flashcards.csv file.
  3. Set Field separator to Comma.
  4. Check Allow HTML in fields (preserves any formatting the model adds).
  5. Map columns: Field 1 → Front, Field 2 → Back, Field 3 → Tags.
  6. Choose a deck and click Import.

That's it. Your lecture is now a spaced-repetition deck.

Sensible Extensions

Once the basic pipeline works, here's where to push it:

  • Multi-file batch mode: Point the script at a directory of PDFs and generate one consolidated deck. Trivial to add with pathlib.glob.
  • Image-based notes: Swap pypdf2 for a vision model. Groq doesn't offer vision yet, but you could route images to Gemini's free tier. This is essentially the reverse of the multi-agent research assistant pattern—specialized models for specialized tasks.
  • Cloze deletion cards: Modify the prompt to output Anki cloze format ({{c1::answer}}). Requires a different CSV structure but the chunking pipeline stays identical.
  • Confidence scoring: Ask the model to include a 1-5 confidence score per card in a fourth column. Filter out low-confidence cards before import.
  • Incremental updates: Hash each chunk and store a manifest. On re-run, only process new or modified chunks. Saves API calls when you tweak the prompt.

If you're thinking about productionizing this into a study tool that multiple people use, you'll want to think about the post-sale handoff between engineering and product patterns—even for internal tools, the interface between the pipeline and the user matters.

Common Pitfalls and Debugging Tactics

Model outputs markdown fences around the CSV.

  • Fix: Strengthen the system prompt. Add "If you output markdown fences, the user's import will fail. Output raw CSV only." If it persists, add a post-processing step that strips lines starting with triple backticks.

Empty or garbled PDF text extraction.

  • pypdf2 can't handle scanned PDFs. If your lecture slides are image-based, you need OCR. Tesseract is free but adds complexity. For now, test with text-based PDFs or export your slides to Markdown first.

Rate limit 429 errors despite retry logic.

  • Groq's free tier allows ~30 requests per minute. If you're processing 50+ chunks, add a time.sleep(2) between chunks. Better to run it over coffee than to debug retry storms.

Model generates cards that are too trivial.

  • Adjust the prompt: add "Avoid 'what is X' questions where X is defined in a single sentence. Prefer 'why does X matter' or 'how does X relate to Y'." The prompt is your curriculum designer—iterate on it.

CSV parsing fails because the model added a header row.

  • The prompt explicitly says "Do NOT include a header row," but models sometimes ignore this. Add a filter: if row[0].lower().strip('"') == 'front': continue.

FAQ

Q: Does this work with languages other than English? A: Yes. Llama-3.1-70B is multilingual. Change the system prompt to your target language and the flashcards will follow. The CSV format is language-agnostic.

Q: How much does this cost? A: Zero. Groq's free tier is genuinely free with no credit card. The rate limits are generous enough for personal study use. If you process 500+ pages daily, you might hit limits, but that's an edge case.

Q: Can I use this for non-lecture content like research papers? A: Absolutely. The chunking strategy works on any prose-dense document. For papers, consider adding a prompt instruction to generate "methods critique" and "future work implication" style cards.

Q: What if my lecture notes are already in Anki format? A: Then you're ahead of the game. This tool is for the 90% of notes that are raw text. If you're looking for a different kind of knowledge extraction, the Slack digest bot with Groq and Whisper demonstrates a similar free-tier pipeline for conversational data.

Q: The flashcards feel shallow. How do I get deeper questions? A: Prompt engineering is the lever. Add examples of your ideal flashcard in the system prompt (few-shot). The model will mimic the depth and style. Treat the prompt like a spec document: the more precise the examples, the more precise the output.

Q: Can I deploy this as a web app? A: Yes, but you'll need to handle Groq API key security on the backend. The core pipeline is stateless and easily wrapped in a Flask or FastAPI endpoint. If you're thinking about shipping internal tools like this regularly, understanding what an FDE actually ships in a high-intensity week gives you a realistic calibration of scope versus polish.

#education#flashcards#anki#groq

Want to build like a Forward Deployed Engineer?

FDE Coach is a cohort-based program in frontend, backend, AWS, and AI. Build real products and get referred to 200+ hiring partners.

Explore the program

More build guides

August 15 · 0d left
Enroll Now