lyricslm 0.7.0
Language Model Applied to Song Lyrics
Requires Python: <3.10,>=3.9

<div align="center">
  <h1>♬&nbsp;&nbsp;lyricslm&nbsp;&nbsp;✧</h1>
  <div><img src="https://github.com/user-attachments/assets/4f17d3f9-835c-42c7-aadf-cb7288daf83c" alt="lyricslm Logo" width="300"></div>
  <br>
  <p><em>Language Models Applied to Song Lyrics</em></p>
  <hr>
</div>

#### Requirements

- [uv](https://docs.astral.sh/uv/) for managing Python environments
- [ffmpeg](https://ffmpeg.org/) for processing audio files
- Genius CLIENT ACCESS TOKEN ([API docs](https://docs.genius.com/))
- HuggingFace write TOKEN ([API docs](https://huggingface.co/docs/hub/security-tokens))

#### Setup

1. Clone and cd to the repository:

```
git clone https://github.com/S1M0N38/lyricslm.git
cd lyricslm
```

2. Set up the virtual environment:

```
uv python install 3.9
uv venv
source .venv/bin/activate
uv sync
```

3. Copy `.env.example` to `.env` and fill in the values:

```
cp .env.example .env
```

______________________________________________________________________

## 📂 Database

<div align="center">

```mermaid
graph LR
    A[artists.txt]
    A --> B[artist_ids]
    B --> C[song_ids]
    B --> D[artists]
    C --> F[annotations]
    E --> G[lyrics]
    C --> E[songs]
    H[cookies.txt]
    H --> I[audio]
    E --> I
```

</div>

- Create a txt file like `data/examples/artist.example.txt` and add one artist per line.
- Generate a `cookies.txt` following the instructions in `data/examples/cookies.example.txt`.

1. Update `artist_ids` and `song_ids`

```
python -m lyricslm.database artist_ids --artists-txt data/examples/artists.example.txt
python -m lyricslm.database song_ids
```

2. Update `artists`, `songs` and `annotations`

```
python -m lyricslm.database artists
python -m lyricslm.database songs
python -m lyricslm.database annotations
```

3. Update `lyrics`

```
python -m lyricslm.database lyrics -c 1
```

4. Update `audio`

```
python -m lyricslm.database audio \
  --cookies-txt data/examples/cookies.example.txt \
  --post-process
```

Run `python -m lyricslm.database -h` for options documentation:

```
usage: python -m lyricslm.database [-h] [--log-level LOG_LEVEL]
                        [--max-concurrent-requests MAX_CONCURRENT_REQUESTS]
                        [--batch-size BATCH_SIZE]
                        [--sleep-between-batches SLEEP_BETWEEN_BATCHES] [--post-process]
                        [--artists-txt ARTISTS_TXT] [--cookies-txt COOKIES_TXT]
                        {artist_ids,song_ids,artists,songs,annotations,lyrics,audio,all}

Populate the database with data from the internet

positional arguments:
  {artist_ids,song_ids,artists,songs,annotations,lyrics,audio,all}
                        the type of update to perform (e.g., artist_ids, songs, etc.)

optional arguments:
  -h, --help            show this help message and exit
  --log-level LOG_LEVEL
                        log level for httpx
  --max-concurrent-requests MAX_CONCURRENT_REQUESTS, -c MAX_CONCURRENT_REQUESTS
                        max concurrent requests
  --batch-size BATCH_SIZE, -b BATCH_SIZE
                        batch size for processing
  --sleep-between-batches SLEEP_BETWEEN_BATCHES, -s SLEEP_BETWEEN_BATCHES
                        sleep between batches (seconds)
  --post-process, -p    post process the audio files
  --artists-txt ARTISTS_TXT
                        path to the artists.txt file (required for artist_ids update_type)
  --cookies-txt COOKIES_TXT
                        path to the cookies.txt file (required for audio update_type)
```

## 📄 NLP

<div align="center">

```mermaid
graph LR
    A[database] --> B
    C[task] --> B[messages]
```

</div>

After the database has been populated, we can make use of that data to generate messages for the LLM.
In order to generate messages in a well-formatted way, including data from the database, we can make use of [jinja2](https://jinja.palletsprojects.com/en/3.1.x/templates/) templates (one template per message).

- Templates are defined in `src/lyricslm/nlp/templates`.
- Data is injected into the templates by `task` functions defined in `src/lyricslm/nlp/messages.py`.

For example, to generate messages for the `song_summaries` task, run:

```
python -m lyricslm.nlp song_summaries
```

Run `python -m lyricslm.nlp -h` for options documentation:

```
usage: python -m lyricslm.nlp [-h] [--example EXAMPLE] [--save] {<task1>,<task2>,...}

Generate messages using predefined tasks

positional arguments:
  {<task1>,<task2>,...}
                        NLP tasks defined in lyricslm/nlp/messages.py

optional arguments:
  -h, --help            show this help message and exit
  --example EXAMPLE     print the example-th request to stdout, 0 for none
  --save-messages       save the messages to a jsonl file
```

## 📝 Inference

<div align="center">

```mermaid
graph LR
    A[template] --> B
    C[messages] --> B
    B[requests] --> D
    D[provider] --> E[responses]

```

</div>

We can now process the generated messages with LLM. We have to specify the path to the messages JSONL file with `--messages-path` and combine it with a `template`. A (request) template is a JSON file included in `lyricslm/inference/templates` which specifies all the body of a request to the `/chat/completion` endpoint (except for the list of messages). It follows OpenAI's ["Create chat completion"](https://platform.openai.com/docs/api-reference/chat/create) specification.

Messages are merged with the template to create requests which can be saved in the format of [OpenAI Batch Requests](https://platform.openai.com/docs/guides/batch#getting-started) as a JSONL file.

Requests can then be processed with the `provider` if the flag `--save-responses` is included. Requests are saved in the format of [OpenAI Batch Responses](https://platform.openai.com/docs/guides/batch#5-retrieving-the-results).
Alternatively, you can simply save the requests and manually process the JSONL by uploading it to OpenAI or Mistral as batch requests. VLLM is also capable of processing requests in this format ([Batch Offline Inference](https://github.com/vllm-project/vllm/blob/main/examples/offline_inference_openai.md)).

For example, to perform inference over the first 5 `song_summaries` prompts using OpenAI API, run:

```
python -m lyricslm.inference openai 001 \
  --messages-path $DATA_PATH/nlp/messages/song_summaries/song_summaries.[datetime].jsonl \
  --save-requests --save-responses --example 1 --limit 5
```

It will generate:

- a requests file in `$DATA_PATH/inference/requests/openai/001/song_summaries.[datetime].json`
- a responses file in `$DATA_PATH/inference/responses/openai/001/song_summaries.[datetime].jsonl`.

Run `python -m lyricslm.inference -h` for options documentation:

```
usage: python -m lyricslm.inference [-h] [--messages-path MESSAGES_PATH]
                                    [--save-requests] [--save-responses]
                                    [--example EXAMPLE]
                                    {openai} {001,002,...}

Generate requests, send them to the provider and get the responses

positional arguments:
  {openai}              provider used to process requests
  {001,002,...}         template used to generate the requests

optional arguments:
  -h, --help            show this help message and exit
  --messages-path MESSAGES_PATH
                        path to the jsonl messages file
  --save-requests       save the requests to a jsonl file
  --save-responses      save the responses to a jsonl file
  --example EXAMPLE     print the example-th request to stdout, 0 for none
  --limit LIMIT         limit the number of requests to process, 0 for all
```

## 🧠 Training

*Work in progress ...*