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 dev page to rebuild #163

Merged
merged 22 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dfd9495
Add dev page with sidebar and agent view
miurla Aug 3, 2023
595a3c9
Add AI module and AgentStream implementation
miurla Aug 5, 2023
d809c06
Refactor AgentStream.ts to handle a new message format
miurla Aug 6, 2023
8c2ef20
Add AgentInput component and useAgent hook
miurla Aug 7, 2023
fe1db65
Refactor message grouping and rendering in AgentView
miurla Aug 7, 2023
874c3f3
Refactor code and add new class BabyElfAGI for agent execution
miurla Aug 11, 2023
c363fd3
Refactor code for executing tasks in parallel
miurla Aug 11, 2023
a8cd966
Refactor getExportText to getExportAgentMessage
miurla Aug 11, 2023
7772acc
Refactor web search skill to use new web browsing tool
miurla Aug 12, 2023
084c6c7
Add WebLoader skill to skill registry and task registry and update We…
miurla Aug 12, 2023
376a305
Add YoutubeSearch skill and callback message
miurla Aug 12, 2023
2b85753
Update skils
miurla Aug 13, 2023
94136ac
Improve groupMessages function to handle isRunning status
miurla Aug 13, 2023
32f3ac9
Refactor AgentView to use AgentLoading component
miurla Aug 13, 2023
7f122be
Refactor skill loading logic in BabyElfAGI constructor
miurla Aug 13, 2023
ebd3afc
Merge branch 'main' of github.com:miurla/babyagi-ui into rebuild
miurla Aug 13, 2023
62ddaf0
Convert messages to AgentMessages in AgentView.tsx and message.ts
miurla Aug 14, 2023
ba9d25b
Set agent messages if available, otherwise set agentMessages
miurla Aug 14, 2023
db381b1
Refactor code to include userApiKey in the constructor and pass it to…
miurla Aug 14, 2023
acb93a5
Update package.json and taskRegistry.ts
miurla Aug 14, 2023
d0919dc
Refactor AgentView component and add custom hooks
miurla Aug 14, 2023
38fbc57
Add abort signal to agent executer
miurla Aug 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
829 changes: 558 additions & 271 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/react": "18.0.33",
"@types/react-dom": "18.0.11",
"@vercel/analytics": "^1.0.0",
"ai": "^2.1.31",
"airtable": "^0.12.1",
"axios": "^1.4.0",
"cheerio": "^1.0.0-rc.12",
Expand All @@ -38,7 +39,7 @@
"install": "^0.13.0",
"langchain": "^0.0.64",
"lodash": "^4.17.21",
"next": "13.2.4",
"next": "^13.4.16",
"next-i18next": "^13.2.2",
"next-themes": "^0.2.1",
"npm": "^9.6.6",
Expand Down
49 changes: 49 additions & 0 deletions src/agents/base/AgentStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AIStreamCallbacks, createCallbacksTransformer } from 'ai';
import { AgentMessage } from '@/types';

export function AgentStream(callbacks?: AIStreamCallbacks) {
const stream = new TransformStream();
const writer = stream.writable.getWriter();
let observers: ((isActive: boolean) => void)[] = [];

let wasActive: boolean | null = null;
const isWriterActive = () => writer.desiredSize !== null;
const notifyObservers = () => {
const isActive = isWriterActive();
if (wasActive !== isActive) {
observers.forEach((observer) => observer(isActive));
wasActive = isActive;
}
};

return {
stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)),
handlers: {
handleMessage: async (message: AgentMessage) => {
notifyObservers();
if (!isWriterActive()) return;
await writer.ready;
await writer.write(
`${JSON.stringify({
message,
})}\n`,
);
},
handleEnd: async () => {
notifyObservers();
if (!isWriterActive()) return;
await writer.ready;
await writer.close();
},
handleError: async (e: Error) => {
notifyObservers();
if (!isWriterActive()) return;
await writer.ready;
await writer.abort(e);
},
},
addObserver: (observer: (isActive: boolean) => void) => {
observers.push(observer);
},
};
}
58 changes: 58 additions & 0 deletions src/agents/base/Executer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AgentMessage, AgentTask } from '@/types';
import { Printer } from '@/utils/elf/print';

export class Executer {
objective: string;
modelName: string;
handlers: {
handleMessage: (message: AgentMessage) => Promise<void>;
handleEnd: () => Promise<void>;
};
language: string;
verbose: boolean;
signal?: AbortSignal;

printer: Printer;
taskList: AgentTask[] = [];

constructor(
objective: string,
modelName: string,
handlers: {
handleMessage: (message: AgentMessage) => Promise<void>;
handleEnd: () => Promise<void>;
},
language: string = 'en',
varbose: boolean = false,
signal?: AbortSignal,
) {
this.objective = objective;
this.modelName = modelName;
this.handlers = handlers;
this.language = language;
this.verbose = varbose;
this.signal = signal;
this.printer = new Printer(this.handlers.handleMessage, this.verbose);
}

async run() {
this.taskList = [];
await this.prepare();
await this.loop();
await this.finishup();
}

// prepare() is called before loop()
async prepare() {
this.printer.printObjective(this.objective);
}

async loop() {}

async finishup() {
if (this.signal?.aborted) return;
// Objective completed
this.printer.printAllTaskCompleted();
this.handlers.handleEnd();
}
}
189 changes: 189 additions & 0 deletions src/agents/elf/executer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { AgentMessage, TaskOutputs } from '@/types'; // You need to define these types
import { Executer } from '../base/Executer';
import { SkillRegistry, TaskRegistry } from './registory';
import { v4 as uuidv4 } from 'uuid';

