本文的焦点是说唱音乐。
说唱音乐能让人热泪盈眶、引吭高歌、随之起舞,许多人也通过它学习了英语。许多人可能还记得第一次尝试理解Biggie和Tupac歌词的经历,从中了解到布鲁克林和加利福尼亚的故事、独特的俚语、生活中的挣扎,以及这些歌曲背后蕴含的深刻信息。
说唱音乐是绝佳的例证,展示了纯粹的歌词如何将音乐升华为一件艺术品。一些顶级的嘻哈歌曲,其构成可能仅仅是一个采样、几段鼓点,以及一个人在4/4拍的节奏上诗意地“说话”。
对于那些熟谙语言的人来说,阅读歌词或许并非必需;但对于非英语母语者,像Genius这样的工具极大地简化了理解过程。Genius是一个在线歌词音乐收集平台:如果需要一首歌的歌词,Genius是最佳选择。有了Genius,即使不完全理解Biggie所唱的内容,也可以阅读歌词,然后通过谷歌搜索和翻译来领会。不仅如此,当说唱歌手(或任何歌手)做出难以理解的特定引用时,Genius还会通过侧边栏片段提供清晰的解释。

图片来源:Genius [链接]
那么,Genius是如何做到这些的呢?它又是如何生成这些富有洞察力、实时更新且实用的注释片段的?
追溯到2009年Genius诞生之初,这类注释片段主要通过人工方式生成:用户可以自行添加评论,部分内容可能由版主进行审核,流程相对简单。
然而,如今,凭借我们所拥有的强大AI技术,整个流程可以变得更加顺畅和高效。尽管智能代理AI可能无法完全取代音乐专家的工作(原因众多),但它无疑能为具备领域知识的人员提供辅助,通过提供恰当的工具来帮助他们创建这些注释片段。
而这正是本文将要探讨和实践的内容。 
本文将利用Streamlit、Python和OpenAI构建一个极其简洁的Web应用,它能根据歌曲歌词,提供文本含义的澄清。更具体地说,该应用将允许用户就文本内容提出问题,使Genius的理念更具“互动性”。此外,AI代理还将获取网络搜索结果,以便大型语言模型(LLM)在生成答案时参考其他歌曲和相关资源。
为了增加趣味性(以及出于版权考量),本文还将利用另一个AI代理创建自定义歌曲。
令人兴奋!现在开始构建! 
如果对实验的最终成果感兴趣,可直接跳至第三部分。若希望一同构建这一精彩应用,请从下一部分开始阅读。
1. 系统设计
系统设计图如下所示:

图片由作者制作
具体来说,系统具备以下功能:
- 用户能够利用AI代理从头开始创作歌曲。此功能为可选,已有一批预生成的歌曲可供使用。
- 用户可以选择文本的某个部分并提出问题。
- AI代理能够以“Genius”风格生成回答。
此外,AI代理还配备了:
a. “内部歌曲知识”,包含歌曲的提取特征/元数据(例如,氛围、标题、主题等)。
b. 网络搜索工具,允许代理通过网络搜索歌曲并为问题添加上下文信息。
这种设计具有良好的模块化特性,这意味着可以轻松地添加组件以增加系统的复杂性。例如,如果需要使歌曲生成功能更加精细,可以轻松调整歌曲生成代理,而无需对代码的其他部分进行大规模改动。
现在,将逐步构建该系统。 
2. 代码实现
2.1 环境设置
完整的代码可在以下GitHub仓库中找到
代码项目的结构如下:
- 歌词生成器,对应文件为
generate_madeup_lyrics.py - 歌词问答模块,对应文件为
qa.py - Web应用主体(通过Streamlit运行的文件),即
lyricsgpt_app.py - 一系列辅助工具文件(如
utils.py、constants.py、config.py等)
所有数据将存储在data文件夹中。

