Files
anything-llm/server/utils/agentFlows/executor.js
Sean Hatfield e5f3fb0892 Agent flow builder (#3077)
* wip agent builder

* refactor structure for agent builder

* improve ui for add block menu and sidebar

* lint

* node ui improvement

* handle deleting variable in all nodes

* add headers and body to apiCall node

* lint

* Agent flow builder backend (#3078)

* wip agent builder backend

* save/load agent tasks

* lint

* refactor agent task to use uuids instead of names

* placeholder for run task

* update frontend sidebar + seperate backend to agent-tasks utils

* lint

* add deleting of agent tasks

* create AgentTasks class + wip load agent tasks into aibitat

* lint

* inject + call agent tasks

* wip call agent tasks

* add llm instruction + fix api calling blocks

* add ui + backend for editing/toggling agent tasks

* lint

* add back middlewares

* disable run task + add navigate to home on logo click

* implement normalizePath to prevent path traversal

* wip make api calling more consistent

* lint

* rename all references from task to flow

* patch load flow bug when on editing page

* remove unneeded files/comments

* lint

* fix delete endpoint + rename load flows

* add move block to ui + fix api-call backend + add telemetry

* lint

* add web scraping block

* only allow admin for agent builder

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>

* Move AgentFlowManager flows to static
simplify UI states
Handle LLM prompt flow when provided non-string

* delete/edit menu for agent flow panel + update flow icon

* lint

* fix open builder button hidden bug

* add tooltips to move up/down block buttons

* add tooltip to delete block

* truncate block description to fit on blocklist component

* light mode agent builder sidebar

* light mode api call block

* fix light mode styles for agent builder blocks

* agent flow fetch in UI

* sync delete flow

* agent flow ui/ux improvements

* remove unused AgentSidebar component

* comment out /run

* UI changes and updates for flow builder

* format flow panel info

* update link handling

* ui tweaks to header menu

* remove unused import

* update doc links
update block icons

* bump readme

* Patch code block header oddity
resolves #3117

* bump dev image

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
2025-02-12 16:50:43 -08:00

147 lines
4.3 KiB
JavaScript

const { FLOW_TYPES } = require("./flowTypes");
const executeApiCall = require("./executors/api-call");
const executeWebsite = require("./executors/website");
const executeFile = require("./executors/file");
const executeCode = require("./executors/code");
const executeLLMInstruction = require("./executors/llm-instruction");
const executeWebScraping = require("./executors/web-scraping");
const { Telemetry } = require("../../models/telemetry");
class FlowExecutor {
constructor() {
this.variables = {};
this.introspect = () => {}; // Default no-op introspect
this.logger = console.info; // Default console.info
}
attachLogging(introspectFn, loggerFn) {
this.introspect = introspectFn || (() => {});
this.logger = loggerFn || console.info;
}
// Utility to replace variables in config
replaceVariables(config) {
const deepReplace = (obj) => {
if (typeof obj === "string") {
return obj.replace(/\${([^}]+)}/g, (match, varName) => {
return this.variables[varName] !== undefined
? this.variables[varName]
: match;
});
}
if (Array.isArray(obj)) {
return obj.map((item) => deepReplace(item));
}
if (obj && typeof obj === "object") {
const result = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = deepReplace(value);
}
return result;
}
return obj;
};
return deepReplace(config);
}
// Main execution method
async executeStep(step) {
const config = this.replaceVariables(step.config);
let result;
// Create execution context with introspect
const context = {
introspect: this.introspect,
variables: this.variables,
logger: this.logger,
model: process.env.LLM_PROVIDER_MODEL || "gpt-4",
provider: process.env.LLM_PROVIDER || "openai",
};
switch (step.type) {
case FLOW_TYPES.START.type:
// For start blocks, we just initialize variables if they're not already set
if (config.variables) {
config.variables.forEach((v) => {
if (v.name && !this.variables[v.name]) {
this.variables[v.name] = v.value || "";
}
});
}
result = this.variables;
break;
case FLOW_TYPES.API_CALL.type:
result = await executeApiCall(config, context);
break;
case FLOW_TYPES.WEBSITE.type:
result = await executeWebsite(config, context);
break;
case FLOW_TYPES.FILE.type:
result = await executeFile(config, context);
break;
case FLOW_TYPES.CODE.type:
result = await executeCode(config, context);
break;
case FLOW_TYPES.LLM_INSTRUCTION.type:
result = await executeLLMInstruction(config, context);
break;
case FLOW_TYPES.WEB_SCRAPING.type:
result = await executeWebScraping(config, context);
break;
default:
throw new Error(`Unknown flow type: ${step.type}`);
}
// Store result in variable if specified
if (config.resultVariable || config.responseVariable) {
const varName = config.resultVariable || config.responseVariable;
this.variables[varName] = result;
}
return result;
}
// Execute entire flow
async executeFlow(
flow,
initialVariables = {},
introspectFn = null,
loggerFn = null
) {
await Telemetry.sendTelemetry("agent_flow_execution_started");
// Initialize variables with both initial values and any passed-in values
this.variables = {
...(
flow.config.steps.find((s) => s.type === "start")?.config?.variables ||
[]
).reduce((acc, v) => ({ ...acc, [v.name]: v.value }), {}),
...initialVariables, // This will override any default values with passed-in values
};
this.attachLogging(introspectFn, loggerFn);
const results = [];
for (const step of flow.config.steps) {
try {
const result = await this.executeStep(step);
results.push({ success: true, result });
} catch (error) {
results.push({ success: false, error: error.message });
break;
}
}
return {
success: results.every((r) => r.success),
results,
variables: this.variables,
};
}
}
module.exports = {
FlowExecutor,
FLOW_TYPES,
};