Anki Generated Flashcards
Preface
Hello my amazing coders! I created a Python script that generates Anki Flashcards which you could find on my GitHub. If you were wondering about the bare minimums, downloading and running the script, you could click that link and look at the README.md. Honestly, the README also has some gems in there for learning about the nuances of the Anki API. But today, I’ll be sharing my personal thoughts on developing this script. This script leverages Python, third party APIs, and the Gemini API to generate flashcards. If you’re looking for a fun AI project to start as a student, hopefully, this brings inspiration for an example of how you could use GenAI APIs mixed with easier coding languages such as Python.
The Struggles
Overall, the project took a month and a half to code. I have a full time job as a Software Engineer so it can be draining to code for 8 hours just to code more. I think if I was to dedicate all my time towards this and didn’t have a full time job (e.g. I was a student), I would be able to write this all up in around 2-3 weeks. That’s around half the time it took me if you wanted to build something like this! The hardest part about self explored projects is that there’s barely any documentation for the API you’re using or the even smaller third party libraries you’re trying to install to make the implementation work.
In particular, Anki is an open source project and open source projects especially bigger ones get a good amount of contributors but nowhere near the amount to have a product that is suitable for a developer. We tend to get into the nitty gritty and finding how to implement a barebones approach won’t even show up on Stack Overflow.
Notable Functions I Created
All these functions can be found within the GitHub repo, but I thought I’d highlight the ones that were interesting to me.
Calling Gemini
The first one was calling Gemini. This seemed like a pretty simple function, you can pass system instructions which is also known as a system prompt. This prompt is done behind the scenes and is used to tailor the model’s response before taking into account the user prompt which would be identical to a user asking Gemini something.
def run_gemini(system_instruction:str, content:str):
response = client.models.generate_content(
model="gemini-2.0-flash",
config = types.GenerateContentConfig(system_instruction=system_instruction),
contents=content
)
return response.text
Generating Leftover Flashcards
The main reason why running Gemini was so interesting is because the free version (what I was using) had limitations. For example at the time of writing, you could only make 15 requests to Gemini within a minute. This meant that I couldn’t just do a simple for loop for each flashcard I wanted to create since API requests can give a response and iterate in 1 second or less. My initial solution to this was to pack as much information as I could in one request, but that ended up to prove challenging because Gemini also has a max character limit for the response! I found that if I had a really long list of words I wanted to create flashcards for, the output generated would be cut off. So to alleviate that, I create logic to read the response and find where in the word list I got cut off if at all. Fun fact, I didn’t account for if the list was too short and my code crashed because I iterated over an empty list.
def process_flashcards_and_leftovers():
"""
Cleans up the potentially broken json object in generated_flashcard_metadata.txt and returns a proper json object. Additionally, if the json object is broken, this function returns what words still need to be printed out.
"""
file_name = "generated_flashcard_metadata.txt"
partial_flashcards = []
try:
with open(file_name, 'r', encoding='utf-8') as file:
file_content = file.read()
match = re.search(r'\[.*', file_content, re.DOTALL)
json_match = re.search(r'\[.*\]', file_content, re.DOTALL)
if json_match:
print("JSON object is complete!")
complete_json_obj = json_match.group(0)
partial_flashcards = create_json_object(complete_json_obj)
return partial_flashcards, []
elif match:
# Take an incomplete json object and make it complete.
incomplete_json_obj = match.group(0)
last_brace_index = incomplete_json_obj.rfind('},')
proper_json_obj = incomplete_json_obj[:last_brace_index +1] + "]"
print(f"Extracted JSON string (regex): {proper_json_obj}")
partial_flashcards = create_json_object(proper_json_obj)
except Exception as e :
print(f"Error: The file '{file_name}' was not found.")
Retrospective on logic
Anyways, this logic seems somewhat convoluted, and I wonder if there’s a better way to iterate so that I don’t have to do some leftover logic since I send all the words in for the request. A smarter thing I’m guessing would be to send a subset of words from the list to ensure that we get full responses back, but I think then the question would become “How many words can we fit to be under the 15 request limit.
Post Thoughts
I hope that this script helps people out. It was quite confusing to understand how Anki worked on the backend, but I’m happy I don’t have to do it manually anymore! From my first list of 100 words, I created 400 words since I wanted to auto generate conjugations for verbs. Overall the project seemed a bit difficult but was fun for a month and a half sprint. Let me know if you have any questions!

Leave a Reply