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

Socket message structure #27

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ca8b4e7
move existing repo to agents folder and update devops
foolishsailor Apr 16, 2023
466ab7b
initial project
foolishsailor Apr 16, 2023
a8e73b3
proof of concept and tooling
foolishsailor Apr 16, 2023
46218a0
proof of concept and tooling done
foolishsailor Apr 16, 2023
62f6a55
added readme
foolishsailor Apr 17, 2023
cc7a0b7
added reflecting and decision dialogue to bus
foolishsailor Apr 17, 2023
e682c5d
added reflecting and decision dialogue to bus
foolishsailor Apr 17, 2023
d073356
added reflecting and decision dialogue to bus
foolishsailor Apr 17, 2023
f046ad7
added agent split components
foolishsailor Apr 17, 2023
4dd036f
WIP
foolishsailor Apr 17, 2023
6202139
added ability to send messages
foolishsailor Apr 17, 2023
da3a32f
touch up css
foolishsailor Apr 17, 2023
901fcdd
refactorFE to best practice, tidy up css and add scroll to end for ag…
foolishsailor Apr 18, 2023
2591a00
fix type in yml
foolishsailor Apr 18, 2023
5afe5b2
fix yml directories for repos
foolishsailor Apr 18, 2023
b74929a
added default directories to jobs
foolishsailor Apr 18, 2023
664c72e
small fix to agent height calc
foolishsailor Apr 18, 2023
1fdaaf6
last css fix
foolishsailor Apr 18, 2023
79d1a58
fix indent in yml
foolishsailor Apr 18, 2023
ce53407
setup basic testing infra
foolishsailor Apr 18, 2023
1657a4d
finish infra for testing and add linting
foolishsailor Apr 18, 2023
d8dd64c
allow warnings
foolishsailor Apr 18, 2023
40324df
remove unsupported node for FE
foolishsailor Apr 18, 2023
cdbc189
remove fe from repo
foolishsailor Apr 19, 2023
caeb513
merge conflict
foolishsailor Apr 19, 2023
11d1af5
revert github actions back to old version
foolishsailor Apr 19, 2023
e2f74c2
new package.lock generated
foolishsailor Apr 19, 2023
39396c8
remove old working directory in yml
foolishsailor Apr 19, 2023
77eb392
change to node 14 lowest supported
foolishsailor Apr 19, 2023
8fa2be1
added help and test to console parameters to allow testing agents mes…
foolishsailor Apr 19, 2023
34fb6d2
updated readme
foolishsailor Apr 19, 2023
7ec2370
add test flag to web socket server
foolishsailor Apr 19, 2023
ec247a7
fix lint issue
foolishsailor Apr 19, 2023
9cbe954
prResponse
foolishsailor Apr 20, 2023
b7e2a70
revert github actions
foolishsailor Apr 20, 2023
1dafb41
lint, test, format
foolishsailor Apr 20, 2023
53cc683
add draggable
foolishsailor Apr 20, 2023
c8f1889
command socket reducer added
foolishsailor Apr 23, 2023
e0197de
finish refactor
foolishsailor Apr 24, 2023
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
### Agent state

Each agent stores its state under the `.store` directory. Agent 1, for example has

```
.store/1/memory
.store/1/goals
.store/1/notes
```

You can simply delete any of these things, or the whole agent folder (or the whole `.store`) to selectively wipe whatever state you want between runs. Otherwise agents will pick up where you left off on restart.

A nice aspect of this is that when you want to debug a problem you ran into with a particular agent, you can delete the events in their memory subsequent to the point where the problem occurred, make changes to the code, and restart them to effectively replay that moment until you've fixed the bug. You can also ask an agent to implement a feature, and once they've done so you can restart, tell them that you've loaded the feature and ask them to try it out.

# AI Legion: an LLM-powered autonomous agent platform

A framework for autonomous agents who can work together to accomplish tasks.
Expand Down Expand Up @@ -29,7 +43,8 @@ You'll also need to enable the Google Custom Search API for your Google Cloud ac
Start the program:

```
npm run start [# of agents] [gpt-3.5-turbo|gpt-4]
npm run start agents=[# of agents] model=[gpt-3.5-turbo|gpt-4]

```

Interact with the agents through the console. Anything you type will be sent as a message to all agents currently.
Expand Down
2,873 changes: 1,751 additions & 1,122 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@types/lodash": "^4.14.191",
"@types/node": "^18.15.3",
"@types/turndown": "^5.0.1",
"chalk": "^5.2.0",
"dotenv": "^16.0.3",
"googleapis": "^114.0.0",
"gpt-3-encoder": "^1.1.4",
Expand All @@ -40,6 +41,7 @@
"prettier": "^2.8.7",
"puppeteer": "^19.8.5",
"redis": "^4.6.5",
"socket.io": "^4.6.1",
"ts-jest": "^29.0.5",
"turndown": "^7.1.2",
"ws": "^8.13.0"
Expand Down
21 changes: 21 additions & 0 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import parseAction from "./parse-action";
import TaskQueue from "./task-queue";
import { agentName, sleep } from "./util";

