Chapter 4 · Part 2

Mouth: text to speech

Every desktop operating system comes with a text-to-speech voice built in. It won't win awards, but it's free, works offline and starts instantly, which makes it ideal for building the loop. (Chapter 6 covers swapping in a more natural voice.)

One function, three platforms

Each OS has its own tool, so speak() picks the right one:

voice_agent.py (continued)
import platform
import subprocess

def speak(text):
  system = platform.system()
  if system == "Darwin":                      # macOS
      subprocess.run(["say", text])
  elif system == "Linux":
      subprocess.run(["espeak-ng", text])
  else:                                       # Windows
      import pyttsx3
      engine = pyttsx3.init()
      engine.say(text)
      engine.runAndWait()
  • macOS has the say command. Try say hello in a terminal.
  • Linux uses espeak-ng, which you installed in chapter 1. It sounds robotic, but it's reliable.
  • Windows uses pyttsx3, a Python wrapper around the built-in Windows voices.

subprocess.run waits until the speech has finished before it returns, so the agent won't start listening again while it's still talking. We'll rely on that.

⚠️Why not pyttsx3 everywhere?

pyttsx3 claims to work on every platform, and it's tempting to use it for all three. But when we tested it on macOS, it spoke the first reply and then went silent: every later runAndWait() produced empty audio. The say command doesn't have that problem, so macOS gets say.

Close the loop

You now have all three parts. The main loop is five steps: listen, check for a goodbye, think, speak, repeat.

voice_agent.py (replace the test at the bottom)
if __name__ == "__main__":
  print("Voice agent ready. Say 'goodbye' to quit.")
  while True:
      heard = listen()
      if not heard:
          continue
      print(f"You: {heard}")
      if "goodbye" in heard.lower():
          speak("Goodbye!")
          break
      answer = think(heard)
      print(f"Claude: {answer}")
      speak(answer)

Run it and have a conversation:

Voice agent ready. Say 'goodbye' to quit.

Press Enter, then speak...
Listening... press Enter when you're done.
You: What's the tallest mountain in Japan?
Claude: That's Mount Fuji, at about three thousand seven hundred seventy-six meters.

Press Enter, then speak...
Listening... press Enter when you're done.
You: When did it last erupt?
Claude: Mount Fuji last erupted in seventeen oh seven, in what's called the Hoei eruption.

Notice the second question: "it" works because the history carries the first turn. You've built a voice agent. Ears, brain and mouth, in under a hundred lines of Python.

But listen to the pause

Use it for a minute and one thing stands out. After you press Enter there's a long silence before it speaks, and the silence grows with longer answers. That's because the code waits for Claude's entire reply before saying a single word.

A person doesn't work like that. They start talking once they know how the sentence begins. Next chapter, so will your agent.