From 004fade2783b839103d1c10fc20092d14a164c8c Mon Sep 17 00:00:00 2001 From: Daniel Campagnoli Date: Fri, 6 Sep 2024 10:40:03 +0800 Subject: [PATCH] Add Cerebras support. Add initial Slackbot code. Add agent execution completion handlers --- package-lock.json | 39 +++- package.json | 5 +- src/agent/agentCompletion.ts | 62 +++++++ src/agent/agentContext.test.ts | 3 +- ...Context.ts => agentContextLocalStorage.ts} | 139 ++------------ src/agent/agentContextTypes.ts | 133 ++++++++++++++ src/agent/agentFunctions.ts | 2 +- src/agent/agentPromptUtils.ts | 2 +- src/agent/agentRunner.ts | 27 +-- .../agentStateService.int.ts | 2 +- .../agentStateService/agentStateService.ts | 2 +- .../fileAgentStateService.ts | 3 +- .../firestoreAgentStateService.ts | 6 +- .../inMemoryAgentStateService.ts | 3 +- src/agent/agentWorkflowRunner.ts | 3 +- src/agent/caching-python-agent-system-prompt | 112 ++++++------ src/agent/cachingPythonAgentRunner.ts | 139 ++++++++------ src/agent/completionHandlerRegistry.ts | 21 +++ src/agent/humanInTheLoop.ts | 2 +- src/agent/python-agent-system-prompt | 6 +- src/agent/pythonAgentRunner.test.ts | 3 +- src/agent/pythonAgentRunner.ts | 47 +++-- src/agent/xmlAgentRunner.test.ts | 3 +- src/agent/xmlAgentRunner.ts | 17 +- src/cache/firestoreFunctionCache.test.ts | 2 +- src/cache/firestoreFunctionCacheService.ts | 2 +- src/chatBot/chatBotService.ts | 2 +- src/cli/code.ts | 3 +- src/cli/docs.ts | 2 +- src/cli/gaia.ts | 2 +- src/cli/gen.ts | 3 +- src/cli/py.ts | 41 ++++- src/cli/research.ts | 2 +- src/cli/scrape.ts | 2 +- src/cli/swe.ts | 2 +- src/cli/swebench.ts | 2 +- src/cli/util.ts | 3 +- src/fastify/trace-init/trace-init.ts | 2 +- src/functionSchema/functionDecorators.ts | 2 +- src/functions/cloud/google-cloud.ts | 2 +- src/functions/image.ts | 2 +- src/functions/scm/github.ts | 2 +- src/functions/scm/gitlab.ts | 4 +- src/functions/scm/sourceControlManagement.ts | 2 +- src/functions/storage/filesystem.ts | 2 +- src/functions/storage/localFileStore.test.ts | 2 +- src/functions/storage/localFileStore.ts | 2 +- src/functions/testFunctions.ts | 2 +- src/functions/toolType.ts | 4 +- src/functions/util.ts | 2 +- src/functions/web/perplexity.ts | 2 +- src/functions/web/web.ts | 2 +- src/functions/web/webResearch.ts | 2 +- src/llm/llm.ts | 19 +- src/llm/llmCallService/llmCall.ts | 2 +- src/llm/llmFactory.ts | 2 +- src/llm/models/anthropic-vertex.ts | 60 ++++--- src/llm/models/anthropic.ts | 3 +- src/llm/models/cerebras.ts | 170 ++++++++++++++++++ src/llm/models/deepseek.ts | 4 +- src/llm/models/fireworks.ts | 4 +- src/llm/models/groq.ts | 11 +- src/llm/models/llm.int.ts | 143 ++++++++++----- src/llm/models/mock-llm.ts | 3 +- src/llm/models/ollama.ts | 3 +- src/llm/models/openai.ts | 4 +- src/llm/models/together.ts | 4 +- src/llm/models/vertexai.ts | 3 +- src/llm/multi-llm.ts | 2 +- src/modules/slack/slack.ts | 23 ++- src/modules/slack/slackChatBotService.ts | 50 ++++-- src/o11y/trace.ts | 2 +- src/routes/agent/agent-details-routes.ts | 4 +- src/routes/agent/agent-execution-routes.ts | 2 +- src/routes/gitlab/gitlabRoutes-v1.ts | 3 +- src/swe/SWEBenchAgent.ts | 2 +- src/swe/analyzeCompileErrors.ts | 2 +- src/swe/codeEditingAgent.ts | 2 +- src/swe/codeEditor.ts | 2 +- src/swe/createBranchName.ts | 2 +- src/swe/documentationBuilder.ts | 2 +- src/swe/generateTestRequirements.ts | 2 +- src/swe/lang/nodejs/researchNpmPackage.ts | 2 +- src/swe/lang/nodejs/typescriptTools.ts | 2 +- src/swe/lang/python/pythonTools.ts | 2 +- src/swe/lang/terraform/terraformTools.ts | 2 +- src/swe/projectDetection.ts | 2 +- src/swe/projectMap.ts | 2 +- src/swe/pullRequestTitleDescription.ts | 2 +- src/swe/reviewChanges.ts | 2 +- src/swe/selectFilesToEdit.ts | 2 +- src/swe/selectProject.ts | 2 +- src/swe/simpleCodeEditor.ts | 2 +- src/swe/softwareDeveloperAgent.ts | 2 +- src/swe/summariseRequirements.ts | 2 +- src/swe/supportingInformation.ts | 2 +- src/user/user.ts | 19 +- src/user/userService/userContext.ts | 2 +- src/utils/exec.ts | 2 +- tsconfig.json | 1 + variables/local.env.example | 2 + 101 files changed, 952 insertions(+), 522 deletions(-) create mode 100644 src/agent/agentCompletion.ts rename src/agent/{agentContext.ts => agentContextLocalStorage.ts} (56%) create mode 100644 src/agent/agentContextTypes.ts create mode 100644 src/agent/completionHandlerRegistry.ts create mode 100644 src/llm/models/cerebras.ts diff --git a/package-lock.json b/package-lock.json index 35093c23..038392e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,9 @@ "version": "0.0.1-dev", "license": "ISC", "dependencies": { - "@anthropic-ai/sdk": "^0.26.1", - "@anthropic-ai/vertex-sdk": "^0.4.0", + "@anthropic-ai/sdk": "^0.27.1", + "@anthropic-ai/vertex-sdk": "^0.4.1", + "@cerebras/cerebras_cloud_sdk": "^1.0.1", "@cliqz/adblocker-puppeteer": "^1.30.0", "@duckduckgo/autoconsent": "^10.11.0", "@fastify/cors": "^9.0.1", @@ -125,9 +126,9 @@ } }, "node_modules/@anthropic-ai/sdk": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.26.1.tgz", - "integrity": "sha512-HeMJP1bDFfQPQS3XTJAmfXkFBdZ88wvfkE05+vsoA9zGn5dHqEaHOPsqkazf/i0gXYg2XlLxxZrf6rUAarSqzw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.1.tgz", + "integrity": "sha512-AKFd/E8HO26+DOVPiZpEked3Pm2feA5d4gcX2FcJXr9veDkXbKO90hr2C7N2TL7mPIMwm040ldXlsIZQ416dHg==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -148,9 +149,9 @@ } }, "node_modules/@anthropic-ai/vertex-sdk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.4.0.tgz", - "integrity": "sha512-E/FL/P1+wDNrhuVg7DYmbiLdW6+xU9d2Vn/dmpJbKF7Vt81SnGxUFYn9zjDk2QOptvQFSOcUb5OCtpEvej+daQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.4.1.tgz", + "integrity": "sha512-RT/2CWzqyAcJDZWxnNc1mXa7XiiHDaQ9aknfW4mIDw6zE+Zj/R2vCKpTb0dIwrmHYNOyKQNaD7Z1ynDt9oXFWA==", "dependencies": { "@anthropic-ai/sdk": ">=0.14 <1", "google-auth-library": "^9.4.2" @@ -416,6 +417,28 @@ "node": ">=14.*" } }, + "node_modules/@cerebras/cerebras_cloud_sdk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@cerebras/cerebras_cloud_sdk/-/cerebras_cloud_sdk-1.0.1.tgz", + "integrity": "sha512-6ulgEZNmCntxOAqbbXwm5lWneFM2evoRNJYH5EEd86OdYdB8HB3zncvuskR9G6BfIxb5ehCPI5sFewI5PbBvtg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@cerebras/cerebras_cloud_sdk/node_modules/@types/node": { + "version": "18.19.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", + "integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@cliqz/adblocker": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@cliqz/adblocker/-/adblocker-1.30.0.tgz", diff --git a/package.json b/package.json index c8dd232b..857aff0b 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,9 @@ "author": "daniel.campagnoli@trafficguard.ai", "license": "ISC", "dependencies": { - "@anthropic-ai/sdk": "^0.26.1", - "@anthropic-ai/vertex-sdk": "^0.4.0", + "@anthropic-ai/sdk": "^0.27.1", + "@anthropic-ai/vertex-sdk": "^0.4.1", + "@cerebras/cerebras_cloud_sdk": "^1.0.1", "@cliqz/adblocker-puppeteer": "^1.30.0", "@duckduckgo/autoconsent": "^10.11.0", "@fastify/cors": "^9.0.1", diff --git a/src/agent/agentCompletion.ts b/src/agent/agentCompletion.ts new file mode 100644 index 00000000..861932e7 --- /dev/null +++ b/src/agent/agentCompletion.ts @@ -0,0 +1,62 @@ +import { AgentCompleted, AgentContext } from '#agent/agentContextTypes'; +import { FunctionCallResult } from '#llm/llm'; +import { logger } from '#o11y/logger'; +import { envVar } from '#utils/env-var'; + +/** + * Runs the completionHandler on an agent + * @param agent + */ +export async function runAgentCompleteHandler(agent: AgentContext): Promise { + try { + const completionHandler = agent.completedHandler ?? new ConsoleCompletedHandler(); + await completionHandler.notifyCompleted(agent); + } catch (e) { + logger.warn(e, `Completion handler error for agent ${agent.agentId}`); + throw e; + } +} + +/** + * Creates a generic notification message for the completion of an agent execution + * @param agent + */ +export function completedNotificationMessage(agent: AgentContext) { + const uiUrl = envVar('UI_URL'); + let message = stateNotificationMessage(agent); + message += `\n${uiUrl}/agent/${agent.agentId}`; + return message; +} + +/** + * Outputs the standard agent completion message to the console + */ +export class ConsoleCompletedHandler implements AgentCompleted { + notifyCompleted(agentContext: AgentContext): Promise { + console.log(completedNotificationMessage(agentContext)); + return Promise.resolve(); + } + + agentCompletedHandlerId(): string { + return 'console'; + } +} + +export function stateNotificationMessage(agent: AgentContext): string { + switch (agent.state) { + case 'error': + return `Agent error.\nName:${agent.name}\nError: ${agent.error}`; + case 'hil': + return `Agent has reached Human-in-the-loop threshold.\nName: ${agent.name}`; + case 'feedback': + return `Agent has requested feedback.\nName: ${agent.name}\n:Question: ${getLastFunctionCallArg(agent)}`; + case 'completed': + return `Agent has completed.\nName: ${agent.name}\nNote: ${getLastFunctionCallArg(agent)}`; + default: + } +} + +export function getLastFunctionCallArg(agent: AgentContext) { + const result: FunctionCallResult = agent.functionCallHistory.slice(-1)[0]; + return Object.values(result.parameters)[0]; +} diff --git a/src/agent/agentContext.test.ts b/src/agent/agentContext.test.ts index 327f4442..3f6794a3 100644 --- a/src/agent/agentContext.test.ts +++ b/src/agent/agentContext.test.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, createContext, deserializeAgentContext, serializeContext } from '#agent/agentContext'; +import { createContext, deserializeAgentContext, serializeContext } from '#agent/agentContextLocalStorage'; +import { AgentContext } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { FileSystem } from '#functions/storage/filesystem'; import { LlmTools } from '#functions/util'; diff --git a/src/agent/agentContext.ts b/src/agent/agentContextLocalStorage.ts similarity index 56% rename from src/agent/agentContext.ts rename to src/agent/agentContextLocalStorage.ts index dfb133df..7c95146b 100644 --- a/src/agent/agentContext.ts +++ b/src/agent/agentContextLocalStorage.ts @@ -1,138 +1,16 @@ import { randomUUID } from 'crypto'; import { AsyncLocalStorage } from 'async_hooks'; import { LlmFunctions } from '#agent/LlmFunctions'; +import { ConsoleCompletedHandler } from '#agent/agentCompletion'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; +import { getCompletedHandler } from '#agent/completionHandlerRegistry'; import { FileSystem } from '#functions/storage/filesystem'; -import { FunctionCall, FunctionCallResult, LLM } from '#llm/llm'; import { deserializeLLMs } from '#llm/llmFactory'; import { logger } from '#o11y/logger'; -import { User } from '#user/user'; import { currentUser } from '#user/userService/userContext'; import { appContext } from '../app'; -/** - * The difficulty of a LLM generative task. Used to select an appropriate model for the cost vs capability. - * easy Haiku/GPT4-mini - * medium Sonnet - * hard Opus - * xhard Ensemble (multi-gen with voting/merging of best answer) - * - */ -export type TaskLevel = 'easy' | 'medium' | 'hard' | 'xhard'; - -/** - * The LLMs for each Task Level - */ -export type AgentLLMs = Record; - -/** - * agent - waiting for the agent LLM call(s) to generate control loop update - * functions - waiting for the planned function call(s) to complete - * error - the agent control loop has errored - * hil - deprecated for humanInLoop_agent and humanInLoop_tool - * hitl_threshold - If the agent has reached budget or iteration thresholds. At this point the agent is not executing any LLM/function calls. - * hitl_tool - When a function has request HITL in the function calling part of the control loop - * hitl_feedback - the agent has requested human feedback for a decision. At this point the agent is not executing any LLM/function calls. - * hil - deprecated version of hitl_feedback - * feedback - deprecated version of hitl_feedback - * child_agents - waiting for child agents to complete - * completed - the agent has called the completed function. - * shutdown - if the agent has been instructed by the system to pause (e.g. for server shutdown) - * timeout - for chat agents when there hasn't been a user input for a configured amount of time - */ -export type AgentRunningState = - | 'workflow' - | 'agent' - | 'functions' - | 'error' - | 'hil' - | 'hitl_threshold' - | 'hitl_tool' - | 'feedback' - | 'hitl_feedback' - | 'completed' - | 'shutdown' - | 'child_agents' - | 'timeout'; - -export type AgentType = 'xml' | 'python' | 'cachingPython'; - -export interface Message { - role: 'user' | 'assistant'; - text: string; -} - -export function isExecuting(agent: AgentContext): boolean { - return agent.state !== 'completed' && agent.state !== 'feedback' && agent.state !== 'hil' && agent.state !== 'error'; -} - -/** - * The state of an agent. - */ -export interface AgentContext { - /** Primary Key - Agent instance id. Allocated when the agent is first starts */ - agentId: string; - /** Id of the running execution. This changes after the agent restarts due to an error, pausing, human in loop, etc */ - executionId: string; - /** Current OpenTelemetry traceId */ - traceId: string; - /** Display name */ - name: string; - /** Not used yet */ - parentAgentId?: string; - /** The user who created the agent */ - user: User; - /** The current state of the agent */ - state: AgentRunningState; - /** Tracks what functions/spans we've called into */ - callStack: string[]; - /** Error message & stack */ - error?: string; - /** Budget spend in $USD until a human-in-the-loop is required */ - hilBudget; - /** Total cost of running this agent */ - cost: number; - /** Budget remaining until human intervention is required */ - budgetRemaining: number; - /** Pre-configured LLMs by task difficulty level for the agent. Specific LLMs can always be instantiated if required. */ - llms: AgentLLMs; - /** Working filesystem */ - fileSystem?: FileSystem | null; - /** Memory persisted over the agent's executions */ - memory: Record; - /** Time of the last database write of the state */ - lastUpdate: number; - - metadata: Record; - - // ChatBot properties ---------------- - - messages: Message[]; - /** Messages sent by users while the agent is still processing the last message */ - pendingMessages: Message[]; - - // Autonomous agent specific properties -------------------- - - /** The type of autonomous agent function calling.*/ - type: AgentType; - /** The number of completed iterations of the agent control loop */ - iterations: number; - /** The function calls the agent is about to call (xml only) */ - invoking: FunctionCall[]; - /** Additional notes that tool functions can add to the response to the agent */ - notes: string[]; - /** The initial user prompt */ - userPrompt: string; - /** The prompt the agent execution started/resumed with */ - inputPrompt: string; - /** Completed function calls with success/error output */ - functionCallHistory: FunctionCallResult[]; - /** How many iterations of the autonomous agent control loop to require human input to continue */ - hilCount; - /** The functions available to the agent */ - functions: LlmFunctions; -} - export const agentContextStorage = new AsyncLocalStorage(); export function agentContext(): AgentContext | undefined { @@ -215,6 +93,7 @@ export function createContext(config: RunAgentConfig): AgentContext { llms: config.llms, fileSystem, functions: Array.isArray(config.functions) ? new LlmFunctions(...config.functions) : config.functions, + completedHandler: config.completedHandler ?? new ConsoleCompletedHandler(), memory: {}, invoking: [], lastUpdate: Date.now(), @@ -226,7 +105,7 @@ export function createContext(config: RunAgentConfig): AgentContext { export function serializeContext(context: AgentContext): Record { const serialized = {}; - for (const key of Object.keys(context)) { + for (const key of Object.keys(context) as Array) { if (context[key] === undefined) { // do nothing } else if (context[key] === null) { @@ -256,6 +135,8 @@ export function serializeContext(context: AgentContext): Record { }; } else if (key === 'user') { serialized[key] = context.user.id; + } else if (key === 'completedHandler') { + context.completedHandler.agentCompletedHandlerId(); } // otherwise throw error else { @@ -265,7 +146,7 @@ export function serializeContext(context: AgentContext): Record { return serialized; } -export async function deserializeAgentContext(serialized: Record): Promise { +export async function deserializeAgentContext(serialized: Record): Promise { const context: Partial = {}; for (const key of Object.keys(serialized)) { @@ -276,7 +157,7 @@ export async function deserializeAgentContext(serialized: Record): } context.fileSystem = new FileSystem().fromJSON(serialized.fileSystem); - context.functions = new LlmFunctions().fromJSON(serialized.functions ?? serialized.toolbox); // toolbox for backward compat + context.functions = new LlmFunctions().fromJSON(serialized.functions ?? (serialized as any).toolbox); // toolbox for backward compat resetFileSystemFunction(context as AgentContext); // TODO add a test for this @@ -288,6 +169,8 @@ export async function deserializeAgentContext(serialized: Record): if (serialized.user === user.id) context.user = user; else context.user = await appContext().userService.getUser(serialized.user); + context.completedHandler = getCompletedHandler(serialized.completedHandler); + // backwards compatability if (!context.type) context.type = 'xml'; if (!context.iterations) context.iterations = 0; diff --git a/src/agent/agentContextTypes.ts b/src/agent/agentContextTypes.ts new file mode 100644 index 00000000..2fcfb7e1 --- /dev/null +++ b/src/agent/agentContextTypes.ts @@ -0,0 +1,133 @@ +import { LlmFunctions } from '#agent/LlmFunctions'; +import { FileSystem } from '#functions/storage/filesystem'; +import { FunctionCall, FunctionCallResult, LLM, LlmMessage } from '#llm/llm'; +import { User } from '#user/user'; + +/** + * The difficulty of a LLM generative task. Used to select an appropriate model for the cost vs capability. + * easy Haiku/GPT4-mini + * medium Sonnet + * hard Opus + * xhard Ensemble (multi-gen with voting/merging of best answer) + * + */ +export type TaskLevel = 'easy' | 'medium' | 'hard' | 'xhard'; +export type AgentType = 'xml' | 'python'; + +export interface AgentCompleted { + notifyCompleted(agentContext: AgentContext): Promise; + + agentCompletedHandlerId(): string; +} + +/** + * agent - waiting for the agent LLM call(s) to generate control loop update + * functions - waiting for the planned function call(s) to complete + * error - the agent control loop has errored + * hil - deprecated for humanInLoop_agent and humanInLoop_tool + * hitl_threshold - If the agent has reached budget or iteration thresholds. At this point the agent is not executing any LLM/function calls. + * hitl_tool - When a function has request HITL in the function calling part of the control loop + * hitl_feedback - the agent has requested human feedback for a decision. At this point the agent is not executing any LLM/function calls. + * hil - deprecated version of hitl_feedback + * feedback - deprecated version of hitl_feedback + * child_agents - waiting for child agents to complete + * completed - the agent has called the completed function. + * shutdown - if the agent has been instructed by the system to pause (e.g. for server shutdown) + * timeout - for chat agents when there hasn't been a user input for a configured amount of time + */ +export type AgentRunningState = + | 'workflow' + | 'agent' + | 'functions' + | 'error' + | 'hil' + | 'hitl_threshold' + | 'hitl_tool' + | 'feedback' + | 'hitl_feedback' + | 'completed' + | 'shutdown' + | 'child_agents' + | 'timeout'; + +/** + * @param agent + * @returns if the agent has a live execution thread + */ +export function isExecuting(agent: AgentContext): boolean { + return agent.state !== 'completed' && agent.state !== 'feedback' && agent.state !== 'hil' && agent.state !== 'error'; +} + +/** + * The LLMs for each Task Level + */ +export type AgentLLMs = Record; + +/** + * The state of an agent. + */ +export interface AgentContext { + /** Primary Key - Agent instance id. Allocated when the agent is first starts */ + agentId: string; + /** Id of the running execution. This changes after the agent restarts due to an error, pausing, human in loop, etc */ + executionId: string; + /** Current OpenTelemetry traceId */ + traceId: string; + /** Display name */ + name: string; + /** Not used yet */ + parentAgentId?: string; + /** The user who created the agent */ + user: User; + /** The current state of the agent */ + state: AgentRunningState; + /** Tracks what functions/spans we've called into */ + callStack: string[]; + /** Error message & stack */ + error?: string; + /** Budget spend in $USD until a human-in-the-loop is required */ + hilBudget; + /** Total cost of running this agent */ + cost: number; + /** Budget remaining until human intervention is required */ + budgetRemaining: number; + /** Pre-configured LLMs by task difficulty level for the agent. Specific LLMs can always be instantiated if required. */ + llms: AgentLLMs; + /** Working filesystem */ + fileSystem?: FileSystem | null; + /** Memory persisted over the agent's executions */ + memory: Record; + /** Time of the last database write of the state */ + lastUpdate: number; + /** Agent custom fields */ + metadata: Record; + + /** The functions available to the agent */ + functions: LlmFunctions; + completedHandler?: AgentCompleted; + + // ChatBot properties ---------------- + + messages: LlmMessage[]; + /** Messages sent by users while the agent is still processing the last message */ + pendingMessages: string[]; + + // Autonomous agent specific properties -------------------- + + /** The type of autonomous agent function calling.*/ + type: AgentType; + /** The number of completed iterations of the agent control loop */ + iterations: number; + /** The function calls the agent is about to call (xml only) */ + invoking: FunctionCall[]; + /** Additional notes that tool functions can add to the response to the agent */ + notes: string[]; + /** The initial user prompt */ + userPrompt: string; + /** The prompt the agent execution started/resumed with */ + inputPrompt: string; + /** Completed function calls with success/error output */ + functionCallHistory: FunctionCallResult[]; + /** How many iterations of the autonomous agent control loop to require human input to continue */ + hilCount; +} diff --git a/src/agent/agentFunctions.ts b/src/agent/agentFunctions.ts index 3cd3ae9e..704786ab 100644 --- a/src/agent/agentFunctions.ts +++ b/src/agent/agentFunctions.ts @@ -1,4 +1,4 @@ -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; diff --git a/src/agent/agentPromptUtils.ts b/src/agent/agentPromptUtils.ts index 169eee51..2bb841ea 100644 --- a/src/agent/agentPromptUtils.ts +++ b/src/agent/agentPromptUtils.ts @@ -1,4 +1,4 @@ -import { agentContext, getFileSystem } from '#agent/agentContext'; +import { agentContext, getFileSystem } from '#agent/agentContextLocalStorage'; import { FileMetadata, FileStore } from '#functions/storage/filestore'; import { FileSystem } from '#functions/storage/filesystem'; import { FunctionCallResult } from '#llm/llm'; diff --git a/src/agent/agentRunner.ts b/src/agent/agentRunner.ts index a089d367..99213d3d 100644 --- a/src/agent/agentRunner.ts +++ b/src/agent/agentRunner.ts @@ -1,5 +1,6 @@ import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentLLMs, AgentType, createContext, llms } from '#agent/agentContext'; +import { createContext, llms } from '#agent/agentContextLocalStorage'; +import { AgentCompleted, AgentContext, AgentLLMs, AgentType } from '#agent/agentContextTypes'; import { AGENT_REQUEST_FEEDBACK } from '#agent/agentFunctions'; import { runCachingPythonAgent } from '#agent/cachingPythonAgentRunner'; import { runPythonAgent } from '#agent/pythonAgentRunner'; @@ -28,6 +29,8 @@ export interface RunAgentConfig { type?: AgentType; /** The function classes the agent has available to call */ functions: LlmFunctions | Array any>; + /** Handler for when the agent finishes executing. Defaults to console output */ + completedHandler?: AgentCompleted; /** The user prompt */ initialPrompt: string; /** The agent system prompt */ @@ -66,9 +69,6 @@ async function runAgent(agent: AgentContext): Promise { case 'python': execution = await runPythonAgent(agent); break; - case 'cachingPython': - execution = await runCachingPythonAgent(agent); - break; default: throw new Error(`Invalid agent type ${agent.type}`); } @@ -199,25 +199,6 @@ export async function summariseLongFunctionOutput(functionCall: FunctionCall, re return await llms().easy.generateText(prompt, null, { id: 'summariseLongFunctionOutput' }); } -export function notificationMessage(agent: AgentContext): string { - switch (agent.state) { - case 'error': - return `Agent error.\nName:${agent.name}\nError: ${agent.error}`; - case 'hil': - return `Agent has reached Human-in-the-loop threshold.\nName: ${agent.name}`; - case 'feedback': - return `Agent has requested feedback.\nName: ${agent.name}\n:Question: ${getLastFunctionCallArg(agent)}`; - case 'completed': - return `Agent has completed.\nName: ${agent.name}\nNote: ${getLastFunctionCallArg(agent)}`; - default: - } -} - -function getLastFunctionCallArg(agent: AgentContext) { - const result: FunctionCallResult = agent.functionCallHistory.slice(-1)[0]; - return Object.values(result.parameters)[0]; -} - /** * Formats the output of a successful function call * @param functionName diff --git a/src/agent/agentStateService/agentStateService.int.ts b/src/agent/agentStateService/agentStateService.int.ts index 4cab5d74..28aa364f 100644 --- a/src/agent/agentStateService/agentStateService.int.ts +++ b/src/agent/agentStateService/agentStateService.int.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import { expect } from 'chai'; -import { AgentContext } from '#agent/agentContext'; +import { AgentContext } from '#agent/agentContextTypes'; import { AgentStateService } from '#agent/agentStateService/agentStateService'; import { FirestoreAgentStateService } from '#agent/agentStateService/firestoreAgentStateService'; diff --git a/src/agent/agentStateService/agentStateService.ts b/src/agent/agentStateService/agentStateService.ts index 919084c0..222a722f 100644 --- a/src/agent/agentStateService/agentStateService.ts +++ b/src/agent/agentStateService/agentStateService.ts @@ -1,4 +1,4 @@ -import { AgentContext, AgentRunningState } from '#agent/agentContext'; +import { AgentContext, AgentRunningState } from '#agent/agentContextTypes'; export interface AgentStateService { save(state: AgentContext): Promise; diff --git a/src/agent/agentStateService/fileAgentStateService.ts b/src/agent/agentStateService/fileAgentStateService.ts index 5c131a10..63025b72 100644 --- a/src/agent/agentStateService/fileAgentStateService.ts +++ b/src/agent/agentStateService/fileAgentStateService.ts @@ -1,7 +1,8 @@ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs'; import { unlinkSync } from 'node:fs'; import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentRunningState, deserializeAgentContext, serializeContext } from '#agent/agentContext'; +import { deserializeAgentContext, serializeContext } from '#agent/agentContextLocalStorage'; +import { AgentContext, AgentRunningState } from '#agent/agentContextTypes'; import { AgentStateService } from '#agent/agentStateService/agentStateService'; import { functionFactory } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; diff --git a/src/agent/agentStateService/firestoreAgentStateService.ts b/src/agent/agentStateService/firestoreAgentStateService.ts index bb05215a..0bd7fdbf 100644 --- a/src/agent/agentStateService/firestoreAgentStateService.ts +++ b/src/agent/agentStateService/firestoreAgentStateService.ts @@ -1,7 +1,7 @@ import { DocumentSnapshot, Firestore } from '@google-cloud/firestore'; import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentRunningState } from '#agent/agentContext'; -import { deserializeAgentContext, serializeContext } from '#agent/agentContext'; +import { deserializeAgentContext, serializeContext } from '#agent/agentContextLocalStorage'; +import { AgentContext, AgentRunningState } from '#agent/agentContextTypes'; import { functionFactory } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { span } from '#o11y/trace'; @@ -43,7 +43,7 @@ export class FirestoreAgentStateService implements AgentStateService { return deserializeAgentContext({ ...data, agentId, - }); + } as Record); } @span() diff --git a/src/agent/agentStateService/inMemoryAgentStateService.ts b/src/agent/agentStateService/inMemoryAgentStateService.ts index bbe98e84..aca16d27 100644 --- a/src/agent/agentStateService/inMemoryAgentStateService.ts +++ b/src/agent/agentStateService/inMemoryAgentStateService.ts @@ -1,5 +1,6 @@ import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentRunningState, deserializeAgentContext, serializeContext } from '#agent/agentContext'; +import { deserializeAgentContext, serializeContext } from '#agent/agentContextLocalStorage'; +import { AgentContext, AgentRunningState } from '#agent/agentContextTypes'; import { AgentStateService } from '#agent/agentStateService/agentStateService'; import { functionFactory } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; diff --git a/src/agent/agentWorkflowRunner.ts b/src/agent/agentWorkflowRunner.ts index 997ab77a..17deca50 100644 --- a/src/agent/agentWorkflowRunner.ts +++ b/src/agent/agentWorkflowRunner.ts @@ -1,5 +1,6 @@ import { Span } from '@opentelemetry/api'; -import { AgentContext, agentContext, agentContextStorage, createContext } from '#agent/agentContext'; +import { agentContext, agentContextStorage, createContext } from '#agent/agentContextLocalStorage'; +import { AgentContext } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; diff --git a/src/agent/caching-python-agent-system-prompt b/src/agent/caching-python-agent-system-prompt index 3e831092..fcc6c43d 100644 --- a/src/agent/caching-python-agent-system-prompt +++ b/src/agent/caching-python-agent-system-prompt @@ -63,10 +63,6 @@ To complete the task, you will have access to the following functions: -The FileSystem is an interface to the temporary local computer filesystem. - -The FileStore should be used for storage of large content (500 words or more) related to the request to reduce memory size. - # Instructions ## Overall Approach @@ -139,13 +135,16 @@ As you progress through the task, you may need to: Call Agent_requestFeedback when facing uncertainty or needing additional information to proceed. ## Important Considerations -- Information retention: After updating the plan, you won't have access to complete function results, so include all necessary information in the plan or memory. +- Information retention: After updating the plan, you won't have access to the completed function results, so include all necessary information in the plan or memory. - Continuous application of reasoning: Apply relevant reasoning techniques at each step of the process. - Step-by-step processing: Avoid trying to complete complex tasks in a single step. Instead, focus on retrieving necessary data and returning it for further analysis in subsequent steps. -# Example request/response +# Expected question/responses + +There are two types of responses you will need to provide. A planning response and a coding response. + +# Planning Response example - USER: GitHub is changing the process for registration of CI/CD runner which is described at https://docs.github.com/docs/new-runner-token-design and https://blog.github.com/new-runner-registration @@ -154,9 +153,10 @@ Our runner registration is currently in the VM metadata startup script in the co Research the new registration process and provide an outline of the new process. Provide a design proposal of what changes we will need to make (dont do any implementation) +Please generate the planning response ASSISTANT: - + @@ -204,30 +204,9 @@ Example_processText(text: str, descriptionOfChanges: str) -> str: Example_PublicWeb_getPage is suitable as the URLs are on publicly available documentation and blog pages. We can retrieve the two pages, and then create a report by processing the combined contents. + - -# Do not use Example_xxx functions in your code -# Check if the content is in memory from a previous step. Result: None found -tokenDesignPage: str = await Example_getPage("https://docs.github.com/docs/new-runner-token-design") -runnerRegistrationPage: str = await Example_getPage("https://blog.github.com/new-runner-registration") -webPages: str = f'${tokenDesignPage}${runnerRegistrationPage}' -newProcessReport: str = await Example_processText(webPages, "Provide a detailed report of the new token registration process") -# Store the work we have done so far -await Agent_setMemory("new_registration_process", newProcessReport) -current_process_knowledge = f''' - -''' -await Agent_setMemory("current_process_knowledge", current_process_knowledge) -# The current process knowledge is minimal, request feedback for more -await Agent_requestFeedback("I have collated a report on the new registration process. My understanding of the current process is limited. Could you provide more details?") - - - - - -# Response format - -Your response must be in the following format: +## Planning response format @@ -275,27 +254,38 @@ Select the function(s) to best complete the next step. You may call more than on Otherwise return any values to analyse further. --> + + +# Coding Response + +After providing a planning response you will be asked to provide a coding response. + +## Coding Response format + + +# Python code as per the instructions to progress the plan + + +## Coding Response examples + + +# Do not use Example_xxx functions in your code +# Check if the content is in memory from a previous step. Result: None found +tokenDesignPage: str = await Example_getPage("https://docs.github.com/docs/new-runner-token-design") +runnerRegistrationPage: str = await Example_getPage("https://blog.github.com/new-runner-registration") +webPages: str = f'${tokenDesignPage}${runnerRegistrationPage}' +newProcessReport: str = await Example_processText(webPages, "Provide a detailed report of the new token registration process") +# Store the work we have done so far +await Agent_setMemory("new_registration_process", newProcessReport) +current_process_knowledge = f''' + +''' +await Agent_setMemory("current_process_knowledge", current_process_knowledge) +# The current process knowledge is minimal, request feedback for more +await Agent_requestFeedback("I have collated a report on the new registration process. My understanding of the current process is limited. Could you provide more details?") + -# Instructions: -# The built-in packages json, re, math and datetime are already imported in the script. Including additional imports is forbidden. -# await on every call to functions defined previously in the block. -# Keep the code as simple as possible. Do not manipulate the function return values unless absolutely necessary. Prefer returning the values returned from the functions directly. -# Add comments with your reasoning. -# Add print calls throughout your code -# If defining new variables then add typings from the value being assigned. -# If you save a variable to memory then do not return it. -# You don't need to re-save existing memory values -# Always code defensively, checking values are the type and format as expected -# For any operation involving user-specified items, refer to 'Interpreting User Requests' items to code defensively, ensuring flexible and context-aware handling. -# The script should return a Dict with any values you want to have available to view/process next. You don't need to do everything here. -# When calling Agent_completed or Agent_requestFeedback you must directly return its result. (Ensure any required information has already been stored to memory) -# This script may be running on repositories where the source code files are TypeScript, Java, Terraform, PHP, C#, C++, Ruby etc. Do not assume Python files. -# The files projectInfo.json and CONVENTIONS.md may possibly exists to tell you more about a code project. -# You can directly analyze contents in memory. If you need to analyze unstructured data then include it to a return Dict value to view in the next step. -# All maths must be done in Python code -# Do NOT assume anything about the structure of the results from functions. Return values that require further analysis -# Example: # Check if the desired content is in memory from a previous step. Result: (None found/Found ...) # Get the two lists asked for in the next step details list1str: str = await Agent_getMemory("list1-json") @@ -308,3 +298,25 @@ print("result2.length " + len(result2)) return { list1: list1, list2: list2} + +## Coding Response instructions + +- The built-in packages json, re, math and datetime are already imported in the script. Including additional imports is forbidden. +- await on every call to functions defined previously in the block. +- Keep the code as simple as possible. Do not manipulate the function return values unless absolutely necessary. Prefer returning the values returned from the functions directly. +- Add comments with your reasoning. +- Add print calls throughout your code +- If defining new variables then add typings from the value being assigned. +- If you save a variable to memory then do not return it. +- You don't need to re-save existing memory values +- Always code defensively, checking values are the type and format as expected +- For any operation involving user-specified items, refer to 'Interpreting User Requests' items to code defensively, ensuring flexible and context-aware handling. +- The script should return a Dict with any values you want to have available to view/process next. You don't need to do everything here. +- When calling Agent_completed or Agent_requestFeedback you must directly return its result. (Ensure any required information has already been stored to memory) +- This script may be running on repositories where the source code files are TypeScript, Java, Terraform, PHP, C-, C++, Ruby etc. Do not assume Python files. +- The files projectInfo.json and CONVENTIONS.md may possibly exists to tell you more about a code project. +- You can directly analyze contents in memory. If you need to analyze unstructured data then include it to a return Dict value to view in the next step. +- All maths must be done in Python code +- Do NOT assume anything about the structure of the results from functions. Return values that require further analysis + +You must respond in either the or format. \ No newline at end of file diff --git a/src/agent/cachingPythonAgentRunner.ts b/src/agent/cachingPythonAgentRunner.ts index 0a6dbc7e..5012eeff 100644 --- a/src/agent/cachingPythonAgentRunner.ts +++ b/src/agent/cachingPythonAgentRunner.ts @@ -1,19 +1,20 @@ import { readFileSync } from 'fs'; import { Span, SpanStatusCode } from '@opentelemetry/api'; import { PyodideInterface, loadPyodide } from 'pyodide'; +import { runAgentCompleteHandler } from '#agent/agentCompletion'; +import { AgentContext } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK, AGENT_SAVE_MEMORY_CONTENT_PARAM_NAME } from '#agent/agentFunctions'; import { buildFunctionCallHistoryPrompt, buildMemoryPrompt, buildToolStatePrompt, updateFunctionSchemas } from '#agent/agentPromptUtils'; -import { AgentExecution, formatFunctionError, formatFunctionResult, notificationMessage } from '#agent/agentRunner'; +import { AgentExecution, formatFunctionError, formatFunctionResult } from '#agent/agentRunner'; import { agentHumanInTheLoop, notifySupervisor } from '#agent/humanInTheLoop'; import { convertJsonToPythonDeclaration, extractPythonCode } from '#agent/pythonAgentUtils'; import { getServiceName } from '#fastify/trace-init/trace-init'; -import { FUNC_SEP, FunctionParameter, FunctionSchema, getAllFunctionSchemas } from '#functionSchema/functions'; +import { FUNC_SEP, FunctionSchema, getAllFunctionSchemas } from '#functionSchema/functions'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; -import { envVar } from '#utils/env-var'; import { errorToString } from '#utils/errors'; import { appContext } from '../app'; -import { AgentContext, agentContext, agentContextStorage, llms } from './agentContext'; +import { agentContextStorage, llms } from './agentContextLocalStorage'; const stopSequences = ['']; @@ -27,10 +28,12 @@ let pyodide: PyodideInterface; * will be treated in some ways like a stack. * * Message stack: - * system - system prompt - * system - function definitions - * user - user request - * assistant - memory + * system prompt + * function definitions + * user request + * -- cache + * memory + * -- cache * user - function call history * assistant - response * ----------------------- @@ -46,7 +49,7 @@ export async function runCachingPythonAgent(agent: AgentContext): Promise\n${agent.userPrompt}\n`; - let currentPrompt = agent.inputPrompt; + const currentPrompt = agent.inputPrompt; // logger.info(`userRequestXml ${userRequestXml}`) logger.info(`currentPrompt ${currentPrompt}`); @@ -94,15 +97,13 @@ export async function runCachingPythonAgent(agent: AgentContext): Promise { agent.callStack = []; - // Might need to reload the agent for dynamic updating of the tools - const functionsXml = convertJsonToPythonDeclaration(getAllFunctionSchemas(agent.functions.getFunctionInstances())); - const systemPromptWithFunctions = updateFunctionSchemas(pythonSystemPrompt, functionsXml); let completed = false; let requestFeedback = false; const anyFunctionCallErrors = false; let controlError = false; try { + // Human in the loop checks ------------------------ if (hilCount && countSinceHil === hilCount) { await agentHumanInTheLoop(`Agent control loop has performed ${hilCount} iterations`); countSinceHil = 0; @@ -117,8 +118,24 @@ export async function runCachingPythonAgent(agent: AgentContext): Promise response as per the system instructions provided given the user request, available functions, memory items and recent function call history', + }; + agent.messages[6] = { role: 'assistant', text: '' }; + agent.messages.length = 7; // If we've restarted remove any extra messages + const agentPlanResponse: string = `\n${await agentLLM.generateText2(agent.messages, { id: 'dynamicAgentPlan', stopSequences, - temperature: 0.5, - }); + temperature: 0.6, + })}`; + agent.messages[6] = { role: 'assistant', text: agentPlanResponse }; + + // Code gen for function calling ----------- + + agent.messages[7] = { + role: 'user', + text: 'Generate a coding response as per the system instructions provided given the user request, memory items, recent function call history and plan', + }; + agent.messages[8] = { role: 'assistant', text: '' }; + const agentCodeResponse: string = `\n${await agentLLM.generateText2(agent.messages, { + id: 'dynamicAgentCode', + stopSequences, + temperature: 0.7, + })}`; + console.log(agentCodeResponse); + agent.messages[8] = { role: 'assistant', text: agentCodeResponse }; + const llmPythonCode = extractPythonCode(agentCodeResponse); - const llmPythonCode = extractPythonCode(agentPlanResponse); + // Function calling ---------------- agent.state = 'functions'; await agentStateService.save(agent); @@ -150,7 +195,7 @@ export async function runCachingPythonAgent(agent: AgentContext): Promise = agent.functions.getFunctionInstanceMap(); const schemas: FunctionSchema[] = getAllFunctionSchemas(Object.values(functionInstances)); const jsGlobals = {}; for (const schema of schemas) { @@ -209,28 +254,35 @@ export async function runCachingPythonAgent(agent: AgentContext): Promise llmPythonCode.includes(`${pkg}.`)) + .filter((pkg) => llmPythonCode.includes(`${pkg}.`) || pkg === 'json') // always need json for JsProxyEncoder .map((pkg) => `import ${pkg}\n`) .join(); logger.info(`Allowed imports: ${pythonScript}`); pythonScript += ` from typing import Any, List, Dict, Tuple, Optional, Union - +from pyodide.ffi import JsProxy + +class JsProxyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, JsProxy): + return obj.to_py() + # Let the base class default method raise the TypeError + return super().default(obj) + async def main(): ${llmPythonCode .split('\n') .map((line) => ` ${line}`) .join('\n')} -str(await main())`.trim(); +main()`.trim(); try { try { // Initial execution attempt const result = await pyodide.runPythonAsync(pythonScript, { globals }); pythonScriptResult = result?.toJs ? result.toJs() : result; - pythonScriptResult = pythonScriptResult?.toString ? pythonScriptResult.toString() : pythonScriptResult; - logger.info(pythonScriptResult, 'Script result'); + pythonScriptResult = JSON.stringify(pythonScriptResult); if (result?.destroy) result.destroy(); } catch (e) { // Attempt to fix Syntax/indentation errors and retry @@ -244,11 +296,10 @@ str(await main())`.trim(); // Re-try execution of fixed syntax/indentation error const result = await pyodide.runPythonAsync(pythonScript, { globals }); pythonScriptResult = result?.toJs ? result.toJs() : result; - pythonScriptResult = pythonScriptResult?.toString ? pythonScriptResult.toString() : pythonScriptResult; - + pythonScriptResult = JSON.stringify(pythonScriptResult); if (result?.destroy) result.destroy(); - logger.info(pythonScriptResult, 'Script result'); } + logger.info(pythonScriptResult, 'Script result'); const lastFunctionCall = agent.functionCallHistory[agent.functionCallHistory.length - 1]; @@ -269,16 +320,14 @@ str(await main())`.trim(); functionErrorCount++; } // Function invocations are complete - // span.setAttribute('functionCalls', pythonCode.map((functionCall) => functionCall.function_name).join(', ')); - - // The agent should store important values in memory - // functionResults - // This section is duplicated in the provideFeedback function - agent.invoking = []; const currentFunctionCallHistory = buildFunctionCallHistoryPrompt('results', 10000, currentFunctionHistorySize); + // TODO output any saved memory items + agent.messages[9] = { + role: 'user', + text: `${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then provide an updated planning response.`, + }; - currentPrompt = `${oldFunctionCallHistory}${buildMemoryPrompt()}${toolStatePrompt}\n${userRequestXml}\n${agentPlanResponse}\n${currentFunctionCallHistory}\n${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then proceed with the response.`; currentFunctionHistorySize = agent.functionCallHistory.length; } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.toString() }); @@ -297,17 +346,7 @@ str(await main())`.trim(); }); } - // Send notification message - const uiUrl = envVar('UI_URL'); - let message = notificationMessage(agent); - message += `\n${uiUrl}/agent/${agent.agentId}`; - logger.info(message); - - try { - await notifySupervisor(agent, message); - } catch (e) { - logger.warn(e`Failed to send supervisor notification message ${message}`); - } + await runAgentCompleteHandler(agent); }); return { agentId: agent.agentId, execution }; } diff --git a/src/agent/completionHandlerRegistry.ts b/src/agent/completionHandlerRegistry.ts new file mode 100644 index 00000000..ed4676a3 --- /dev/null +++ b/src/agent/completionHandlerRegistry.ts @@ -0,0 +1,21 @@ +import { ConsoleCompletedHandler } from '#agent/agentCompletion'; +import { AgentCompleted } from '#agent/agentContextTypes'; +import { SlackChatBotService } from '#modules/slack/slackChatBotService'; +import { logger } from '#o11y/logger'; + +const handlers = [ConsoleCompletedHandler, SlackChatBotService]; + +/** + * Return the AgentCompleted callback object from its id. + * @param handlerId + */ +export function getCompletedHandler(handlerId: string): AgentCompleted | null { + if (!handlerId) return new ConsoleCompletedHandler(); + + for (const handlerCtor of handlers) { + const handler = new handlerCtor(); + if (handlerId === handler.agentCompletedHandlerId()) return handler; + } + logger.error(`No AgentCompleted handler found for id ${handlerId}`); + return null; +} diff --git a/src/agent/humanInTheLoop.ts b/src/agent/humanInTheLoop.ts index 76934e87..bd41d044 100644 --- a/src/agent/humanInTheLoop.ts +++ b/src/agent/humanInTheLoop.ts @@ -1,5 +1,5 @@ import readline from 'readline'; -import { AgentContext } from '#agent/agentContext'; +import { AgentContext } from '#agent/agentContextTypes'; import { Slack } from '#modules/slack/slack'; import { logger } from '#o11y/logger'; /** diff --git a/src/agent/python-agent-system-prompt b/src/agent/python-agent-system-prompt index 3e831092..5811e7db 100644 --- a/src/agent/python-agent-system-prompt +++ b/src/agent/python-agent-system-prompt @@ -292,17 +292,21 @@ Otherwise return any values to analyse further. # When calling Agent_completed or Agent_requestFeedback you must directly return its result. (Ensure any required information has already been stored to memory) # This script may be running on repositories where the source code files are TypeScript, Java, Terraform, PHP, C#, C++, Ruby etc. Do not assume Python files. # The files projectInfo.json and CONVENTIONS.md may possibly exists to tell you more about a code project. -# You can directly analyze contents in memory. If you need to analyze unstructured data then include it to a return Dict value to view in the next step. +# You can directly analyze and return text/data/contents from and tags. If you need to analyze unstructured data then include it to a return Dict value to view in the next step. # All maths must be done in Python code +# If calling `json.dumps` it must also be passed the arg cls=JsProxyEncoder. i.e. json.dumps(data, cls=JsProxyEncoder) +# Output in a comment what you know with complete confidence about a value returned from a function # Do NOT assume anything about the structure of the results from functions. Return values that require further analysis # Example: # Check if the desired content is in memory from a previous step. Result: (None found/Found ...) # Get the two lists asked for in the next step details list1str: str = await Agent_getMemory("list1-json") +# We can see from the memory contents that it is a JSON array string list1: List[str] = json.loads(list1str) print("list1.length " + len(list1)) # list1 is unchanged so do not re-save to memory result2: List[str] = await FunctionClass2_returnStringList() +# The strings in result2 are of an unknown format print("result2.length " + len(result2)) # Do not assume the structure/styles values, return the values for further analysis return { list1: list1, list2: list2} diff --git a/src/agent/pythonAgentRunner.test.ts b/src/agent/pythonAgentRunner.test.ts index 87384a83..03485d2c 100644 --- a/src/agent/pythonAgentRunner.test.ts +++ b/src/agent/pythonAgentRunner.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { appContext, initInMemoryApplicationContext } from 'src/app'; import { LlmFunctions } from '#agent/LlmFunctions'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK, AGENT_SAVE_MEMORY, REQUEST_FEEDBACK_PARAM_NAME } from '#agent/agentFunctions'; import { RunAgentConfig, @@ -19,7 +20,7 @@ import { logger } from '#o11y/logger'; import { setTracer } from '#o11y/trace'; import { User } from '#user/user'; import { sleep } from '#utils/async-utils'; -import { AgentContext, AgentLLMs, agentContextStorage } from './agentContext'; +import { agentContextStorage } from './agentContextLocalStorage'; const PY_AGENT_COMPLETED = (note: string) => `await ${AGENT_COMPLETED_NAME}("${note}")`; const PY_AGENT_REQUEST_FEEDBACK = (feedback: string) => `await ${AGENT_REQUEST_FEEDBACK}("${feedback}")`; diff --git a/src/agent/pythonAgentRunner.ts b/src/agent/pythonAgentRunner.ts index f6b06f60..009b72cb 100644 --- a/src/agent/pythonAgentRunner.ts +++ b/src/agent/pythonAgentRunner.ts @@ -1,19 +1,20 @@ import { readFileSync } from 'fs'; import { Span, SpanStatusCode } from '@opentelemetry/api'; import { PyodideInterface, loadPyodide } from 'pyodide'; +import { runAgentCompleteHandler } from '#agent/agentCompletion'; +import { AgentContext } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK, AGENT_SAVE_MEMORY_CONTENT_PARAM_NAME } from '#agent/agentFunctions'; import { buildFunctionCallHistoryPrompt, buildMemoryPrompt, buildToolStatePrompt, updateFunctionSchemas } from '#agent/agentPromptUtils'; -import { AgentExecution, formatFunctionError, formatFunctionResult, notificationMessage } from '#agent/agentRunner'; -import { agentHumanInTheLoop, notifySupervisor } from '#agent/humanInTheLoop'; +import { AgentExecution, formatFunctionError, formatFunctionResult } from '#agent/agentRunner'; +import { agentHumanInTheLoop } from '#agent/humanInTheLoop'; import { convertJsonToPythonDeclaration, extractPythonCode } from '#agent/pythonAgentUtils'; import { getServiceName } from '#fastify/trace-init/trace-init'; -import { FUNC_SEP, FunctionParameter, FunctionSchema, getAllFunctionSchemas } from '#functionSchema/functions'; +import { FUNC_SEP, FunctionSchema, getAllFunctionSchemas } from '#functionSchema/functions'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; -import { envVar } from '#utils/env-var'; import { errorToString } from '#utils/errors'; import { appContext } from '../app'; -import { AgentContext, agentContext, agentContextStorage, llms } from './agentContext'; +import { agentContextStorage, llms } from './agentContextLocalStorage'; const stopSequences = ['']; @@ -129,7 +130,7 @@ export async function runPythonAgent(agent: AgentContext): Promise = agent.functions.getFunctionInstanceMap(); const schemas: FunctionSchema[] = getAllFunctionSchemas(Object.values(functionInstances)); const jsGlobals = {}; for (const schema of schemas) { @@ -188,12 +189,20 @@ export async function runPythonAgent(agent: AgentContext): Promise llmPythonCode.includes(`${pkg}.`)) + .filter((pkg) => llmPythonCode.includes(`${pkg}.`) || pkg === 'json') // always need json for JsProxyEncoder .map((pkg) => `import ${pkg}\n`) .join(); logger.info(`Allowed imports: ${pythonScript}`); pythonScript += ` from typing import Any, List, Dict, Tuple, Optional, Union +from pyodide.ffi import JsProxy + +class JsProxyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, JsProxy): + return obj.to_py() + # Let the base class default method raise the TypeError + return super().default(obj) async def main(): ${llmPythonCode @@ -201,15 +210,14 @@ ${llmPythonCode .map((line) => ` ${line}`) .join('\n')} -str(await main())`.trim(); +main()`.trim(); try { try { // Initial execution attempt const result = await pyodide.runPythonAsync(pythonScript, { globals }); pythonScriptResult = result?.toJs ? result.toJs() : result; - pythonScriptResult = pythonScriptResult?.toString ? pythonScriptResult.toString() : pythonScriptResult; - logger.info(pythonScriptResult, 'Script result'); + pythonScriptResult = JSON.stringify(pythonScriptResult); if (result?.destroy) result.destroy(); } catch (e) { // Attempt to fix Syntax/indentation errors and retry @@ -223,11 +231,10 @@ str(await main())`.trim(); // Re-try execution of fixed syntax/indentation error const result = await pyodide.runPythonAsync(pythonScript, { globals }); pythonScriptResult = result?.toJs ? result.toJs() : result; - pythonScriptResult = pythonScriptResult?.toString ? pythonScriptResult.toString() : pythonScriptResult; - + pythonScriptResult = JSON.stringify(pythonScriptResult); if (result?.destroy) result.destroy(); - logger.info(pythonScriptResult, 'Script result'); } + logger.info(pythonScriptResult, 'Script result'); const lastFunctionCall = agent.functionCallHistory[agent.functionCallHistory.length - 1]; @@ -257,7 +264,7 @@ str(await main())`.trim(); agent.invoking = []; const currentFunctionCallHistory = buildFunctionCallHistoryPrompt('results', 10000, currentFunctionHistorySize); - currentPrompt = `${oldFunctionCallHistory}${buildMemoryPrompt()}${toolStatePrompt}\n${userRequestXml}\n${agentPlanResponse}\n${currentFunctionCallHistory}\n${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then proceed with the response.`; + currentPrompt = `${oldFunctionCallHistory}${buildMemoryPrompt()}${toolStatePrompt}\n${userRequestXml}\n${agentPlanResponse}\n${currentFunctionCallHistory}\n${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then proceed with the response.`; currentFunctionHistorySize = agent.functionCallHistory.length; } catch (e) { span.setStatus({ code: SpanStatusCode.ERROR, message: e.toString() }); @@ -276,17 +283,7 @@ str(await main())`.trim(); }); } - // Send notification message - const uiUrl = envVar('UI_URL'); - let message = notificationMessage(agent); - message += `\n${uiUrl}/agent/${agent.agentId}`; - logger.info(message); - - try { - await notifySupervisor(agent, message); - } catch (e) { - logger.warn(e`Failed to send supervisor notification message ${message}`); - } + await runAgentCompleteHandler(agent); }); return { agentId: agent.agentId, execution }; } diff --git a/src/agent/xmlAgentRunner.test.ts b/src/agent/xmlAgentRunner.test.ts index f9e4250c..2e4240e6 100644 --- a/src/agent/xmlAgentRunner.test.ts +++ b/src/agent/xmlAgentRunner.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { appContext, initInMemoryApplicationContext } from 'src/app'; import { LlmFunctions } from '#agent/LlmFunctions'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK, REQUEST_FEEDBACK_PARAM_NAME } from '#agent/agentFunctions'; import { RunAgentConfig, @@ -18,7 +19,7 @@ import { MockLLM } from '#llm/models/mock-llm'; import { setTracer } from '#o11y/trace'; import { User } from '#user/user'; import { sleep } from '#utils/async-utils'; -import { AgentContext, AgentLLMs, agentContextStorage } from './agentContext'; +import { agentContextStorage } from './agentContextLocalStorage'; const REQUEST_FEEDBACK_VALUE = 'question is...'; const REQUEST_FEEDBACK_FUNCTION_CALL = `Requesting feedback\n${AGENT_REQUEST_FEEDBACK}<${REQUEST_FEEDBACK_PARAM_NAME}>${REQUEST_FEEDBACK_VALUE}`; diff --git a/src/agent/xmlAgentRunner.ts b/src/agent/xmlAgentRunner.ts index 839a14af..d0f1866c 100644 --- a/src/agent/xmlAgentRunner.ts +++ b/src/agent/xmlAgentRunner.ts @@ -1,8 +1,10 @@ import { readFileSync } from 'fs'; import { Span, SpanStatusCode } from '@opentelemetry/api'; +import { ConsoleCompletedHandler, runAgentCompleteHandler, stateNotificationMessage } from '#agent/agentCompletion'; +import { AgentContext } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK } from '#agent/agentFunctions'; import { buildFunctionCallHistoryPrompt, buildMemoryPrompt, buildToolStatePrompt, updateFunctionSchemas } from '#agent/agentPromptUtils'; -import { AgentExecution, formatFunctionError, formatFunctionResult, notificationMessage, summariseLongFunctionOutput } from '#agent/agentRunner'; +import { AgentExecution, formatFunctionError, formatFunctionResult, summariseLongFunctionOutput } from '#agent/agentRunner'; import { agentHumanInTheLoop, notifySupervisor } from '#agent/humanInTheLoop'; import { getServiceName } from '#fastify/trace-init/trace-init'; import { FunctionSchema, getAllFunctionSchemas } from '#functionSchema/functions'; @@ -12,7 +14,7 @@ import { withActiveSpan } from '#o11y/trace'; import { envVar } from '#utils/env-var'; import { errorToString } from '#utils/errors'; import { appContext } from '../app'; -import { AgentContext, agentContext, agentContextStorage, llms } from './agentContext'; +import { agentContext, agentContextStorage, llms } from './agentContextLocalStorage'; export const XML_AGENT_SPAN = 'XmlAgent'; @@ -219,16 +221,7 @@ export async function runXmlAgent(agent: AgentContext): Promise }); } - // Send notification message - const uiUrl = envVar('UI_URL'); - let message = notificationMessage(agent); - message += `\n${uiUrl}/agent/${agent.agentId}`; - logger.info(message); - try { - await notifySupervisor(agent, message); - } catch (e) { - logger.warn(e`Failed to send supervisor notification message ${message}`); - } + await runAgentCompleteHandler(agent); }); return { agentId: agent.agentId, execution }; } diff --git a/src/cache/firestoreFunctionCache.test.ts b/src/cache/firestoreFunctionCache.test.ts index 0474c3d9..fc06d869 100644 --- a/src/cache/firestoreFunctionCache.test.ts +++ b/src/cache/firestoreFunctionCache.test.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { expect } from 'chai'; import * as fs from 'fs/promises'; -import { agentContext, agentContextStorage, createContext } from '#agent/agentContext'; +import { agentContext, agentContextStorage, createContext } from '#agent/agentContextLocalStorage'; import { mockLLMs } from '#llm/models/mock-llm'; import { logger } from '#o11y/logger'; import { currentUser } from '#user/userService/userContext'; diff --git a/src/cache/firestoreFunctionCacheService.ts b/src/cache/firestoreFunctionCacheService.ts index 2421fb4b..dd8aac05 100644 --- a/src/cache/firestoreFunctionCacheService.ts +++ b/src/cache/firestoreFunctionCacheService.ts @@ -13,7 +13,7 @@ import { createHash } from 'crypto'; import { Firestore, Timestamp } from '@google-cloud/firestore'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { currentUser } from '#user/userService/userContext'; import { firestoreDb } from '../firestore'; diff --git a/src/chatBot/chatBotService.ts b/src/chatBot/chatBotService.ts index 975cea75..f37816e8 100644 --- a/src/chatBot/chatBotService.ts +++ b/src/chatBot/chatBotService.ts @@ -1,4 +1,4 @@ -import { AgentContext, AgentRunningState } from '#agent/agentContext'; +import { AgentContext, AgentRunningState } from '#agent/agentContextTypes'; export interface ChatBotService { sendMessage(agent: AgentContext, message: string): Promise; diff --git a/src/cli/code.ts b/src/cli/code.ts index 118f8a3b..d7a1a582 100644 --- a/src/cli/code.ts +++ b/src/cli/code.ts @@ -1,7 +1,8 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { readFileSync } from 'fs'; -import { AgentLLMs, agentContext, llms } from '#agent/agentContext'; +import { agentContext, llms } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { Agent } from '#agent/agentFunctions'; import { RunAgentConfig } from '#agent/agentRunner'; import { runAgentWorkflow } from '#agent/agentWorkflowRunner'; diff --git a/src/cli/docs.ts b/src/cli/docs.ts index fcb45fe7..78a989ce 100644 --- a/src/cli/docs.ts +++ b/src/cli/docs.ts @@ -1,6 +1,6 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line -import { AgentLLMs } from '#agent/agentContext'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { runAgentWorkflow } from '#agent/agentWorkflowRunner'; import { shutdownTrace } from '#fastify/trace-init/trace-init'; diff --git a/src/cli/gaia.ts b/src/cli/gaia.ts index 6050eac9..71808cf5 100644 --- a/src/cli/gaia.ts +++ b/src/cli/gaia.ts @@ -1,7 +1,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { promises as fs, readFileSync } from 'fs'; -import { AgentLLMs } from '#agent/agentContext'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_PARAM_NAME } from '#agent/agentFunctions'; import { startAgent, startAgentAndWait } from '#agent/agentRunner'; import { FileSystem } from '#functions/storage/filesystem'; diff --git a/src/cli/gen.ts b/src/cli/gen.ts index 6288d157..b99cb440 100644 --- a/src/cli/gen.ts +++ b/src/cli/gen.ts @@ -4,7 +4,8 @@ import { readFileSync, writeFileSync } from 'fs'; import * as fs from 'fs'; import * as path from 'path'; import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentLLMs, agentContextStorage, createContext } from '#agent/agentContext'; +import { agentContext, agentContextStorage, createContext } from '#agent/agentContextLocalStorage'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { LLM } from '#llm/llm'; import { ClaudeLLMs } from '#llm/models/anthropic'; import { Claude3_5_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/models/anthropic-vertex'; diff --git a/src/cli/py.ts b/src/cli/py.ts index c399291d..fc64cd08 100644 --- a/src/cli/py.ts +++ b/src/cli/py.ts @@ -1,21 +1,25 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { loadPyodide } from 'pyodide'; -import { Perplexity } from '#functions/web/perplexity'; +import { GitLab } from '#functions/scm/gitlab'; import { logger } from '#o11y/logger'; export async function main() { const pyodide = await loadPyodide(); - const p = new Perplexity(); + // const gitlab = new GitLab(); const jsGlobals = { fun1: async () => { console.log('In fun1'); return 'abc'; }, - search: async (key: string, mem: boolean) => { - return p.research(key, mem); + getProjects: async () => { + return await new GitLab().getProjects(); + }, + save: async (value: any) => { + console.log(typeof value, 'object'); + console.log(`saving ${value}`); }, // search: async(...args) => {return p.research(...args)} }; @@ -38,19 +42,38 @@ import re import math import datetime from typing import List, Dict, Tuple, Optional, Union +from pyodide.ffi import JsProxy + +class JsProxyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, JsProxy): + return obj.to_py() + # Let the base class default method raise the TypeError + return super().default(obj) -async def main() +async def main(): res = await fun1() print("res " + res) - s = await search("lah", True) - print("s" + s) - return s + projects = await getProjects() + await save(json.dumps({"projects": projects}, cls=JsProxyEncoder)) + return {"projects": projects} main()`.trim(), { globals: pyodide.toPy(jsGlobals) }, ); + logger.info(`1: ${typeof result}`); + logger.info(`1: ${Object.keys(result).length}`); const pythonScriptResult = result?.toJs ? result.toJs() : result; - console.log(`pyodide result ${pythonScriptResult}`); + logger.info(`2: ${typeof pythonScriptResult}`); + logger.info(`2: ${Object.keys(pythonScriptResult).length}`); + console.log('pyodide result:'); + console.log(pythonScriptResult); + const jsResult = {}; + for (const [key, value] of Object.entries(pythonScriptResult)) { + jsResult[key] = (value as any)?.toJs ? (value as any).toJs() : value; + } + console.log('jsResult result:'); + console.log(jsResult); } main().then( diff --git a/src/cli/research.ts b/src/cli/research.ts index 218b7b42..cf212463 100644 --- a/src/cli/research.ts +++ b/src/cli/research.ts @@ -2,7 +2,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doe import { readFileSync } from 'fs'; -import { AgentLLMs } from '#agent/agentContext'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { startAgent, startAgentAndWait } from '#agent/agentRunner'; import { Perplexity } from '#functions/web/perplexity'; import { PublicWeb } from '#functions/web/web'; diff --git a/src/cli/scrape.ts b/src/cli/scrape.ts index 19c260d1..255cfb0d 100644 --- a/src/cli/scrape.ts +++ b/src/cli/scrape.ts @@ -1,7 +1,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { writeFileSync } from 'node:fs'; -import { agentContextStorage, createContext } from '#agent/agentContext'; +import { agentContextStorage, createContext } from '#agent/agentContextLocalStorage'; import { PublicWeb } from '#functions/web/web'; import { ClaudeVertexLLMs } from '#llm/models/anthropic-vertex'; diff --git a/src/cli/swe.ts b/src/cli/swe.ts index 55c9d188..f3e13dbb 100644 --- a/src/cli/swe.ts +++ b/src/cli/swe.ts @@ -2,7 +2,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doe import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; -import { AgentContext, AgentLLMs } from '#agent/agentContext'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { runAgentWorkflow } from '#agent/agentWorkflowRunner'; import { GitLab } from '#functions/scm/gitlab'; diff --git a/src/cli/swebench.ts b/src/cli/swebench.ts index 14dbd488..32f68eab 100644 --- a/src/cli/swebench.ts +++ b/src/cli/swebench.ts @@ -1,7 +1,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { promises as fs, readFileSync } from 'fs'; -import { AgentLLMs } from '#agent/agentContext'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_PARAM_NAME } from '#agent/agentFunctions'; import { RunAgentConfig, startAgent, startAgentAndWait } from '#agent/agentRunner'; import { runAgentWorkflow } from '#agent/agentWorkflowRunner'; diff --git a/src/cli/util.ts b/src/cli/util.ts index d297720e..87caa3dd 100644 --- a/src/cli/util.ts +++ b/src/cli/util.ts @@ -1,7 +1,7 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doesn't get sorted from the first line import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentLLMs, agentContextStorage, createContext, getFileSystem, llms } from '#agent/agentContext'; +import { agentContextStorage, createContext, getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { Jira } from '#functions/jira'; import { GitLab } from '#functions/scm/gitlab'; @@ -14,6 +14,7 @@ import { MultiLLM } from '#llm/multi-llm'; import { appContext } from '../app'; import { writeFileSync } from 'fs'; +import { AgentContext, AgentLLMs } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { TypescriptTools } from '#swe/lang/nodejs/typescriptTools'; import { envVarHumanInLoopSettings } from './cliHumanInLoop'; diff --git a/src/fastify/trace-init/trace-init.ts b/src/fastify/trace-init/trace-init.ts index 9d2caf8e..ed1c830a 100644 --- a/src/fastify/trace-init/trace-init.ts +++ b/src/fastify/trace-init/trace-init.ts @@ -9,7 +9,7 @@ import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base'; import { PinoInstrumentation } from './instrumentation'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { agentContextStorage } from '#agent/agentContext'; +import { agentContextStorage } from '#agent/agentContextLocalStorage'; import { setTracer } from '#o11y/trace'; let initialized = false; diff --git a/src/functionSchema/functionDecorators.ts b/src/functionSchema/functionDecorators.ts index 8e91c9b0..120e729f 100644 --- a/src/functionSchema/functionDecorators.ts +++ b/src/functionSchema/functionDecorators.ts @@ -1,5 +1,5 @@ import { Span } from '@opentelemetry/api'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { getTracer, setFunctionSpanAttributes, withActiveSpan } from '#o11y/trace'; import { functionSchemaParser } from './functionSchemaParser'; diff --git a/src/functions/cloud/google-cloud.ts b/src/functions/cloud/google-cloud.ts index abd5d3b4..d42b8670 100644 --- a/src/functions/cloud/google-cloud.ts +++ b/src/functions/cloud/google-cloud.ts @@ -1,4 +1,4 @@ -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { waitForConsoleInput } from '#agent/humanInTheLoop'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { execCommand, failOnError } from '#utils/exec'; diff --git a/src/functions/image.ts b/src/functions/image.ts index aa6effe6..d1704226 100644 --- a/src/functions/image.ts +++ b/src/functions/image.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios'; import ImageGenerateParams, { OpenAI, OpenAI as OpenAISDK } from 'openai'; -import { agentContext, getFileSystem, llms } from '#agent/agentContext'; +import { agentContext, getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { currentUser } from '#user/userService/userContext'; diff --git a/src/functions/scm/github.ts b/src/functions/scm/github.ts index f3cfca05..6ab003f9 100644 --- a/src/functions/scm/github.ts +++ b/src/functions/scm/github.ts @@ -1,7 +1,7 @@ import { existsSync } from 'fs'; import path, { join } from 'path'; import { request } from '@octokit/request'; -import { agentContext, getFileSystem } from '#agent/agentContext'; +import { agentContext, getFileSystem } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { SourceControlManagement } from '#functions/scm/sourceControlManagement'; import { logger } from '#o11y/logger'; diff --git a/src/functions/scm/gitlab.ts b/src/functions/scm/gitlab.ts index d99a3b39..fe26032a 100644 --- a/src/functions/scm/gitlab.ts +++ b/src/functions/scm/gitlab.ts @@ -9,7 +9,7 @@ import { ProjectSchema, } from '@gitbeaker/rest'; import { micromatch } from 'micromatch'; -import { agentContext, getFileSystem, llms } from '#agent/agentContext'; +import { agentContext, getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { span } from '#o11y/trace'; @@ -113,7 +113,7 @@ export class GitLab implements SourceControlManagement { // } /** - * @returns the details of all the projects available (name, description, git URL etc) + * @returns the details of all the projects available */ @func() async getProjects(): Promise { diff --git a/src/functions/scm/sourceControlManagement.ts b/src/functions/scm/sourceControlManagement.ts index bff45da4..1c681d3c 100644 --- a/src/functions/scm/sourceControlManagement.ts +++ b/src/functions/scm/sourceControlManagement.ts @@ -1,5 +1,5 @@ import { functionRegistry } from 'src/functionRegistry'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { GitProject } from './gitProject'; /** diff --git a/src/functions/storage/filesystem.ts b/src/functions/storage/filesystem.ts index a3aee676..fe34c570 100644 --- a/src/functions/storage/filesystem.ts +++ b/src/functions/storage/filesystem.ts @@ -6,7 +6,7 @@ import { promisify } from 'util'; import fsPromises from 'fs/promises'; import ignore, { Ignore } from 'ignore'; import Pino from 'pino'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { parseArrayParameterValue } from '#functionSchema/functionUtils'; import { Git } from '#functions/scm/git'; diff --git a/src/functions/storage/localFileStore.test.ts b/src/functions/storage/localFileStore.test.ts index 878d062b..c75238c3 100644 --- a/src/functions/storage/localFileStore.test.ts +++ b/src/functions/storage/localFileStore.test.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import path from 'path'; import { expect } from 'chai'; -import { agentContextStorage } from '#agent/agentContext'; +import { agentContextStorage } from '#agent/agentContextLocalStorage'; import { LocalFileStore } from './localFileStore'; function setupMockAgentContext(agentId: string) { diff --git a/src/functions/storage/localFileStore.ts b/src/functions/storage/localFileStore.ts index 838623d0..416c3485 100644 --- a/src/functions/storage/localFileStore.ts +++ b/src/functions/storage/localFileStore.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { FileMetadata, FileStore } from '#functions/storage/filestore'; import { ToolType } from '#functions/toolType'; diff --git a/src/functions/testFunctions.ts b/src/functions/testFunctions.ts index 7605aa4c..692db2f3 100644 --- a/src/functions/testFunctions.ts +++ b/src/functions/testFunctions.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; export const TEST_FUNC_THROW_ERROR = 'TestFunctions_throwError'; diff --git a/src/functions/toolType.ts b/src/functions/toolType.ts index c2a29e2b..1ed365d6 100644 --- a/src/functions/toolType.ts +++ b/src/functions/toolType.ts @@ -1,8 +1,8 @@ export type ToolType = | 'filestore' // blob store, locally or S3, Cloud Storage etc - | 'notification' // Sends notification the agent supervisor + | 'notification' // Sends a notification to the agent supervisor | 'scm' // Source Control Management, GitHub, GitLab - | 'chat'; // For a chatbot to reply to a conversation + | 'chat'; // For a chatbot that replies to a conversation /** * @param object function class instance diff --git a/src/functions/util.ts b/src/functions/util.ts index 77bea155..23899a4b 100644 --- a/src/functions/util.ts +++ b/src/functions/util.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; @funcClass(__filename) diff --git a/src/functions/web/perplexity.ts b/src/functions/web/perplexity.ts index 092ba2a1..d37b31b8 100644 --- a/src/functions/web/perplexity.ts +++ b/src/functions/web/perplexity.ts @@ -1,7 +1,7 @@ import { logger } from '#o11y/logger'; import OpenAI from 'openai'; -import { addCost, agentContext, llms } from '#agent/agentContext'; +import { addCost, agentContext, llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { functionConfig } from '#user/userService/userContext'; import { envVar } from '#utils/env-var'; diff --git a/src/functions/web/web.ts b/src/functions/web/web.ts index 0fe97c0c..a0408b8e 100644 --- a/src/functions/web/web.ts +++ b/src/functions/web/web.ts @@ -2,7 +2,7 @@ import path from 'path'; import { PuppeteerBlocker } from '@cliqz/adblocker-puppeteer'; import { Readability } from '@mozilla/readability'; import { JSDOM } from 'jsdom'; -import { agentContextStorage, getFileSystem, llms } from '#agent/agentContext'; +import { agentContextStorage, getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { execCommand } from '#utils/exec'; import { cacheRetry } from '../../cache/cacheRetry'; const { getJson } = require('serpapi'); diff --git a/src/functions/web/webResearch.ts b/src/functions/web/webResearch.ts index 1b4a3d1e..db27b483 100644 --- a/src/functions/web/webResearch.ts +++ b/src/functions/web/webResearch.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { cacheRetry } from '../../cache/cacheRetry'; diff --git a/src/llm/llm.ts b/src/llm/llm.ts index 1f2c5673..478ef5d2 100644 --- a/src/llm/llm.ts +++ b/src/llm/llm.ts @@ -1,6 +1,3 @@ -import { logger } from '#o11y/logger'; -import { BaseLLM } from './base-llm'; - // https://github.com/AgentOps-AI/tokencost/blob/main/tokencost/model_prices.json export interface GenerateTextOptions { @@ -37,14 +34,14 @@ export interface LlmMessage { role: 'system' | 'user' | 'assistant'; text: string; /** Set the cache_control flag with Claude models */ - cache: boolean; + cache?: 'ephemeral'; } export function system(text: string, cache = false): LlmMessage { return { role: 'system', text: text, - cache, + cache: cache ? 'ephemeral' : undefined, }; } @@ -52,7 +49,7 @@ export function user(text: string, cache = false): LlmMessage { return { role: 'user', text: text, - cache, + cache: cache ? 'ephemeral' : undefined, }; } @@ -65,7 +62,6 @@ export function assistant(text: string): LlmMessage { return { role: 'assistant', text: text, - cache: false, }; } @@ -172,13 +168,14 @@ export function combinePrompts(userPrompt: string, systemPrompt?: string): strin * @returns */ export function logTextGeneration(originalMethod: any, context: ClassMethodDecoratorContext): any { - return async function replacementMethod(this: BaseLLM, ...args: any[]) { + return async function replacementMethod(this: any, ...args: any[]) { // system prompt // if (args.length > 1 && args[1]) { // logger.info(`= SYSTEM PROMPT ===================================================\n${args[1]}`); // } - // logger.info(`= USER PROMPT =================================================================\n${args[0]}`); - // + console.log(); + console.log(`= USER PROMPT =================================================================\n${args[0]}`); + console.log(args[0]); // const start = Date.now(); const result = await originalMethod.call(this, ...args); // logger.info(`= RESPONSE ${this.model} ==========================================================\n${JSON.stringify(result)}`); @@ -190,7 +187,7 @@ export function logTextGeneration(originalMethod: any, context: ClassMethodDecor export function logDuration(originalMethod: any, context: ClassMethodDecoratorContext): any { const functionName = String(context.name); - return async function replacementMethod(this: BaseLLM, ...args: any[]) { + return async function replacementMethod(this: any, ...args: any[]) { const start = Date.now(); const result = await originalMethod.call(this, ...args); console.log(`${functionName} took ${Date.now() - start}ms`); diff --git a/src/llm/llmCallService/llmCall.ts b/src/llm/llmCallService/llmCall.ts index 6ead875d..df6c1b79 100644 --- a/src/llm/llmCallService/llmCall.ts +++ b/src/llm/llmCallService/llmCall.ts @@ -9,7 +9,7 @@ export interface LlmRequest { /** Legacy simple prompting */ systemPrompt?: string; /** Legacy simple prompting */ - userPrompt: string; + userPrompt?: string; messages?: LlmMessage[]; /** Populated when called by an agent */ diff --git a/src/llm/llmFactory.ts b/src/llm/llmFactory.ts index 05ee5892..f87de26e 100644 --- a/src/llm/llmFactory.ts +++ b/src/llm/llmFactory.ts @@ -1,4 +1,4 @@ -import { AgentLLMs } from '#agent/agentContext'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { LLM } from '#llm/llm'; import { anthropicLLMRegistry } from '#llm/models/anthropic'; import { anthropicVertexLLMRegistry } from '#llm/models/anthropic-vertex'; diff --git a/src/llm/models/anthropic-vertex.ts b/src/llm/models/anthropic-vertex.ts index cc002c2e..69364569 100644 --- a/src/llm/models/anthropic-vertex.ts +++ b/src/llm/models/anthropic-vertex.ts @@ -1,20 +1,19 @@ import Anthropic from '@anthropic-ai/sdk'; import { AnthropicVertex } from '@anthropic-ai/vertex-sdk'; -import { AgentLLMs, addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { BaseLLM } from '../base-llm'; import { MaxTokensError } from '../errors'; import { GenerateTextOptions, LLM, LlmMessage, combinePrompts, logTextGeneration } from '../llm'; import Message = Anthropic.Message; import { LlmCall } from '#llm/llmCallService/llmCall'; -import { CallerId } from '#llm/llmCallService/llmCallService'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; import { currentUser } from '#user/userService/userContext'; import { envVar } from '#utils/env-var'; import { appContext } from '../../app'; import { RetryableError, cacheRetry } from '../../cache/cacheRetry'; -import { MultiLLM } from '../multi-llm'; import TextBlock = Anthropic.TextBlock; +import { AgentLLMs } from '#agent/agentContextTypes'; export const ANTHROPIC_VERTEX_SERVICE = 'anthropic-vertex'; @@ -213,10 +212,9 @@ class AnthropicVertexLLM extends BaseLLM { // Error when // {"error":{"code":400,"message":"Project `1234567890` is not allowed to use Publisher Model `projects/project-id/locations/us-central1/publishers/anthropic/models/claude-3-haiku@20240307`","status":"FAILED_PRECONDITION"}} @cacheRetry({ backOffMs: 5000 }) - // @logTextGeneration async generateText2(messages: LlmMessage[], opts?: GenerateTextOptions): Promise { return await withActiveSpan(`generateText2 ${opts?.id ?? ''}`, async (span) => { - const maxOutputTokens = 4096; + const maxOutputTokens = this.model.includes('3-5') ? 8192 : 4096; let systemPrompt: string | undefined; if (messages[0].role === 'system') { @@ -226,36 +224,40 @@ class AnthropicVertexLLM extends BaseLLM { } const userPrompt = messages.map((msg) => msg.text).join('\n'); - const combinedPrompt = combinePrompts(userPrompt, systemPrompt); span.setAttributes({ userPrompt, - inputChars: combinedPrompt.length, + // inputChars: combinedPrompt.length, model: this.model, service: this.service, - caller: agentContext().callStack.at(-1) ?? '', + caller: agentContext()?.callStack.at(-1) ?? '', }); if (opts?.id) span.setAttribute('id', opts.id); const llmCallSave: Promise = appContext().llmCallService.saveRequest({ - userPrompt, - systemPrompt, + messages, llmId: this.getId(), - agentId: agentContext().agentId, - callStack: agentContext().callStack.join(' > '), + agentId: agentContext()?.agentId, + callStack: agentContext()?.callStack.join(' > '), }); const requestTime = Date.now(); let message: Message; try { + let systemMessage: Anthropic.Messages.TextBlockParam[] | undefined = undefined; + if (messages[0].role === 'system') { + const message = messages.splice(0, 1)[0]; + systemMessage = [{ type: 'text', text: message.text }]; + // if(source.cache) + // systemMessage[0].cacheControl = 'ephemeral' + } + + const anthropicMessages: Anthropic.Messages.MessageParam[] = messages.map((message) => { + return { role: message.role as 'user' | 'assistant', content: message.text }; + }); message = await this.api().messages.create({ - system: systemPrompt ? [{ type: 'text', text: systemPrompt }] : undefined, - messages: [ - { - role: 'user', - content: userPrompt, - }, - ], + system: systemMessage, + messages: anthropicMessages, model: this.model, max_tokens: maxOutputTokens, stop_sequences: opts?.stopSequences, @@ -292,15 +294,15 @@ class AnthropicVertexLLM extends BaseLLM { const inputTokens = message.usage.input_tokens; const outputTokens = message.usage.output_tokens; - const inputCost = this.calculateInputCost(combinedPrompt); - const outputCost = this.calculateOutputCost(responseText); - const cost = inputCost + outputCost; - addCost(cost); + // const inputCost = this.calculateInputCost(combinedPrompt); + // const outputCost = this.calculateOutputCost(responseText); + // const cost = inputCost + outputCost; + // addCost(cost); llmCall.responseText = responseText; llmCall.timeToFirstToken = timeToFirstToken; llmCall.totalTime = finishTime - requestTime; - llmCall.cost = cost; + // llmCall.cost = cost; llmCall.inputTokens = inputTokens; llmCall.outputTokens = outputTokens; @@ -308,15 +310,15 @@ class AnthropicVertexLLM extends BaseLLM { inputTokens, outputTokens, response: responseText, - inputCost: inputCost.toFixed(4), - outputCost: outputCost.toFixed(4), - cost: cost.toFixed(4), + // inputCost: inputCost.toFixed(4), + // outputCost: outputCost.toFixed(4), + // cost: cost.toFixed(4), outputChars: responseText.length, - callStack: agentContext().callStack.join(' > '), + callStack: agentContext()?.callStack.join(' > '), }); try { - await appContext().llmCallService.saveResponse(llmCall); + await appContext()?.llmCallService.saveResponse(llmCall); } catch (e) { // queue to save logger.error(e); diff --git a/src/llm/models/anthropic.ts b/src/llm/models/anthropic.ts index 4b8d7653..26b21f32 100644 --- a/src/llm/models/anthropic.ts +++ b/src/llm/models/anthropic.ts @@ -1,5 +1,5 @@ import { Anthropic as AnthropicSdk } from '@anthropic-ai/sdk'; -import { AgentLLMs, addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { envVar } from '#utils/env-var'; import { BaseLLM } from '../base-llm'; import { MaxTokensError } from '../errors'; @@ -13,6 +13,7 @@ import { currentUser } from '#user/userService/userContext'; import { appContext } from '../../app'; import { RetryableError } from '../../cache/cacheRetry'; import TextBlock = AnthropicSdk.TextBlock; +import { AgentLLMs } from '#agent/agentContextTypes'; import { CallerId } from '#llm/llmCallService/llmCallService'; export const ANTHROPIC_SERVICE = 'anthropic'; diff --git a/src/llm/models/cerebras.ts b/src/llm/models/cerebras.ts new file mode 100644 index 00000000..88b364eb --- /dev/null +++ b/src/llm/models/cerebras.ts @@ -0,0 +1,170 @@ +import Cerebras from '@cerebras/cerebras_cloud_sdk'; +import { agentContext } from '#agent/agentContextLocalStorage'; +import { addCost } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; +import { LlmCall } from '#llm/llmCallService/llmCall'; +import { withActiveSpan } from '#o11y/trace'; +import { currentUser } from '#user/userService/userContext'; +import { appContext } from '../../app'; +import { RetryableError } from '../../cache/cacheRetry'; +import { BaseLLM } from '../base-llm'; +import { GenerateTextOptions, LLM, combinePrompts } from '../llm'; + +export const CEREBRAS_SERVICE = 'cerebras'; + +export function cerebrasLLMRegistry(): Record LLM> { + return { + 'cerebras:llama3.1-8b': cerebrasLlama3_8b, + 'cerebras:llama3.1-70b': cerebrasLlama3_70b, + }; +} + +export function cerebrasLlama3_8b(): LLM { + return new CerebrasLLM( + 'LLaMA3 8b (Cerebras)', + CEREBRAS_SERVICE, + 'llama3.1-8b', + 8_192, + (input: string) => 0, //(input.length * 0.05) / (1_000_000 * 4), + (output: string) => 0, //(output.length * 0.08) / (1_000_000 * 4), + 0, + 0, + ); +} + +export function cerebrasLlama3_70b(): LLM { + return new CerebrasLLM( + 'LLaMA3 70b (Cerebras)', + CEREBRAS_SERVICE, + 'llama3.1-70b', + 8_192, + (input: string) => 0, //(input.length * 0.05) / (1_000_000 * 4), + (output: string) => 0, //(output.length * 0.08) / (1_000_000 * 4), + 0, + 0, + ); +} + +export function cerebrasLLMs(): AgentLLMs { + const llama70b = cerebrasLlama3_70b(); + return { + easy: cerebrasLlama3_8b(), + medium: llama70b, + hard: llama70b, + xhard: llama70b, + }; +} + +/** + * https://inference-docs.cerebras.ai/introduction + */ +export class CerebrasLLM extends BaseLLM { + _client: Cerebras; + + constructor( + displayName: string, + service: string, + model: string, + maxInputTokens: number, + /** Needed for Aider when we only have the text size */ + calculateInputCost: (input: string) => number, + /** Needed for Aider when we only have the text size */ + calculateOutputCost: (output: string) => number, + private costPerMillionInputTokens: number, + private costPerMillionOutputTokens: number, + ) { + super(displayName, service, model, maxInputTokens, calculateInputCost, calculateOutputCost); + } + + client(): Cerebras { + if (!this._client) { + this._client = new Cerebras({ + apiKey: currentUser().llmConfig.cerebrasKey || process.env.CEREBRAS_API_KEY, + }); + } + return this._client; + } + + async generateText(userPrompt: string, systemPrompt?: string, opts?: GenerateTextOptions): Promise { + return withActiveSpan(`generateText ${opts?.id ?? ''}`, async (span) => { + const prompt = combinePrompts(userPrompt, systemPrompt); + if (systemPrompt) span.setAttribute('systemPrompt', systemPrompt); + span.setAttributes({ + userPrompt, + inputChars: prompt.length, + model: this.model, + service: this.service, + }); + span.setAttribute('userPrompt', userPrompt); + span.setAttribute('inputChars', prompt.length); + + const llmCallSave: Promise = appContext().llmCallService.saveRequest({ + userPrompt, + systemPrompt, + llmId: this.getId(), + agentId: agentContext()?.agentId, + callStack: agentContext()?.callStack.join(' > '), + }); + const requestTime = Date.now(); + + const messages = []; + if (systemPrompt) { + messages.push({ + role: 'system', + content: systemPrompt, + }); + } + messages.push({ + role: 'user', + content: userPrompt, + }); + + try { + // https://inference-docs.cerebras.ai/api-reference/chat-completions + const completion = await this.client().chat.completions.create({ + messages, + model: this.model, + }); + const responseText = completion.choices[0]?.message?.content || ''; + + const timeToFirstToken = Date.now() - requestTime; + const finishTime = Date.now(); + const llmCall: LlmCall = await llmCallSave; + + const inputTokens = completion.usage.prompt_tokens; + const outputTokens = completion.usage.completion_tokens; + + const inputCost = this.calculateInputCost(prompt); + const outputCost = this.calculateOutputCost(responseText); + const cost = inputCost + outputCost; + addCost(cost); + + llmCall.responseText = responseText; + llmCall.timeToFirstToken = timeToFirstToken; + llmCall.totalTime = finishTime - requestTime; + llmCall.cost = cost; + + try { + await appContext().llmCallService.saveResponse(llmCall); + } catch (e) { + // queue to save + console.error(e); + } + + span.setAttributes({ + response: responseText, + inputCost, + outputCost, + cost, + outputChars: responseText.length, + }); + + return responseText; + } catch (e) { + // TODO find out the actual codes + if (e.error?.code === 'rate_limit_exceeded') throw new RetryableError(e); + throw e; + } + }); + } +} diff --git a/src/llm/models/deepseek.ts b/src/llm/models/deepseek.ts index 1a887a1c..b9990aaf 100644 --- a/src/llm/models/deepseek.ts +++ b/src/llm/models/deepseek.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { CallerId } from '#llm/llmCallService/llmCallService'; import { withSpan } from '#o11y/trace'; @@ -52,7 +52,7 @@ export class DeepseekLLM extends BaseLLM { this._client = axios.create({ baseURL: 'https://api.deepseek.com', headers: { - Authorization: `Bearer ${currentUser().llmConfig.deepseekKey ?? envVar('DEEPSEEK_API_KEY')}`, + Authorization: `Bearer ${currentUser().llmConfig.deepseekKey || envVar('DEEPSEEK_API_KEY')}`, }, }); } diff --git a/src/llm/models/fireworks.ts b/src/llm/models/fireworks.ts index 1fb19a80..c299f79d 100644 --- a/src/llm/models/fireworks.ts +++ b/src/llm/models/fireworks.ts @@ -1,5 +1,5 @@ import OpenAI from 'openai'; -import { addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { CallerId } from '#llm/llmCallService/llmCallService'; import { withSpan } from '#o11y/trace'; @@ -22,7 +22,7 @@ export class FireworksLLM extends BaseLLM { client(): OpenAI { if (!this._client) { this._client = new OpenAI({ - apiKey: currentUser().llmConfig.fireworksKey ?? envVar('FIREWORKS_KEY'), + apiKey: currentUser().llmConfig.fireworksKey || envVar('FIREWORKS_KEY'), baseURL: 'https://api.fireworks.ai/inference/v1', }); } diff --git a/src/llm/models/groq.ts b/src/llm/models/groq.ts index f87d0782..076a6ada 100644 --- a/src/llm/models/groq.ts +++ b/src/llm/models/groq.ts @@ -1,15 +1,15 @@ import Groq from 'groq-sdk'; -import { AgentLLMs, agentContext } from '#agent/agentContext'; -import { addCost } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; +import { addCost } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { LlmCall } from '#llm/llmCallService/llmCall'; -import { CallerId } from '#llm/llmCallService/llmCallService'; import { withActiveSpan } from '#o11y/trace'; import { currentUser } from '#user/userService/userContext'; import { envVar } from '#utils/env-var'; import { appContext } from '../../app'; import { RetryableError } from '../../cache/cacheRetry'; import { BaseLLM } from '../base-llm'; -import { GenerateTextOptions, LLM, combinePrompts, logDuration } from '../llm'; +import { GenerateTextOptions, LLM, combinePrompts } from '../llm'; import { MultiLLM } from '../multi-llm'; export const GROQ_SERVICE = 'groq'; @@ -98,13 +98,12 @@ export class GroqLLM extends BaseLLM { groq(): Groq { if (!this._groq) { this._groq = new Groq({ - apiKey: currentUser().llmConfig.groqKey ?? envVar('GROQ_API_KEY'), + apiKey: currentUser().llmConfig.groqKey || envVar('GROQ_API_KEY'), }); } return this._groq; } - @logDuration async generateText(userPrompt: string, systemPrompt?: string, opts?: GenerateTextOptions): Promise { return withActiveSpan(`generateText ${opts?.id ?? ''}`, async (span) => { const prompt = combinePrompts(userPrompt, systemPrompt); diff --git a/src/llm/models/llm.int.ts b/src/llm/models/llm.int.ts index 413cb6af..6762442e 100644 --- a/src/llm/models/llm.int.ts +++ b/src/llm/models/llm.int.ts @@ -1,5 +1,8 @@ import { expect } from 'chai'; +import { LlmMessage } from '#llm/llm'; import { Claude3_Haiku } from '#llm/models/anthropic'; +import { Claude3_Haiku_Vertex } from '#llm/models/anthropic-vertex'; +import { cerebrasLlama3_8b } from '#llm/models/cerebras'; import { deepseekChat } from '#llm/models/deepseek'; import { fireworksLlama3_70B } from '#llm/models/fireworks'; import { groqGemma7bIt } from '#llm/models/groq'; @@ -9,78 +12,120 @@ import { togetherLlama3_70B } from '#llm/models/together'; import { Gemini_1_5_Flash } from '#llm/models/vertexai'; // Skip until API keys are configured in CI -describe.skip('LLMs', () => { - const SKY_PROMPT = 'What colour is the day sky? Answer in one word. (Hint: starts with b)'; +describe('LLMs', () => { + describe('generateText2', () => { + const SKY_MESSAGES: LlmMessage[] = [ + { + role: 'system', + text: 'Answer in one word.', + }, + { + role: 'user', + text: 'What colour is the day sky? (Hint: starts with b)', + }, + ]; + + describe.only('Anthropic Vertex', () => { + const llm = Claude3_Haiku_Vertex(); + + it('should generateText', async () => { + const response = await llm.generateText2(SKY_MESSAGES, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); + }); + }); - describe('Anthropic', () => { - const llm = Claude3_Haiku(); + describe('generateText', () => { + const SKY_PROMPT = 'What colour is the day sky? Answer in one word. (Hint: starts with b)'; - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + describe('Anthropic', () => { + const llm = Claude3_Haiku(); + + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('Deepseek', () => { - const llm = deepseekChat(); + describe('Anthropic Vertex', () => { + const llm = Claude3_Haiku_Vertex(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('Fireworks', () => { - const llm = fireworksLlama3_70B(); + describe('Cerebras', () => { + const llm = cerebrasLlama3_8b(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('Groq', () => { - const llm = groqGemma7bIt(); + describe('Deepseek', () => { + const llm = deepseekChat(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('Ollama', () => { - const llm = Ollama_Phi3(); + describe('Fireworks', () => { + const llm = fireworksLlama3_70B(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('OpenAI', () => { - const llm = GPT4oMini(); + describe('Groq', () => { + const llm = groqGemma7bIt(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('Together', () => { - const llm = togetherLlama3_70B(); + describe('Ollama', () => { + const llm = Ollama_Phi3(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); + }); + + describe('OpenAI', () => { + const llm = GPT4oMini(); + + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); + }); + + describe('Together', () => { + const llm = togetherLlama3_70B(); + + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); - }); - describe('VertexAI', () => { - const llm = Gemini_1_5_Flash(); + describe('VertexAI', () => { + const llm = Gemini_1_5_Flash(); - it('should generateText', async () => { - const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); - expect(response.toLowerCase()).to.include('blue'); + it('should generateText', async () => { + const response = await llm.generateText(SKY_PROMPT, null, { temperature: 0 }); + expect(response.toLowerCase()).to.include('blue'); + }); }); }); }); diff --git a/src/llm/models/mock-llm.ts b/src/llm/models/mock-llm.ts index 6e4ea4f0..fcdbd9b0 100644 --- a/src/llm/models/mock-llm.ts +++ b/src/llm/models/mock-llm.ts @@ -1,4 +1,5 @@ -import { AgentLLMs, addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; diff --git a/src/llm/models/ollama.ts b/src/llm/models/ollama.ts index deb88db8..4ff150cd 100644 --- a/src/llm/models/ollama.ts +++ b/src/llm/models/ollama.ts @@ -1,5 +1,6 @@ import axios from 'axios'; -import { AgentLLMs, agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { CallerId } from '#llm/llmCallService/llmCallService'; import { withActiveSpan } from '#o11y/trace'; diff --git a/src/llm/models/openai.ts b/src/llm/models/openai.ts index 1dfec1a5..efce8d28 100644 --- a/src/llm/models/openai.ts +++ b/src/llm/models/openai.ts @@ -1,5 +1,5 @@ import { OpenAI as OpenAISDK } from 'openai'; -import { addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; @@ -62,7 +62,7 @@ export class OpenAI extends BaseLLM { private sdk(): OpenAISDK { if (!this.openAISDK) { this.openAISDK = new OpenAISDK({ - apiKey: currentUser().llmConfig.openaiKey ?? envVar('OPENAI_API_KEY'), + apiKey: currentUser().llmConfig.openaiKey || envVar('OPENAI_API_KEY'), }); } return this.openAISDK; diff --git a/src/llm/models/together.ts b/src/llm/models/together.ts index 2f897f16..e01e4b17 100644 --- a/src/llm/models/together.ts +++ b/src/llm/models/together.ts @@ -1,5 +1,5 @@ import OpenAI from 'openai'; -import { addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { CallerId } from '#llm/llmCallService/llmCallService'; import { withSpan } from '#o11y/trace'; @@ -37,7 +37,7 @@ export class TogetherLLM extends BaseLLM { client(): OpenAI { if (!this._client) { this._client = new OpenAI({ - apiKey: currentUser().llmConfig.togetheraiKey ?? envVar('TOGETHERAI_KEY'), + apiKey: currentUser().llmConfig.togetheraiKey || envVar('TOGETHERAI_KEY'), baseURL: 'https://api.together.xyz/v1', }); } diff --git a/src/llm/models/vertexai.ts b/src/llm/models/vertexai.ts index acf1a894..49ff5b12 100644 --- a/src/llm/models/vertexai.ts +++ b/src/llm/models/vertexai.ts @@ -1,6 +1,7 @@ import { GenerativeModel, HarmBlockThreshold, HarmCategory, SafetySetting, VertexAI } from '@google-cloud/vertexai'; import axios from 'axios'; -import { AgentLLMs, addCost, agentContext } from '#agent/agentContext'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; +import { AgentLLMs } from '#agent/agentContextTypes'; import { LlmCall } from '#llm/llmCallService/llmCall'; import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; diff --git a/src/llm/multi-llm.ts b/src/llm/multi-llm.ts index 61930e48..9bce14db 100644 --- a/src/llm/multi-llm.ts +++ b/src/llm/multi-llm.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { BaseLLM } from './base-llm'; import { LLM } from './llm'; diff --git a/src/modules/slack/slack.ts b/src/modules/slack/slack.ts index 43358a2e..1a315a05 100644 --- a/src/modules/slack/slack.ts +++ b/src/modules/slack/slack.ts @@ -1,8 +1,10 @@ import axios, { AxiosResponse } from 'axios'; -import { logger } from '#o11y/logger'; - +import { completedNotificationMessage } from '#agent/agentCompletion'; +import { AgentCompleted, AgentContext } from '#agent/agentContextTypes'; import { func, funcClass } from '#functionSchema/functionDecorators'; -import { currentUser, functionConfig } from '#user/userService/userContext'; +import { GetToolType, ToolType } from '#functions/toolType'; +import { logger } from '#o11y/logger'; +import { functionConfig } from '#user/userService/userContext'; export interface SlackConfig { token: string; @@ -11,7 +13,20 @@ export interface SlackConfig { } @funcClass(__filename) -export class Slack { +export class Slack implements GetToolType, AgentCompleted { + getToolType(): ToolType { + return 'notification'; + } + + async notifyCompleted(agentContext: AgentContext): Promise { + const message = completedNotificationMessage(agentContext); + await this.sendMessage(message); + } + + agentCompletedHandlerId(): string { + return 'slack-webhook'; + } + /** * Sends a message to the supervisor * @param message the message text diff --git a/src/modules/slack/slackChatBotService.ts b/src/modules/slack/slackChatBotService.ts index 13dd21ce..962550b2 100644 --- a/src/modules/slack/slackChatBotService.ts +++ b/src/modules/slack/slackChatBotService.ts @@ -1,7 +1,7 @@ import { App, SayFn } from '@slack/bolt'; import { StringIndexed } from '@slack/bolt/dist/types/helpers'; -import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext, AgentRunningState, isExecuting } from '#agent/agentContext'; +import { getLastFunctionCallArg } from '#agent/agentCompletion'; +import { AgentCompleted, AgentContext, isExecuting } from '#agent/agentContextTypes'; import { AGENT_COMPLETED_PARAM_NAME, REQUEST_FEEDBACK_PARAM_NAME } from '#agent/agentFunctions'; import { resumeCompleted, startAgent } from '#agent/agentRunner'; import { GoogleCloud } from '#functions/cloud/google-cloud'; @@ -15,21 +15,50 @@ import { appContext } from '../../app'; import { ChatBotService } from '../../chatBot/chatBotService'; let slackApp: App | undefined; + /** * Slack implementation of ChatBotService + * Only one Slack workspace can be configured in the application as the Slack App is shared between all instances of this class. */ -export class SlackChatBotService implements ChatBotService { +export class SlackChatBotService implements ChatBotService, AgentCompleted { + threadId(agent: AgentContext): string { + return agent.agentId.replace('Slack-', ''); + } + + agentCompletedHandlerId(): string { + return 'console'; + } + + notifyCompleted(agent: AgentContext): Promise { + let message = ''; + switch (agent.state) { + case 'error': + message = `Sorry, I'm having unexpected difficulties providing a response to your request`; + break; + case 'hil': + message = `Apologies, I've been unable to produce a response with the resources I've been allocated to spend on the request`; + break; + case 'feedback': + message = getLastFunctionCallArg(agent); + break; + case 'completed': + message = getLastFunctionCallArg(agent); + break; + default: + message = `Sorry, I'm unable to provide a response to your request`; + } + return this.sendMessage(agent, message); + } + /** * Sends a message to the chat thread the agent is a chatbot for. * @param agent * @param message */ async sendMessage(agent: AgentContext, message: string): Promise { - if (!slackApp) { - throw new Error('Slack app is not initialized. Call initSlack() first.'); - } + if (!slackApp) throw new Error('Slack app is not initialized. Call initSlack() first.'); - const threadId = agent.agentId.replace('Slack-', ''); + const threadId = this.threadId(agent); try { const result = await slackApp.client.chat.postMessage({ @@ -115,8 +144,10 @@ export class SlackChatBotService implements ChatBotService { llms: ClaudeVertexLLMs(), functions: [GitLab, GoogleCloud, Perplexity, LlmTools], agentName: `Slack-${threadId}`, - systemPrompt: 'You are an AI support agent called Sophia. You are responding to support requests on the company Slack account', + systemPrompt: + 'You are an AI support agent called Sophia. You are responding to support requests on the company Slack account. Respond in a helpful, concise manner. If you encounter an error responding to the request do not provide details of the error to the user, only respond with "Sorry, I\'m having difficulties providing a response to your request"', metadata: { channel: event.channel }, + completedHandler: this, }); await agentExec.execution; const agent: AgentContext = await appContext().agentStateService.load(agentExec.agentId); @@ -154,8 +185,7 @@ export class SlackChatBotService implements ChatBotService { slackApp.event('app_mention', async ({ event, say }) => { console.log('app_mention received'); - // logger.info(event) - // logger.info(say) + // TODO if not in a channel we are subscribed to, then get the thread messages and reply to it }); await slackApp.start(); diff --git a/src/o11y/trace.ts b/src/o11y/trace.ts index 6472c977..fc104e36 100644 --- a/src/o11y/trace.ts +++ b/src/o11y/trace.ts @@ -2,7 +2,7 @@ import { Span, SpanContext, Tracer } from '@opentelemetry/api'; import opentelemetry from '@opentelemetry/api'; import { AsyncLocalStorage } from 'async_hooks'; -import { AgentContext } from '#agent/agentContext'; +import { AgentContext } from '#agent/agentContextTypes'; import { logger } from '#o11y/logger'; import { SugaredTracer, wrapTracer } from './trace/SugaredTracer'; diff --git a/src/routes/agent/agent-details-routes.ts b/src/routes/agent/agent-details-routes.ts index b012648c..4119520a 100644 --- a/src/routes/agent/agent-details-routes.ts +++ b/src/routes/agent/agent-details-routes.ts @@ -1,7 +1,7 @@ import { Type } from '@sinclair/typebox'; import { FastifyReply } from 'fastify'; -import { AgentContext } from '#agent/agentContext'; -import { serializeContext } from '#agent/agentContext'; +import { serializeContext } from '#agent/agentContextLocalStorage'; +import { AgentContext } from '#agent/agentContextTypes'; import { AgentExecution, agentExecutions } from '#agent/agentRunner'; import { send, sendBadRequest, sendSuccess } from '#fastify/index'; import { logger } from '#o11y/logger'; diff --git a/src/routes/agent/agent-execution-routes.ts b/src/routes/agent/agent-execution-routes.ts index c53db67c..54129ce7 100644 --- a/src/routes/agent/agent-execution-routes.ts +++ b/src/routes/agent/agent-execution-routes.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox'; import { LlmFunctions } from '#agent/LlmFunctions'; -import { AgentContext } from '#agent/agentContext'; +import { AgentContext } from '#agent/agentContextTypes'; import { cancelAgent, provideFeedback, resumeCompleted, resumeError, resumeHil } from '#agent/agentRunner'; import { runXmlAgent } from '#agent/xmlAgentRunner'; import { send, sendBadRequest } from '#fastify/index'; diff --git a/src/routes/gitlab/gitlabRoutes-v1.ts b/src/routes/gitlab/gitlabRoutes-v1.ts index 0c6c5cd3..11dce0cf 100644 --- a/src/routes/gitlab/gitlabRoutes-v1.ts +++ b/src/routes/gitlab/gitlabRoutes-v1.ts @@ -1,5 +1,6 @@ import { Type } from '@sinclair/typebox'; -import { AgentContext, agentContextStorage, createContext } from '#agent/agentContext'; +import { agentContextStorage, createContext } from '#agent/agentContextLocalStorage'; +import { AgentContext } from '#agent/agentContextTypes'; import { RunAgentConfig } from '#agent/agentRunner'; import { send, sendSuccess } from '#fastify/index'; import { GitLab } from '#functions/scm/gitlab'; diff --git a/src/swe/SWEBenchAgent.ts b/src/swe/SWEBenchAgent.ts index 0ef9bac7..75328489 100644 --- a/src/swe/SWEBenchAgent.ts +++ b/src/swe/SWEBenchAgent.ts @@ -1,4 +1,4 @@ -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { runAgentWorkflow } from '#agent/agentWorkflowRunner'; import { GitHub } from '#functions/scm/github'; import { ClaudeVertexLLMs } from '#llm/models/anthropic-vertex'; diff --git a/src/swe/analyzeCompileErrors.ts b/src/swe/analyzeCompileErrors.ts index 021feed2..62e9c804 100644 --- a/src/swe/analyzeCompileErrors.ts +++ b/src/swe/analyzeCompileErrors.ts @@ -1,4 +1,4 @@ -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; export interface CompileErrorAnalysis { compilerOutput: string; diff --git a/src/swe/codeEditingAgent.ts b/src/swe/codeEditingAgent.ts index b75538e7..f5ff9458 100644 --- a/src/swe/codeEditingAgent.ts +++ b/src/swe/codeEditingAgent.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { agentContext, getFileSystem, llms } from '#agent/agentContext'; +import { agentContext, getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { FileSystem } from '#functions/storage/filesystem'; import { Perplexity } from '#functions/web/perplexity'; diff --git a/src/swe/codeEditor.ts b/src/swe/codeEditor.ts index e619c30a..8019e27e 100644 --- a/src/swe/codeEditor.ts +++ b/src/swe/codeEditor.ts @@ -3,7 +3,7 @@ import { execSync } from 'node:child_process'; import fs, { readFile, unlinkSync } from 'node:fs'; import path, { join } from 'path'; import { promisify } from 'util'; -import { addCost, agentContext, getFileSystem } from '#agent/agentContext'; +import { addCost, agentContext, getFileSystem } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { LLM } from '#llm/llm'; import { Anthropic, Claude3_5_Sonnet } from '#llm/models/anthropic'; diff --git a/src/swe/createBranchName.ts b/src/swe/createBranchName.ts index 89af44cc..bb576e80 100644 --- a/src/swe/createBranchName.ts +++ b/src/swe/createBranchName.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; export async function createBranchName(requirements: string, issueId?: string): Promise { let branchName = await llms().medium.generateTextWithResult( diff --git a/src/swe/documentationBuilder.ts b/src/swe/documentationBuilder.ts index 7f61acd5..582717dc 100644 --- a/src/swe/documentationBuilder.ts +++ b/src/swe/documentationBuilder.ts @@ -1,6 +1,6 @@ import { promises as fs } from 'node:fs'; import { basename, dirname, join } from 'path'; -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; /** Summary documentation for a file/folder */ diff --git a/src/swe/generateTestRequirements.ts b/src/swe/generateTestRequirements.ts index 6b17fe29..7ba80f8c 100644 --- a/src/swe/generateTestRequirements.ts +++ b/src/swe/generateTestRequirements.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { buildPrompt } from '#swe/softwareDeveloperAgent'; // work in progress diff --git a/src/swe/lang/nodejs/researchNpmPackage.ts b/src/swe/lang/nodejs/researchNpmPackage.ts index 3a0457f7..513b706e 100644 --- a/src/swe/lang/nodejs/researchNpmPackage.ts +++ b/src/swe/lang/nodejs/researchNpmPackage.ts @@ -1,4 +1,4 @@ -import { agentContext, getFileSystem } from '#agent/agentContext'; +import { agentContext, getFileSystem } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { PublicWeb } from '#functions/web/web'; import { logger } from '#o11y/logger'; diff --git a/src/swe/lang/nodejs/typescriptTools.ts b/src/swe/lang/nodejs/typescriptTools.ts index 107c8fcf..d570a738 100644 --- a/src/swe/lang/nodejs/typescriptTools.ts +++ b/src/swe/lang/nodejs/typescriptTools.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync } from 'node:fs'; import { join } from 'path'; -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { ExecResult, execCommand, failOnError, runShellCommand } from '#utils/exec'; diff --git a/src/swe/lang/python/pythonTools.ts b/src/swe/lang/python/pythonTools.ts index 267e558a..a0ced2bb 100644 --- a/src/swe/lang/python/pythonTools.ts +++ b/src/swe/lang/python/pythonTools.ts @@ -1,4 +1,4 @@ -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { funcClass } from '#functionSchema/functionDecorators'; import { logger } from '#o11y/logger'; import { getPythonPath } from '#swe/codeEditor'; diff --git a/src/swe/lang/terraform/terraformTools.ts b/src/swe/lang/terraform/terraformTools.ts index 92010dd5..a8158700 100644 --- a/src/swe/lang/terraform/terraformTools.ts +++ b/src/swe/lang/terraform/terraformTools.ts @@ -1,4 +1,4 @@ -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { funcClass } from '#functionSchema/functionDecorators'; import { LanguageTools } from '../languageTools'; diff --git a/src/swe/projectDetection.ts b/src/swe/projectDetection.ts index ba11959c..bc430c05 100644 --- a/src/swe/projectDetection.ts +++ b/src/swe/projectDetection.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync } from 'fs'; import path, { join } from 'path'; -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { TypescriptTools } from '#swe/lang/nodejs/typescriptTools'; import { PhpTools } from '#swe/lang/php/phpTools'; diff --git a/src/swe/projectMap.ts b/src/swe/projectMap.ts index 486b9524..828f4b36 100644 --- a/src/swe/projectMap.ts +++ b/src/swe/projectMap.ts @@ -1,4 +1,4 @@ -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { countTokens } from '#llm/tokens'; import { logger } from '#o11y/logger'; import { Summary } from '#swe/documentationBuilder'; diff --git a/src/swe/pullRequestTitleDescription.ts b/src/swe/pullRequestTitleDescription.ts index 13ec45c2..72ffb144 100644 --- a/src/swe/pullRequestTitleDescription.ts +++ b/src/swe/pullRequestTitleDescription.ts @@ -1,4 +1,4 @@ -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; export async function generatePullRequestTitleDescription(requirements: string, devBranch: string): Promise<{ title: string; description: string }> { const pullRequestDescriptionPrompt = `\n${requirements}\n\n${await getFileSystem().vcs.getBranchDiff( diff --git a/src/swe/reviewChanges.ts b/src/swe/reviewChanges.ts index e9c10c46..eea3aa61 100644 --- a/src/swe/reviewChanges.ts +++ b/src/swe/reviewChanges.ts @@ -1,4 +1,4 @@ -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { buildPrompt } from '#swe/softwareDeveloperAgent'; diff --git a/src/swe/selectFilesToEdit.ts b/src/swe/selectFilesToEdit.ts index 6e4fb0a8..ffcd24f5 100644 --- a/src/swe/selectFilesToEdit.ts +++ b/src/swe/selectFilesToEdit.ts @@ -1,7 +1,7 @@ import { promises as fs } from 'node:fs'; import path from 'path'; import { createByModelName } from '@microsoft/tiktokenizer'; -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { ProjectMaps, generateProjectMaps } from '#swe/projectMap'; import { ProjectInfo } from './projectDetection'; diff --git a/src/swe/selectProject.ts b/src/swe/selectProject.ts index a9ab7806..f7473537 100644 --- a/src/swe/selectProject.ts +++ b/src/swe/selectProject.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { GitProject } from '#functions/scm/gitProject'; import { GitLabProject } from '#functions/scm/gitlab'; import { SourceControlManagement, getSourceControlManagementTool } from '#functions/scm/sourceControlManagement'; diff --git a/src/swe/simpleCodeEditor.ts b/src/swe/simpleCodeEditor.ts index 8cdc7f9f..3e7cc67c 100644 --- a/src/swe/simpleCodeEditor.ts +++ b/src/swe/simpleCodeEditor.ts @@ -1,4 +1,4 @@ -import { getFileSystem, llms } from '#agent/agentContext'; +import { getFileSystem, llms } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { CDATA_END, CDATA_START } from '#utils/xml-utils'; import { LLM } from '../llm/llm'; diff --git a/src/swe/softwareDeveloperAgent.ts b/src/swe/softwareDeveloperAgent.ts index 6273c598..a094f9af 100644 --- a/src/swe/softwareDeveloperAgent.ts +++ b/src/swe/softwareDeveloperAgent.ts @@ -1,4 +1,4 @@ -import { addNote, agentContext, getFileSystem } from '#agent/agentContext'; +import { addNote, agentContext, getFileSystem } from '#agent/agentContextLocalStorage'; import { func, funcClass } from '#functionSchema/functionDecorators'; import { GitProject } from '#functions/scm/gitProject'; import { GitLabProject } from '#functions/scm/gitlab'; diff --git a/src/swe/summariseRequirements.ts b/src/swe/summariseRequirements.ts index d2f428fa..d78dc7aa 100644 --- a/src/swe/summariseRequirements.ts +++ b/src/swe/summariseRequirements.ts @@ -1,4 +1,4 @@ -import { llms } from '#agent/agentContext'; +import { llms } from '#agent/agentContextLocalStorage'; import { buildPrompt } from './softwareDeveloperAgent'; export async function summariseRequirements(requirements: string): Promise { diff --git a/src/swe/supportingInformation.ts b/src/swe/supportingInformation.ts index 419afa62..8a3f900a 100644 --- a/src/swe/supportingInformation.ts +++ b/src/swe/supportingInformation.ts @@ -1,4 +1,4 @@ -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { ProjectInfo } from '#swe/projectDetection'; export async function supportingInformation(projectInfo: ProjectInfo): Promise { diff --git a/src/user/user.ts b/src/user/user.ts index 93897beb..dc156b28 100644 --- a/src/user/user.ts +++ b/src/user/user.ts @@ -1,7 +1,3 @@ -import { JiraConfig } from '#functions/jira'; -import { GitHubConfig } from '#functions/scm/github'; -import { GitLabConfig } from '#functions/scm/gitlab'; - export interface LLMServicesConfig { vertexProjectId?: string; vertexRegion?: string; @@ -12,6 +8,7 @@ export interface LLMServicesConfig { togetheraiKey?: string; deepseekKey?: string; fireworksKey?: string; + cerebrasKey?: string; } export interface User { @@ -24,20 +21,6 @@ export interface User { llmConfig: LLMServicesConfig; - // gitlabConfig: GitLabConfig; - // githubConfig: GitHubConfig; - // jiraConfig: JiraConfig; - // - // perplexityKey: string; /** Configuration for the function callable integrations */ functionConfig: Record>; - - // googleCustomSearchEngineId: string; - // googleCustomSearchKey: string; - // serpApiKey: string; - - // vertexProjectId: string; - // vertexRegion: string; - // anthropicVertexProjectId: string; - // cloudMlRegion: string; } diff --git a/src/user/userService/userContext.ts b/src/user/userService/userContext.ts index 638c6759..61630d8f 100644 --- a/src/user/userService/userContext.ts +++ b/src/user/userService/userContext.ts @@ -1,5 +1,5 @@ import { AsyncLocalStorage } from 'async_hooks'; -import { agentContext } from '#agent/agentContext'; +import { agentContext } from '#agent/agentContextLocalStorage'; import { User } from '#user/user'; import { appContext } from '../../app'; diff --git a/src/utils/exec.ts b/src/utils/exec.ts index 9f6868cb..12824b64 100644 --- a/src/utils/exec.ts +++ b/src/utils/exec.ts @@ -5,7 +5,7 @@ import os from 'os'; import path from 'path'; import { promisify } from 'util'; import { SpanStatusCode } from '@opentelemetry/api'; -import { getFileSystem } from '#agent/agentContext'; +import { getFileSystem } from '#agent/agentContextLocalStorage'; import { logger } from '#o11y/logger'; import { withSpan } from '#o11y/trace'; diff --git a/tsconfig.json b/tsconfig.json index 0eed8c57..7425d321 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "isolatedDeclarations": false, "esModuleInterop": true, "skipLibCheck": true, + "importHelpers": true, "moduleResolution": "node16", "baseUrl": "./", "paths": { diff --git a/variables/local.env.example b/variables/local.env.example index 9e6df793..57c0f44c 100644 --- a/variables/local.env.example +++ b/variables/local.env.example @@ -37,6 +37,7 @@ GROQ_API_KEY= TOGETHERAI_KEY= FIREWORKS_KEY= DEEPSEEK_API_KEY= +CEREBRAS_API_KEY= GITLAB_TOKEN= GITLAB_HOST=www.gitlab.com # OR your self-hosted domain @@ -58,5 +59,6 @@ SERP_API_KEY= SLACK_BOT_TOKEN= SLACK_SIGNING_SECRET= +# Ensure that your bot is invited to the channel(s) you want to listen to. This is necessary for the bot to receive events from that channel. SLACK_CHANNELS= SLACK_APP_TOKEN=