const AGENT_ID = "0";
const actionInterval = 1000;
// const heartbeatInterval = 60 * 1000;

Expand Down Expand Up @@ -57,15 +58,35 @@ export class Agent {
// Do not act again if the last event was a decision
if (last(events)?.type === "decision") return;

const t0 = Date.now();
this.messageBus.send(
messageBuilder.agentToAgent(
this.id,
[AGENT_ID],
`Reflecting on ${events.length} events...`
)
);

const actionText = await makeDecision(events);

this.messageBus.send(
messageBuilder.agentToAgent(
this.id,
[AGENT_ID],
`Arrived at a decision after ${((Date.now() - t0) / 1000).toFixed(
1
)}s \n\n ${actionText}`
)
);

// Reassign events in case summarization occurred
events = await this.memory.append({
type: "decision",
actionText,
});

const result = parseAction(this.moduleManager.actions, actionText);

if (result.type === "error") {
this.messageBus.send(messageBuilder.error(this.id, result.message));
} else {
Expand Down
1 change: 1 addition & 0 deletions src/in-memory-message-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class InMemoryMessageBus implements MessageBus {
}

send(message: Message): void {
console.log("message bus console ===>", message);
this.emitter.emit("message", message);
}
}
72 changes: 41 additions & 31 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import notes from "./module/definitions/notes";
import web from "./module/definitions/web";
import { ModuleManager } from "./module/module-manager";
import { contextWindowSize } from "./openai";
import { model, numberOfAgents } from "./parameters";

import { model, numberOfAgents, test } from "./parameters";

import FileStore from "./store/file-store";
import JsonStore from "./store/json-store";
import { webSocketServer } from "./web-socket-server";
import webSocketServer from "./socket-server/socket-server";

dotenv.config();

Expand All @@ -29,34 +31,42 @@ main();
async function main() {
startConsole(agentIds, messageBus);

webSocketServer(messageBus, 8080);

for (const id of agentIds.slice(1)) {
const moduleManager = new ModuleManager(id, agentIds, [
core,
goals,
notes,
messaging,
filesystem,
web,
]);
const actionHandler = new ActionHandler(
agentIds,
messageBus,
moduleManager
);

const store = new JsonStore<Event[]>(new FileStore([id]));
// We have to leave room for the agent's next action, which is of unknown size
const compressionThreshold = Math.round(contextWindowSize[model] * 0.75);
const memory = new Memory(id, moduleManager, store, compressionThreshold);
const agent = new Agent(
id,
memory,
messageBus,
moduleManager,
actionHandler
);
await agent.start();
webSocketServer(messageBus, 4331, agentIds);

if (!test) {
for (const id of agentIds.slice(1)) {
const moduleManager = new ModuleManager(id, agentIds, [
core,
goals,
notes,
messaging,
filesystem,
web,
]);
const actionHandler = new ActionHandler(
agentIds,
messageBus,
moduleManager
);

const store = new JsonStore<Event[]>(new FileStore([id]));
// We have to leave room for the agent's next action, which is of unknown size
const compressionThreshold = Math.round(contextWindowSize[model] * 0.75);
const memory = new Memory(
id,
moduleManager,
store,
compressionThreshold,
messageBus
);
const agent = new Agent(
id,
memory,
messageBus,
moduleManager,
actionHandler
);
await agent.start();
}
}
}
5 changes: 0 additions & 5 deletions src/make-decision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const taskQueue = new TaskQueue();
export default function makeDecision(events: Event[]): Promise<string> {
const decisionPromise = taskQueue.run(async (): Promise<string> => {
console.log(`Reflecting on ${events.length} events...`);
const t0 = Date.now();

const messages = events.map(toOpenAiMessage);

Expand All @@ -25,10 +24,6 @@ export default function makeDecision(events: Event[]): Promise<string> {
temperature,
});

console.log(
`Arrived at a decision after ${((Date.now() - t0) / 1000).toFixed(1)}s`
);

return responseContent;
});

Expand Down
4 changes: 3 additions & 1 deletion src/memory/memory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Event } from ".";
import makeDecision, { toOpenAiMessage } from "../make-decision";
import { messageBuilder } from "../message";
import { MessageBus } from "../message-bus";
import { ModuleManager } from "../module/module-manager";
import { Store } from "../store";
import {
Expand All @@ -17,7 +18,8 @@ export class Memory {
private agentId: string,
private moduleManager: ModuleManager,
private store: Store<Event[]>,
private compressionThreshold: number
private compressionThreshold: number,
private messageBus: MessageBus
) {}

async append(event: Event): Promise<Event[]> {
Expand Down
1 change: 1 addition & 0 deletions src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface Message {
source: MessageSource;
targetAgentIds: string[];
content: string;
activeAgents?: string[];
}

type TypelessMessage = Omit<Message, "type">;
Expand Down
82 changes: 65 additions & 17 deletions src/parameters.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,66 @@
import { GPT_3_5_TURBO, GPT_4, Model } from "./openai";

const args = process.argv.slice(2); // Remove the first two elements (Node.js executable and script path)

export const numberOfAgents = args.length > 0 ? parseInt(args[0]) : 1;
console.log(`Number of agents: ${numberOfAgents}`);

const modelText = args.length > 1 ? args[1] : "gpt-3.5-turbo";
export let model: Model;
switch (modelText) {
case GPT_3_5_TURBO:
case GPT_4:
model = modelText;
break;
default:
throw Error(`Unrecognized OpenAI model: '${modelText}'`);
import { Model } from "./openai";

interface CommandLineFlags {
numberOfAgents: number;
model: Model;
test: boolean;
}

function processFlags(args: string[]): CommandLineFlags {
const flags: CommandLineFlags = {
numberOfAgents: 1,
model: "gpt-3.5-turbo",
test: false,
};

for (const arg of args) {
const [flag, value] = arg.split("=");

switch (flag) {
case "agents":
if (isNaN(parseInt(value))) {
throw new Error("Error: --agents flag value must be a number");
}
flags.numberOfAgents = parseInt(value);
break;

case "model":
if (value !== "String") {
throw new Error('Error: --model flag value must be "String"');
}
flags.model = value as Model;
break;

case "test":
if (
!value ||
(value.toLowerCase() !== "true" && value.toLowerCase() !== "false")
) {
throw new Error('Error: --test flag value must be "true" or "false"');
}
flags.test = value.toLowerCase() === "true";
break;

case "help":
console.log(` Options:

agents=X Set the value of numberOfAgents to X, where X is a number between 1-12
model=gpt-3.5-turbo | gpt-4 Set the value of the model to "String", which should match the Message interface
test=true|false Set the test value as a boolean (either "true" or "false")
help Display this help message and exit
`);

process.exit();
break;
default:
break;
}
}

return flags;
}
console.log(`Model: ${model}`);

const commandLineArgs = process.argv.slice(2);
const { model, numberOfAgents, test } = processFlags(commandLineArgs);

export { model, numberOfAgents, test };
37 changes: 37 additions & 0 deletions src/socket-server/command-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export const CommandActions = () => {
return {
startSystem: () => {
console.log("startSystem..........");
// Implement start system logic
},

stopSystem: () => {
console.log("stopSystem..........");
// Implement stop system logic
},

addAgent: (content: { id: string; name?: string; type?: string }) => {
// Implement add agent logic
},

removeAgent: (content: { id: string }) => {
// Implement remove agent logic
},

stopAgent: (content: { id: string }) => {
// Implement stop agent logic
},

startAgent: (content: { id: string }) => {
// Implement start agent logic
},

changeAgentType: (content: { id: string; type: string }) => {
// Implement change agent type logic
},

changeAgentName: (content: { id: string; name: string }) => {
// Implement change agent name logic
},
};
};
47 changes: 47 additions & 0 deletions src/socket-server/command-reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
CommandActionsEnum,
CommandActionsType,
CommandActionToMessage,
} from "./command-types";

export type CommandMessage =
CommandActionToMessage[keyof CommandActionToMessage];

type ProcessMessageFunction = (
message: CommandMessage,
actions: CommandActionsType
) => void;

export const commandMessageReducer: ProcessMessageFunction = (
message,
actions
) => {
switch (message.action) {
case CommandActionsEnum.Start:
actions.startSystem();
break;
case CommandActionsEnum.Stop:
actions.stopSystem();
break;
case CommandActionsEnum.AddAgent:
actions.addAgent(message.content);
break;
case CommandActionsEnum.RemoveAgent:
actions.removeAgent(message.content);
break;
case CommandActionsEnum.StopAgent:
actions.stopAgent(message.content);
break;
case CommandActionsEnum.StartAgent:
actions.startAgent(message.content);
break;
case CommandActionsEnum.ChangeAgentType:
actions.changeAgentType(message.content);
break;
case CommandActionsEnum.ChangeAgentName:
actions.changeAgentName(message.content);
break;
default:
console.error("Invalid action:");
}
};
Loading