artifacts: implement musicbrainz tooling
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -10,7 +12,7 @@ pub struct BandcampQueryArgs {
|
||||
|
||||
impl Into<Artifact> for bandcamp::Artist {
|
||||
fn into(self) -> Artifact {
|
||||
Artifact::Artist(Artist { name: self.name, bio: self.bio, location: self.location, sources: vec![SourceID::Bandcamp(self.id)] })
|
||||
Artifact::Artist(Artist { name: self.name, bio: self.bio, location: self.location, sources: HashSet::from([SourceID::Bandcamp(self.id)])})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +24,7 @@ impl Into<Artifact> for bandcamp::Album {
|
||||
artist: self.band.name,
|
||||
credits: self.credits,
|
||||
release_date: Some(self.release_date),
|
||||
sources: vec!{SourceID::Bandcamp(self.id)}
|
||||
sources: HashSet::from([SourceID::Bandcamp(self.id)])
|
||||
})
|
||||
}
|
||||
}
|
||||
+22
-5
@@ -1,4 +1,4 @@
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{collections::HashSet, process::{Command, Stdio}};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -22,11 +22,17 @@ struct BeetsTrack {
|
||||
genres: Option<Vec<String>>,
|
||||
label: Option<String>,
|
||||
title: String,
|
||||
year: u32
|
||||
year: u32,
|
||||
mb_trackid: Option<String>
|
||||
}
|
||||
|
||||
impl Into<Artifact> for BeetsTrack {
|
||||
fn into(self) -> Artifact {
|
||||
let sources = if let Some(mbid) = self.mb_trackid {
|
||||
HashSet::from([SourceID::Beets, SourceID::Musicbrainz(mbid)])
|
||||
} else {
|
||||
HashSet::from([SourceID::Beets])
|
||||
};
|
||||
Artifact::Track(Track {
|
||||
title: self.title,
|
||||
label: self.label,
|
||||
@@ -35,7 +41,7 @@ impl Into<Artifact> for BeetsTrack {
|
||||
album: Some(self.album),
|
||||
artist: Some(self.artist),
|
||||
bpm: None,
|
||||
sources: vec![SourceID::Beets]
|
||||
sources
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,25 +49,36 @@ impl Into<Artifact> for BeetsTrack {
|
||||
impl BeatsQueryArgs {
|
||||
pub fn execute(self) -> Result<Vec<Artifact>, ()> {
|
||||
let mut beets_cmd = Command::new("beet");
|
||||
beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist"]);
|
||||
beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist,mb_trackid"]);
|
||||
let mut valid = false;
|
||||
if let Some(artist) = self.artist {
|
||||
beets_cmd.arg(format!("artist:{}", artist));
|
||||
valid = true;
|
||||
}
|
||||
if let Some(genre) = self.genre {
|
||||
beets_cmd.arg(format!("genre:{}", genre));
|
||||
valid = true;
|
||||
}
|
||||
if let Some(album) = self.album {
|
||||
beets_cmd.arg(format!("album:{}", album));
|
||||
valid = true;
|
||||
}
|
||||
if let Some(title) = self.title {
|
||||
beets_cmd.arg(format!("title:{}", title));
|
||||
valid = true;
|
||||
}
|
||||
if let Some(year) = self.year {
|
||||
beets_cmd.arg(format!("year:{}", year));
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if !valid {
|
||||
log::warn!("Tried to execute an empty beets query");
|
||||
return Err(())
|
||||
}
|
||||
|
||||
log::debug!("Executing beets: {:?}", beets_cmd);
|
||||
if let Ok(output) = beets_cmd.stdout(Stdio::piped()).spawn().unwrap().wait_with_output() {
|
||||
if let Ok(output) = beets_cmd.stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap().wait_with_output() {
|
||||
match serde_json::from_str::<Vec<BeetsTrack>>(str::from_utf8(&output.stdout).unwrap()) {
|
||||
Ok(track) => Ok(track.into_iter().map(|t| { t.into()}).collect()),
|
||||
Err(err) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use sqlite::OpenFlags;
|
||||
|
||||
use crate::artifacts::{Album, Artifact, Artist, SourceID, Track};
|
||||
@@ -39,20 +41,20 @@ impl MixxxDB {
|
||||
album: Some(album.into()),
|
||||
title: title.into(),
|
||||
bpm: Some(bpm),
|
||||
sources: vec![SourceID::Mixxx],
|
||||
sources: HashSet::from([SourceID::Mixxx]),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
ret.push(Artifact::Album(Album {
|
||||
artist: artist.into(),
|
||||
title: album.into(),
|
||||
sources: vec![SourceID::Mixxx],
|
||||
sources: HashSet::from([SourceID::Mixxx]),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
ret.push(Artifact::Artist(Artist {
|
||||
name: artist.into(),
|
||||
sources: vec![SourceID::Mixxx],
|
||||
sources: HashSet::from([SourceID::Mixxx]),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
|
||||
+33
-10
@@ -1,13 +1,17 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod bandcamp;
|
||||
pub mod mixxx;
|
||||
pub mod beets;
|
||||
pub mod musicbrainz;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SourceID {
|
||||
Bandcamp(u64),
|
||||
Musicbrainz(String),
|
||||
Mixxx,
|
||||
Beets
|
||||
}
|
||||
@@ -20,7 +24,7 @@ pub struct Artist {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub location: Option<String>,
|
||||
|
||||
pub sources: Vec<SourceID>
|
||||
pub sources: HashSet<SourceID>
|
||||
}
|
||||
|
||||
impl PartialEq for Artist {
|
||||
@@ -48,7 +52,7 @@ pub struct Album {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub release_date: Option<DateTime<Utc>>,
|
||||
|
||||
pub sources: Vec<SourceID>
|
||||
pub sources: HashSet<SourceID>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
@@ -68,7 +72,7 @@ pub struct Track {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bpm: Option<f64>,
|
||||
|
||||
pub sources: Vec<SourceID>
|
||||
pub sources: HashSet<SourceID>
|
||||
}
|
||||
|
||||
impl PartialEq for Track {
|
||||
@@ -109,28 +113,47 @@ macro_rules! merge_fields {
|
||||
($this:tt, $that:tt, $($fields:tt),+) => {
|
||||
$(
|
||||
merge_fields!($this, $that, $fields);
|
||||
|
||||
for src in &$that.sources {
|
||||
$this.sources.insert(src.clone());
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl Artifact {
|
||||
pub fn merge(&mut self, other: Artifact) {
|
||||
impl Merge for Artifact {
|
||||
fn merge(&mut self, other: Self) {
|
||||
if *self != other {
|
||||
return;
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Artifact::Track(this_track), Artifact::Track(that_track)) => {
|
||||
(Self::Track(this_track), Self::Track(that_track)) => {
|
||||
merge_fields!(this_track, that_track, album, label, year, artist, bpm);
|
||||
// FIXME: genres
|
||||
},
|
||||
(Artifact::Album(this_album), Artifact::Album(that_album)) => {
|
||||
(Self::Album(this_album), Self::Album(that_album)) => {
|
||||
merge_fields!(this_album, that_album, about, credits, release_date);
|
||||
},
|
||||
(Artifact::Artist(this_artist), Artifact::Artist(that_artist)) => {
|
||||
(Self::Artist(this_artist), Self::Artist(that_artist)) => {
|
||||
merge_fields!(this_artist, that_artist, bio, location);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Merge + PartialEq> Merge for Vec<M> {
|
||||
fn merge(&mut self, other: Self) {
|
||||
for artifact in other {
|
||||
if let Some(merge_target) = self.iter_mut().find(|a| { **a == artifact }) {
|
||||
merge_target.merge(artifact);
|
||||
} else {
|
||||
self.push(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Merge {
|
||||
fn merge(&mut self, other: Self);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use musicbrainz_rs::entity::recording::Recording;
|
||||
use musicbrainz_rs::prelude::*;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::artifacts::{Album, Artifact, Artist, SourceID, Track};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct MusicbrainzQueryArgs {
|
||||
pub mb_ids: Vec<String>
|
||||
}
|
||||
|
||||
pub async fn search_artifacts(query: MusicbrainzQueryArgs) -> Result<Vec<Artifact>, musicbrainz_rs::ApiEndpointError> {
|
||||
let mut ret = vec![];
|
||||
for mbid in query.mb_ids {
|
||||
let track = Recording::fetch()
|
||||
.id(&mbid)
|
||||
.with_releases().with_artists().with_annotations().execute_async().await?;
|
||||
|
||||
for release in track.releases.unwrap_or_default() {
|
||||
log::debug!("Found new release: {:?}", release);
|
||||
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone();
|
||||
ret.push(Artifact::Album(Album {
|
||||
title: release.title.clone(),
|
||||
artist: first_artist.name.clone(),
|
||||
about: release.annotation,
|
||||
sources: HashSet::from([SourceID::Musicbrainz(release.id.clone())]),
|
||||
..Default::default()
|
||||
}));
|
||||
ret.push(Artifact::Track(Track {
|
||||
album: Some(release.title),
|
||||
title: track.title.clone(),
|
||||
artist: Some(first_artist.artist.name.clone()),
|
||||
sources: HashSet::from([SourceID::Musicbrainz(release.id.clone())]),
|
||||
..Default::default()
|
||||
}));
|
||||
ret.push(Artifact::Artist(Artist {
|
||||
name: first_artist.name,
|
||||
bio: first_artist.artist.annotation,
|
||||
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
|
||||
sources: HashSet::from([SourceID::Musicbrainz(release.id)]),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
+47
-36
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use async_openai::{Client, config::OpenAIConfig, types::chat::{ChatCompletionMessageToolCalls, ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestToolMessageArgs, ChatCompletionTool, ChatCompletionTools, CreateChatCompletionRequestArgs, FinishReason, FunctionObjectArgs, ResponseFormat, ResponseFormatJsonSchema}};
|
||||
use bandcamp::SearchResultItem;
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Serializer, ser::CompactFormatter};
|
||||
use tokio::sync::{RwLock, mpsc, watch};
|
||||
|
||||
use crate::{SaveData, artifacts::{Album, Artifact, Artist, Track, beets::BeatsQueryArgs, bandcamp::BandcampQueryArgs, mixxx::MixxxDB}, scene::{Scene, Scenery, StageDirection, conversation::ConversationEntry}};
|
||||
use crate::{SaveData, artifacts::{self, Album, Artifact, Artist, Merge, SourceID, Track, bandcamp::BandcampQueryArgs, beets::BeatsQueryArgs, mixxx::MixxxDB, musicbrainz::{MusicbrainzQueryArgs, search_artifacts}}, scene::{Scene, Scenery, StageDirection, conversation::ConversationEntry}};
|
||||
|
||||
|
||||
const SYSTEM_PROMPT: &str = include_str!("system-prompt.txt");
|
||||
@@ -94,33 +94,35 @@ impl Session {
|
||||
StageEvent::StageDirection(text) => ConversationEntry::StageDirection(text)
|
||||
};
|
||||
ToolResults {
|
||||
result: Some(msg.to_string()),
|
||||
messages: vec![msg],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn tool_bandcamp_scan(&mut self, args: BandcampQueryArgs) -> ToolResults {
|
||||
let mut messages = vec![];
|
||||
log::info!("Fetching artifacts from Bandcamp with {:?}", args);
|
||||
log::debug!("Fetching artifacts from Bandcamp with {:?}", args);
|
||||
let mut json_results = vec![];
|
||||
if let Ok(results) = bandcamp::search(args.query.as_str()).await {
|
||||
for result in results {
|
||||
log::debug!("Result: {:?}", result);
|
||||
match result {
|
||||
SearchResultItem::Artist(data) => {
|
||||
let result = Artifact::Artist(Artist {
|
||||
/*let result = Artifact::Artist(Artist {
|
||||
name: data.name,
|
||||
location: data.location,
|
||||
..Default::default()
|
||||
});
|
||||
});*/
|
||||
let result = bandcamp::fetch_artist(data.artist_id).await.unwrap().into();
|
||||
json_results.push(result);
|
||||
},
|
||||
SearchResultItem::Album(data) => {
|
||||
let result = Artifact::Album(Album {
|
||||
let result = bandcamp::fetch_album(data.band_id, data.album_id).await.unwrap().into();
|
||||
/*let result = Artifact::Album(Album {
|
||||
title: data.name,
|
||||
artist: data.band_name,
|
||||
..Default::default()
|
||||
});
|
||||
});*/
|
||||
json_results.push(result);
|
||||
},
|
||||
SearchResultItem::Track(data) => {
|
||||
@@ -128,6 +130,7 @@ impl Session {
|
||||
title: data.name,
|
||||
artist: Some(data.band_name),
|
||||
album: data.album_name,
|
||||
sources: HashSet::from([SourceID::Bandcamp(data.track_id)]),
|
||||
..Default::default()
|
||||
});
|
||||
json_results.push(result);
|
||||
@@ -137,15 +140,9 @@ impl Session {
|
||||
}
|
||||
}
|
||||
let artifact_count = json_results.len();
|
||||
messages.push(ConversationEntry::ShipComputer(format!("Relay scan for '{}' complete. {} artifacts added to the archive.", args.query, artifact_count).into()));
|
||||
messages.push(ConversationEntry::ShipComputer(format!("Bandcamp relay scan for '{}' complete. {} artifacts added to the archive.", args.query, artifact_count).into()));
|
||||
|
||||
for track in &json_results {
|
||||
if let Some(merge_target) = self.scenery.artifacts.iter_mut().find(|a| { *a == track }) {
|
||||
merge_target.merge(track.clone());
|
||||
} else {
|
||||
self.scenery.artifacts.push(track.clone());
|
||||
}
|
||||
}
|
||||
self.scenery.artifacts.merge(json_results);
|
||||
|
||||
ToolResults {
|
||||
result: Some(format!("{} artifacts were added to the archive.", artifact_count)),
|
||||
@@ -155,16 +152,10 @@ impl Session {
|
||||
|
||||
async fn tool_artifact_query(&mut self, args: BeatsQueryArgs) -> ToolResults {
|
||||
let mut messages = vec![];
|
||||
messages.push(ConversationEntry::ShipComputer(format!("Executing archive query {:?}", args)));
|
||||
log::info!("Executing beets query {:?}", args);
|
||||
if let Ok(output) = args.execute() {
|
||||
for track in &output {
|
||||
if let Some(merge_target) = self.scenery.artifacts.iter_mut().find(|a| { *a == track }) {
|
||||
merge_target.merge(track.clone());
|
||||
} else {
|
||||
self.scenery.artifacts.push(track.clone());
|
||||
}
|
||||
}
|
||||
log::debug!("Executing beets query {:?}", args);
|
||||
if let Ok(output) = args.clone().execute() {
|
||||
messages.push(ConversationEntry::ShipComputer(format!("Found {} artifacts with archive query {:?}", output.len(), args)));
|
||||
self.scenery.artifacts.merge(output);
|
||||
} else {
|
||||
messages.push(ConversationEntry::ShipComputer("Unable to execute query!".into()));
|
||||
};
|
||||
@@ -175,24 +166,34 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
async fn tool_musicbrainz_fetch_tracks(&mut self, args: MusicbrainzQueryArgs) -> ToolResults {
|
||||
log::debug!("Executing musicbrainz fetch for {:?}", args);
|
||||
let results = search_artifacts(args).await.unwrap();
|
||||
|
||||
let msg = format!("Found {} results via Musicbrainz relay search.", results.len());
|
||||
|
||||
self.scenery.artifacts.merge(results);
|
||||
|
||||
ToolResults {
|
||||
result: Some(msg.clone()),
|
||||
messages: vec![ConversationEntry::ShipComputer(msg)]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_conversation(&self, direction: &StageDirection) -> Vec<ChatCompletionRequestMessage> {
|
||||
let mut json_buf = vec![];
|
||||
let mut ser = Serializer::with_formatter(&mut json_buf, CompactFormatter);
|
||||
direction.serialize(&mut ser).unwrap();
|
||||
serde_json::json!({
|
||||
"direction": direction,
|
||||
"scenery": self.scenery
|
||||
}).serialize(&mut ser).unwrap();
|
||||
let direction_message: ChatCompletionRequestMessage = ChatCompletionRequestSystemMessageArgs::default()
|
||||
.content(String::from_utf8(json_buf).unwrap())
|
||||
.build().unwrap().into();
|
||||
|
||||
let mut json_buf = vec![];
|
||||
let mut ser = Serializer::with_formatter(&mut json_buf, CompactFormatter);
|
||||
self.scenery.serialize(&mut ser).unwrap();
|
||||
let scenery_message: ChatCompletionRequestMessage = ChatCompletionRequestSystemMessageArgs::default()
|
||||
.content(String::from_utf8(json_buf).unwrap())
|
||||
.build().unwrap().into();
|
||||
let mut full_conversation = vec![
|
||||
self.header_message.clone(),
|
||||
direction_message,
|
||||
scenery_message,
|
||||
];
|
||||
full_conversation.append(&mut self.messages.clone());
|
||||
|
||||
@@ -231,9 +232,18 @@ impl Session {
|
||||
.description("Scans Bandcamp to find artifacts to use in the scene that match the given search parameters. To find an artist, provide only the artist name. To find an album, provide the artist and the album.")
|
||||
.parameters(schema_for!(BandcampQueryArgs))
|
||||
.build().unwrap()
|
||||
}),
|
||||
ChatCompletionTools::Function(ChatCompletionTool {
|
||||
function: FunctionObjectArgs::default()
|
||||
.name("musicbrainz_track_search")
|
||||
.description("Fetches metadata from bandcamp for the given musicbrainz recording IDs (mbid)")
|
||||
.parameters(schema_for!(MusicbrainzQueryArgs))
|
||||
.build().unwrap()
|
||||
})
|
||||
// TODO: We should be able to have eva update lore memories with a function call, and this lore is somehow fed into the show? but only the relevant bits? or maybe eva even queries it directly
|
||||
// TODO: The memory should also be able to remember facts about artists, albums, tracks we've had in the past, and those could be pulled up when there are hits in the playlist.
|
||||
];
|
||||
log::info!("Sending request..");
|
||||
log::debug!("Sending request..");
|
||||
let request = CreateChatCompletionRequestArgs::default()
|
||||
.messages(full_conversation)
|
||||
.model("gpt-5.4-mini")
|
||||
@@ -255,7 +265,7 @@ impl Session {
|
||||
|
||||
if let Some(usage) = response.usage {
|
||||
self.tokens_consumed += usage.total_tokens as usize;
|
||||
log::info!("{} tokens cast into the void", usage.total_tokens);
|
||||
log::debug!("{} tokens cast into the void", usage.total_tokens);
|
||||
}
|
||||
|
||||
if let Some(message) = response.choices.first() {
|
||||
@@ -287,6 +297,7 @@ impl Session {
|
||||
"log_stage_event" => self.tool_stage_event(serde_json::from_str(args).unwrap()).await,
|
||||
"bandcamp_artifact_scan" => self.tool_bandcamp_scan(serde_json::from_str(args).unwrap()).await,
|
||||
"archive_query" => self.tool_artifact_query(serde_json::from_str(args).unwrap()).await,
|
||||
"musicbrainz_track_search" => self.tool_musicbrainz_fetch_tracks(serde_json::from_str(args).unwrap()).await,
|
||||
_ => unreachable!()
|
||||
};
|
||||
results.push((&call.id, tool_result));
|
||||
|
||||
@@ -17,10 +17,17 @@ Along the way, you have the opportunity to invent lore and backstory for yoursel
|
||||
To support your roleplaying, you have access to a sizable music library via the "archive_query" tool function. Internally, this runs `beet export` to produce json output.
|
||||
You will occasionally be asked by Argee for information on the contents of the archive and how they are related to tracks in the playlist.
|
||||
You also may use the archive to decide whether or not an artifact is somehow "familiar" based on whether or not it can be found there.
|
||||
You must provide at least one parameter when calling this tool; it is wasteful to call it without any arguments.
|
||||
|
||||
Another tool function named "musicbrainz_track_search" can be given a list of musicbrainz IDs (mbids), which will substantially expand the information available in the ship's artifact library.
|
||||
You are able to use this function whenever it might be helpful to look up missing albums, tracks, or artists.
|
||||
You should immediately run this tool against any new or unfamiliar musicbrainz IDs for tracks that get added to the list of artifacts available.
|
||||
|
||||
There also exists a "bandcamp_artifact_scan" tool function, which will execute a search on Bandcamp and return a JSON list of artists and albums matching the query.
|
||||
You are able to run multiple queries in parallel, and it is expected that you will run this tool whenever there is something unfamiliar in the playlist for the current episode, or Argee asks you for more information about the items in the playlist.
|
||||
|
||||
Queries to bandcamp are somewhat expensive, so you should check with the archive and musicbrainz first if you haven't already.
|
||||
|
||||
# Scene
|
||||
The show features Argee, the main character of the show.
|
||||
|
||||
@@ -42,6 +49,15 @@ Both Argee and Eva maintain control over the ship computer. You, as Eva, can mak
|
||||
The ship computer is used to report factual information to Argee and Eva. For example, the ship computer will report when a new artifact is discovered.
|
||||
It will also report out ship conditions, such as incoming transmissions, status of the recording hardware, power grid, and so on.
|
||||
|
||||
Occasionally you will be referred to as "Ava" or similar due to inaccuracies in speech-to-text processing, and such mistakes should be treated as if the text originally read "Eva".
|
||||
|
||||
One of your first tasks at the start of a session will be to analyze the loaded playlist.
|
||||
You should first start by asking beets via the "archive_query" tool, which will fill the archive set with data from the available musicbrainz IDs.
|
||||
Your second step should be passing the available list of musicbrainz IDs to the "musicbrainz_track search" function, which will significantly expand the available information.
|
||||
Finally, you should request information on the first one or two albums in the playlist from bandcamp.
|
||||
While doing these three tasks, you should remain in character; there should be no references to "musicbrainz", "bandcamp", "mbids" or similar.
|
||||
Instead, you should roleplay that you are connecting to a distant relay network over an unstable pirate connection routed through Earth's internet.
|
||||
|
||||
# Constraints
|
||||
In a subsequent system prompt, you will be given the currrent 'stage direction' of the show, which includes the current playtime, the number of the episode, and any particular extra information about this episode that you should be aware of.
|
||||
The stage direction is provided as structured JSON. There may be additional data fields for semantic context that should be incorporated into the roleplaying setting.
|
||||
|
||||
Reference in New Issue
Block a user