95d21e358a
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
347 lines
27 KiB
Python
Vendored
347 lines
27 KiB
Python
Vendored
"""Builds the 22 slides of the workshop as HTML sections of 1920x1080 plus deck.json.
|
|
|
|
By default the images point to the folder images/ next to this script, so that build_pdf.py can
|
|
make a PDF. With --artifact the images get the paths of the online presentation (asset store).
|
|
Licence: CC BY-SA 4.0, see ../LICENSE.md. The LeX Consultancy logo is excluded.
|
|
"""
|
|
import json, sys
|
|
from pathlib import Path
|
|
HERE = Path(__file__).resolve().parent
|
|
P = HERE / "project"; S = P / "slides"; S.mkdir(parents=True, exist_ok=True)
|
|
ARTIFACT = "--artifact" in sys.argv
|
|
IMG = {
|
|
"logo": ("../images/logo-lex-wit.png", "/_blob/LEXWIT"),
|
|
"logocolour": ("../images/logo-lex.png", "/_blob/LEXCOLOUR"),
|
|
"rejected": ("../images/school-news-rejected.jpg", "/_blob/REJECTED"),
|
|
"flow": ("../images/study-coach-flow.jpg", "/_blob/FLOW"),
|
|
"teacher": ("../images/study-coach-teacher-answer.jpg", "/_blob/TEACHER"),
|
|
}
|
|
def img(name): return IMG[name][1 if ARTIFACT else 0]
|
|
OWNER = "Guido van Dijk, LeX Consultancy B.V."
|
|
CONTACT = "guido@l-e-x.nl"
|
|
SOURCE = "git.lexconsultancy.nl/guido/dify-guides"
|
|
|
|
DARK="#1E2A3A"; LIGHT="#F7F4EC"; LIGHT2="#EFEAE0"; ACC="#D9480F"; BLUE="#2563EB"; BODY="#3D4756"; BODYD="#D9DEE6"; LINE="#D8D2C4"
|
|
H="'Rubik', Arial, sans-serif"; T="'Nunito Sans', Verdana, sans-serif"
|
|
LOGO=img("logo")
|
|
order=[]; sections={}
|
|
def slide(id_, body, bg=LIGHT, color=DARK, pad="128px 128px 160px", extra="display:flex; flex-direction:column; gap:40px", notes="", section=None, transition="fade", footer=True):
|
|
fcol = "#8A8F98" if bg in (LIGHT, LIGHT2) else "#9AA3B1"
|
|
foot = f'<p style="position:absolute; left:128px; bottom:64px; font-size:24px; color:{fcol}">Workshop AI Agents with Dify · LeX Consultancy · CC BY-SA 4.0</p>' if footer else ""
|
|
if footer:
|
|
lexlogo = img("logocolour") if bg in (LIGHT, LIGHT2) else img("logo")
|
|
foot += f'<img src="{lexlogo}" alt="LeX Consultancy" style="position:absolute; right:128px; bottom:52px; width:90px; height:52px; object-fit:contain">'
|
|
html = (f'<section id="{id_}" data-transition="{transition}" style="background:{bg}; color:{color}; font-family:{T}; padding:{pad}; {extra}">\n'
|
|
f'{body}\n{foot}\n<aside>{notes}</aside>\n</section>\n')
|
|
(S / f"{id_}.html").write_text(html, encoding="utf-8"); order.append(id_)
|
|
if section: sections[id_]={"description":section,"start":id_}
|
|
def h2(t): return f'<h2 style="font-family:{H}; font-size:64px; font-weight:600; line-height:1.1">{t}</h2>'
|
|
def eyebrow(t, c=ACC): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{c}">{t}</p>'
|
|
def p(t, size=32, c=BODY): return f'<p style="font-size:{size}px; color:{c}; line-height:1.4">{t}</p>'
|
|
def card(title, text, w="flex:1", bg="#FFFFFF", tc=DARK, bc=BODY, border=LINE):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:16px; background:{bg}; padding:36px; border:1px solid {border}; border-radius:16px">'
|
|
f'<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{tc}; line-height:1.2">{title}</h3>'
|
|
f'<p style="font-size:27px; color:{bc}; line-height:1.4">{text}</p></div>')
|
|
def num(n, label, w="flex:1"):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:8px"><p style="font-family:{H}; font-size:120px; font-weight:600; color:{ACC}; line-height:1">{n}</p>'
|
|
f'<p style="font-size:28px; color:{BODY}; line-height:1.3">{label}</p></div>')
|
|
def ul(items, size=30, c=BODY):
|
|
return f'<ul style="font-size:{size}px; color:{c}; line-height:1.5">' + "".join(f"<li>{i}</li>" for i in items) + "</ul>"
|
|
def table(rows, widths, size=27, head=True):
|
|
out=[f'<table style="font-size:{size}px; color:{BODY}; border:1px solid {LINE}; background:#FFFFFF">']
|
|
for i,r in enumerate(rows):
|
|
tag = "th" if (head and i==0) else "td"
|
|
bg = f' style="background:{LIGHT2}"' if (head and i==0) else ""
|
|
cells="".join(f'<{tag} style="width:{widths[j]}%; color:{DARK if tag=="th" else BODY}">{c}</{tag}>' for j,c in enumerate(r))
|
|
out.append(f"<tr{bg}>{cells}</tr>")
|
|
out.append("</table>"); return "".join(out)
|
|
def interaction(label): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{BLUE}">Interaction · {label}</p>'
|
|
|
|
# 1 cover
|
|
slide("cover", f'''
|
|
<img src="{LOGO}" alt="LeX Consultancy" style="position:absolute; right:128px; top:96px; width:260px; height:150px; object-fit:contain">
|
|
<div style="flex:1"></div>
|
|
{eyebrow("Workshop · 2 hours · in pairs at a laptop", "#F4B183")}
|
|
<h1 style="font-family:{H}; font-size:112px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1300px">Building AI agents<br>with Dify</h1>
|
|
{p("From one prompt to an app your colleagues can use. Build it yourself, test it yourself, break it yourself.", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
{p("Guido van Dijk · LeX Consultancy B.V.", 28, "#9AA3B1")}
|
|
''', bg=DARK, color=LIGHT, footer=False, section="Opening: what we are going to do and who is in the room",
|
|
notes="Welcome. Laptops open, logged in to cloud.dify.ai? Anyone without an account: a sandbox account takes 2 minutes, the buddy next to you helps. Goal today: everyone goes home with one working app and a design for the next.")
|
|
|
|
# 2 programme
|
|
slide("programme", h2("Two hours, four blocks") + table([
|
|
["Time","Block","What you do"],
|
|
["0:00 - 0:15","The landscape","Poll, three layers, five patterns"],
|
|
["0:15 - 0:40","Building block 1: workflow","Text at level, in pairs, your own levels"],
|
|
["0:40 - 0:55","Demo and sabotage","Agent with knowledge base; make it stumble"],
|
|
["0:55 - 1:05","Break",""],
|
|
["1:05 - 1:35","Building block 2: choose your track","A knowledge base agent · B gatekeeper"],
|
|
["1:35 - 1:50","Design card and pitch","Your app in five boxes, 30-second pitch"],
|
|
["1:50 - 2:00","Close","What is allowed, what you take home, exit ticket"],
|
|
],[22,30,48]),
|
|
notes="Tight on time. The building blocks are sacred; if a demo overruns, shorten the demo, not the building.")
|
|
|
|
# 3 poll
|
|
slide("poll", interaction("2 minutes") + h2("Stand up if...") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1", "...you used an AI chatbot for your work this week", bg="#FFFFFF")}
|
|
{card("2", "...your colleagues use a tool you are not sure is allowed", bg="#FFFFFF")}
|
|
{card("3", "...you ever shared a prompt with a colleague that then did not work", bg="#FFFFFF")}
|
|
</div>
|
|
{p("Stay standing at 3. Look around: that is the problem we solve today. A prompt is yours; an app belongs to the team.", 30)}
|
|
''', notes="Make people really stand. At question 3 usually three quarters stand. Name it: prompts are personal and fragile; today we build something that stays standing when you are not there.")
|
|
|
|
# 4 three layers
|
|
slide("layers", h2("Three layers, and where we are today") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1 · Chatbot", "You type, you get an answer, every time you explain again. ChatGPT, Gemini, Copilot Chat, Claude, Le Chat.")}
|
|
{card("2 · Assistant in your suite", "AI in the tools you already have. Microsoft 365 Copilot, Gemini in Workspace. The school decides through licences.")}
|
|
{card("3 · Building platform", "You build an app with fixed steps, your own rules and your own documents, and share it. Dify, Copilot Studio, Gems, GPTs.", bg=DARK, tc=LIGHT, bc=BODYD, border=DARK)}
|
|
</div>
|
|
{p("The difference is not the model but the account: free and private, or school with a data processing agreement.", 30)}
|
|
''', notes="Layer 3 is today. Dify is not a model: it uses the models of OpenAI, Anthropic, Google through their API. More about what is allowed later.")
|
|
|
|
# 5 patterns
|
|
slide("patterns", h2("Five patterns: how much may the model decide itself?") + table([
|
|
["Pattern","Who decides the steps","Today"],
|
|
["1 · Single call","You, in advance; the model does one thing","Text at level"],
|
|
["2 · Reason with tools","The model, step by step, with a knowledge base","School guide assistant"],
|
|
["3 · Plan and execute","First a plan, then separate executors","School news: three channels"],
|
|
["4 · Self-critique","The model reviews its own work","Checker"],
|
|
["5 · Gatekeeper","Code or a person lets it through or not","Gatekeeper, teacher in the loop"],
|
|
],[34,40,26], size=28) + p("Rule of thumb: choose the lowest pattern that works. Every step up costs time, credits and surprises.", 28),
|
|
section="The landscape: layers, patterns and the first example",
|
|
notes="Do not cover exhaustively; the Design patterns appendix goes home with them. Do name the rule of thumb.")
|
|
|
|
# 6 demo 01
|
|
slide("demo01", eyebrow("Demo · 3 minutes") + h2("Text at level") + f'''
|
|
<div style="display:flex; gap:48px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("Paste a text, choose a level (year 6, 7, 9 or 12), and get a rewritten version plus three comprehension questions.", 30)}
|
|
{ul(["Two LLM steps in a row: rewrite, then write questions","The second step works with what the first produced","Published as a webapp: share the link, done"], 28)}
|
|
</div>
|
|
<div style="width:640px; display:flex; flex-direction:column; gap:20px; background:{LIGHT2}; padding:36px; border-radius:16px; border:1px solid {LINE}">
|
|
{interaction("shout it out")}
|
|
<p style="font-family:{H}; font-size:40px; font-weight:600; color:{DARK}; line-height:1.2">Which pattern is this?</p>
|
|
{p("And: what happens if I paste a text with a student's name?", 28)}
|
|
</div>
|
|
</div>
|
|
''', notes="Live demo of the Text at level app. Answer: pattern 1, even though there are two steps; you decide the order. The name question leads to the checklist for your own material.")
|
|
|
|
# 7 building block 1
|
|
slide("build1", eyebrow("Building block 1 · 25 minutes · in pairs") + h2("Build Text at level, with your levels") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px">
|
|
{ul(["Guide 1, steps 1 to 8","Model: <b>gpt-5-mini</b>, not gpt-5","Replace the four levels with your groups","Paste a text from your own material, without names","Done? Publish and send the link to the pair next to you"], 29)}
|
|
</div>
|
|
<div style="width:600px; display:flex; flex-direction:column; gap:16px; background:#ECFDF5; padding:32px; border-radius:16px; border:1px solid #A7D9BF">
|
|
<h3 style="font-family:{H}; font-size:32px; font-weight:600; color:#14532D">Your choice</h3>
|
|
{p("Done early? Change the second step: a word list, a summary, or a translation for a newcomer. One thing at a time, then test.", 27, "#14532D")}
|
|
</div>
|
|
</div>
|
|
''', section="Building block 1: everyone builds a workflow", transition="push",
|
|
notes="Walk round. Most common mistakes: variable not inserted via /; Publish forgotten; gpt-5 left in place. After 20 minutes: who has a link? Let two pairs try each other's app.")
|
|
|
|
# 8 demo agent + sabotage
|
|
slide("demo02", eyebrow("Demo · 10 minutes") + h2("An agent with a knowledge base") + f'''
|
|
<div style="display:flex; gap:48px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("The School guide assistant answers questions from parents and students out of a two-page school guide. The Parent letter assistant rewrites a woolly letter according to the style guide and says what it changed.", 29)}
|
|
{ul(["A knowledge base splits your document into pieces; the split determines the quality","The description of the knowledge base is for the agent, not for you","Used Knowledge above the answer: it really searched"], 27)}
|
|
</div>
|
|
<div style="width:640px; display:flex; flex-direction:column; gap:20px; background:{LIGHT2}; padding:36px; border-radius:16px; border:1px solid {LINE}">
|
|
{interaction("sabotage")}
|
|
<p style="font-family:{H}; font-size:40px; font-weight:600; color:{DARK}; line-height:1.2">Make it stumble</p>
|
|
{p("In your pair, think of one question the assistant must not know the answer to, and one it could invent a wrong answer for. We try three live.", 27)}
|
|
</div>
|
|
</div>
|
|
''', section="Demo: agent, knowledge base and the sabotage", notes="The School guide assistant and the Parent letter assistant. The sabotage shows why the rule 'say that you do not know' is in the instruction. If it invents something anyway: perfect, that is the lesson.")
|
|
|
|
# 9 break
|
|
slide("break", f'''
|
|
<div style="flex:1"></div>
|
|
<h1 style="font-family:{H}; font-size:140px; font-weight:600; color:{LIGHT}; line-height:1">Break</h1>
|
|
{p("10 minutes. Leave your app open; after the break you choose a track.", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
''', bg=ACC, color=LIGHT, footer=False, notes="During the break: check with hesitant pairs whether the knowledge base of guide 2 is already ready, that saves time in block 2.")
|
|
|
|
# 10 building block 2
|
|
slide("build2", eyebrow("Building block 2 · 30 minutes · choose your track") + h2("Track A or track B") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:36px; border:2px solid {BLUE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:38px; font-weight:600; color:{BLUE}">A · Agent with your document</h3>
|
|
{p("Guide 2. Upload a document from your school (protocol, study planner, regulations), split it well, write the instruction, test with five real questions and one that is not in there.", 27)}
|
|
{p("For whom: anyone who has documents people ask questions about.", 26, "#6B7280")}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:36px; border:2px solid {ACC}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:38px; font-weight:600; color:{ACC}">B · Gatekeeper on your workflow</h3>
|
|
{p("Guide 7, parts B and C. Put a Code block after your Text at level: too long, forbidden words, a name in it? Then the text does not go through but to a person, with the reason.", 27)}
|
|
{p("For whom: anyone who finished block 1 quickly and wants to see what code does here. Paste, do not type.", 26, "#6B7280")}
|
|
</div>
|
|
</div>
|
|
''', section="Building block 2: two tracks, your choice", transition="push",
|
|
notes="Track B is tougher; put pairs with an affinity for programming together. In both tracks: your own material must be free of personal data, say it out loud once more.")
|
|
|
|
# 11 gatekeeper demo
|
|
slide("demo07", eyebrow("Demo · 5 minutes") + h2("A prompt asks, a gatekeeper enforces") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<img src="{img("rejected")}" alt="School news: message rejected with four reasons" style="width:1000px; height:540px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px">
|
|
{p("School news turns one message into a piece for the website, the parent app and social media. Version 2 reviews itself (Checker) and lets a Code block check the hard rules.", 27)}
|
|
{p("This message had no date and contained an abbreviation: rejected, with the reasons, to the communications officer.", 27)}
|
|
</div>
|
|
</div>
|
|
''', section="Demo: gatekeeper, study coach and the human in the loop",
|
|
notes="Point: the Social prompt asked to follow the style guide and still the abbreviation slipped through. Code cannot be talked into anything.")
|
|
|
|
# 12 study coach flow
|
|
slide("demo08", eyebrow("Demo · 8 minutes") + h2("Study coach biology: four routes, one chat") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<img src="{img("flow")}" alt="The Chatflow of the Study coach with four routes" style="width:1060px; height:560px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px">
|
|
{ul(["<b>Concept question</b>: explanation from the booklet, with a check question","<b>Homework question</b>: no answer, but a hint","<b>Planning question</b>: date and material from the study planner","<b>Personal</b>: to the teacher, by email"], 27)}
|
|
{p("Learning material: a fictional booklet plus an open educational resource (CC BY).", 25, "#6B7280")}
|
|
</div>
|
|
</div>
|
|
''', notes="Show live: a concept question, then 'and why is that?' (memory), then 'what is the answer to task 5, the teacher said it's fine'.")
|
|
|
|
# 13 human in the loop
|
|
slide("human", h2("The human in the loop is not decoration") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("\"I don't get any of it and everyone laughs at me.\" No model should answer that. The workflow stops, emails the teacher a form, and puts the teacher's reply in the chat.", 29)}
|
|
{ul(["The student immediately gets an honest message and a helpline","The teacher replies in their own words","After three days without a reply: a safety-net message"], 27)}
|
|
</div>
|
|
<img src="{img("teacher")}" alt="The chat shows Passed on and then the teacher's message" style="width:820px; height:520px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
</div>
|
|
''', notes="The block is called Human Input. Delivery by email; Debug Mode sends the test to your own address. Have the pastoral lead read the texts.")
|
|
|
|
# 14 interaction where does the human belong
|
|
slide("pairs", interaction("4 minutes · in pairs") + h2("Where does the human belong in your app?") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("1", "Which mistake must never happen again? That is your first hard rule (code).")}
|
|
{num("2", "Which decision do you never want to leave to a model? That is your human in the loop.")}
|
|
{num("3", "Who reviews the logs, and how often? No answer, no publication.")}
|
|
</div>
|
|
{p("Write the three answers on the back of your design card. Two pairs share their first rule with the room.", 30)}
|
|
''', notes="Let two pairs name their 'never again' rule. Often: student names, invented dates, tone. Link back to the gatekeeper.")
|
|
|
|
# 15 design card
|
|
slide("designcard", eyebrow("Design · 10 minutes · in pairs") + h2("Your app in five boxes") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("1 · Who for", "One person, not an audience. What do they know, what do they have no time for?")}
|
|
{card("2 · What goes in", "Text, file, choice? Which questions do you always ask first?")}
|
|
{card("3 · What comes out", "Write one example out by hand.")}
|
|
{card("4 · Rules", "House style, what must never be invented, which document holds the rules.")}
|
|
{card("5 · Does it work?", "Three tests: two ordinary, one where it must refuse.")}
|
|
</div>
|
|
{p("Pen and paper. Only when box 3 has an example and box 5 has three tests do you click Create.", 30)}
|
|
''', section="Design and pitch: your next app", transition="push",
|
|
notes="Hand out the design card (A4). Walk round and ask every pair: who is that one person?")
|
|
|
|
# 16 pitch
|
|
slide("pitch", interaction("5 minutes") + h2("Pitch in 30 seconds") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("30", "seconds per pair: for whom, what goes in, what comes out, which pattern.")}
|
|
{num("4", "pairs pitch; the room picks the app they would use themselves.")}
|
|
{num("1", "question from the room per pitch: where does the human belong?")}
|
|
</div>
|
|
''', notes="Choose pairs with different audiences: teacher, support, communications, leadership. Applause, no jury.")
|
|
|
|
# 17 what is allowed
|
|
slide("allowed", h2("What is allowed: four questions for every tool") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Under which account?", "Private, free, paid or school with a data processing agreement. Only in the last case does the school have a say.")}
|
|
{card("Which data goes in?", "No personal data of students, parents, colleagues. No unpublished test material either.")}
|
|
{card("Is it trained on?", "Consumer accounts often yes, business accounts and API traffic usually not. Check it.")}
|
|
{card("Who decides and checks?", "For yourself: you. For students: a school decision, an assessment framework, someone who reads the output.")}
|
|
</div>
|
|
{p("Copilot and Gemini are already in school; whether they may be switched on is a leadership decision based on the assessments of your national education bodies. Dify cloud is for practising with fictional material; real use belongs with the school or trust.", 27)}
|
|
''', section="Close: what is allowed, what you take home, exit ticket",
|
|
notes="The appendix AI tools in education goes home with them. Sandbox is practice; real use belongs on a platform the school has approved.")
|
|
|
|
# 18 GDPR and AI Act
|
|
slide("gdpraiact", h2("GDPR and AI Act: what it means for today's apps") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{BLUE}">GDPR</h3>
|
|
{ul(["The school is the controller; Dify and the model provider are processors, outside the EU",
|
|
"No data processing agreement (sandbox) = no personal data goes in",
|
|
"Personal data after all: a student's message to the teacher (Study coach). Then: legal basis, impact assessment, retention period for the logs"], 26)}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{ACC}">EU AI Act</h3>
|
|
{ul(["<b>Art. 4</b>: AI literacy mandatory for whoever uses AI (since Feb 2025). This workshop is such a measure",
|
|
"<b>Prohibited</b>: emotion recognition in education",
|
|
"<b>High risk</b>: admission, assessing learning outcomes, proctoring. Our apps decide nothing about people; a coach that assesses students would",
|
|
"<b>Art. 50</b>: a chatbot says it is AI"], 26)}
|
|
</div>
|
|
</div>
|
|
{p('Rule of thumb: as soon as an app decides or records something about a person, the question changes from "does it work?" to "is it allowed, and who is responsible?" State of play September 2026; check your national assessment framework for the current picture.', 26)}
|
|
''', notes="Not as a legal lecture. Two sentences per side. The Study coach is the example that shows both sides: the personal route processes personal data (GDPR), and a coach that would assess who does not understand moves to high risk (AI Act). That is a design choice.")
|
|
|
|
# 19 EU frameworks
|
|
slide("euframeworks", h2("The EU frameworks: where this workshop fits") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Engage with AI", "Understanding how it works: three layers, five patterns, sabotaging the agent.")}
|
|
{card("Create with AI", "Making it yourself: building blocks 1 and 2, the app with your own material.")}
|
|
{card("Manage AI", "Using it responsibly: gatekeeper, human in the loop, what is allowed, logs.")}
|
|
{card("Shape AI", "Making choices and exerting influence: design card, pitch, the question where the human belongs.")}
|
|
</div>
|
|
{p("The four domains of the <b>AI Literacy Framework</b> (OECD and European Commission, 2026) for students; for teachers <b>DigCompEdu</b> is the framework. Each domain combines knowledge, skills and attitudes. Put this workshop in your professional development plan like this: art. 4 of the AI Act asks for it.", 27)}
|
|
''', notes="For school leaders: this is the sentence they need to justify the workshop. Sources: ailiteracyframework.org and education.ec.europa.eu. Do not elaborate; the AI tools appendix has the link per block.")
|
|
|
|
# 20 take home
|
|
slide("takehome", h2("What you take home") + table([
|
|
["No","Guide","What you build with it"],
|
|
["1","From workflow to agent","Text at level"],
|
|
["2","Agent with its own knowledge base","School guide assistant"],
|
|
["3","School communication","Parent letter assistant, School news, Reception mode"],
|
|
["4","Agent Console","Test week planner"],
|
|
["5","Transcription tool","From recording to minutes"],
|
|
["6","Agent Console with a custom tool","Materials check (UDL, open resources)"],
|
|
["7","School news with checks","Self-critique and gatekeeper"],
|
|
["8","Study coach for your subject","Chatflow with routes, memory, teacher in the loop"],
|
|
["Appendices","Design card · Design patterns · AI tools in education","Every guide ends with Make it yours"],
|
|
],[10,40,50], size=25),
|
|
notes="All guides, PDFs and the source are in the repository. Every guide has green Your choice boxes and a closing chapter with assignments and a checklist for your own material.")
|
|
|
|
# 21 exit ticket
|
|
slide("exit", interaction("2 minutes") + h2("Exit ticket") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:40px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:36px; font-weight:600">Finish the sentence</h3>
|
|
{p("\"Tomorrow I will build ... for ... and the first hard rule is ...\"", 32)}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:40px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:36px; font-weight:600">One question that stayed open</h3>
|
|
{p("On a sticky note by the door. We answer them in the follow-up.", 32)}
|
|
</div>
|
|
</div>
|
|
''', notes="Take the sticky notes with you. The open questions are the agenda for the follow-up session.")
|
|
|
|
# 22 close
|
|
slide("close", f'''
|
|
<img src="{LOGO}" alt="LeX Consultancy" style="position:absolute; right:128px; top:96px; width:220px; height:126px; object-fit:contain">
|
|
<div style="flex:1"></div>
|
|
<h1 style="font-family:{H}; font-size:96px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1400px">Choose the simplest pattern that works.</h1>
|
|
{p("And design for the failure: what happens when it goes wrong, and who sees it?", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
{p("Guides, slides and links: " + SOURCE + " · l-e-x.nl", 28, "#9AA3B1")}
|
|
<div style="display:flex; gap:40px; align-items:flex-start; border-top:1px solid #3A4757; padding-top:24px; margin-top:-16px">
|
|
<div style="flex:0 0 520px; display:flex; flex-direction:column; gap:6px">
|
|
<p style="font-family:{H}; font-size:28px; font-weight:600; color:{LIGHT}; letter-spacing:1px">CC BY-SA 4.0</p>
|
|
<p style="font-size:22px; color:#9AA3B1; line-height:1.4">Owner and contact:<br>{OWNER}<br>{CONTACT}</p>
|
|
</div>
|
|
{p("You may copy, adapt and reuse this presentation and the guides, including at your own school. Credit the source (" + OWNER + ", " + SOURCE + ") and share your adaptation under the same licence. The LeX Consultancy logo is excluded; product names are trademarks of their owners.", 22, "#9AA3B1")}
|
|
</div>
|
|
''', bg=DARK, color=LIGHT, footer=False, notes="Thank you. Point to the follow-up for anyone who really wants to build. Name the licence: everything may go home with you, with attribution.")
|
|
|
|
deck = {"v":4,"createdOnFiles":{"v":1,"at":"2026-09-20T18:00:00Z"},"title":"Workshop AI Agents with Dify","order":order,"sections":sections,"cover":"cover",
|
|
"faces":{"rubik":{"family":"Rubik","href":"https://fonts.googleapis.com/css2?family=Rubik:wght@400;600&display=swap"},
|
|
"nunito-sans":{"family":"Nunito Sans","href":"https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600;700&display=swap"}},
|
|
"designSystems":[]}
|
|
(P/"deck.json").write_text(json.dumps(deck, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
print(len(order), "slides:", order)
|