图片由作者制作
为避免冗余细节,本文仅描述该结构中的主要组件。首先介绍核心部分:Streamlit应用。
2.2 Streamlit 应用
请注意:在使用Streamlit应用以及任何需要LLM生成的部分时,都需要准备好OpenAI API密钥。在Streamlit应用之外,最简单的设置方法是使用操作系统环境变量:os.getenv(“OPENAIAPIKEY”) = “api_key”。在应用内部,系统会提示用户复制粘贴密钥。
该应用通过以下命令运行:
streamlit run lyricsgpt_app.py
其中,lyricsgpt_app.py的代码内容如下:
from future import annotations
import os
from pathlib import Path
from typing import Dict, List
import streamlit as st
from lyricsgpt import (
GenerationConfig,
SONGPROMPTS,
answerquestion,
createlyricsdataset,
getclient,
loadsongsfromjson,
)
APP_TITLE=”Use LLM to explore the lyrics of your favorite song”
@st.cachedata(showspinner=False)
def loaddataset(path: Path) ->List[Dict[str, str]]:
if path.exists():
return loadsongsfromjson(path)
return []
def main() ->None:
st.setpageconfig(pagetitle=”LyricsGPT”, layout=”centered”)
st.title(APPTITLE)
st.caption(
“LyricsGPT lets you explore AI-generated lyrics for a curated set of song “
“concepts. Provide your OpenAI API key to regenerate lyrics or ask new questions.”
)
config=GenerationConfig()
datasetpath=config.output_path
apikeyinput=st.textinput(
“OpenAI API Key”,
value=os.getenv(“OPENAIAPIKEY”, “”),
type=”password”,
help=(
“Enter your API key to access lyric regeneration and Q&A. Leave blank to “
“browse previously generated lyrics only.”
),
) apikey=apikeyinput.strip()
dataset=loaddataset(datasetpath)
if not dataset:
st.warning(
“No lyrics dataset found. Provide an OpenAI API key above and press”
” ‘Regenerate lyrics with LLM’ to create one.”
)
return
songtitles= [record[“title”] for record in dataset]
selectedtitle=st.selectbox(“Select a song”, songtitles)
selectedsong=next(record for record in dataset if record[“title”] ==selectedtitle)
st.markdown(f”## {selected_song[‘title’]}”)
st.divider()
st.markdown(“### Lyrics”)
st.markdown(selected_song[“lyrics”])
st.divider()
st.markdown(“### Ask a question about a specific excerpt”)
if not api_key:
st.info(“Enter your OpenAI API key above to ask new questions about the lyrics.”)
st.stop()
st.write(
“Highlight the relevant lines in the lyrics above, copy them, and paste “
“into the excerpt box below before submitting your question.”
)
excerpt=st.textarea(
“Selected excerpt”,
placeholder=”Paste the lyric lines you want to ask about…”,
height=120,
)
question=st.textarea(
“Your question”,
placeholder=”What would you like to know about this excerpt?”,
height=120,
)
colsubmit, coloptions=st.columns([1, 2])
allowweb=coloptions.checkbox(“Allow supplementary web search”, value=True)
maxresults=coloptions.slider(“Max search results”, 1, 5, 3, disabled=not allow_web)
submitclicked=colsubmit.button(“Get answer”, disabled=not apikey)
if submitclicked:
if not question.strip():
st.error(“Please enter your question.”)
else:
client=getclient(apikey)
with st.spinner(“Thinking…”):
result=answerquestion(
client,
config,
song=selectedsong,
excerpt=excerpt,
question=question,
allowweb=allowweb,
maxsearchresults=max_results,
)
st.subheader(“Answer”)
st.markdown(result[“answer”])
if result.get(“searchresults”):
st.subheader(“Research references”)
for res in result[“searchresults”]:
st.markdown(f”{res[‘title’]} — {res[‘snippet’]}”)
st.markdown(f”{res[‘url’]}“)
st.write(“”)
elif result.get(“searcherror”):
st.warning(f”Web search failed: {result[‘searcherror’]}”)
st.info(f”Dataset source: {dataset_path}“)
if name ==”main“:
main()
这段代码虽然较长,但结构清晰直观:每一行都代表Web应用的一个组成部分:
- Web应用的标题
- 歌曲选择器,允许用户从数据文件夹中选择歌词(后续将详细介绍)
- 文本块输入框,用户可在此复制粘贴感兴趣的歌词片段
- 问题输入框,用户可就上述选定的歌词片段提出问题
- 答案显示框,LLM将在此处回答问题。
然而,这“仅仅”是执行层;真正的核心工作由lyricsgpt模块及其对象/函数完成。接下来,将介绍其中一些关键部分!
2.3 歌词生成器
此部分为可选功能,并未集成到侧重于Genius风格歌词解读的Web应用中。如果仅对歌词解读功能感兴趣,可以跳过此节。但必须指出:此部分功能相当酷炫。
对于歌词生成功能,其机制十分简单:
- 用户提供一个标题、一种氛围、一个主题,以及一个隐藏的“反转”信息。
- 系统便能生成相应的歌词。
例如:
[
{
“title”: “Glitter in the Rearview”,
“theme”: “letting go of a high-profile love story”,
“vibe”: “late-night highway pop ballad”,
“twist”: “a hidden reference to a fleet number 13”,
}
]
生成结果如下:
第一节
城市灯火之下,细语舞动天际线,我们彻夜闪耀,如同午夜星空中的钻石。但爱只是一场高楼之梦,建立在脆弱的缝隙之上,现在我驶离高速公路,留下回声在其中。
副歌
后视镜中尽是点点星光,逐渐褪成蓝色调,每一次欢笑和泪水,都随之飘远。当前方道路展开,我让记忆沉淀,后视镜中尽是点点星光,放手你我。
[更多LLM生成文本]
尾声
我驶入寂静,那里过往无法误解,后视镜中尽是点点星光,以及你我的碎片。后视镜中尽是点点星光,一段爱情故事过早落幕。
效果很不错吧?实现此功能的代码如下:
