You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
4.3 KiB

11 months ago
  1. const { FLOW_TYPES } = require("./flowTypes");
  2. const executeApiCall = require("./executors/api-call");
  3. const executeWebsite = require("./executors/website");
  4. const executeFile = require("./executors/file");
  5. const executeCode = require("./executors/code");
  6. const executeLLMInstruction = require("./executors/llm-instruction");
  7. const executeWebScraping = require("./executors/web-scraping");
  8. const { Telemetry } = require("../../models/telemetry");
  9. class FlowExecutor {
  10. constructor() {
  11. this.variables = {};
  12. this.introspect = () => {}; // Default no-op introspect
  13. this.logger = console.info; // Default console.info
  14. }
  15. attachLogging(introspectFn, loggerFn) {
  16. this.introspect = introspectFn || (() => {});
  17. this.logger = loggerFn || console.info;
  18. }
  19. // Utility to replace variables in config
  20. replaceVariables(config) {
  21. const deepReplace = (obj) => {
  22. if (typeof obj === "string") {
  23. return obj.replace(/\${([^}]+)}/g, (match, varName) => {
  24. return this.variables[varName] !== undefined
  25. ? this.variables[varName]
  26. : match;
  27. });
  28. }
  29. if (Array.isArray(obj)) {
  30. return obj.map((item) => deepReplace(item));
  31. }
  32. if (obj && typeof obj === "object") {
  33. const result = {};
  34. for (const [key, value] of Object.entries(obj)) {
  35. result[key] = deepReplace(value);
  36. }
  37. return result;
  38. }
  39. return obj;
  40. };
  41. return deepReplace(config);
  42. }
  43. // Main execution method
  44. async executeStep(step) {
  45. const config = this.replaceVariables(step.config);
  46. let result;
  47. // Create execution context with introspect
  48. const context = {
  49. introspect: this.introspect,
  50. variables: this.variables,
  51. logger: this.logger,
  52. model: process.env.LLM_PROVIDER_MODEL || "gpt-4",
  53. provider: process.env.LLM_PROVIDER || "openai",
  54. };
  55. switch (step.type) {
  56. case FLOW_TYPES.START.type:
  57. // For start blocks, we just initialize variables if they're not already set
  58. if (config.variables) {
  59. config.variables.forEach((v) => {
  60. if (v.name && !this.variables[v.name]) {
  61. this.variables[v.name] = v.value || "";
  62. }
  63. });
  64. }
  65. result = this.variables;
  66. break;
  67. case FLOW_TYPES.API_CALL.type:
  68. result = await executeApiCall(config, context);
  69. break;
  70. case FLOW_TYPES.WEBSITE.type:
  71. result = await executeWebsite(config, context);
  72. break;
  73. case FLOW_TYPES.FILE.type:
  74. result = await executeFile(config, context);
  75. break;
  76. case FLOW_TYPES.CODE.type:
  77. result = await executeCode(config, context);
  78. break;
  79. case FLOW_TYPES.LLM_INSTRUCTION.type:
  80. result = await executeLLMInstruction(config, context);
  81. break;
  82. case FLOW_TYPES.WEB_SCRAPING.type:
  83. result = await executeWebScraping(config, context);
  84. break;
  85. default:
  86. throw new Error(`Unknown flow type: ${step.type}`);
  87. }
  88. // Store result in variable if specified
  89. if (config.resultVariable || config.responseVariable) {
  90. const varName = config.resultVariable || config.responseVariable;
  91. this.variables[varName] = result;
  92. }
  93. return result;
  94. }
  95. // Execute entire flow
  96. async executeFlow(
  97. flow,
  98. initialVariables = {},
  99. introspectFn = null,
  100. loggerFn = null
  101. ) {
  102. await Telemetry.sendTelemetry("agent_flow_execution_started");
  103. // Initialize variables with both initial values and any passed-in values
  104. this.variables = {
  105. ...(
  106. flow.config.steps.find((s) => s.type === "start")?.config?.variables ||
  107. []
  108. ).reduce((acc, v) => ({ ...acc, [v.name]: v.value }), {}),
  109. ...initialVariables, // This will override any default values with passed-in values
  110. };
  111. this.attachLogging(introspectFn, loggerFn);
  112. const results = [];
  113. for (const step of flow.config.steps) {
  114. try {
  115. const result = await this.executeStep(step);
  116. results.push({ success: true, result });
  117. } catch (error) {
  118. results.push({ success: false, error: error.message });
  119. break;
  120. }
  121. }
  122. return {
  123. success: results.every((r) => r.success),
  124. results,
  125. variables: this.variables,
  126. };
  127. }
  128. }
  129. module.exports = {
  130. FlowExecutor,
  131. FLOW_TYPES,
  132. };