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

Multiple browser goals #672

Merged
merged 5 commits into from
Dec 29, 2023
Merged
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
15 changes: 7 additions & 8 deletions apps/browser/lib/hooks/useEvoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EvoThreadCallbacks, EvoThreadConfig } from "@/lib/services/evo/EvoThrea
import { useAddChatLog } from "@/lib/mutations/useAddChatLog";
import { useAddMessages } from "@/lib/mutations/useAddMessages";
import { useAddVariable } from "@/lib/mutations/useAddVariable";
import { fetchChats, useChats } from "@/lib/queries/useChats";
import { Chat, fetchChats, useChats } from "@/lib/queries/useChats";
import { SupabaseWorkspace } from "@/lib/supabase/SupabaseWorkspace";
import { useWorkspaceFilesUpdate } from "@/lib/hooks/useWorkspaceFilesUpdate";
import { useWorkspaceUploadUpdate } from "@/lib/hooks/useWorkspaceUploadUpdate";
Expand Down Expand Up @@ -89,7 +89,7 @@ export const useEvoService = (

const config: EvoThreadConfig = {
chatId,
loadChatLog,
loadChat,
loadWorkspace,
onChatLogAdded: handleChatLogAdded,
onMessagesAdded: handleMessagesAdded,
Expand All @@ -113,26 +113,25 @@ export const useEvoService = (
setIsConnected(true);
};

const loadChatLog = async (chatId: string) => {
const loadChat = async (chatId: string): Promise<Chat> => {
if (chatId === "<anon>") {
return [];
throw new Error("Cannot load chat for anonymous user.");
}

const { data: chats, error } = await fetchChats(supabase!);

if (error) {
console.error(error);
setError("Failed to fetch user chats.");
return [];
throw error;
}

const currentChat = chats?.find(c => c.id === chatId);

if (!currentChat) {
return [];
throw new Error(`Chat with id ${chatId} not found.`);
}

return currentChat.logs;
return currentChat;
};

async function loadWorkspace(chatId: string): Promise<Workspace> {
Expand Down
49 changes: 32 additions & 17 deletions apps/browser/lib/queries/useChats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ export interface Chat {
id: string;
created_at: string;
title: string | null;
messages: ChatMessage[];
messages: SavedMessage[];
logs: ChatLog[];
variables: Map<string, string>
}

export interface SavedMessage {
msg: ChatMessage,
temporary: boolean
}

interface MessageDTO {
id: string;
created_at: string;
Expand Down Expand Up @@ -49,42 +54,52 @@ interface ChatDTO {
messages: MessageDTO[];
}

const mapMessageDTOtoMessage = (dto: MessageDTO): ChatMessage & { temporary: boolean } => {
const mapMessageDTOtoMessage = (dto: MessageDTO): SavedMessage => {
const messageRole = dto.role as "function" | "user" | "tool" | "system" | "assistant"

switch (messageRole) {
case "user":
case "system": {
return {
role: messageRole,
content: dto.content,
temporary: dto.temporary
msg: {
role: messageRole,
content: dto.content,
},
temporary: dto.temporary,
}
}
case "function": {
return {
role: messageRole,
content: dto.content,
msg: {
role: messageRole,
content: dto.content,
name: dto.name as string
},
temporary: dto.temporary,
name: dto.name as string
}
}
case "assistant": {
return {
role: messageRole,
content: dto.content,
msg: {
role: messageRole,
content: dto.content,
// TODO: Json casting
function_call: dto.function_call as any ?? undefined,
tool_calls: dto.tool_calls as any
? dto.tool_calls as any
: undefined,
},
temporary: dto.temporary,
// TODO: Json casting
function_call: dto.function_call as any,
tool_calls: dto.tool_calls as any,
}
}
case "tool": {
return {
role: messageRole,
content: dto.content,
msg: {
role: messageRole,
content: dto.content,
tool_call_id: dto.tool_call_id as string,
},
temporary: dto.temporary,
tool_call_id: dto.tool_call_id as string,
}
}
}
Expand All @@ -104,7 +119,7 @@ const mapChatDTOtoChat = (dto: ChatDTO): Chat => {
id: dto.id,
created_at: dto.created_at,
title: dto.title,
messages,
messages: messages,
variables,
logs
}
Expand Down
81 changes: 57 additions & 24 deletions apps/browser/lib/services/evo/EvoThread.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { createEvoInstance } from "@/lib/services/evo/createEvoInstance";
import { GoalApi } from "@/lib/api";
import { GoalApi, ProxyEmbeddingApi, ProxyLlmApi } from "@/lib/api";
import { ChatLog } from "@/components/Chat";
import {
Evo,
ChatLogType,
ChatMessage,
Workspace,
InMemoryWorkspace,
EmbeddingApi,
LlmApi,
} from "@evo-ninja/agents";
import { Chat } from "@/lib/queries/useChats";

export interface EvoThreadConfig {
chatId: string;
loadChatLog: (chatId: string) => Promise<ChatLog[]>;
loadChat: (chatId: string) => Promise<Chat>;
loadWorkspace: (chatId: string) => Promise<Workspace>;
onChatLogAdded: (chatLog: ChatLog) => Promise<void>;
onMessagesAdded: (
Expand All @@ -23,10 +26,12 @@ export interface EvoThreadConfig {

export interface EvoThreadState {
goal: string | undefined;
evo: Evo | undefined;
status: string | undefined;
isRunning: boolean;
isLoading: boolean;
logs: ChatLog[];
chat: Chat | undefined;
workspace: Workspace;
}

Expand All @@ -47,10 +52,12 @@ export interface EvoThreadStartOptions {

const INIT_STATE: EvoThreadState = {
goal: undefined,
evo: undefined,
status: undefined,
isRunning: false,
isLoading: false,
logs: [],
chat: undefined,
workspace: new InMemoryWorkspace()
};

Expand All @@ -77,20 +84,21 @@ export class EvoThread {
thread._state.isLoading = true;

const results = await Promise.all<[
Promise<ChatLog[]>,
Promise<Chat>,
Promise<Workspace>
]>([
thread._config.loadChatLog(chatId).catch((reason) => {
thread._config.loadChat(chatId).catch((reason) => {
thread._callbacks?.onError(reason.toString());
return [];
throw reason;
}),
thread._config.loadWorkspace(chatId).catch((reason) => {
thread._callbacks?.onError(reason.toString());
return new InMemoryWorkspace();
})
]);

thread._state.logs = results[0];
thread._state.chat = results[0];
thread._state.logs = results[0].logs;
thread._state.workspace = results[1];
thread._state.isLoading = false;

Expand Down Expand Up @@ -169,29 +177,54 @@ export class EvoThread {
return;
}

// Create an Evo instance
const evo = createEvoInstance(
goalId,
this._state.workspace,
options.openAiApiKey,
this._config.onMessagesAdded,
this._config.onVariableSet,
(chatLog) => this.onChatLog(chatLog),
(status) => this.onStatusUpdate(status),
() => this._callbacks?.onGoalCapReached(),
// onError
(error) => this._callbacks?.onError(error)
);
if (!this._state.evo) {
const evo = createEvoInstance(
this._state.workspace,
options.openAiApiKey,
this._config.onMessagesAdded,
this._config.onVariableSet,
(chatLog) => this.onChatLog(chatLog),
(status) => this.onStatusUpdate(status),
() => this._callbacks?.onGoalCapReached(),
(error) => this._callbacks?.onError(error)
);

if (!evo) {
this.setIsRunning(false);
return;
if (!evo) {
this.setIsRunning(false);
return;
}

this._state.evo = evo;

if (this._state.chat?.messages.length) {
await this._state.evo.context.chat.addWithoutEvents(
"persistent",
this._state.chat.messages
.filter(x => !x.temporary)
.map(x => x.msg)
);
await this._state.evo.context.chat.addWithoutEvents(
"temporary",
this._state.chat.messages
.filter(x => x.temporary)
.map(x => x.msg)
);
} else {
await this._state.evo.init();
}
}

await evo.init();
const { llm, embedding } = this._state.evo.context;

if (llm instanceof ProxyLlmApi) {
llm.setGoalId(goalId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice 🧽

}
if (embedding instanceof ProxyEmbeddingApi) {
embedding.setGoalId(goalId);
}

// Run the evo instance against the goal
await this.runEvo(evo, options.goal);
await this.runEvo(this._state.evo, options.goal);
this._state.goal = undefined;
}

Expand Down
9 changes: 3 additions & 6 deletions apps/browser/lib/services/evo/createEvoInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
import cl100k_base from "gpt-tokenizer/esm/encoding/cl100k_base";

export function createEvoInstance(
goalId: string,
workspace: Workspace,
openAiApiKey: string | undefined,
onMessagesAdded: (type: ChatLogType, messages: ChatMessage[]) => Promise<void>,
Expand All @@ -34,6 +33,9 @@ export function createEvoInstance(
onGoalCapReached: () => void,
onError: (error: string) => void
): Evo | undefined {
let llm: LlmApi;
let embedding: EmbeddingApi;

try {
const browserLogger = new BrowserLogger({
onLog: async (message: string) => {
Expand Down Expand Up @@ -66,9 +68,6 @@ export function createEvoInstance(
MAX_RESPONSE_TOKENS: "4096",
});

let llm: LlmApi;
let embedding: EmbeddingApi;

if (openAiApiKey) {
llm = new OpenAILlmApi(
env.OPENAI_API_KEY,
Expand All @@ -91,13 +90,11 @@ export function createEvoInstance(
env.MAX_RESPONSE_TOKENS,
onGoalCapReached,
);
llmProxy.setGoalId(goalId);
llm = llmProxy;
const embeddingProxy = new ProxyEmbeddingApi(
cl100k_base,
onGoalCapReached
);
embeddingProxy.setGoalId(goalId);
embedding = embeddingProxy;
}

Expand Down
14 changes: 10 additions & 4 deletions packages/agents/src/agent-core/llm/chat/Chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export class Chat {

public async add(type: ChatLogType, msg: ChatMessage | ChatMessage[]): Promise<void> {
const msgs = Array.isArray(msg) ? msg : [msg];

this.addWithoutEvents(type, msgs);

if (this.options?.onMessagesAdded) {
await this.options.onMessagesAdded(type, msgs);
}
}

public addWithoutEvents(type: ChatLogType, msg: ChatMessage | ChatMessage[]) {
const msgs = Array.isArray(msg) ? msg : [msg];

const msgsWithTokens = msgs.map((msg) => {
const tokens = this._tokenizer.encode(JSON.stringify(msg)).length;
Expand All @@ -42,10 +52,6 @@ export class Chat {
const tokens = msgsWithTokens.map(({ tokens }) => tokens);

this._chatLogs.add(type, msgs, tokens)

if (this.options?.onMessagesAdded) {
await this.options.onMessagesAdded(type, msgs);
}
}

public async persistent(role: ChatRole, content: string): Promise<void>;
Expand Down
Loading