-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add media creation engine #2452
Changes from all commits
a5cc630
2431ee2
c7ff3d6
66ee7b2
6a04ab7
9573e6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# @elizaos/plugin-beatsfoundation | ||
|
||
A plugin for Eliza that enables AI music generation using the Beats Foundation API. | ||
|
||
## Features | ||
- AI-powered music generation from text prompts | ||
- Support for multiple genres and moods | ||
- Optional lyrics input | ||
- Instrumental track generation | ||
- Natural language processing for music generation requests | ||
- Access to the Beats Foundation song library | ||
|
||
## Installation | ||
```bash | ||
npm install @elizaos/plugin-beatsfoundation | ||
``` | ||
|
||
## Configuration | ||
1. Get your API key from [Beats Foundation](https://www.beatsfoundation.com) | ||
2. Set up your environment variables: | ||
```bash | ||
BEATS_FOUNDATION_API_KEY=your_api_key | ||
``` | ||
3. Register the plugin in your Eliza configuration: | ||
```typescript | ||
import { BeatsFoundationPlugin } from "@elizaos/plugin-beatsfoundation"; | ||
// In your Eliza configuration | ||
plugins: [ | ||
new BeatsFoundationPlugin(), | ||
// ... other plugins | ||
]; | ||
``` | ||
|
||
## Usage | ||
The plugin responds to natural language queries for music generation. Here are some examples: | ||
```plaintext | ||
"Generate a happy pop song about summer" | ||
"Create an instrumental jazz track" | ||
"Make me a rock song with these lyrics: [lyrics]" | ||
"List recent AI-generated songs" | ||
``` | ||
|
||
### Supported Parameters | ||
The plugin supports various music generation parameters including: | ||
- Genre (pop, rock, jazz, etc.) | ||
- Mood (happy, sad, energetic, etc.) | ||
- Lyrics (optional) | ||
- Instrumental toggle (boolean, will generate instrumental track or with vocals) | ||
- Custom prompts (up to 200 characters) | ||
|
||
### Available Actions | ||
#### GENERATE_SONG | ||
Generates a new AI song based on provided parameters. | ||
```typescript | ||
// Example response format | ||
{ | ||
id: "song_123", | ||
title: "Summer Vibes", | ||
audio_url: "https://...", | ||
streams: 0, | ||
upvote_count: 0, | ||
song_url: "https://...", | ||
username: "user123" | ||
} | ||
``` | ||
|
||
#### LIST_SONGS | ||
Retrieves a paginated list of generated songs. | ||
|
||
## API Reference | ||
For detailed API documentation, visit [docs.beatsfoundation.com](https://docs.beatsfoundation.com) | ||
|
||
### Environment Variables | ||
| Variable | Description | Required | | ||
| -------- | ----------- | -------- | | ||
| BEATS_FOUNDATION_API_KEY | Your Beats Foundation API key | Yes | | ||
|
||
### Types | ||
```typescript | ||
interface GenerateSongRequest { | ||
prompt: string; | ||
lyrics?: string; | ||
genre?: string; | ||
mood?: string; | ||
isInstrumental?: boolean; | ||
} | ||
|
||
interface Song { | ||
id: string; | ||
title: string; | ||
audio_url: string; | ||
streams: number; | ||
upvote_count: number; | ||
song_url: string; | ||
username: string; | ||
} | ||
``` | ||
|
||
## Error Handling | ||
The plugin includes comprehensive error handling for: | ||
- Invalid API keys | ||
- Rate limiting (2 generations per hour) | ||
- Network timeouts | ||
- Invalid generation parameters | ||
- Server errors | ||
|
||
## Rate Limits | ||
The Beats Foundation API is currently free to use and has a rate limit of 2 song generations per hour per API key. Public endpoints like song listing and retrieval are not rate limited. | ||
|
||
## Support | ||
For support, please: | ||
- Visit [docs.beatsfoundation.com](https://docs.beatsfoundation.com) | ||
- Open an issue in the repository | ||
- Join our Discord community | ||
|
||
## Links | ||
- [Beats Foundation API Documentation](https://docs.beatsfoundation.com) | ||
- [GitHub Repository](https://github.com/elizaos/eliza/tree/main/packages/plugin-beatsfoundation) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "@elizaos/plugin-beatsfoundation", | ||
"version": "0.0.1", | ||
"description": "Beats Foundation plugin for ElizaOS", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsc", | ||
"clean": "rimraf dist", | ||
"dev": "tsc -w", | ||
"lint": "eslint src --ext .ts", | ||
"test": "jest" | ||
}, | ||
"dependencies": { | ||
"@elizaos/core": "workspace:*", | ||
"axios": "^1.6.7" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.11.19", | ||
"typescript": "^5.3.3" | ||
}, | ||
"peerDependencies": { | ||
"@elizaos/core": "workspace:*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export const createSongExamples = [ | ||
{ | ||
input: "Create a happy pop song about summer", | ||
output: { | ||
prompt: "Create a happy pop song about summer", | ||
genre: "pop", | ||
mood: "happy" | ||
} | ||
}, | ||
{ | ||
input: "Generate an instrumental jazz piece with a relaxing vibe", | ||
output: { | ||
prompt: "Generate an instrumental jazz piece with a relaxing vibe", | ||
genre: "jazz", | ||
mood: "relaxing", | ||
isInstrumental: true | ||
} | ||
}, | ||
{ | ||
input: "Make a rock song with these lyrics: Life is a highway, I wanna ride it all night long", | ||
output: { | ||
prompt: "Make a rock song", | ||
genre: "rock", | ||
lyrics: "Life is a highway, I wanna ride it all night long" | ||
} | ||
} | ||
]; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,104 @@ | ||||||||||||||||||||||||
import { | ||||||||||||||||||||||||
composeContext, | ||||||||||||||||||||||||
elizaLogger, | ||||||||||||||||||||||||
generateObjectDeprecated, | ||||||||||||||||||||||||
HandlerCallback, | ||||||||||||||||||||||||
IAgentRuntime, | ||||||||||||||||||||||||
Memory, | ||||||||||||||||||||||||
ModelClass, | ||||||||||||||||||||||||
State, | ||||||||||||||||||||||||
type Action, | ||||||||||||||||||||||||
} from "@elizaos/core"; | ||||||||||||||||||||||||
import { validateBeatsFoundationConfig } from "../../environment"; | ||||||||||||||||||||||||
import { createSongExamples } from "./examples"; | ||||||||||||||||||||||||
import { createSongService } from "./service"; | ||||||||||||||||||||||||
import { createSongTemplate } from "./template"; | ||||||||||||||||||||||||
import { CreateSongContent } from "./types"; | ||||||||||||||||||||||||
import { isCreateSongContent } from "./validation"; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
export default { | ||||||||||||||||||||||||
name: "CREATE_SONG", | ||||||||||||||||||||||||
similes: ["GENERATE_SONG", "MAKE_SONG", "COMPOSE_SONG"], | ||||||||||||||||||||||||
validate: async (runtime: IAgentRuntime, _message: Memory) => { | ||||||||||||||||||||||||
await validateBeatsFoundationConfig(runtime); | ||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||
}, | ||||||||||||||||||||||||
description: "Create a new song using Beats Foundation", | ||||||||||||||||||||||||
handler: async ( | ||||||||||||||||||||||||
runtime: IAgentRuntime, | ||||||||||||||||||||||||
message: Memory, | ||||||||||||||||||||||||
state: State, | ||||||||||||||||||||||||
_options: { [key: string]: unknown }, | ||||||||||||||||||||||||
callback?: HandlerCallback | ||||||||||||||||||||||||
): Promise<boolean> => { | ||||||||||||||||||||||||
elizaLogger.log("Starting Beats Foundation CREATE_SONG handler..."); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Initialize or update state | ||||||||||||||||||||||||
if (!state) { | ||||||||||||||||||||||||
state = (await runtime.composeState(message)) as State; | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
state = await runtime.updateRecentMessageState(state); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
// Compose and generate content | ||||||||||||||||||||||||
const context = composeContext({ | ||||||||||||||||||||||||
state, | ||||||||||||||||||||||||
template: createSongTemplate, | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const content = (await generateObjectDeprecated({ | ||||||||||||||||||||||||
runtime, | ||||||||||||||||||||||||
context, | ||||||||||||||||||||||||
modelClass: ModelClass.SMALL, | ||||||||||||||||||||||||
})) as unknown as CreateSongContent; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Validate content | ||||||||||||||||||||||||
if (!isCreateSongContent(content)) { | ||||||||||||||||||||||||
throw new Error("Invalid song creation content"); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Get config with validation | ||||||||||||||||||||||||
const config = await validateBeatsFoundationConfig(runtime); | ||||||||||||||||||||||||
const songService = createSongService(config.BEATSFOUNDATION_API_KEY); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
const song = await songService.createSong(content); | ||||||||||||||||||||||||
elizaLogger.success( | ||||||||||||||||||||||||
`Song created successfully! Title: ${song.title}` | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if (callback) { | ||||||||||||||||||||||||
callback({ | ||||||||||||||||||||||||
text: `Created new song: ${song.title}`, | ||||||||||||||||||||||||
content: { | ||||||||||||||||||||||||
song, | ||||||||||||||||||||||||
request: content | ||||||||||||||||||||||||
}, | ||||||||||||||||||||||||
Comment on lines
+74
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize sensitive information in callback response The callback includes the full request content which might contain sensitive information. content: {
song,
- request: content
+ request: {
+ title: content.title,
+ // Add other non-sensitive fields as needed
+ }
}, 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||
} catch (error: any) { | ||||||||||||||||||||||||
elizaLogger.error("Error in CREATE_SONG handler:", error); | ||||||||||||||||||||||||
if (callback) { | ||||||||||||||||||||||||
callback({ | ||||||||||||||||||||||||
text: `Error creating song: ${error.message}`, | ||||||||||||||||||||||||
content: { error: error.message }, | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} catch (error: any) { | ||||||||||||||||||||||||
elizaLogger.error("Error in CREATE_SONG handler:", error); | ||||||||||||||||||||||||
if (callback) { | ||||||||||||||||||||||||
callback({ | ||||||||||||||||||||||||
text: `Error creating song: ${error.message}`, | ||||||||||||||||||||||||
content: { error: error.message }, | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
}, | ||||||||||||||||||||||||
examples: createSongExamples, | ||||||||||||||||||||||||
} as Action; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import axios from 'axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Song } from '../../types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { CreateSongContent } from './types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function createSongService(apiKey: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
createSong: async (content: CreateSongContent): Promise<Song> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const response = await axios.post( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'https://www.beatsfoundation.com/api/songs', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
content, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Authorization: `Bearer ${apiKey}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
timeout: 300000, // 5 minutes timeout for song generation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return response.data.song; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (error: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+8
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add retry mechanism and request cancellation For long-running song generation requests:
+import { CancelToken } from 'axios';
+import axiosRetry from 'axios-retry';
createSong: async (content: CreateSongContent): Promise<Song> => {
+ const source = CancelToken.source();
try {
+ const client = axios.create();
+ axiosRetry(client, {
+ retries: 3,
+ retryDelay: axiosRetry.exponentialDelay,
+ retryCondition: (error) => {
+ return axiosRetry.isNetworkOrIdempotentRequestError(error)
+ || error.code === 'ECONNABORTED';
+ }
+ });
- const response = await axios.post(
+ const response = await client.post(
'https://www.beatsfoundation.com/api/songs',
content,
{
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 300000,
+ cancelToken: source.token
}
); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (error.response) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error(`Beats Foundation API Error: ${error.response.data.error || error.response.status}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw error; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const createSongTemplate = ` | ||
Given the conversation context, extract the song creation parameters. | ||
Return a JSON object with the following structure: | ||
{ | ||
"prompt": string (required), | ||
"lyrics": string (optional), | ||
"genre": string (optional), | ||
"mood": string (optional), | ||
"isInstrumental": boolean (optional) | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Content } from "@elizaos/core"; | ||
|
||
export interface CreateSongContent extends Content { | ||
prompt: string; | ||
lyrics?: string; | ||
genre?: string; | ||
mood?: string; | ||
isInstrumental?: boolean; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||||||
import { CreateSongContent } from "./types"; | ||||||||||
|
||||||||||
export function isCreateSongContent(content: unknown): content is CreateSongContent { | ||||||||||
if (!content || typeof content !== "object") return false; | ||||||||||
const c = content as CreateSongContent; | ||||||||||
Comment on lines
+4
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid type casting and strengthen validation The type casting with - if (!content || typeof content !== "object") return false;
- const c = content as CreateSongContent;
+ if (!content || typeof content !== "object" || Array.isArray(content) || content === null) return false;
+ const c = content; 📝 Committable suggestion
Suggested change
|
||||||||||
|
||||||||||
if (typeof c.prompt !== "string" || c.prompt.length === 0) return false; | ||||||||||
if (c.lyrics !== undefined && typeof c.lyrics !== "string") return false; | ||||||||||
if (c.genre !== undefined && typeof c.genre !== "string") return false; | ||||||||||
if (c.mood !== undefined && typeof c.mood !== "string") return false; | ||||||||||
if (c.isInstrumental !== undefined && typeof c.isInstrumental !== "boolean") return false; | ||||||||||
|
||||||||||
return true; | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input sanitization
Validate and sanitize input content before sending to the API.