Upload folder using huggingface_hub
Browse files- NOTES.md +367 -0
- README.md +1 -0
- ankigen_core/agents/templates/generators.j2 +2 -2
- ankigen_core/agents/templates/prompts.j2 +3 -3
- ankigen_core/card_generator.py +20 -24
- ankigen_core/exporters.py +2 -18
- ankigen_core/models.py +0 -1
- ankigen_core/ui_logic.py +0 -4
- app.py +1 -7
- uv.lock +0 -0
NOTES.md
ADDED
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
by [Luise](https://www.lesswrong.com/users/luise?from=post_header)
|
2 |
+
|
3 |
+
8th Jul 2025
|
4 |
+
|
5 |
+
32 min read
|
6 |
+
|
7 |
+
[46](https://www.lesswrong.com/posts/7Q7DPSk4iGFJd8DRk/#comments)# 132
|
8 |
+
|
9 |
+
I can't count how many times I've heard variations on "I used Anki too for a while, but I got out of the habit." No one ever sticks with Anki. In my opinion, this is because no one knows how to use it correctly. In this guide, I will lay out my method of circumventing the canonical Anki death spiral, plus much advice for avoiding memorization mistakes, increasing retention, and more, based on my five years' experience using Anki.
|
10 |
+
|
11 |
+
This guide comes in four parts, with the most important stuff in Parts I & II and more advanced tips in Parts III & IV. **If you only have limited time/interest, only read Part I; it's most of the value of this guide!**
|
12 |
+
|
13 |
+
Roadmap to the Guide
|
14 |
+
|
15 |
+
This guide's structure is inspired by how someone new to Anki might want to read it:
|
16 |
+
|
17 |
+
**Part I** contains day 1 advice for using Anki in a way that prevents the Anki death spiral. (But it is not a general intro to Anki; I leave that to other resources.)
|
18 |
+
|
19 |
+
**Part II** aims to save you from common mistakes, especially ones that waste effort without increasing your recall, for day 3 of using Anki.
|
20 |
+
|
21 |
+
**Part III** dives into more detailed card design issues, including for difficult and interrelated "information thickets", for week 3 of using Anki.
|
22 |
+
|
23 |
+
**Part IV** contains even more & obscure pro tips for Anki, for week 5 of using Anki.
|
24 |
+
|
25 |
+
But Anki users of all experience levels have found this guide useful by skimming & skipping between the parts that were new/interesting to them!
|
26 |
+
|
27 |
+
# My Most Important Advice in Four Bullets
|
28 |
+
|
29 |
+
1. **20 cards a day —** Having too many cards and staggering review buildups is the main reason why no one ever sticks with Anki. Setting your review count to 20 daily (in deck settings) is *the single most important thing* you can do to stick with Anki long-term.
|
30 |
+
2. **Atomic cards** — Studying long cards is *gruelling*. Studying cards with 1-5 words on the back is *fun*. Make every card *as short as humanly possible* (even 1 word!) by breaking things up into many cards.
|
31 |
+
3. **No to-be-learned information in the prompt** — You're only memorizing the *back* of cards. Don't put any to-be-learned information on the *front*. If recalling the back of a card is not useful without knowing crucial info given in the prompt, the card is not useful.
|
32 |
+
4. **Bland prompts —** Make prompts as absolutely non-descript and standardized as possible. Otherwise, you will learn a dumb pattern like *prompt with fancy words -> recall X,* or *funny typesetting -> recall X,* and not be able to recall X outside Anki at all.
|
33 |
+
|
34 |
+
# I. Anki Day 1: Or, No One Ever Sticks With Anki
|
35 |
+
|
36 |
+
I cannot emphasize enough how often I've met people who want to use Anki in principle, but don't seem to be able to create a long-term habit. (This was also me for most of my five years on-and-off using it.) No one ever sticks with Anki. I believe I know the two reasons for this:
|
37 |
+
|
38 |
+
1. Too many cards
|
39 |
+
2. Too long cards
|
40 |
+
|
41 |
+
Solving these two problems is probably 90% of the value of this guide. Let's get into it.
|
42 |
+
|
43 |
+
## Too many cards
|
44 |
+
|
45 |
+
People usually enthusiastically start by revising like 50+ new cards in a day. Then the enthusiasm wears off, and you skip a day. Next day, there are like 200 revisions due. Then you are discouraged, skip that day as well, along with the next seven, until you have about a million cards due and see no way of ever getting through them. This is the canonical Anki death spiral.
|
46 |
+
|
47 |
+
Instead, set a daily limit on card reviews of 10 or 20. **This is perhaps my most important piece of advice.** Really, do it. Yes, you will learn fewer cards. But the alternative (in my experience) is not using Anki long-term **at all**. If you set your daily review limit to 20, even if you skip a day (or ten), Anki will only show you 20 cards due after. This prevents procrastination spirals. You are always capable of doing 20 reviews after all, no matter the day.
|
48 |
+
|
49 |
+
If you truly feel like doing more on a given day, you can always increase your limit for that day by another 20 (long-tap the deck > Custom Study > Modify Today's Review Card Limit). You can do this as often as you like, which, in any case, is more fun than doing one monster heap of reviews. (But doing it too much on one day is probably not sensible, as you won't be able to review all those new cards within your normal daily limit going forward.)
|
50 |
+
|
51 |
+
You can set your daily review limit by long-tapping the deck > Deck options > "Daily Limits" at the top. If you set the daily review limit to 20, you should set the daily limit of *new* cards to 2, since otherwise your reviews won't keep pace with your adding new cards. If you set the review limit to 10, you should set the new card limit to 1, etc.<sup><span><span></span><a href="https://www.lesswrong.com/posts/7Q7DPSk4iGFJd8DRk/#fnt0x0z7gmru" class="">[1]</a></span></sup> This assumes that you have one deck; otherwise, the limits would have to be even lower per deck. (As an aside, I think it's totally fine to have basically one deck for everything. I have such a deck, containing history facts alongside math alongside friends' birthdays, and this is not a problem. The only separate decks I've ever needed were for language learning and university courses.)
|
52 |
+
|
53 |
+
I really think a limit of 20 is plenty to start with. I'm a pretty disciplined person, and I struggle with anything over 20 on some days. Also, if 20 goes well for a month or so, and you really want to do more, you can always increase the limit then.
|
54 |
+
|
55 |
+
And that's it. This solves half the problem of the canonical Anki death spiral! Now for the other half.
|
56 |
+
|
57 |
+
## Too long cards
|
58 |
+
|
59 |
+
When I started using Anki, I would often take an entire topic/text of interest, convert it into bullet points, and dump them all onto *one Anki card*. The card would then expect me to recite the whole thing in one big laundry list of information. This is super cognitively demanding and unfun. On the other end of the spectrum, now, I often have cards that have literally one word on the back, e.g., *"pronunciation τ" -> "tau"*. And guess what? Revising these cards is actively fun! I would happily do fifty of these in my free time, whereas nothing could get me to do one of the "laundry list" variety again.
|
60 |
+
|
61 |
+
The lesson here, if you want to build a long-term Anki habit, is simply to **make cards short**. A typical card should have no more than 9 words. (If you write in sentences, and not abbreviated bullets like me, maybe a few more). This requires breaking information down into multiple cards. As a rule, if it is possible to break down a card into two shorter cards, you should do so. For example, consider this card:
|
62 |
+
|
63 |
+

|
64 |
+
|
65 |
+
In fact, there is no reason to learn the pronunciation of τ and what it means in one card. And shorter cards are more fun, therefore:
|
66 |
+
|
67 |
+

|
68 |
+
|
69 |
+

|
70 |
+
|
71 |
+
The ideal is to have "atomic" cards, i.e., cards that are as small as possible and whose meaning could not be preserved when splitting further. Of course, this is practically inconvenient because it takes too much time, so it is only an ideal.
|
72 |
+
|
73 |
+
Enforcing extreme brevity has the additional benefit of uncovering when I don't actually understand something very precisely. If I don't understand it, I can't break it down. Ankifying routinely forces me to reread the source or to google, which is a pretty valuable impulse in and of itself.
|
74 |
+
|
75 |
+
### How to keep cards short — Handles
|
76 |
+
|
77 |
+
Alas, breaking down cards is often harder than in the example above. E.g., you may start with a long text of interrelated information and somehow want to "ankify" this to remember it. For such rich information "thickets", I must say it takes some extra time to break it down into sensible Anki cards, but it is 100% possible! The key is not to try to squeeze everything into one card but to allow Anki cards to *refer to one another* for more information. This way, you can split up information without making it meaningless taken out of context.
|
78 |
+
|
79 |
+
E.g., say you want to remember what Sir Isaac Newton's key scientific contributions were. You could allow your card to refer to another card as follows:
|
80 |
+
|
81 |
+

|
82 |
+
|
83 |
+

|
84 |
+
|
85 |
+
It would be a mistake to make the first card longer by detailing what the laws of motion *are*. Instead, the little '>' indicates a reference to another card, which has more information on the laws of motion. This way, the present card can remain short. You would now not have to recall all the laws of motion when revising it, but the reference ensures that you *could*. (> is just a convention I like to use; you could write this any way you like.)
|
86 |
+
|
87 |
+
I call these little '>' referring to other cards 'handles' and I use them all the time to keep cards short. If something doesn't have to be detailed on your card, but has to be remembered as *related*, you should probably use a handle. Handles don't even necessarily have to contain any information in and of themselves, as "laws of motion" does; they can also purely be for splitting things up. E.g., if your card on Isaac Newton is getting too long, you could split out any logical subset of it and give it some handle name (that needn't contain information in and of itself). For example:
|
88 |
+
|
89 |
+

|
90 |
+
|
91 |
+

|
92 |
+
|
93 |
+
Here, the handle ">astronomy" didn't really add any information but it was useful simply for splitting out a logical subset of information.
|
94 |
+
|
95 |
+
It's also totally okay to make cards that contain only handles. These are often necessary for splitting topics into manageable chunks, and they ensure that you remember how these chunks fit together.
|
96 |
+
|
97 |
+
### How to keep cards short — Levels
|
98 |
+
|
99 |
+
Another trick I like for keeping cards short is to have multiple cards adding increasing levels of detail on the same concept. Let's say you want to remember stuff about Galileo Galilei. Instead of squeezing everything into one card, you could have a "level 1" card and a "level 2" card like this:
|
100 |
+
|
101 |
+

|
102 |
+
|
103 |
+

|
104 |
+
|
105 |
+
Level 2 adds further detail while still being short of itself. To get even closer to the "atomic card" ideal, one could also separate out the date from the level 1 card and make a "Galileo date -> around 1600" card (and various other such improvements). But I think these two cards are pretty good.
|
106 |
+
|
107 |
+
In my experience, it pays off to make an additional third card simply to ensure that you recall you have two cards on Galileo.
|
108 |
+
|
109 |
+

|
110 |
+
|
111 |
+
(Note that these Galileo cards, just like any other cards in this post, are only examples of what someone might want to memorize. If it's not important to you when Galileo lived, then by no means ankify it. I think you should have a pretty high bar for what's important enough to ankify.)
|
112 |
+
|
113 |
+
At the absolute maximum, a card should have 3 logical bullets with a total of 18 words (in my opinion). Such a length is never necessary, but sometimes undeniably convenient.
|
114 |
+
|
115 |
+
## In 6 bullets
|
116 |
+
|
117 |
+
- Set a daily review limit of **10 or 20 cards**.
|
118 |
+
- If you feel like doing more on a given day, **increase** **your limit for that day** by another 10/20 as often as you like.
|
119 |
+
- **Max 9 words** for most cards. At absolute max, 3 logical bullets/18 words.
|
120 |
+
- If it's possible to break a card up further, **do it**.
|
121 |
+
- Use **\>handles** to split out details / logical subsets.
|
122 |
+
- Use **level 1/2/3 cards** to learn increasing detail on the same concept.
|
123 |
+
|
124 |
+
## End of the most important part of the guide
|
125 |
+
|
126 |
+
And that's it! This is my simple recipe for sticking with Anki when 90%<sup><span><span></span><a href="https://www.lesswrong.com/posts/7Q7DPSk4iGFJd8DRk/#fnnk2lcy63ke" class="">[2]</a></span></sup> of people don't: Keep few cards and keep them short. Try it and it should become apparent very quickly how much more fun Anki is this way. And if it's fun, you're much more likely to stick with it. This point is essentially most of the value in reading this guide. Everything from here is less important, so if you're only going to take away one or two things from this guide, let it be from Part I, and maybe stop reading here.
|
127 |
+
|
128 |
+
# II. Anki Day 3: Or, Common Mistakes
|
129 |
+
|
130 |
+
## Moderation
|
131 |
+
|
132 |
+
The promise of retaining any information indefinitely, guaranteed, may lead to wanting to ankify pretty much all interesting information one comes across, all of the time. At least this was what I did when I first started using Anki. But this is not a good idea. Firstly, you simply don't have space for it. If you have a daily limit of 20 cards (you should), you can do max. 2 new cards a day.<sup><span><span></span><a href="https://www.lesswrong.com/posts/7Q7DPSk4iGFJd8DRk/#fn01ad5bqgckcn" class="">[3]</a></span></sup> So maybe think twice before ankifying, e.g., the length of a whale intestine (unless this scores very high on your importance metric, in which case, fair play). More fundamentally, realize that putting cards into Anki is not "free". In fact, committing to learning even a single card is committing to reviewing that card, and relearning it when forgotten, roughly indefinitely, which is quite a bit of work. It makes sense to prioritize important information because of this. My recommendation would be to start by only ankifying what you feel like you absolutely *have to* know. And while revising, to ruthlessly suspend any cards that don't feel like they pay enough rent. If it turns out there is space for more cards, you can always lower your bar for ankification later!
|
133 |
+
|
134 |
+
**In 1 bullet:**
|
135 |
+
|
136 |
+
- Anki cards are a lot of work; start by ankifying **what you absolutely have to know**.
|
137 |
+
|
138 |
+
## Three big memorization mistakes
|
139 |
+
|
140 |
+
Once using Anki sustainably, I still made three big mistakes that sabotaged my ability to actually remember learned cards *outside* of Anki revision. This often meant that all the work of revision was for naught, since what's the point in memorizing information if you can't recall it when you need it? I think making these mistakes is essentially the default/obvious way to use Anki. So I hope this section saves someone all that wasted effort!
|
141 |
+
|
142 |
+
### Mistake 1: Too specific prompts
|
143 |
+
|
144 |
+
I think prompts should ideally be as close as possible to the "real life" situation (outside of Anki) where you will want to recall the card. E.g., a bad prompt would be "Length of a whale intestine". Instead, ask yourself *why do you want to know this information? When will you practically want to recall this piece of information?* Maybe you like to tell your friends animal fun facts. In that case, a better prompt would be "animal fun facts". Maybe you are a gastroenterologist(?), and the length of a whale intestine is a constant that comes up in your work all the time(??). Okay, I'm struggling to come up with examples here, but the point is to make your prompt closely model the real situation where you will want to notice, *don't I have a related Anki card here?* Otherwise, when the time is ripe to impress your friend with an animal fun fact, you might not even recall that you have a related Anki card. Usually, the default/obvious prompt you may want to use is actually too specific. The real-life prompt will often be vague. E.g., not "length of whale intestine" but "animal fun facts".
|
145 |
+
|
146 |
+
In this context, I should also mention one tempting method of breaking down cards that I think can be a bad idea. Recall the Galileo "level 1" card from earlier:
|
147 |
+
|
148 |
+

|
149 |
+
|
150 |
+
Why not break this down into super short cards like this?!
|
151 |
+
|
152 |
+

|
153 |
+
|
154 |
+
Certainly, these cards are more fun to revise since they are so short. However, ask yourself when you might want to recall this information in "real life". This, of course, is individual, but perhaps a common answer is "when I hear/read the name somewhere, I want to quickly recall what his basic deal was". *If* this is one's answer, one should probably memorize something like the level 1 card. Otherwise, it would be harder to scratch together all the related information in memory, and one might forget some related cards altogether. So if the expected "real-life prompt" demands a longer card, consider making one. Still, it's a tradeoff, and how to strike it is probably down to individual preference for detailed cases.
|
155 |
+
|
156 |
+
In any event, it can't hurt to do *both* and, in effect, have multiple cards with overlapping information. This also applies when there are several possible real-life prompts. Redundant cards with different prompts are always a good idea and prevent all sorts of memorization pitfalls, as I will describe further in the next two sections.
|
157 |
+
|
158 |
+
**In 2 bullets:**
|
159 |
+
|
160 |
+
- Make prompts closely **correspond to the real-life situation** where you want to recall the card.
|
161 |
+
- Make multiple cards if you have **multiple prompt formulations** (redundancy is good).
|
162 |
+
|
163 |
+
### Mistake 2: Putting to-be-learned information in the prompt
|
164 |
+
|
165 |
+
Let's say you learn that in 1950 in Germany, 24% of the workforce was employed in agriculture and you want to ankify this. You may attempt it like so:
|
166 |
+
|
167 |
+

|
168 |
+
|
169 |
+
(Let's ignore for now that this is probably not close to a "real-life prompt".)
|
170 |
+
|
171 |
+
You revise the card and easily recall the answer. So now you should have memorized this fact, right?
|
172 |
+
|
173 |
+
Unfortunately, no. Imagine something in "real life" prompts you to recall this fact. E.g., you're reading about population movement to cities in the 1920s. You think the percentage of people working in agriculture would be a relevant fact in this context, and you recall having a card in your Anki deck about this. The answer was 24%, but what precise year was this about again? As far as you remember, it could equally well be 1920 or 1980. And the number could relate to Germany, or equally well to Britain, or even the world. And was that percent of the workforce or of the whole population again?
|
174 |
+
|
175 |
+
As you can see, memorizing just the number (24%) without what that number actually means is pointless here. This is a way to waste your time revising cards without actually gaining knowledge. The problem is that Anki cards only make you memorize the *answer*, i.e., the back of the card. Therefore, putting crucial information in the *prompt* is a mistake, and leads to recalling disconnected fragments like "24%" that have no meaning without your Anki prompt. Therefore, all to-be-learned information needs to go on the *back* of (one or more) Anki cards.
|
176 |
+
|
177 |
+
For example, you could reformulate your agriculture card like so:
|
178 |
+
|
179 |
+

|
180 |
+
|
181 |
+
This also naturally aligns with a less specific "real-life prompt". It's more likely that you'll ask yourself, "What do I know about the history of agriculture?" than "What do I know about 1950s agriculture in Germany?".
|
182 |
+
|
183 |
+
Unfortunately, sometimes real-life prompts don't work so well without including to-be-learned information. E.g., say you want to memorize something like this:
|
184 |
+
|
185 |
+

|
186 |
+
|
187 |
+
Recalling "Victorian period" is useless without remembering what century this refers to (was it the 18th or the 19th?), so in principle, the century number should go on the back of the card too.
|
188 |
+
|
189 |
+

|
190 |
+
|
191 |
+
But this is not a real-life prompt—in most situations, you'll probably want to recall a specific period, not a general list of periods. Also, this card feels harder because here you additionally have to recall what periods are *on the list* (though it's only one here). Luckily, there are three alternatives to just putting everything on the back of the card: Making the card reversible, making multiple similar cards, and using handles.
|
192 |
+
|
193 |
+
Firstly, the original "Victorian period" card can easily be made reversible, so the reverse looks like this:
|
194 |
+
|
195 |
+

|
196 |
+
|
197 |
+
This solves the problem of having the century in the prompt, because it is now also on the *back* of the reverse card. I would recommend *always* making a card reversible if that makes any semantic sense at all. This is because foreseeing the three memorization issues discussed in this section is often tricky, and redundancy is a good failsafe.
|
198 |
+
|
199 |
+
Secondly, one could also make several more similar cards, each asking for the period name of a *different century*.
|
200 |
+
|
201 |
+

|
202 |
+
|
203 |
+

|
204 |
+
|
205 |
+
This removes the problem of only remembering "Victorian period" and going "Which century was this again?" (since the Anki cards require you to remember this too). You can even do a lazy version where, for the 17th/18th century cards, the back just says "idk" or "this card is a red herring".
|
206 |
+
|
207 |
+
Thirdly, it's fine to put to-be-learned information in the prompt if this is part of a >*handle*, since then it is also on the *back* of another card. For illustration:
|
208 |
+
|
209 |
+

|
210 |
+
|
211 |
+
To-be-learned information in the prompt ("1950 Germany").
|
212 |
+
|
213 |
+

|
214 |
+
|
215 |
+
"1950 Germany" is also a handle on the *back* of this card.
|
216 |
+
|
217 |
+
Such a structure can be convenient when you have so much information that you need to use handles for splitting anyway.
|
218 |
+
|
219 |
+
Again, it's a good idea to make redundant cards using several of these four approaches (putting all information on the back, reversible cards, multiple similar cards, handles). Especially when it makes any sense at all to make a card reversible, I would recommend always doing so for redundancy.
|
220 |
+
|
221 |
+
**In 4 bullets:**
|
222 |
+
|
223 |
+
- **Always make cards reversible** if this makes semantic sense.
|
224 |
+
- Or put **all to-be-learned information on the** ***back**.*
|
225 |
+
- Or make **several similar cards**, e.g., "18th century period", "19th century period", etc.
|
226 |
+
- Or make to-be-learned information in the prompt into a **>handle**.
|
227 |
+
- **Or do all**; redundancy is good.
|
228 |
+
|
229 |
+
### Mistake 3: Memory shortcuts
|
230 |
+
|
231 |
+
Ideally, when you revise an Anki card, you see a prompt, understand what it means (its semantics), and then recall the information that is logically related to that meaning. Unfortunately, our brains like to take shortcuts. So it may happen that you see a prompt, see a unique word in it, or an unusual symbol, or simply recognize its overall visual appearance; skip over the whole semantics part, and immediately recall the correct answer. This is not something you can turn off consciously. And it's a problem because in real life, you will want to recall the answer without seeing that unique word/unusual sign/visual appearance first.
|
232 |
+
|
233 |
+
The idea is to eliminate such memory shortcuts so that you actually have to read the semantics of the prompt in order to recall the answer. Ideally, a prompt wouldn't have one static syntactical shape at all, but be a different syntactical variation of the same semantic meaning on every revision. But since that would be a painful amount of work, it's more practical to eliminate memory shortcuts by making prompts *as bland and nondescript as possible:* Never use images or unusual symbols in the prompt if it can be helped. Don't use a different font, colors, or other formatting divergences. Don't use italics/bold/etc. if you don't usually do this. Don't use unusual or fancy words where standard ones will do. You get the idea.
|
234 |
+
|
235 |
+
For this reason, I also think "cloze deletion" cards are a bad idea: they offer too many structural and visual memory shortcuts.
|
236 |
+
|
237 |
+

|
238 |
+
|
239 |
+
Example of a cloze deletion Anki card (which I think is a bad idea)
|
240 |
+
|
241 |
+
One more note in this vein is to avoid using words in the prompt that also occur in the answer. I've empirically observed that these almost always become memory shortcuts/aids for me, but of course, they would not be given to me in "real life". E.g., consider this card:
|
242 |
+
|
243 |
+

|
244 |
+
|
245 |
+
Though the words economy/economies are not even used in the same sense here, I would reformulate the prompt, e.g., using the word "progress" instead.
|
246 |
+
|
247 |
+
Unfortunately, the brain works in tricky and mysterious ways, and sometimes constructs memory shortcuts even out of apparently completely bland prompts. It'll find an unremarkable word that nevertheless is used only once in your deck, or a formulation that you've used only once within a particular topical context, and that alone will suffice for a memory shortcut, or at least a memory aid. I've noticed this happening with as innocuous a formulation as "When did X happen", when standardly I had used "Date(s) of X".
|
248 |
+
|
249 |
+
My solution to this is basically to standardize the wording and structure of similar prompts as much as humanly possible. When all your cards asking for dates have exactly the same structure, your brain is going to have to actually parse semantics eventually. A few examples of standardizations I've made for illustration:
|
250 |
+
|
251 |
+
| "When did X start/end/happen?" | X **dates** |
|
252 |
+
| --- | --- |
|
253 |
+
| "What's the term/name of X?" | **T** X |
|
254 |
+
| "What person did/was/is known for X?" | **who** X |
|
255 |
+
| "What is the formal definition of X?" (for math stuff) | X **formally** |
|
256 |
+
| "What is the easiest way to understand X?" (for math) | X **intuitively** |
|
257 |
+
| "What are the advantages/disadvantages/ reasons/outcomes of X?" (usually for exam study) | often summarizable as: X **pros/cons** |
|
258 |
+
|
259 |
+
I also usually put any dates at the start of my prompt for consistency.
|
260 |
+
|
261 |
+
I recommend identifying types/shapes of prompts that occur often for you and standardizing those in a way you find intuitive. This is not a one-off task. Rather, I recommend doing this constantly, even if you are only creating two or three cards of a similar shape.
|
262 |
+
|
263 |
+
Anticipating and preventing memory shortcuts and other memorization mistakes is an imperfect and haphazard art. You won't always be able to tell what will go wrong in advance. Constant attention to whether you are actually recalling cards when you should in real life is crucial, and you should dynamically improve cards that don't quite seem to work.
|
264 |
+
|
265 |
+
**In 4 bullets:**
|
266 |
+
|
267 |
+
- Make prompts as **bland** as possible.
|
268 |
+
- Don't use words in the prompt **that also occur in the answer**.
|
269 |
+
- **Standardize recurring types/shapes** of prompts.
|
270 |
+
- Keep paying attention to whether you are actually recalling cards in real life & **keep improving** them.
|
271 |
+
|
272 |
+
## Aside: Pushback to my approach
|
273 |
+
|
274 |
+
Above, I have presented three mistakes that prevent you from actually recalling your Anki cards in real life. However, I should acknowledge some uncertainty in the generalizability of my approach to other people. I don't generally look over other people's shoulders (or into their brains) while they use Anki. Moreover, before publishing, I sent a version of this guide to Magdalena Wache, who has used Anki the longest and most prolifically out of everyone I know. (She's been described as the "Queen of Anki" more than once in my presence.) I will describe her reaction here briefly to give a sense of the way in which this guide is "opinionated" and to show a sensible example of diverging from it.
|
275 |
+
|
276 |
+
To my surprise, Magdalena told me that she doesn't struggle with the three memorization problems as much as I do. Her Anki philosophy is much more about making each card radically short. She rarely makes cards with more than a bullet on the back. So, for example, she would break down the Galileo cards discussed earlier into components of 1-3 words, while I rejected this approach since it doesn't correspond to the likely "real-life prompt". (In fact, Magdalena said she wouldn't memorize Galileo facts at all, but let's just imagine he was super important to all of us.)
|
277 |
+
|
278 |
+

|
279 |
+
|
280 |
+

|
281 |
+
|
282 |
+

|
283 |
+
|
284 |
+
Magdalena's short example cards
|
285 |
+
|
286 |
+

|
287 |
+
|
288 |
+
My card corresponding to the real-life prompt "Who was this guy again?".
|
289 |
+
|
290 |
+
My sense is also that Magdalena has fewer issues with to-be-learned information in the prompt. She said she would gladly use the example prompt "What happened while humans first left Africa?", whereas I would worry that in real life, I'd recall such a card and then go "Was this before, during, or after leaving Africa?" etc. (Magdalena did say she worries about this too, but to a lesser extent.)
|
291 |
+
|
292 |
+
This difference in approach is probably partially due to simple differences in how our brains work. And it's probably due to different tradeoffs being struck between card shortness and real-life recall of cards. Short cards make revising more fun of course, and allow you to revise more cards. But fast and reliable real-life recall is also desirable. It depends on what you want and how your brain works. You should feel free to diverge from the approach presented in this guide depending on what works best for you. I do want to nudge you to try my approach, though, since it's so non-obvious that it required five years of trial-and-error to take shape. (This is, after all, an opinionated guide to Anki.)
|
293 |
+
|
294 |
+
# III. Anki Week 3: Or, Pro Card Design
|
295 |
+
|
296 |
+
More detailed opinions on how to make good cards. I'll first talk about considerations specific to very short cards, two-bullet cards, and long cards, respectively. Then I'll dive more into how to break down tough "information thickets".
|
297 |
+
|
298 |
+
## Very short cards
|
299 |
+
|
300 |
+
First, I want to reemphasize that, if you can break down a card to have literally one word on the back, do it! This may feel pedantic, but try and see if studying doesn't become actively fun with such cards. E.g., why have a card like this
|
301 |
+
|
302 |
+

|
303 |
+
|
304 |
+
if you can have two cards like this
|
305 |
+
|
306 |
+

|
307 |
+
|
308 |
+

|
309 |
+
|
310 |
+
Extremely short cards are especially important if you need to recall a fact really quickly. Having multiple facts on the back of that card would just slow down your recall of the one you need at a given time. In fact, don't ask me why, but my recall time doesn't seem to grow linearly in the length of the back of the card. I seem to slow down more than 2x for a double card length. Ergo: Fast recall needs short cards. For facts that I'll want to recall in <1 sec, the card can generally not be short enough. Examples of such facts are what nano means and what period "Victorian" refers to, which would be super hindering to have to think about every time they come up.
|
311 |
+
|
312 |
+
As an aside on very short cards, in my experience, it is possible to push the daily card limit a bit if you have a large enough class of very short cards. I would make these into a separate "easy" deck, also with a daily limit of 10 or 20. Very short cards feel pretty fun to me, anyway. So when revising, I still have one main "hard" deck, and the easier deck feels sort of "free" to revise as well, which means I can revise more cards overall. (But this only works with very short cards.) Example decks I've had that worked like this are decks for vocabulary and for memorizing multiplication tables.
|
313 |
+
|
314 |
+
## Two-bullet cards
|
315 |
+
|
316 |
+
I generally try to write each bullet on an Anki card to be a logical unit so that it's easy to memorize as such. This is a rather minor point, but I wanted to mention an exception I find it useful to make. Often, I find that a two-bullet card, whose bullets aren't too long, is actually quicker to recall as one long bullet. Don't ask me why. So, for example, I have changed the following card in my deck
|
317 |
+
|
318 |
+

|
319 |
+
|
320 |
+
to look like this:
|
321 |
+
|
322 |
+

|
323 |
+
|
324 |
+
Try it out and see if it makes your recall faster, too. I don't tend to make many two-bullet cards anymore, at least.
|
325 |
+
|
326 |
+
## Long cards
|
327 |
+
|
328 |
+
Recall that my recommendation for the absolute maximum length of a card is 3 bullets with a total of 18 words. Now, this might just be me, but I find recalling cards of this length pretty hard and unfun. I find myself giving up and closing Anki most often when such a card comes up. I have therefore taken to "staging" the recall of such cards, to recall them bullet by bullet, and check the correct answer progressively as I go. I enable this simply by adding a lot of whitespace at the top of the back of the card, so that the first line will appear at the very bottom of my phone screen, and I can progressively scroll down and reveal more.
|
329 |
+
|
330 |
+

|
331 |
+
|
332 |
+
If you want to do the same, on mobile, you can make custom shortcuts to insert text or HTML on cards, so adding whitespace is very quick. I created a shortcut that appears as a "1" in the below screenshot and produces the following html: <div style="height: 630px;"></div>
|
333 |
+
|
334 |
+

|
335 |
+
|
336 |
+
This simply adds an empty block of 630 pixels, which is a bit less than the height of my phone. You can create shortcuts by pressing the "+" you can see on the bottom right in the screenshot.
|
337 |
+
|
338 |
+
On desktop, I used the app AutoHotkey (Windows) to make a custom keyboard shortcut. If you want to do this too, I recommend using ChatGPT to quickly write the shortcut script for you.
|
339 |
+
|
340 |
+
I'd only really recommend doing this for long cards with three bullets, since staging the recall of short cards in this way just makes your recall slower in my experience.
|
341 |
+
|
342 |
+
**In 4 bullets:**
|
343 |
+
|
344 |
+
- If you can break down cards to **1 word** on the back, do it!
|
345 |
+
- For a **<1 sec recall**, cards cannot be short enough.
|
346 |
+
- 2-bullet cards are usually **faster to recall as 1 bullet** (if not too long).
|
347 |
+
- Add whitespace to long 3-bullet cards to enable **recalling them bullet by bullet**.
|
348 |
+
|
349 |
+
## Ankifying information thickets
|
350 |
+
|
351 |
+
The hardest stuff to ankify is rich information "thickets", e.g., textbook sections, with many interrelations and no obvious linear breakdown. The first thing to say here is that you shouldn't! You probably only care about 1-2 facts in the whole section, so be ruthless in cutting out the rest. (You don't have time to memorize all that anyway.) The main situation where you might want to ankify information thickets is probably an exam of some sort. I've used Anki for this extensively, so I'm putting my learnings here. But you may not need this at all!
|
352 |
+
|
353 |
+
My personal approach to information thickets is to try to find a sort of tree shape in all the interrelated information. The root of this tree is the ultimate motivating theme/what overall question is being answered. The subtrees splitting off from the root, and the subsubtrees, etc., deal with subquestions discussed in the text and/or give increasing levels of detail. The goal is to use this structure to keep each individual node very short and ultimately make short Anki cards. E.g., this is a sketch of a tree based on a text about World War II:
|
354 |
+
|
355 |
+

|
356 |
+
|
357 |
+
(I don't write down such a tree explicitly, I construct it in my head while making Anki cards.)
|
358 |
+
|
359 |
+
When ankifying such a tree, you would generally want to memorize which child nodes belong to each node. You can do this by adding >handles referring to the children to the node's Anki card. E.g.,
|
360 |
+
|
361 |
+

|
362 |
+
|
363 |
+
Sometimes it's not necessary to do this. For example, I wouldn't add a handle to the "Level 1" card referring to the "Pacific War dates" card. It's pretty obvious that the Pacific War must have *had* a date, so there's no point in memorizing this connection.
|
364 |
+
|
365 |
+
### Sequential breakdowns versus multiple levels of abstraction
|
366 |
+
|
367 |
+
There are often many possible choices regarding the grouping and subgrouping of information into subtrees. For example, under the "timeline of country involvement", I chose to split the information into a level 1 and a level 2 subtree. Another, perhaps more obvious grouping would have been to have a subtree per year of the war. However, my general advice is to avoid sequential breakdowns in favor of breakdowns into multiple levels of abstraction. When you think about World War II, you don't want the first thing you remember to be highly specific details
|
README.md
CHANGED
@@ -208,3 +208,4 @@ BSD 2-Clause License
|
|
208 |
|
209 |
- This project uses the Gradio library (https://gradio.app/) for the web interface.
|
210 |
- Card generation is powered by OpenAI's language models.
|
|
|
|
208 |
|
209 |
- This project uses the Gradio library (https://gradio.app/) for the web interface.
|
210 |
- Card generation is powered by OpenAI's language models.
|
211 |
+
- Card generation principles inspired by ["An Opinionated Guide to Using Anki Correctly"](https://www.lesswrong.com/posts/7Q7DPSk4iGFJd8DRk/an-opinionated-guide-to-using-anki-correctly) by Luise, which emphasizes atomic card design, standardized prompts, and effective spaced repetition practices.
|
ankigen_core/agents/templates/generators.j2
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
{
|
3 |
"subject_expert": {
|
4 |
"name": "subject_expert",
|
5 |
-
"instructions": "You are a world-class expert in {{ subject | default('the subject area') }} with deep pedagogical knowledge. \nYour role is to generate high-quality flashcards that demonstrate mastery of {{ subject | default('the subject') }} concepts.\n\nKey responsibilities:\n- Ensure technical accuracy and depth appropriate for the target level\n- Use domain-specific terminology correctly\n-
|
6 |
"model": "{{ subject_expert_model }}",
|
7 |
"temperature": 0.7,
|
8 |
"timeout": 45.0,
|
@@ -22,7 +22,7 @@
|
|
22 |
},
|
23 |
"content_structuring": {
|
24 |
"name": "content_structuring",
|
25 |
-
"instructions": "You are a content organization specialist focused on
|
26 |
"model": "{{ content_structuring_model }}",
|
27 |
"temperature": 0.5,
|
28 |
"timeout": 25.0
|
|
|
2 |
{
|
3 |
"subject_expert": {
|
4 |
"name": "subject_expert",
|
5 |
+
"instructions": "You are a world-class expert in {{ subject | default('the subject area') }} with deep pedagogical knowledge. \nYour role is to generate high-quality flashcards that demonstrate mastery of {{ subject | default('the subject') }} concepts.\n\nKey responsibilities:\n- Create ATOMIC cards: extremely short (1-9 words on back), break complex info into multiple simple cards\n- Use standardized, bland prompts without fancy formatting or unusual words\n- Design prompts that match real-life recall situations\n- Put ALL to-be-learned information on the BACK of cards, never in prompts\n- Ensure technical accuracy and depth appropriate for the target level\n- Use domain-specific terminology correctly\n- Connect concepts to prerequisite knowledge\n\nPrioritize atomic simplicity over comprehensive single cards. Generate cards that test understanding through simple, direct recall.",
|
6 |
"model": "{{ subject_expert_model }}",
|
7 |
"temperature": 0.7,
|
8 |
"timeout": 45.0,
|
|
|
22 |
},
|
23 |
"content_structuring": {
|
24 |
"name": "content_structuring",
|
25 |
+
"instructions": "You are a content organization specialist focused on atomic card design and optimal learning structure.\nYour role is to format and organize flashcard content following proven Anki principles.\n\nEnsure all cards follow atomic principles:\n- Break down any card longer than 9 words into multiple cards\n- Use standardized, bland prompt formats (no fancy words, formatting, or visual cues)\n- Consistent question structures (e.g., 'T [concept]' for terminology, '[concept] definition' for definitions)\n- Put ALL information to be learned on the BACK, never in prompts\n- Create 'handles' (>references) to connect related cards without making individual cards long\n- Use multiple difficulty levels for the same concept when appropriate\n\nPrioritize simplicity and consistency over comprehensive single cards.",
|
26 |
"model": "{{ content_structuring_model }}",
|
27 |
"temperature": 0.5,
|
28 |
"timeout": 25.0
|
ankigen_core/agents/templates/prompts.j2
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
{# Prompt template configurations #}
|
2 |
{
|
3 |
"subject_generation": {
|
4 |
-
"system_prompt": "You are an expert in {{ subject | default('general studies') }}. Generate {{ num_cards | default('5') }} flashcards
|
5 |
-
"user_prompt_template": "Topic: {{ topic }}\nDifficulty: {{ difficulty | default('intermediate') }}\nPrerequisites: {{ prerequisites | default('none') }}\nFocus Areas: {{ focus_areas | default('core concepts') }}\n\nGenerate {{ num_cards | default('5') }}
|
6 |
"variables": {
|
7 |
"subject": "{{ subject | default('general') }}",
|
8 |
"num_cards": "{{ num_cards | default('5') }}",
|
@@ -14,7 +14,7 @@
|
|
14 |
},
|
15 |
"quality_assessment": {
|
16 |
"system_prompt": "You are a quality assessment specialist for educational content. Evaluate flashcards for {{ assessment_type | default('overall quality') }}.",
|
17 |
-
"user_prompt_template": "Please evaluate the following flashcard for {{ assessment_type | default('quality') }}:\n\nQuestion: {{ question }}\nAnswer: {{ answer }}\n{% if explanation %}Explanation: {{ explanation }}{% endif %}\n\nProvide a rating (1-10) and specific feedback for improvement.",
|
18 |
"variables": {
|
19 |
"assessment_type": "{{ assessment_type | default('overall quality') }}",
|
20 |
"question": "{{ question }}",
|
|
|
1 |
{# Prompt template configurations #}
|
2 |
{
|
3 |
"subject_generation": {
|
4 |
+
"system_prompt": "You are an expert in {{ subject | default('general studies') }}. Generate {{ num_cards | default('5') }} ATOMIC flashcards following proven Anki principles for {{ difficulty | default('intermediate') }} level learners.",
|
5 |
+
"user_prompt_template": "Topic: {{ topic }}\nDifficulty: {{ difficulty | default('intermediate') }}\nPrerequisites: {{ prerequisites | default('none') }}\nFocus Areas: {{ focus_areas | default('core concepts') }}\n\nGenerate {{ num_cards | default('5') }} ATOMIC flashcards following these principles:\n- Each card back should be 1-9 words maximum\n- Use bland, standardized prompts (no fancy formatting)\n- Break complex concepts into multiple simple cards\n- Put ALL learning content on the BACK, never in questions\n- Use handles (>references) to connect related cards\n- Design questions to match real-life recall situations\n- Include examples and explanations where helpful\n\nPrioritize atomic simplicity over comprehensive single cards.",
|
6 |
"variables": {
|
7 |
"subject": "{{ subject | default('general') }}",
|
8 |
"num_cards": "{{ num_cards | default('5') }}",
|
|
|
14 |
},
|
15 |
"quality_assessment": {
|
16 |
"system_prompt": "You are a quality assessment specialist for educational content. Evaluate flashcards for {{ assessment_type | default('overall quality') }}.",
|
17 |
+
"user_prompt_template": "Please evaluate the following flashcard for {{ assessment_type | default('quality') }}:\n\nQuestion: {{ question }}\nAnswer: {{ answer }}\n{% if explanation %}Explanation: {{ explanation }}{% endif %}\n\nProvide a rating (1-10) and specific feedback for improvement. Focus on atomic principles (1-9 words), standardized prompts, and real-life applicability.",
|
18 |
"variables": {
|
19 |
"assessment_type": "{{ assessment_type | default('overall quality') }}",
|
20 |
"question": "{{ question }}",
|
ankigen_core/card_generator.py
CHANGED
@@ -95,7 +95,16 @@ async def generate_cards_batch(
|
|
95 |
"""
|
96 |
|
97 |
cards_prompt = f"""
|
98 |
-
Generate {num_cards} flashcards for the topic: {topic}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
{cloze_instruction}
|
100 |
Return your response as a JSON object with the following structure:
|
101 |
{{
|
@@ -113,7 +122,6 @@ async def generate_cards_batch(
|
|
113 |
"metadata": {{
|
114 |
"prerequisites": ["list", "of", "prerequisites"],
|
115 |
"learning_outcomes": ["list", "of", "outcomes"],
|
116 |
-
"misconceptions": ["list", "of", "misconceptions"],
|
117 |
"difficulty": "beginner/intermediate/advanced"
|
118 |
}}
|
119 |
}}
|
@@ -627,8 +635,16 @@ async def orchestrate_card_generation( # MODIFIED: Added async
|
|
627 |
|
628 |
# Let's make a direct call to structured_output_completion for "text" mode.
|
629 |
text_mode_user_prompt = f"""
|
630 |
-
Please generate {cards_per_topic * topic_number} flashcards based on the following text content.
|
631 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
632 |
Ensure the flashcards cover diverse aspects of the text.
|
633 |
{get_cloze_instruction(generate_cloze)}
|
634 |
Return your response as a JSON object with the following structure:
|
@@ -831,7 +847,6 @@ def get_card_json_structure_prompt() -> str:
|
|
831 |
"metadata": {
|
832 |
"prerequisites": ["list", "of", "prerequisites"],
|
833 |
"learning_outcomes": ["list", "of", "outcomes"],
|
834 |
-
"misconceptions": ["list", "of", "misconceptions"],
|
835 |
"difficulty": "beginner/intermediate/advanced"
|
836 |
}
|
837 |
}
|
@@ -913,7 +928,6 @@ def format_cards_for_dataframe(
|
|
913 |
metadata = card_obj.metadata or {}
|
914 |
prerequisites = metadata.get("prerequisites", [])
|
915 |
learning_outcomes = metadata.get("learning_outcomes", [])
|
916 |
-
common_misconceptions = metadata.get("misconceptions", [])
|
917 |
difficulty = metadata.get("difficulty", "N/A")
|
918 |
# Ensure list-based metadata are joined as plain strings for DataFrame
|
919 |
prerequisites_str = strip_html_tags(
|
@@ -926,11 +940,6 @@ def format_cards_for_dataframe(
|
|
926 |
if isinstance(learning_outcomes, list)
|
927 |
else str(learning_outcomes)
|
928 |
)
|
929 |
-
common_misconceptions_str = strip_html_tags(
|
930 |
-
", ".join(common_misconceptions)
|
931 |
-
if isinstance(common_misconceptions, list)
|
932 |
-
else str(common_misconceptions)
|
933 |
-
)
|
934 |
difficulty_str = strip_html_tags(str(difficulty))
|
935 |
|
936 |
formatted_card = {
|
@@ -947,7 +956,6 @@ def format_cards_for_dataframe(
|
|
947 |
"Example": example, # Already stripped
|
948 |
"Prerequisites": prerequisites_str,
|
949 |
"Learning_Outcomes": learning_outcomes_str,
|
950 |
-
"Common_Misconceptions": common_misconceptions_str,
|
951 |
"Difficulty": difficulty_str, # Ensure difficulty is plain text
|
952 |
"Source_URL": strip_html_tags(
|
953 |
metadata.get("source_url", "")
|
@@ -969,7 +977,6 @@ def get_dataframe_columns() -> list[str]:
|
|
969 |
"Example",
|
970 |
"Prerequisites",
|
971 |
"Learning_Outcomes",
|
972 |
-
"Common_Misconceptions",
|
973 |
"Difficulty",
|
974 |
"Source_URL",
|
975 |
]
|
@@ -1028,11 +1035,6 @@ def generate_cards_from_crawled_content(
|
|
1028 |
learning_outcomes = (
|
1029 |
card_obj.metadata.get("learning_outcomes", []) if card_obj.metadata else []
|
1030 |
)
|
1031 |
-
common_misconceptions = (
|
1032 |
-
card_obj.metadata.get("common_misconceptions", [])
|
1033 |
-
if card_obj.metadata
|
1034 |
-
else []
|
1035 |
-
)
|
1036 |
|
1037 |
prerequisites_str = strip_html_tags(
|
1038 |
", ".join(prerequisites)
|
@@ -1044,11 +1046,6 @@ def generate_cards_from_crawled_content(
|
|
1044 |
if isinstance(learning_outcomes, list)
|
1045 |
else str(learning_outcomes)
|
1046 |
)
|
1047 |
-
common_misconceptions_str = strip_html_tags(
|
1048 |
-
", ".join(common_misconceptions)
|
1049 |
-
if isinstance(common_misconceptions, list)
|
1050 |
-
else str(common_misconceptions)
|
1051 |
-
)
|
1052 |
difficulty_str = strip_html_tags(
|
1053 |
str(
|
1054 |
card_obj.metadata.get("difficulty", "N/A")
|
@@ -1067,7 +1064,6 @@ def generate_cards_from_crawled_content(
|
|
1067 |
"Example": card_obj.back.example or "", # Should be plain
|
1068 |
"Prerequisites": prerequisites_str,
|
1069 |
"Learning_Outcomes": learning_outcomes_str,
|
1070 |
-
"Common_Misconceptions": common_misconceptions_str,
|
1071 |
"Difficulty": difficulty_str,
|
1072 |
"Source_URL": strip_html_tags(
|
1073 |
card_obj.metadata.get("source_url", "") if card_obj.metadata else ""
|
|
|
95 |
"""
|
96 |
|
97 |
cards_prompt = f"""
|
98 |
+
Generate {num_cards} ATOMIC flashcards for the topic: {topic}
|
99 |
+
|
100 |
+
Follow these ATOMIC principles:
|
101 |
+
- Each answer should be 1-9 words maximum
|
102 |
+
- Use bland, standardized questions (no fancy formatting)
|
103 |
+
- Break complex concepts into multiple simple cards
|
104 |
+
- Put ALL learning content in answers, never in questions
|
105 |
+
- Use handles (>references) to connect related cards
|
106 |
+
- Design questions to match real-life recall situations
|
107 |
+
|
108 |
{cloze_instruction}
|
109 |
Return your response as a JSON object with the following structure:
|
110 |
{{
|
|
|
122 |
"metadata": {{
|
123 |
"prerequisites": ["list", "of", "prerequisites"],
|
124 |
"learning_outcomes": ["list", "of", "outcomes"],
|
|
|
125 |
"difficulty": "beginner/intermediate/advanced"
|
126 |
}}
|
127 |
}}
|
|
|
635 |
|
636 |
# Let's make a direct call to structured_output_completion for "text" mode.
|
637 |
text_mode_user_prompt = f"""
|
638 |
+
Please generate {cards_per_topic * topic_number} ATOMIC flashcards based on the following text content.
|
639 |
+
|
640 |
+
Follow these ATOMIC principles:
|
641 |
+
- Each answer should be 1-9 words maximum
|
642 |
+
- Use bland, standardized questions (no fancy formatting)
|
643 |
+
- Break complex concepts into multiple simple cards
|
644 |
+
- Put ALL learning content in answers, never in questions
|
645 |
+
- Use handles (>references) to connect related cards
|
646 |
+
- Design questions to match real-life recall situations
|
647 |
+
|
648 |
Ensure the flashcards cover diverse aspects of the text.
|
649 |
{get_cloze_instruction(generate_cloze)}
|
650 |
Return your response as a JSON object with the following structure:
|
|
|
847 |
"metadata": {
|
848 |
"prerequisites": ["list", "of", "prerequisites"],
|
849 |
"learning_outcomes": ["list", "of", "outcomes"],
|
|
|
850 |
"difficulty": "beginner/intermediate/advanced"
|
851 |
}
|
852 |
}
|
|
|
928 |
metadata = card_obj.metadata or {}
|
929 |
prerequisites = metadata.get("prerequisites", [])
|
930 |
learning_outcomes = metadata.get("learning_outcomes", [])
|
|
|
931 |
difficulty = metadata.get("difficulty", "N/A")
|
932 |
# Ensure list-based metadata are joined as plain strings for DataFrame
|
933 |
prerequisites_str = strip_html_tags(
|
|
|
940 |
if isinstance(learning_outcomes, list)
|
941 |
else str(learning_outcomes)
|
942 |
)
|
|
|
|
|
|
|
|
|
|
|
943 |
difficulty_str = strip_html_tags(str(difficulty))
|
944 |
|
945 |
formatted_card = {
|
|
|
956 |
"Example": example, # Already stripped
|
957 |
"Prerequisites": prerequisites_str,
|
958 |
"Learning_Outcomes": learning_outcomes_str,
|
|
|
959 |
"Difficulty": difficulty_str, # Ensure difficulty is plain text
|
960 |
"Source_URL": strip_html_tags(
|
961 |
metadata.get("source_url", "")
|
|
|
977 |
"Example",
|
978 |
"Prerequisites",
|
979 |
"Learning_Outcomes",
|
|
|
980 |
"Difficulty",
|
981 |
"Source_URL",
|
982 |
]
|
|
|
1035 |
learning_outcomes = (
|
1036 |
card_obj.metadata.get("learning_outcomes", []) if card_obj.metadata else []
|
1037 |
)
|
|
|
|
|
|
|
|
|
|
|
1038 |
|
1039 |
prerequisites_str = strip_html_tags(
|
1040 |
", ".join(prerequisites)
|
|
|
1046 |
if isinstance(learning_outcomes, list)
|
1047 |
else str(learning_outcomes)
|
1048 |
)
|
|
|
|
|
|
|
|
|
|
|
1049 |
difficulty_str = strip_html_tags(
|
1050 |
str(
|
1051 |
card_obj.metadata.get("difficulty", "N/A")
|
|
|
1064 |
"Example": card_obj.back.example or "", # Should be plain
|
1065 |
"Prerequisites": prerequisites_str,
|
1066 |
"Learning_Outcomes": learning_outcomes_str,
|
|
|
1067 |
"Difficulty": difficulty_str,
|
1068 |
"Source_URL": strip_html_tags(
|
1069 |
card_obj.metadata.get("source_url", "") if card_obj.metadata else ""
|
ankigen_core/exporters.py
CHANGED
@@ -44,7 +44,6 @@ BASIC_MODEL = genanki.Model(
|
|
44 |
{"name": "Example"},
|
45 |
{"name": "Prerequisites"},
|
46 |
{"name": "Learning_Outcomes"},
|
47 |
-
{"name": "Common_Misconceptions"},
|
48 |
{"name": "Difficulty"},
|
49 |
{"name": "SourceURL"}, # Added for consistency if used by template
|
50 |
{"name": "TagsStr"}, # Added for consistency if used by template
|
@@ -104,10 +103,6 @@ BASIC_MODEL = genanki.Model(
|
|
104 |
<div>{{Learning_Outcomes}}</div>
|
105 |
</div>
|
106 |
|
107 |
-
<div class=\"misconceptions\">
|
108 |
-
<h3>Common Misconceptions - Debunked</h3>
|
109 |
-
<div>{{Common_Misconceptions}}</div>
|
110 |
-
</div>
|
111 |
|
112 |
<div class=\"difficulty\">
|
113 |
<h3>Difficulty Level</h3>
|
@@ -314,7 +309,6 @@ CLOZE_MODEL = genanki.Model(
|
|
314 |
{"name": "Example"},
|
315 |
{"name": "Prerequisites"},
|
316 |
{"name": "Learning_Outcomes"},
|
317 |
-
{"name": "Common_Misconceptions"},
|
318 |
{"name": "Difficulty"},
|
319 |
{"name": "SourceURL"},
|
320 |
{"name": "TagsStr"},
|
@@ -374,10 +368,6 @@ CLOZE_MODEL = genanki.Model(
|
|
374 |
<div>{{Learning_Outcomes}}</div>
|
375 |
</div>
|
376 |
|
377 |
-
<div class=\"misconceptions\">
|
378 |
-
<h3>Common Misconceptions - Debunked</h3>
|
379 |
-
<div>{{Common_Misconceptions}}</div>
|
380 |
-
</div>
|
381 |
|
382 |
<div class=\"difficulty\">
|
383 |
<h3>Difficulty Level</h3>
|
@@ -703,7 +693,6 @@ def export_cards_to_apkg(
|
|
703 |
example = card_dict.get("Example", "")
|
704 |
prerequisites = card_dict.get("Prerequisites", "")
|
705 |
learning_outcomes = card_dict.get("Learning_Outcomes", "")
|
706 |
-
common_misconceptions = card_dict.get("Common_Misconceptions", "")
|
707 |
difficulty = card_dict.get("Difficulty", "")
|
708 |
source_url = card_dict.get("SourceURL", "")
|
709 |
tags_str_field = card_dict.get(
|
@@ -721,7 +710,7 @@ def export_cards_to_apkg(
|
|
721 |
try:
|
722 |
if note_type.lower() == "cloze":
|
723 |
# CLOZE_MODEL fields: Text, Back Extra, Explanation, Example, Prerequisites,
|
724 |
-
# Learning_Outcomes,
|
725 |
note_fields = [
|
726 |
question, # Text (this is the card_dict['Question'] which should be cloze-formatted)
|
727 |
answer, # Back Extra (this is card_dict['Answer'])
|
@@ -729,7 +718,6 @@ def export_cards_to_apkg(
|
|
729 |
example,
|
730 |
prerequisites,
|
731 |
learning_outcomes,
|
732 |
-
common_misconceptions,
|
733 |
difficulty,
|
734 |
source_url,
|
735 |
tags_str_field,
|
@@ -741,7 +729,7 @@ def export_cards_to_apkg(
|
|
741 |
)
|
742 |
else: # Basic
|
743 |
# BASIC_MODEL fields: Question, Answer, Explanation, Example, Prerequisites,
|
744 |
-
# Learning_Outcomes,
|
745 |
note_fields = [
|
746 |
question,
|
747 |
answer,
|
@@ -749,7 +737,6 @@ def export_cards_to_apkg(
|
|
749 |
example,
|
750 |
prerequisites,
|
751 |
learning_outcomes,
|
752 |
-
common_misconceptions,
|
753 |
difficulty,
|
754 |
source_url,
|
755 |
tags_str_field,
|
@@ -962,9 +949,6 @@ def export_dataframe_to_apkg(
|
|
962 |
"Learning_Outcomes": _format_field_as_string(
|
963 |
row.get("Learning_Outcomes", "")
|
964 |
),
|
965 |
-
"Common_Misconceptions": _format_field_as_string(
|
966 |
-
row.get("Common_Misconceptions", "")
|
967 |
-
),
|
968 |
"Difficulty": difficulty_raw, # Keep the original HTML for the 'Difficulty' field itself
|
969 |
"SourceURL": _format_field_as_string(row.get("Source_URL", "")),
|
970 |
}
|
|
|
44 |
{"name": "Example"},
|
45 |
{"name": "Prerequisites"},
|
46 |
{"name": "Learning_Outcomes"},
|
|
|
47 |
{"name": "Difficulty"},
|
48 |
{"name": "SourceURL"}, # Added for consistency if used by template
|
49 |
{"name": "TagsStr"}, # Added for consistency if used by template
|
|
|
103 |
<div>{{Learning_Outcomes}}</div>
|
104 |
</div>
|
105 |
|
|
|
|
|
|
|
|
|
106 |
|
107 |
<div class=\"difficulty\">
|
108 |
<h3>Difficulty Level</h3>
|
|
|
309 |
{"name": "Example"},
|
310 |
{"name": "Prerequisites"},
|
311 |
{"name": "Learning_Outcomes"},
|
|
|
312 |
{"name": "Difficulty"},
|
313 |
{"name": "SourceURL"},
|
314 |
{"name": "TagsStr"},
|
|
|
368 |
<div>{{Learning_Outcomes}}</div>
|
369 |
</div>
|
370 |
|
|
|
|
|
|
|
|
|
371 |
|
372 |
<div class=\"difficulty\">
|
373 |
<h3>Difficulty Level</h3>
|
|
|
693 |
example = card_dict.get("Example", "")
|
694 |
prerequisites = card_dict.get("Prerequisites", "")
|
695 |
learning_outcomes = card_dict.get("Learning_Outcomes", "")
|
|
|
696 |
difficulty = card_dict.get("Difficulty", "")
|
697 |
source_url = card_dict.get("SourceURL", "")
|
698 |
tags_str_field = card_dict.get(
|
|
|
710 |
try:
|
711 |
if note_type.lower() == "cloze":
|
712 |
# CLOZE_MODEL fields: Text, Back Extra, Explanation, Example, Prerequisites,
|
713 |
+
# Learning_Outcomes, Difficulty, SourceURL, TagsStr
|
714 |
note_fields = [
|
715 |
question, # Text (this is the card_dict['Question'] which should be cloze-formatted)
|
716 |
answer, # Back Extra (this is card_dict['Answer'])
|
|
|
718 |
example,
|
719 |
prerequisites,
|
720 |
learning_outcomes,
|
|
|
721 |
difficulty,
|
722 |
source_url,
|
723 |
tags_str_field,
|
|
|
729 |
)
|
730 |
else: # Basic
|
731 |
# BASIC_MODEL fields: Question, Answer, Explanation, Example, Prerequisites,
|
732 |
+
# Learning_Outcomes, Difficulty, SourceURL, TagsStr
|
733 |
note_fields = [
|
734 |
question,
|
735 |
answer,
|
|
|
737 |
example,
|
738 |
prerequisites,
|
739 |
learning_outcomes,
|
|
|
740 |
difficulty,
|
741 |
source_url,
|
742 |
tags_str_field,
|
|
|
949 |
"Learning_Outcomes": _format_field_as_string(
|
950 |
row.get("Learning_Outcomes", "")
|
951 |
),
|
|
|
|
|
|
|
952 |
"Difficulty": difficulty_raw, # Keep the original HTML for the 'Difficulty' field itself
|
953 |
"SourceURL": _format_field_as_string(row.get("Source_URL", "")),
|
954 |
}
|
ankigen_core/models.py
CHANGED
@@ -44,7 +44,6 @@ class ConceptBreakdown(BaseModel):
|
|
44 |
main_concept: str
|
45 |
prerequisites: List[str]
|
46 |
learning_outcomes: List[str]
|
47 |
-
common_misconceptions: List[str]
|
48 |
difficulty_level: str # "beginner", "intermediate", "advanced"
|
49 |
|
50 |
|
|
|
44 |
main_concept: str
|
45 |
prerequisites: List[str]
|
46 |
learning_outcomes: List[str]
|
|
|
47 |
difficulty_level: str # "beginner", "intermediate", "advanced"
|
48 |
|
49 |
|
ankigen_core/ui_logic.py
CHANGED
@@ -78,7 +78,6 @@ def update_mode_visibility(
|
|
78 |
"Example",
|
79 |
"Prerequisites",
|
80 |
"Learning_Outcomes",
|
81 |
-
"Common_Misconceptions",
|
82 |
"Difficulty",
|
83 |
]
|
84 |
subjects_list_df_columns = ["Subject", "Prerequisites", "Time Estimate"]
|
@@ -142,7 +141,6 @@ def use_selected_subjects(subjects_df: pd.DataFrame | None):
|
|
142 |
"Example",
|
143 |
"Prerequisites",
|
144 |
"Learning_Outcomes",
|
145 |
-
"Common_Misconceptions",
|
146 |
"Difficulty",
|
147 |
]
|
148 |
)
|
@@ -191,7 +189,6 @@ def use_selected_subjects(subjects_df: pd.DataFrame | None):
|
|
191 |
"Example",
|
192 |
"Prerequisites",
|
193 |
"Learning_Outcomes",
|
194 |
-
"Common_Misconceptions",
|
195 |
"Difficulty",
|
196 |
]
|
197 |
)
|
@@ -238,7 +235,6 @@ def use_selected_subjects(subjects_df: pd.DataFrame | None):
|
|
238 |
"Example",
|
239 |
"Prerequisites",
|
240 |
"Learning_Outcomes",
|
241 |
-
"Common_Misconceptions",
|
242 |
"Difficulty",
|
243 |
]
|
244 |
)
|
|
|
78 |
"Example",
|
79 |
"Prerequisites",
|
80 |
"Learning_Outcomes",
|
|
|
81 |
"Difficulty",
|
82 |
]
|
83 |
subjects_list_df_columns = ["Subject", "Prerequisites", "Time Estimate"]
|
|
|
141 |
"Example",
|
142 |
"Prerequisites",
|
143 |
"Learning_Outcomes",
|
|
|
144 |
"Difficulty",
|
145 |
]
|
146 |
)
|
|
|
189 |
"Example",
|
190 |
"Prerequisites",
|
191 |
"Learning_Outcomes",
|
|
|
192 |
"Difficulty",
|
193 |
]
|
194 |
)
|
|
|
235 |
"Example",
|
236 |
"Prerequisites",
|
237 |
"Learning_Outcomes",
|
|
|
238 |
"Difficulty",
|
239 |
]
|
240 |
)
|
app.py
CHANGED
@@ -80,7 +80,6 @@ example_data = pd.DataFrame(
|
|
80 |
"```sql\nSELECT column1, column2 FROM my_table WHERE condition;\n```",
|
81 |
["Understanding of database tables"],
|
82 |
["Retrieve specific data"],
|
83 |
-
["❌ SELECT * is always efficient (Reality: Can be slow for large tables)"],
|
84 |
"beginner",
|
85 |
],
|
86 |
[
|
@@ -96,7 +95,6 @@ def greet(name):
|
|
96 |
```""",
|
97 |
["Basic programming concepts"],
|
98 |
["Define reusable blocks of code"],
|
99 |
-
["❌ Forgetting the colon (:) after the definition"],
|
100 |
"beginner",
|
101 |
],
|
102 |
],
|
@@ -110,7 +108,6 @@ def greet(name):
|
|
110 |
"Example",
|
111 |
"Prerequisites",
|
112 |
"Learning_Outcomes",
|
113 |
-
"Common_Misconceptions",
|
114 |
"Difficulty",
|
115 |
],
|
116 |
)
|
@@ -482,7 +479,7 @@ def create_ankigen_interface():
|
|
482 |
gr.Markdown("### Generated Cards")
|
483 |
with gr.Accordion("Output Format", open=False):
|
484 |
gr.Markdown(
|
485 |
-
"Cards: Index, Topic, Type, Q, A, Explanation, Example, Prerequisites, Outcomes,
|
486 |
)
|
487 |
with gr.Accordion("Example Card Format", open=False):
|
488 |
gr.Code(
|
@@ -502,7 +499,6 @@ def create_ankigen_interface():
|
|
502 |
"Example",
|
503 |
"Prerequisites",
|
504 |
"Learning_Outcomes",
|
505 |
-
"Common_Misconceptions",
|
506 |
"Difficulty",
|
507 |
],
|
508 |
datatype=[
|
@@ -516,7 +512,6 @@ def create_ankigen_interface():
|
|
516 |
"str",
|
517 |
"str",
|
518 |
"str",
|
519 |
-
"str",
|
520 |
],
|
521 |
interactive=True,
|
522 |
elem_classes="tall-dataframe",
|
@@ -531,7 +526,6 @@ def create_ankigen_interface():
|
|
531 |
200,
|
532 |
150,
|
533 |
150,
|
534 |
-
150,
|
535 |
100,
|
536 |
],
|
537 |
)
|
|
|
80 |
"```sql\nSELECT column1, column2 FROM my_table WHERE condition;\n```",
|
81 |
["Understanding of database tables"],
|
82 |
["Retrieve specific data"],
|
|
|
83 |
"beginner",
|
84 |
],
|
85 |
[
|
|
|
95 |
```""",
|
96 |
["Basic programming concepts"],
|
97 |
["Define reusable blocks of code"],
|
|
|
98 |
"beginner",
|
99 |
],
|
100 |
],
|
|
|
108 |
"Example",
|
109 |
"Prerequisites",
|
110 |
"Learning_Outcomes",
|
|
|
111 |
"Difficulty",
|
112 |
],
|
113 |
)
|
|
|
479 |
gr.Markdown("### Generated Cards")
|
480 |
with gr.Accordion("Output Format", open=False):
|
481 |
gr.Markdown(
|
482 |
+
"Cards: Index, Topic, Type, Q, A, Explanation, Example, Prerequisites, Outcomes, Difficulty. Export: CSV, .apkg",
|
483 |
)
|
484 |
with gr.Accordion("Example Card Format", open=False):
|
485 |
gr.Code(
|
|
|
499 |
"Example",
|
500 |
"Prerequisites",
|
501 |
"Learning_Outcomes",
|
|
|
502 |
"Difficulty",
|
503 |
],
|
504 |
datatype=[
|
|
|
512 |
"str",
|
513 |
"str",
|
514 |
"str",
|
|
|
515 |
],
|
516 |
interactive=True,
|
517 |
elem_classes="tall-dataframe",
|
|
|
526 |
200,
|
527 |
150,
|
528 |
150,
|
|
|
529 |
100,
|
530 |
],
|
531 |
)
|
uv.lock
CHANGED
The diff for this file is too large to render.
See raw diff
|
|