Project: Impromptu speaking


A while back I joined a public speaking course. Each week the group was split in two. One half would present on a topic of their choosing, usually within a specific category of speech. If you weren’t speaking, you were expected to give constructive feedback. That kept everyone engaged and pushed us to pay attention to verbal delivery, body language, and the structure of each speech.

People say you learn the most when you’re outside your comfort zone. I definitely learnt a lot. There was one part of each session, though, that always had me on edge: impromptu speaking. Picture this. The facilitator announces that you’re up next. As you walk to the front, you’re handed a topic: “Should college education be free?” The timer starts and you begin. I’d be lying if I said I ever felt comfortable doing it, but each session felt a little less daunting.

Why I Built Speech Topics

At the time I was taking the speech course, I was also dabbling with Cloudflare. When it comes to new tech, I’m a bit of a magpie, especially when it introduces a novel way of solving a problem. I liked how Cloudflare was flipping the deployment model on its head, and I bought into the idea that the network between users and datacenters would eventually become the bottleneck. I also liked the idea of building applications close to end users, where responses could be really fast. Between Workers, D1, and Workers AI, I had a stack I wanted to try.

So I had a problem worth solving and a bit of tech I wanted to use. It was time to start chipping away.

Keeping the Idea Small

Often, simplification is the answer. Want a great user experience? Simplify. Want a performant app? Simplify.

It’s easy to add extra crap, and it’s often a mistake. I wanted to focus on building something simple that I would actually use. This is the design I landed on.

Speechtopics<<Some speech topic goes on this line here>>

What the App Actually Needed to Do

Once I had a rough idea of the app, I dug into the Cloudflare docs to understand how inference worked with Workers AI. It turned out to be pretty straightforward: define a binding so the worker can talk to Workers AI, then call .run(...).

const response = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
    prompt: "What is the origin of the phrase Hello, World",
});

An early version of the prompt that started giving me decent results looked like this:

const result = await c.env.AI.run("@cf/meta/llama-4-scout-17b-16e-instruct", {
    max_tokens: 50,
    temperature: 2.5,
    frequency_penalty: 1,
    messages: [
        { role: "system", content: "You give creative topics for impromptu speaking. You only respond with the topic when asked. Without quotes. In the format of a question"},
        { role: "user", content: "Give me a one liner topic in the format of a question"}
    ],
})

The basic integration was simple. Getting the output to behave the way I wanted took a bit more fiddling:

  • max_tokens: I kept this low so usage wouldn’t go through the roof.
  • temperature and frequency_penalty: These helped push the model toward more varied responses. For whatever reason, when I moved to Llama Scout from an earlier model, I started seeing more duplicate outputs. That might be useful in other contexts, but here I wanted creativity and variety.

What Worked, and What Didn’t

Most of the issues I ran into were surprisingly around prompting. Formatting wasn’t always reliable. Sometimes the model would return a clean topic, and other times it would spit out random ASCII characters. Even with the same prompt, there was no guarantee it would give me exactly what I wanted every time.

Sometimes the model went too far in the other direction and repeated itself like a parrot. Tuning the temperature and frequency penalty helped, but it still took some trial and error.

Adding a Bit Extra

Once the basics were working, I decided to add a little extra. I wanted the app to send topics by email. The idea was simple: a user signs up with their email address, and the app sends them a topic each day. It would look something like this:

SubscribeEmailJoin

What sounded like a small extra feature led me down a rabbit hole around sending emails safely from a worker through AWS SES, and making sure those emails didn’t get treated as spam. I didn’t want to worry about credentials leaking or an email provider blocking me for sending too many messages.

My biggest takeaway from the email subscription feature was how complicated email delivery becomes once you want to do it properly. The end result looked alright, though.

Email from speechtopics

What I Learnt

Working with Cloudflare Workers AI was a great experience. I found it easy to use, and the documentation made it simple to get started. With a bit of tweaking, the model could generate some genuinely creative topics for impromptu speaking.

I also learnt a lot about prompting. Getting the best results was mostly a process of trial and error, but with enough experimentation I could steer the model toward the kind of output I wanted.

Workers D1 was also a good fit for this project. For something small like this, it felt simple and effective. I probably wouldn’t reach for it first if the data model were much more complex, but for a lightweight project it worked well.

More than anything, this project reminded me that small apps are a good way to learn quickly. I got to practice impromptu speaking, build something I actually wanted, and spend time with a stack I was curious about. Overall win.