Skip to content
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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { autonomePlugin } from "@elizaos/plugin-autonome";
import { availPlugin } from "@elizaos/plugin-avail";
import { avalanchePlugin } from "@elizaos/plugin-avalanche";
import { b2Plugin } from "@elizaos/plugin-b2";
import { beatsfoundationPlugin } from "@elizaos/plugin-beatsfoundation";
import { binancePlugin } from "@elizaos/plugin-binance";
import { birdeyePlugin } from "@elizaos/plugin-birdeye";
import {
Expand Down Expand Up @@ -1003,6 +1004,7 @@ export async function createAgent(
? abstractPlugin
: null,
getSecret(character, "B2_PRIVATE_KEY") ? b2Plugin : null,
getSecret(character, "BEATSFOUNDATION_API_KEY") ? beatsfoundationPlugin : null,
getSecret(character, "BINANCE_API_KEY") &&
getSecret(character, "BINANCE_SECRET_KEY")
? binancePlugin
Expand Down
118 changes: 118 additions & 0 deletions packages/plugin-beatsfoundation/README.md
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)
25 changes: 25 additions & 0 deletions packages/plugin-beatsfoundation/package.json
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:*"
}
}
27 changes: 27 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/examples.ts
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"
}
}
];
104 changes: 104 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/index.ts
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");
}
Comment on lines +56 to +59
Copy link
Contributor

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.

+            const sanitizeContent = (content: CreateSongContent): CreateSongContent => {
+                return {
+                    ...content,
+                    title: content.title.trim(),
+                    // Add other sanitization as needed
+                };
+            };

             // Validate content
             if (!isCreateSongContent(content)) {
                 throw new Error("Invalid song creation content");
             }
+            const sanitizedContent = sanitizeContent(content);

Committable suggestion skipped: line range outside the PR's diff.


// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content: {
song,
request: content
},
content: {
song,
request: {
title: content.title,
// Add other non-sensitive fields as needed
}
},

});
}

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;
29 changes: 29 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/service.ts
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

  1. Implement retry logic for transient failures
  2. Add request cancellation support
+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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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) {
import { CancelToken } from 'axios';
import axiosRetry from 'axios-retry';
try {
const source = CancelToken.source();
const client = axios.create();
axiosRetry(client, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error)
|| error.code === 'ECONNABORTED';
}
});
const response = await client.post(
'https://www.beatsfoundation.com/api/songs',
content,
{
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 300000, // 5 minutes timeout for song generation
cancelToken: source.token
}
);
return response.data.song;
} catch (error: any) {

if (error.response) {
throw new Error(`Beats Foundation API Error: ${error.response.data.error || error.response.status}`);
}
throw error;
}
}
};
}
11 changes: 11 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/template.ts
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid type casting and strengthen validation

The type casting with as could be unsafe. Consider using type narrowing instead.

-    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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;


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 @@

Loading
Loading