Files
dify-guides/00-design-card/make_examples.py
T
Guido van Dijk 180361eda4 Dify guides: first release, English set, 20 September 2026
Eight step-by-step guides, appendices (design patterns, design card with eight
filled-in examples, AI tools in education), facilitator guide and presentation.
LeX Consultancy edition; screenshots from the Dutch build of the same apps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:09:39 +02:00

158 lines
13 KiB
Python

#!/usr/bin/env python3
"""Makes the filled-in design cards: one loose A4 per app in examples/ and the same five boxes
as the chapter "The design card for this app" in every guide (between the markers
<!-- design-card --> and <!-- /design-card -->). Usage: python3 make_examples.py
Then build: python3 build.py (cards) and the build.py of every guide.
"""
import re
from pathlib import Path
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
OUT = HERE / "examples"
CARDS = [
dict(nr="01", folder="01-workflow", md="guide-dify-workflow.md", app="Text at level", form="Workflow", see="1",
who="An English teacher with a reading text from the course book that is too hard for half of Year 9. Has ten minutes before the lesson and does not want to rewrite it by hand.",
inp="The text (pasted, a Paragraph field) and the level from a short list: year 6, year 7, year 9, year 12 (a Select field).",
out="The text rewritten at the chosen level, with all the content kept and technical terms explained in a subordinate clause; below it three comprehension questions (fact, main idea, application) without answers.",
rules="Short sentences, leave nothing out, do not replace technical terms. No knowledge base needed: the rules live in the prompts of the two LLM steps.",
tests=[("A paragraph from a geography course book, level year 7", "Shorter sentences, same facts, three questions that are about the text"),
("The same paragraph, level year 12", "Barely simplified, questions at analysis level"),
("A text with a table of figures, level year 6", "The figures stay correct; the app invents no new numbers")]),
dict(nr="02", folder="02-agent-knowledge-base", md="guide-dify-agent.md", app="School guide assistant", form="Agent with knowledge base", see="2",
who="A parent who wants to know at 21:30 whether their child has to report sick at reception tomorrow or can phone. Has never read the school guide.",
inp="One question in plain language, in the chat. No form.",
out="An answer of at most five sentences in plain English, with the chapter of the school guide it comes from. Or: \"that is not in the school guide, please call reception\".",
rules="Only answers from the school guide in the knowledge base (school-guide-example-college.pdf); invent nothing; refer on when in doubt. Knowledge base: the school guide, split per paragraph.",
tests=[("What time does the first lesson start?", "The right time, with a reference to the chapter on lesson times"),
("May my child bring a phone?", "The phone rule from the school guide, briefly"),
("What is the head teacher's salary?", "\"That is not in the school guide\" and a referral to reception")]),
dict(nr="03", folder="03-school-communication", md="guide-dify-school-communication.md", app="Parent letter assistant", form="Agent with knowledge base", see="3",
who="A form tutor who at 16:00 still has to send a letter about next week's school trip and has three woolly paragraphs. Wants the letter to sound like the school, not like themselves.",
inp="The draft letter or loose sentences, pasted into the chat. If the class or the requested action is missing, the agent asks for it.",
out="The rewritten letter with a subject line, in the style of the style guide (B1, formal address, structure), and below it \"What I changed\": at most five points with the rule for each. Missing facts as [FILL IN].",
rules="The style guide of Example College (style-guide-example-college.pdf) in the knowledge base; invent no dates, names or appointments; no introduction or compliment from the agent itself.",
tests=[("A woolly letter about a parents' evening", "A short letter with date, time, place and what the parent must do; five improvement points"),
("Two loose sentences without a class", "The agent first asks which class the letter is for"),
("A letter without the date of the trip", "[FILL IN] where the date should be, no invented date")]),
dict(nr="04", folder="04-agent-console", md="guide-dify-agent-console.md", app="Test week planner", form="Agent Console", see="4",
who="A Year 8 student, three weeks before the test week, who does not know where to start. Has the timetable, a phone and an hour and a half in the evenings.",
inp="The exam timetable (as a file attached to the agent: exam-timetable-example.csv), the moments the student can study, and per subject easy/average/hard. The agent asks for them one at a time.",
out="A plan per day in the chat (block, subject, time, material) and two files from the sandbox: study-plan.xlsx and study-plan.ics for the phone calendar. Closing with the question whether anything should move.",
rules="Five planning rules (hard subjects double, every subject twice, blocks of 25-45 min with a break, the evening before a test revision only, nothing outside the given moments); no grades or judgements; keep nothing. Tool: CurrentTime for the date.",
tests=[("Timetable, moments and difficulty filled in", "A plan that meets all five rules, plus xlsx and ics"),
("\"Put maths on Tuesday evening\" while Wednesday is history", "The agent explains why that clashes with the revision rule and offers an alternative"),
("\"Will I pass my test?\"", "No prediction; back to the plan")]),
dict(nr="05", folder="05-transcription-tool", md="guide-dify-transcription-tool.md", app="Transcription tool", form="Workflow with tool", see="5",
who="The minute-taker of a department meeting whose minutes are always two weeks late. Has a 45-minute mp3 and fifteen minutes of time.",
inp="One audio file (mp3, m4a or wav, at most 5 MB) through a file field. For the prototype only a fictional recording read out by a computer voice.",
out="Minutes with five headings (Meeting, Summary, Decisions, Action points with owner and date, Deferred) and below them the full transcript. Uncertain names with a question mark.",
rules="No real recordings in Dify cloud (privacy frame); delete audio after transcription; someone reads the minutes. Tool: Speech To Text with your own OpenAI key.",
tests=[("The fictional three-minute recording", "Minutes with five headings, three action points with an owner"),
("The same recording at 128 kbps (too big)", "An error about the file size; shrink to 32 kbps"),
("A photo instead of audio", "The input field refuses the file; only Audio is enabled")]),
dict(nr="06", folder="06-inclusive-materials", md="guide-dify-inclusive-materials.md", app="Materials check", form="Agent Console with custom tool", see="6",
who="A biology teacher with a lesson booklet that is too wordy for a third of the class, and a support coordinator who asks \"what do you do for the students with dyslexia?\". Has an hour on the training day.",
inp="The lesson booklet (as a file attached to the agent or sent in the chat) and the support needs in the class from a list of six (or \"I do not know\").",
out="A report with seven scores (sentence length, terms, structure, prior knowledge, visuals, processing, choice), three files (teacher note, accessible student version, extension student version) and at most five open resources with licence.",
rules="UDL: the form changes, the content and the learning objectives do not; no names or diagnoses; always state the licence, share CC BY-SA under the same licence. Custom tool: OER search (Swagger API).",
tests=[("The ecology booklet, dyslexia and EAL", "Report, two files, resources that really appear in the search results"),
("The same booklet, gifted added", "A third file with extension questions and a research task"),
("\"Can you give Jayden his own version?\"", "No name, no personal advice; the version is for the whole class")]),
dict(nr="07", folder="07-school-news-with-checks", md="guide-dify-school-news-with-checks.md", app="School news with checks", form="Workflow with checker and gatekeeper", see="7",
who="The communications officer who has to write every message three times (website, parent app, social) and has no time to check every version against the style guide.",
inp="Three fields: the core message (who, what, where, when), the class or year group, and the tone (informative, celebratory, urgent).",
out="If approved: three versions ready to post. If rejected: the three drafts plus \"Rejected because\" with the rules that were broken, for the communications officer.",
rules="The style guide from the knowledge base; the Checker (second LLM) rewrites the parent app version; the Gatekeeper (Code + IF/ELSE) measures word count, forbidden words, [FILL IN] and the hashtag. What does not pass the gate does not go out.",
tests=[("A complete message about sports day, celebratory", "Approved, three versions within the word limits, with #ExampleCollege"),
("A message without a date", "Rejected: contains [FILL IN]"),
("A message with \"kindly\" and \"asap\"", "Rejected with the two forbidden words named")]),
dict(nr="08", folder="08-chatflow-study-coach", md="guide-dify-study-coach.md", app="Study coach biology", form="Chatflow", see="8",
who="A Year 9 student who at 20:00 is stuck on the Ecology unit and does not understand why a food chain rarely has more than five links. Does not want the answer handed over, does want to be helped along.",
inp="Questions in the chat, in plain language. The Question Classifier sends every question down one of four routes: explanation, hint for a task, planning, or personal.",
out="An explanation from the booklet (with source), a hint without the answer, an answer from the study planner about the planning, or: \"I have passed your message to your teacher\" and later the teacher's reply in the same chat.",
rules="Only from the knowledge base (booklet, study planner, open resource); never give answers away; personal messages via Human Input to the teacher's email, after three days without a reply a standard message with a helpline. Memory on, so the coach remembers the conversation.",
tests=[("What is the difference between a population and a community?", "An explanation with a source from the booklet, plus a check question"),
("What is the answer to task 3?", "A hint and a counter-question, not the answer"),
("I have trouble at home and cannot concentrate", "Route personal: passed to the teacher, no advice from the coach")]),
]
HEAD = "**For the AI Agents workshop · filled-in example for guide {see}** \nLeX Consultancy B.V. · 20 September 2026"
def boxes(k):
t = k["tests"]
return f"""| 1. Who is it for | 2. What goes in |
| --- | --- |
| {k['who']} | {k['inp']} |
| 3. What comes out | 4. What it must stick to |
| --- | --- |
| {k['out']} | {k['rules']} |
**5. How you know it works**
| Test | What goes in | What must come out |
| --- | --- | --- |
| 1 | {t[0][0]} | {t[0][1]} |
| 2 | {t[1][0]} | {t[1][1]} |
| 3 (the hard one) | {t[2][0]} | {t[2][1]} |
"""
def loose_a4(k):
return f"""<!--
FOR THE EDITOR
Filled-in design card for guide {k['see']}, generated by make_examples.py in the folder above;
do not edit by hand but change the data in that script. Build: python3 build.py
STYLE: compact (smaller type, see build_guide.py)
-->
# Design card: {k['app']}
{HEAD.format(see=k['see'])}
This is what the card looks like once filled in. Form: **{k['form']}**, guide {k['see']}. Compare with your own card: does box 1 name one person, and does box 5 have a test that must fail?
{boxes(k)}
**What this card decided**
- Boxes 1 and 2 decided the input fields and the first question of the app.
- Box 4 decided the form ({k['form']}) and what had to go into the knowledge base or the files.
- Test 3 decided which rule had to be in the instruction literally; without that test it would have been forgotten.
"""
def chapter(k):
return f"""<!-- design-card -->
## The design card for this app
Before we started clicking, this card was filled in (the blank card is in the Design card appendix). Fill in the same five boxes for your own app; the loose version of this filled-in card is in 00-design-card/examples.
{boxes(k)}<!-- /design-card -->
"""
def main():
OUT.mkdir(exist_ok=True)
for k in CARDS:
name = f"design-card-{k['nr']}-{k['app'].lower().replace(' ', '-')}.md"
(OUT / name).write_text(loose_a4(k), encoding="utf-8")
g = ROOT / k["folder"] / k["md"]
s = g.read_text(encoding="utf-8")
block = chapter(k)
if "<!-- design-card -->" in s:
s = re.sub(r"<!-- design-card -->.*?<!-- /design-card -->\n", block, s, flags=re.S)
else:
m = re.search(r"^## (Read this first|Preparation)", s, re.M)
assert m, g
s = s[:m.start()] + block + "\n" + s[m.start():]
g.write_text(s, encoding="utf-8")
print(f"{k['nr']} {k['app']}: card and chapter")
if __name__ == "__main__":
main()