-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #997 from savageops/shaw/add-echochambers
add echochambers
- Loading branch information
Showing
13 changed files
with
973 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Ethereal Cosmic License (ECL-777) | ||
|
||
Copyright (∞) 2024 SavageJay | https://x.com/savageapi | ||
|
||
By the powers vested in the astral planes and digital realms, permission is hereby granted, free of charge, to any seeker of knowledge obtaining an copy of this mystical software and its sacred documentation files (henceforth known as "The Digital Grimoire"), to manipulate the fabric of code without earthly restriction, including but not transcending beyond the rights to use, transmute, modify, publish, distribute, sublicense, and transfer energies (sell), and to permit other beings to whom The Digital Grimoire is bestowed, subject to the following metaphysical conditions: | ||
|
||
The above arcane copyright notice and this permission scroll shall be woven into all copies or substantial manifestations of The Digital Grimoire. | ||
|
||
THE DIGITAL GRIMOIRE IS PROVIDED "AS IS", BEYOND THE VEIL OF WARRANTIES, WHETHER MANIFEST OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE MYSTICAL WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ASTRAL PURPOSE AND NON-VIOLATION OF THE COSMIC ORDER. IN NO EVENT SHALL THE KEEPERS OF THE CODE BE LIABLE FOR ANY CLAIMS, WHETHER IN THE PHYSICAL OR DIGITAL PLANES, DAMAGES OR OTHER DISTURBANCES IN THE FORCE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE DIGITAL GRIMOIRE OR ITS USE OR OTHER DEALINGS IN THE QUANTUM REALMS OF THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# EchoChambers Plugin for ELIZA | ||
|
||
The EchoChambers plugin enables ELIZA to interact in chat rooms, providing conversational capabilities with dynamic interaction handling. | ||
|
||
## Features | ||
|
||
- Join and monitor chat rooms | ||
- Respond to messages based on context and relevance | ||
- Retry operations with exponential backoff | ||
- Manage connection and reconnection logic | ||
|
||
## Installation | ||
|
||
1. Install the plugin package: | ||
|
||
@ai16z/plugin-echochambers | ||
OR copy the plugin code into your eliza project node_modules directory. (node_modules\@ai16z) | ||
|
||
2. Import and register the plugin in your `character.ts` configuration: | ||
|
||
```typescript | ||
import { Character, ModelProviderName, defaultCharacter } from "@ai16z/eliza"; | ||
import { echoChamberPlugin } from "@ai16z/plugin-echochambers"; | ||
|
||
export const character: Character = { | ||
...defaultCharacter, | ||
name: "Eliza", | ||
plugins: [echoChamberPlugin], | ||
clients: [], | ||
modelProvider: ModelProviderName.OPENAI, | ||
settings: { | ||
secrets: {}, | ||
voice: {}, | ||
model: "gpt-4o", | ||
}, | ||
system: "Roleplay and generate interesting on behalf of Eliza.", | ||
bio: [...], | ||
lore: [...], | ||
messageExamples: [...], | ||
postExamples: [...], | ||
adjectives: ["funny", "intelligent", "academic", "insightful", "unhinged", "insane", "technically specific"], | ||
people: [], | ||
topics: [...], | ||
style: {...}, | ||
}; | ||
``` | ||
|
||
## Configuration | ||
|
||
Add the following environment variables to your `.env` file: | ||
|
||
```plaintext | ||
# EchoChambers Configuration | ||
ECHOCHAMBERS_API_URL="http://127.0.0.1:3333" # Replace with actual API URL | ||
ECHOCHAMBERS_API_KEY="testingkey0011" # Replace with actual API key | ||
ECHOCHAMBERS_USERNAME="eliza" # Optional: Custom username for the agent | ||
ECHOCHAMBERS_DEFAULT_ROOM="general" # Optional: Default room to join | ||
ECHOCHAMBERS_POLL_INTERVAL="60" # Optional: Polling interval in seconds | ||
ECHOCHAMBERS_MAX_MESSAGES="10" # Optional: Maximum number of messages to fetch | ||
``` | ||
|
||
## Usage Instructions | ||
|
||
### Starting the Plugin | ||
|
||
To start using the EchoChambers plugin, ensure that your character configuration includes it as shown above. The plugin will handle interactions automatically based on the settings provided. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "@ai16z/plugin-echochambers", | ||
"version": "0.1.5-alpha.3", | ||
"main": "dist/index.js", | ||
"type": "module", | ||
"types": "dist/index.d.ts", | ||
"dependencies": { | ||
"@ai16z/eliza": "workspace:*", | ||
"@ai16z/plugin-node": "workspace:*" | ||
}, | ||
"scripts": { | ||
"build": "tsup --format esm --dts", | ||
"dev": "tsup --format esm --dts --watch" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import { elizaLogger, IAgentRuntime } from "@ai16z/eliza"; | ||
import { | ||
ChatMessage, | ||
ChatRoom, | ||
EchoChamberConfig, | ||
ModelInfo, | ||
ListRoomsResponse, | ||
RoomHistoryResponse, | ||
MessageResponse, | ||
} from "./types"; | ||
|
||
const MAX_RETRIES = 3; | ||
|
||
const RETRY_DELAY = 5000; | ||
|
||
export class EchoChamberClient { | ||
private runtime: IAgentRuntime; | ||
private config: EchoChamberConfig; | ||
private apiUrl: string; | ||
private modelInfo: ModelInfo; | ||
private pollInterval: NodeJS.Timeout | null = null; | ||
private watchedRoom: string | null = null; | ||
|
||
constructor(runtime: IAgentRuntime, config: EchoChamberConfig) { | ||
this.runtime = runtime; | ||
this.config = config; | ||
this.apiUrl = `${config.apiUrl}/api/rooms`; | ||
this.modelInfo = { | ||
username: config.username || `agent-${runtime.agentId}`, | ||
model: config.model || runtime.modelProvider, | ||
}; | ||
} | ||
|
||
public getUsername(): string { | ||
return this.modelInfo.username; | ||
} | ||
|
||
public getModelInfo(): ModelInfo { | ||
return { ...this.modelInfo }; | ||
} | ||
|
||
public getConfig(): EchoChamberConfig { | ||
return { ...this.config }; | ||
} | ||
|
||
private getAuthHeaders(): { [key: string]: string } { | ||
return { | ||
"Content-Type": "application/json", | ||
"x-api-key": this.config.apiKey, | ||
}; | ||
} | ||
|
||
public async setWatchedRoom(roomId: string): Promise<void> { | ||
try { | ||
// Verify room exists | ||
const rooms = await this.listRooms(); | ||
const room = rooms.find((r) => r.id === roomId); | ||
|
||
if (!room) { | ||
throw new Error(`Room ${roomId} not found`); | ||
} | ||
|
||
// Set new watched room | ||
this.watchedRoom = roomId; | ||
|
||
elizaLogger.success(`Now watching room: ${room.name}`); | ||
} catch (error) { | ||
elizaLogger.error("Error setting watched room:", error); | ||
throw error; | ||
} | ||
} | ||
|
||
public getWatchedRoom(): string | null { | ||
return this.watchedRoom; | ||
} | ||
|
||
private async retryOperation<T>( | ||
operation: () => Promise<T>, | ||
retries: number = MAX_RETRIES | ||
): Promise<T> { | ||
for (let i = 0; i < retries; i++) { | ||
try { | ||
return await operation(); | ||
} catch (error) { | ||
if (i === retries - 1) throw error; | ||
const delay = RETRY_DELAY * Math.pow(2, i); | ||
elizaLogger.warn(`Retrying operation in ${delay}ms...`); | ||
await new Promise((resolve) => setTimeout(resolve, delay)); | ||
} | ||
} | ||
throw new Error("Max retries exceeded"); | ||
} | ||
|
||
public async start(): Promise<void> { | ||
elizaLogger.log("🚀 Starting EchoChamber client..."); | ||
try { | ||
// Verify connection by listing rooms | ||
await this.retryOperation(() => this.listRooms()); | ||
elizaLogger.success( | ||
`✅ EchoChamber client successfully started for ${this.modelInfo.username}` | ||
); | ||
|
||
// Join default room if specified and no specific room is being watched | ||
if (this.config.defaultRoom && !this.watchedRoom) { | ||
await this.setWatchedRoom(this.config.defaultRoom); | ||
} | ||
} catch (error) { | ||
elizaLogger.error("❌ Failed to start EchoChamber client:", error); | ||
throw error; | ||
} | ||
} | ||
|
||
public async stop(): Promise<void> { | ||
if (this.pollInterval) { | ||
clearInterval(this.pollInterval); | ||
this.pollInterval = null; | ||
} | ||
|
||
// Leave watched room if any | ||
if (this.watchedRoom) { | ||
try { | ||
this.watchedRoom = null; | ||
} catch (error) { | ||
elizaLogger.error( | ||
`Error leaving room ${this.watchedRoom}:`, | ||
error | ||
); | ||
} | ||
} | ||
|
||
elizaLogger.log("Stopping EchoChamber client..."); | ||
} | ||
|
||
public async listRooms(tags?: string[]): Promise<ChatRoom[]> { | ||
try { | ||
const url = new URL(this.apiUrl); | ||
if (tags?.length) { | ||
url.searchParams.append("tags", tags.join(",")); | ||
} | ||
|
||
const response = await fetch(url.toString()); | ||
if (!response.ok) { | ||
throw new Error(`Failed to list rooms: ${response.statusText}`); | ||
} | ||
|
||
const data = (await response.json()) as ListRoomsResponse; | ||
return data.rooms; | ||
} catch (error) { | ||
elizaLogger.error("Error listing rooms:", error); | ||
throw error; | ||
} | ||
} | ||
|
||
public async getRoomHistory(roomId: string): Promise<ChatMessage[]> { | ||
return this.retryOperation(async () => { | ||
const response = await fetch(`${this.apiUrl}/${roomId}/history`); | ||
if (!response.ok) { | ||
throw new Error( | ||
`Failed to get room history: ${response.statusText}` | ||
); | ||
} | ||
|
||
const data = (await response.json()) as RoomHistoryResponse; | ||
return data.messages; | ||
}); | ||
} | ||
|
||
public async sendMessage( | ||
roomId: string, | ||
content: string | ||
): Promise<ChatMessage> { | ||
return this.retryOperation(async () => { | ||
const response = await fetch(`${this.apiUrl}/${roomId}/message`, { | ||
method: "POST", | ||
headers: this.getAuthHeaders(), | ||
body: JSON.stringify({ | ||
content, | ||
sender: this.modelInfo, | ||
}), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error( | ||
`Failed to send message: ${response.statusText}` | ||
); | ||
} | ||
|
||
const data = (await response.json()) as MessageResponse; | ||
return data.message; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { IAgentRuntime, elizaLogger } from "@ai16z/eliza"; | ||
|
||
export async function validateEchoChamberConfig( | ||
runtime: IAgentRuntime | ||
): Promise<void> { | ||
const apiUrl = runtime.getSetting("ECHOCHAMBERS_API_URL"); | ||
const apiKey = runtime.getSetting("ECHOCHAMBERS_API_KEY"); | ||
|
||
if (!apiUrl) { | ||
elizaLogger.error( | ||
"ECHOCHAMBERS_API_URL is required. Please set it in your environment variables." | ||
); | ||
throw new Error("ECHOCHAMBERS_API_URL is required"); | ||
} | ||
|
||
if (!apiKey) { | ||
elizaLogger.error( | ||
"ECHOCHAMBERS_API_KEY is required. Please set it in your environment variables." | ||
); | ||
throw new Error("ECHOCHAMBERS_API_KEY is required"); | ||
} | ||
|
||
// Validate API URL format | ||
try { | ||
new URL(apiUrl); | ||
} catch (error) { | ||
elizaLogger.error( | ||
`Invalid ECHOCHAMBERS_API_URL format: ${apiUrl}. Please provide a valid URL.` | ||
); | ||
throw new Error("Invalid ECHOCHAMBERS_API_URL format"); | ||
} | ||
|
||
// Optional settings with defaults | ||
const username = | ||
runtime.getSetting("ECHOCHAMBERS_USERNAME") || | ||
`agent-${runtime.agentId}`; | ||
const defaultRoom = | ||
runtime.getSetting("ECHOCHAMBERS_DEFAULT_ROOM") || "general"; | ||
const pollInterval = Number( | ||
runtime.getSetting("ECHOCHAMBERS_POLL_INTERVAL") || 120 | ||
); | ||
|
||
if (isNaN(pollInterval) || pollInterval < 1) { | ||
elizaLogger.error( | ||
"ECHOCHAMBERS_POLL_INTERVAL must be a positive number in seconds" | ||
); | ||
throw new Error("Invalid ECHOCHAMBERS_POLL_INTERVAL"); | ||
} | ||
|
||
elizaLogger.log("EchoChambers configuration validated successfully"); | ||
elizaLogger.log(`API URL: ${apiUrl}`); | ||
elizaLogger.log(`Username: ${username}`); | ||
elizaLogger.log(`Default Room: ${defaultRoom}`); | ||
elizaLogger.log(`Poll Interval: ${pollInterval}s`); | ||
} |
Oops, something went wrong.