A French voice assistant for digital signage

A Go service I built to let retail staff create digital-signage content by speaking in French.

My role: Sole author. I wrote 109 of 110 commits over eight months.

October 2025 to May 2026

Why voice

This feature was made for bakers, butchers and fishmongers who update the screens in their shops every day. Most of them will not open a content management system and go through several forms just to change a product or a price.

The idea was to let them talk to the system in French and get a finished product slide back from that conversation.

How it works

I first prototyped the idea in Python on a Raspberry Pi, with custom voice-activity detection. Once it worked, I rebuilt it as a Go service using OpenAI’s Realtime API over WebSocket for speech-to-speech, and Chat Completions with structured JSON outputs when text is enough.

The main part is a phase-based conversation orchestrator of roughly 4,400 lines. It moves each conversation through application selection, product identification, field completion and confirmation. Speech and text both use the same orchestrator, and partial results are streamed through Server-Sent Events so the interface can show what is happening.

Application selection is driven by YAML files. Each theme has its own trigger words, with an AI fallback when nothing matches. I also created the product catalogs behind it, covering seven different retail sectors.

Problems I had to solve

French product names. “Filet de bar” and “bar en filet” are the same product, but a basic string comparison does not know that. I used Unicode NFD normalization for accent-insensitive comparisons, then added specific handling for the way French compound product names can be written.

Conversation memory. The first version stored conversations in an unbounded map and never removed them. It looked fine during testing, but eventually caused memory exhaustion in production. I replaced it with a custom LRU cache.

API access. Each subsystem has its own OpenAI key scope, and API keys are compared in constant time.

What it became

Over time, this became the company’s shared AI backend. I moved the customer chatbot into it, including its session store, rate limiting, email hand-off, a 241-line French system prompt and around 800 lines of tests. I also added AI content moderation for screens that play in public.

The moderation is deliberately fail-open. We decided that an outage from the moderation provider should not take every screen down with it.

In numbers

The service is roughly 12,400 lines of Go plus 1,800 lines of tests, with more than fifteen endpoints across four route groups.

Get in touch

I am available for remote freelance work. The easiest way to reach me is by email.