diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index 1fcce57b..875fb8c4 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -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"; @@ -89,7 +89,7 @@ export const useEvoService = ( const config: EvoThreadConfig = { chatId, - loadChatLog, + loadChat, loadWorkspace, onChatLogAdded: handleChatLogAdded, onMessagesAdded: handleMessagesAdded, @@ -113,9 +113,9 @@ export const useEvoService = ( setIsConnected(true); }; - const loadChatLog = async (chatId: string) => { + const loadChat = async (chatId: string): Promise => { if (chatId === "") { - return []; + throw new Error("Cannot load chat for anonymous user."); } const { data: chats, error } = await fetchChats(supabase!); @@ -123,16 +123,15 @@ export const useEvoService = ( 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 { diff --git a/apps/browser/lib/queries/useChats.ts b/apps/browser/lib/queries/useChats.ts index 9bf97c46..2b4494f5 100644 --- a/apps/browser/lib/queries/useChats.ts +++ b/apps/browser/lib/queries/useChats.ts @@ -9,11 +9,16 @@ export interface Chat { id: string; created_at: string; title: string | null; - messages: ChatMessage[]; + messages: SavedMessage[]; logs: ChatLog[]; variables: Map } +export interface SavedMessage { + msg: ChatMessage, + temporary: boolean +} + interface MessageDTO { id: string; created_at: string; @@ -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, } } } @@ -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 } diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 14cd9fe8..494f368c 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -1,5 +1,5 @@ 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, @@ -7,11 +7,14 @@ import { ChatMessage, Workspace, InMemoryWorkspace, + EmbeddingApi, + LlmApi, } from "@evo-ninja/agents"; +import { Chat } from "@/lib/queries/useChats"; export interface EvoThreadConfig { chatId: string; - loadChatLog: (chatId: string) => Promise; + loadChat: (chatId: string) => Promise; loadWorkspace: (chatId: string) => Promise; onChatLogAdded: (chatLog: ChatLog) => Promise; onMessagesAdded: ( @@ -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; } @@ -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() }; @@ -77,12 +84,12 @@ export class EvoThread { thread._state.isLoading = true; const results = await Promise.all<[ - Promise, + Promise, Promise ]>([ - 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()); @@ -90,7 +97,8 @@ export class EvoThread { }) ]); - 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; @@ -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); + } + 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; } diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index 8c648a87..57b28348 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -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, @@ -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) => { @@ -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, @@ -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; } diff --git a/packages/agents/src/agent-core/llm/chat/Chat.ts b/packages/agents/src/agent-core/llm/chat/Chat.ts index 16c793ac..dc619bee 100644 --- a/packages/agents/src/agent-core/llm/chat/Chat.ts +++ b/packages/agents/src/agent-core/llm/chat/Chat.ts @@ -34,6 +34,16 @@ export class Chat { public async add(type: ChatLogType, msg: ChatMessage | ChatMessage[]): Promise { 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; @@ -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;