import openai
import os
from pydantic import BaseModel
class Note(BaseModel):
note: str # scientific pitch notation, e.g. "C4", "F#3", "Bb5"
duration: float # duration in seconds
class Melody(BaseModel):
notes: list[Note]
OPENROUTER_API_KEY = os.environ["OPENROUTER_API_KEY"]
client = openai.OpenAI(
base_url='https://openrouter.ai/api/v1',
api_key=OPENROUTER_API_KEY
)
MODEL = "openai/gpt-5-nano" #@param ["openai/gpt-5-nano"]
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[
{"role": "system", "content": "You are a music teacher. Return melodies as sequences of notes using scientific pitch notation (e.g. C4, F#3, Bb5) with durations in seconds."},
{"role": "user", "content": "Generate the melody for Happy Birthday."},
],
response_format=Melody
)
melody = response.choices[0].message.parsed
print(melody)Structured Output
In this next example, we use structured output to generate the notes for Happy Birthday and then play them via the audio library.
And now, let’s play the notes!
import audio
audio.play_notes([(n.note, n.duration) for n in melody.notes]) You just used AI to generate and play a melody!
What other song would you want the AI to generate? Why was structured output necessary for this example. What do you think would have gone wrong if the model had returned a plain text response instead?
{ “question_type”: “freeform”, “question”: “What notation system is used to represent note names like ‘C4’ or ‘F#3’?”, “answer”: “scientific pitch notation”, “submitted_answer”: “” }
{ “question_type”: “multiple_choice”, “question”: “What does the duration field in the Note class represent?”, “options”: [ { “key”: “a”, “text”: “The volume of the note” }, { “key”: “b”, “text”: “The pitch of the note” }, { “key”: “c”, “text”: “How long the note is played, in seconds” }, { “key”: “d”, “text”: “The instrument that plays the note” } ], “answer”: “c”, “submitted_answer”: “” }
{ “question_type”: “true_false”, “question”: “The Melody class contains a list of Note objects.”, “answer”: “True”, “submitted_answer”: “” }