const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection.

export class BabyElfAGI extends Executer {
skillRegistry: SkillRegistry;
taskRegistry: TaskRegistry;
sessionSummary: string = '';

constructor(
objective: string,
modelName: string,
handlers: {
handleMessage: (message: AgentMessage) => Promise<void>;
handleEnd: () => Promise<void>;
},
language: string = 'en',
verbose: boolean = false,
specifiedSkills: string[] = [],
userApiKey?: string,
signal?: AbortSignal,
) {
super(objective, modelName, handlers, language, verbose, signal);

signal?.addEventListener('abort', () => {
this.verbose &&
console.log('Abort signal received. Stopping execution...');
});

this.skillRegistry = new SkillRegistry(
this.handlers.handleMessage,
this.verbose,
this.language,
specifiedSkills,
userApiKey,
this.signal,
);

const useSpecifiedSkills = specifiedSkills.length > 0;
this.taskRegistry = new TaskRegistry(
this.language,
this.verbose,
useSpecifiedSkills,
userApiKey,
this.signal,
);
}

async prepare() {
await super.prepare();

const skillDescriptions = this.skillRegistry.getSkillDescriptions();
const id = uuidv4();
// Create task list
await this.taskRegistry.createTaskList(
id,
this.objective,
skillDescriptions,
this.modelName,
this.handlers.handleMessage,
);
this.printer.printTaskList(this.taskRegistry.tasks, id);
}

async loop() {
// Initialize task outputs
let taskOutputs: TaskOutputs = {};
for (let task of this.taskRegistry.tasks) {
taskOutputs[task.id] = { completed: false, output: undefined };
}
// Loop until all tasks are completed
while (
!this.signal?.aborted &&
!Object.values(taskOutputs).every((task) => task.completed)
) {
// Get the tasks that are ready to be executed
const tasks = this.taskRegistry.getTasks();

// Update taskOutputs to include new tasks
tasks.forEach((task) => {
if (!(task.id in taskOutputs)) {
taskOutputs[task.id] = { completed: false, output: undefined };
}
});

// Filter taskoutput not completed
const incompleteTasks = tasks.filter((task) => {
return !taskOutputs[task.id].completed;
});

// Filter tasks that have all their dependencies completed
const MaxExecutableTasks = 5;
const executableTasks = incompleteTasks
.filter((task) => {
if (!task.dependentTaskIds) return true;
return task.dependentTaskIds.every((id) => {
return taskOutputs[id]?.completed === true;
});
})
.slice(0, MaxExecutableTasks);

// Execute all executable tasks in parallel
const taskPromises = executableTasks.map(async (task, i) => {
// Update task status to running
this.taskRegistry.updateTasks({
id: task.id,
updates: { status: 'running' },
});
this.printer.printTaskExecute(task);

let output = '';
try {
output = await this.taskRegistry.executeTask(
i,
task,
taskOutputs,
this.objective,
this.skillRegistry,
);
} catch (error) {
console.error(error);
return;
}

taskOutputs[task.id] = { completed: true, output: output };
this.taskRegistry.updateTasks({
id: task.id,
updates: { status: 'complete', result: output },
});
this.printer.printTaskOutput(output, task);
this.sessionSummary += `# ${task.id}: ${task.task}\n${output}\n\n`;

// Reflect on the output of the tasks and possibly add new tasks or update existing ones
if (REFLECTION) {
const skillDescriptions = this.skillRegistry.getSkillDescriptions();
const [newTasks, insertAfterIds, tasksToUpdate] =
await this.taskRegistry.reflectOnOutput(
this.objective,
output,
skillDescriptions,
);

// Insert new tasks
for (let i = 0; i < newTasks.length; i++) {
const newTask = newTasks[i];
const afterId = insertAfterIds[i];
this.taskRegistry.addTask(newTask, afterId);
}

// Update existing tasks
for (const taskToUpdate of tasksToUpdate) {
this.taskRegistry.updateTasks({
id: taskToUpdate.id,
updates: taskToUpdate,
});
}
}
});

// Wait for all tasks to complete
await Promise.all(taskPromises);
}
}

async finishup() {
super.finishup();

const tasks = this.taskRegistry.getTasks();
const lastTask = tasks[tasks.length - 1];
this.handlers.handleMessage({
type: 'result',
content: lastTask.result ?? '',
id: uuidv4(),
});

this.handlers.handleMessage({
type: 'session-summary',
content: this.sessionSummary,
id: uuidv4(),
});

super.finishup();
}

async close() {}
}
2 changes: 2 additions & 0 deletions src/agents/elf/registory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './skillRegistry';
export * from './taskRegistry';