source-code-guide.mdx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. ---
  2. title: Source Code Guide
  3. description: Analysis of FlowGram Runtime source code structure and implementation details
  4. ---
  5. # FlowGram Runtime Source Code Guide
  6. This document aims to help developers gain a deep understanding of FlowGram Runtime's source code structure and implementation details, providing guidance for customization and extension. Since FlowGram Runtime is positioned as a reference implementation rather than an SDK for direct use, understanding its internal implementation is particularly important for developers.
  7. ## Project Structure Overview
  8. ### Directory Structure
  9. The FlowGram Runtime JS project has the following directory structure:
  10. ```
  11. packages/runtime
  12. ├── js-core/ # Core runtime library
  13. │ ├── src/
  14. │ │ ├── application/ # Application layer, API implementation
  15. │ │ ├── domain/ # Domain layer, core business logic
  16. │ │ ├── infrastructure/ # Infrastructure layer, technical support
  17. │ │ ├── nodes/ # Node executor implementations
  18. │ │ └── index.ts # Entry point
  19. │ ├── package.json
  20. │ └── tsconfig.json
  21. ├── interface/ # Interface definitions
  22. │ ├── src/
  23. │ │ ├── api/ # API interface definitions
  24. │ │ ├── domain/ # Domain model interface definitions
  25. │ │ ├── engine/ # Engine interface definitions
  26. │ │ ├── node/ # Node interface definitions
  27. │ │ └── index.ts # Entry point
  28. │ ├── package.json
  29. │ └── tsconfig.json
  30. └── nodejs/ # NodeJS service implementation
  31. ├── src/
  32. │ ├── api/ # HTTP API implementation
  33. │ ├── server/ # Server implementation
  34. │ └── index.ts # Entry point
  35. ├── package.json
  36. └── tsconfig.json
  37. ```
  38. ### Module Organization
  39. FlowGram Runtime JS employs a modular design, primarily divided into three core modules:
  40. 1. **interface**: Defines the system's interfaces and data structures, serving as the foundation for other modules
  41. 2. **js-core**: Implements the core functionality of the workflow engine, including workflow parsing, node execution, state management, etc.
  42. 3. **nodejs**: Provides an HTTP API service based on NodeJS, allowing the workflow engine to be called via HTTP interfaces
  43. ### Dependencies
  44. The dependencies between modules are as follows:
  45. ```mermaid
  46. graph TD
  47. nodejs --> js-core
  48. js-core --> interface
  49. nodejs -.-> interface
  50. ```
  51. - **interface** is the foundational module with no dependencies on other modules
  52. - **js-core** depends on interfaces defined in the interface module
  53. - **nodejs** depends on functionality provided by the js-core module, while also using interface definitions from the interface module
  54. Key external dependencies include:
  55. - **TypeScript**: Provides type safety and object-oriented programming support
  56. - **LangChain**: Used for integrating large language models
  57. - **OpenAI API**: Provides the default implementation for LLM nodes
  58. - **fastify**: Used to implement HTTP API services
  59. - **tRPC**: Used for type-safe API definitions and calls
  60. ## Core Module Analysis
  61. ### js-core Module
  62. The js-core module is the core of FlowGram Runtime, implementing the main functionality of the workflow engine. This module adopts a Domain-Driven Design (DDD) architecture, divided into application, domain, and infrastructure layers.
  63. #### Application Layer
  64. The application layer is responsible for coordinating domain objects and implementing system use cases. Key files:
  65. - `application/workflow.ts`: Workflow application service, implementing workflow validation, execution, cancellation, querying, etc.
  66. - `application/api.ts`: API implementation, including TaskValidate, TaskRun, TaskResult, TaskReport, TaskCancel, etc.
  67. #### Domain Layer
  68. The domain layer contains core business logic and domain models. Key directories and files:
  69. - `domain/engine/`: Workflow execution engine, responsible for workflow parsing and execution
  70. - `engine.ts`: Workflow engine implementation, containing core logic for node execution, state management, etc.
  71. - `validator.ts`: Workflow validator, checking the validity of workflow definitions
  72. - `domain/document/`: Workflow document model, representing the structure of workflows
  73. - `workflow.ts`: Workflow definition model
  74. - `node.ts`: Node definition model
  75. - `edge.ts`: Edge definition model
  76. - `domain/executor/`: Node executors, responsible for executing specific node logic
  77. - `executor.ts`: Node executor base class and factory
  78. - `domain/variable/`: Variable management, handling variable storage and references in workflows
  79. - `manager.ts`: Variable manager, responsible for variable storage, retrieval, and parsing
  80. - `store.ts`: Variable storage, providing variable persistence
  81. - `domain/status/`: Status management, tracking the execution status of workflows and nodes
  82. - `center.ts`: Status center, managing workflow and node statuses
  83. - `domain/snapshot/`: Snapshot management, recording intermediate states of workflow execution
  84. - `center.ts`: Snapshot center, managing node execution snapshots
  85. - `domain/report/`: Report generation, collecting detailed information on workflow execution
  86. - `center.ts`: Report center, generating workflow execution reports
  87. #### Infrastructure Layer
  88. The infrastructure layer provides technical support, including logging, events, containers, etc. Key files:
  89. - `infrastructure/logger.ts`: Logging service, providing logging functionality
  90. - `infrastructure/event.ts`: Event service, providing event publishing and subscription functionality
  91. - `infrastructure/container.ts`: Dependency injection container, managing object creation and lifecycle
  92. - `infrastructure/error.ts`: Error handling, defining error types and handling methods in the system
  93. #### Node Executors
  94. The nodes directory contains executor implementations for various node types. Key files:
  95. - `nodes/start.ts`: Start node executor
  96. - `nodes/end.ts`: End node executor
  97. - `nodes/llm.ts`: LLM node executor, integrating large language models
  98. - `nodes/condition.ts`: Condition node executor, implementing conditional branching
  99. - `nodes/loop.ts`: Loop node executor, implementing loop logic
  100. ### interface Module
  101. The interface module defines the system's interfaces and data structures, serving as the foundation for other modules. Key directories and files:
  102. - `api/`: API interface definitions
  103. - `api.ts`: Defines the API interfaces provided by the system
  104. - `types.ts`: API-related data type definitions
  105. - `domain/`: Domain model interface definitions
  106. - `document.ts`: Workflow document-related interfaces
  107. - `engine.ts`: Workflow engine-related interfaces
  108. - `executor.ts`: Node executor-related interfaces
  109. - `variable.ts`: Variable management-related interfaces
  110. - `status.ts`: Status management-related interfaces
  111. - `snapshot.ts`: Snapshot management-related interfaces
  112. - `report.ts`: Report generation-related interfaces
  113. - `engine/`: Engine interface definitions
  114. - `types.ts`: Engine-related data type definitions
  115. - `node/`: Node interface definitions
  116. - `types.ts`: Node-related data type definitions
  117. ### nodejs Module
  118. The nodejs module provides an HTTP API service based on NodeJS, allowing the workflow engine to be called via HTTP interfaces. Key directories and files:
  119. - `api/`: HTTP API implementation
  120. - `router.ts`: API route definitions
  121. - `handlers.ts`: API handler functions
  122. - `server/`: Server implementation
  123. - `server.ts`: HTTP server implementation
  124. - `config.ts`: Server configuration
  125. ## Key Implementation Details
  126. ### Workflow Engine
  127. The workflow engine is the core of FlowGram Runtime, responsible for workflow parsing and execution. Its main implementation is located in `js-core/src/domain/engine/engine.ts`.
  128. The main functions of the workflow engine include:
  129. 1. **Workflow Parsing**: Converting workflow definitions into internal models
  130. 2. **Node Scheduling**: Determining the execution order of nodes based on edges defined in the workflow
  131. 3. **Node Execution**: Calling node executors to execute node logic
  132. 4. **State Management**: Tracking the execution status of workflows and nodes
  133. 5. **Variable Management**: Handling data transfer between nodes
  134. 6. **Error Handling**: Managing exceptions during execution
  135. Key code snippet:
  136. ```typescript
  137. // Core method for workflow execution
  138. public async run(params: RunParams): Promise<RunResult> {
  139. const { schema, inputs, options } = params;
  140. // Create workflow context
  141. const context = this.createContext(schema, inputs, options);
  142. try {
  143. // Initialize workflow
  144. await this.initialize(context);
  145. // Execute workflow
  146. await this.execute(context);
  147. // Get workflow result
  148. const result = await this.getResult(context);
  149. return {
  150. status: 'success',
  151. outputs: result
  152. };
  153. } catch (error) {
  154. // Error handling
  155. return {
  156. status: 'fail',
  157. error: error.message
  158. };
  159. }
  160. }
  161. // Execute workflow
  162. private async execute(context: IContext): Promise<void> {
  163. // Get start node
  164. const startNode = context.workflow.getStartNode();
  165. // Start execution from the start node
  166. await this.executeNode({ context, node: startNode });
  167. // Wait for all nodes to complete execution
  168. await this.waitForCompletion(context);
  169. }
  170. // Execute node
  171. public async executeNode(params: { context: IContext; node: INode }): Promise<void> {
  172. const { context, node } = params;
  173. // Get node executor
  174. const executor = this.getExecutor(node.type);
  175. // Prepare node inputs
  176. const inputs = await this.prepareInputs(context, node);
  177. // Execute node
  178. const result = await executor.execute({
  179. node,
  180. inputs,
  181. context
  182. });
  183. // Process node outputs
  184. await this.processOutputs(context, node, result.outputs);
  185. // Schedule next nodes
  186. await this.scheduleNextNodes(context, node);
  187. }
  188. ```
  189. ### Node Executors
  190. Node executors are responsible for executing the specific logic of nodes. Each node type has a corresponding executor implementation located in the `js-core/src/nodes/` directory.
  191. The basic interface for node executors is defined in `interface/src/domain/executor.ts`:
  192. ```typescript
  193. export interface INodeExecutor {
  194. type: string;
  195. execute(context: ExecutionContext): Promise<ExecutionResult>;
  196. }
  197. ```
  198. Taking the LLM node executor as an example, its implementation is in `js-core/src/nodes/llm.ts`:
  199. ```typescript
  200. export class LLMExecutor implements INodeExecutor {
  201. public type = 'llm';
  202. public async execute(context: ExecutionContext): Promise<ExecutionResult> {
  203. const inputs = context.inputs as LLMExecutorInputs;
  204. // Create LLM provider
  205. const provider = this.createProvider(inputs);
  206. // Prepare prompts
  207. const systemPrompt = inputs.systemPrompt || '';
  208. const userPrompt = inputs.prompt || '';
  209. // Call LLM
  210. const result = await provider.call({
  211. systemPrompt,
  212. userPrompt,
  213. options: {
  214. temperature: inputs.temperature
  215. }
  216. });
  217. // Return result
  218. return {
  219. outputs: {
  220. result: result.content
  221. }
  222. };
  223. }
  224. private createProvider(inputs: LLMExecutorInputs): ILLMProvider {
  225. // Create different providers based on model name
  226. if (inputs.modelName.startsWith('gpt-')) {
  227. return new OpenAIProvider({
  228. apiKey: inputs.apiKey,
  229. apiHost: inputs.apiHost,
  230. modelName: inputs.modelName
  231. });
  232. }
  233. throw new Error(`Unsupported model: ${inputs.modelName}`);
  234. }
  235. }
  236. ```
  237. ### Variable Management
  238. Variable management is an important part of workflow execution, responsible for handling data transfer between nodes. Its main implementation is in the `js-core/src/domain/variable/` directory.
  239. The core of variable management is the variable manager and variable storage:
  240. - **Variable Manager**: Responsible for parsing, getting, and setting variables
  241. - **Variable Storage**: Provides persistent storage for variables
  242. Key code snippet:
  243. ```typescript
  244. // Variable manager
  245. export class VariableManager implements IVariableManager {
  246. constructor(private store: IVariableStore) {}
  247. // Resolve variable references
  248. public async resolve(ref: ValueSchema, scope?: string): Promise<any> {
  249. if (ref.type === 'constant') {
  250. return ref.content;
  251. } else if (ref.type === 'ref') {
  252. const path = ref.content as string[];
  253. return this.get(path, scope);
  254. }
  255. throw new Error(`Unsupported value type: ${ref.type}`);
  256. }
  257. // Get variable value
  258. public async get(path: string[], scope?: string): Promise<any> {
  259. const [nodeID, key, ...rest] = path;
  260. const value = await this.store.get(nodeID, key, scope);
  261. if (rest.length === 0) {
  262. return value;
  263. }
  264. // Handle nested properties
  265. return this.getNestedProperty(value, rest);
  266. }
  267. // Set variable value
  268. public async set(nodeID: string, key: string, value: any, scope?: string): Promise<void> {
  269. await this.store.set(nodeID, key, value, scope);
  270. }
  271. }
  272. ```
  273. ### State Storage
  274. State storage is responsible for managing the execution state of workflows and nodes. Its main implementation is in the `js-core/src/domain/status/` and `js-core/src/domain/snapshot/` directories.
  275. The core components of state management include:
  276. - **Status Center**: Manages the status of workflows and nodes
  277. - **Snapshot Center**: Records snapshots of node execution
  278. - **Report Center**: Generates workflow execution reports
  279. Key code snippet:
  280. ```typescript
  281. // Status center
  282. export class StatusCenter implements IStatusCenter {
  283. private workflowStatus: Record<string, WorkflowStatus> = {};
  284. private nodeStatus: Record<string, Record<string, NodeStatus>> = {};
  285. // Set workflow status
  286. public setWorkflowStatus(workflowID: string, status: WorkflowStatus): void {
  287. this.workflowStatus[workflowID] = status;
  288. }
  289. // Get workflow status
  290. public getWorkflowStatus(workflowID: string): WorkflowStatus {
  291. return this.workflowStatus[workflowID] || 'idle';
  292. }
  293. // Set node status
  294. public setNodeStatus(workflowID: string, nodeID: string, status: NodeStatus): void {
  295. if (!this.nodeStatus[workflowID]) {
  296. this.nodeStatus[workflowID] = {};
  297. }
  298. this.nodeStatus[workflowID][nodeID] = status;
  299. }
  300. // Get node status
  301. public getNodeStatus(workflowID: string, nodeID: string): NodeStatus {
  302. return this.nodeStatus[workflowID]?.[nodeID] || 'idle';
  303. }
  304. }
  305. ```
  306. ## Design Patterns and Architectural Decisions
  307. ### Domain-Driven Design
  308. FlowGram Runtime adopts a Domain-Driven Design (DDD) architecture, dividing the system into application, domain, and infrastructure layers. This architecture helps separate concerns, making the code more modular and maintainable.
  309. Key domain concepts include:
  310. - **Workflow**: Represents a complete workflow definition
  311. - **Node**: Basic execution unit in a workflow
  312. - **Edge**: Line connecting nodes, representing execution flow
  313. - **Execution Context**: Environment for workflow execution
  314. - **Variable**: Data in the workflow execution process
  315. ### Factory Pattern
  316. FlowGram Runtime uses the Factory pattern to create node executors, enabling the system to dynamically create corresponding executors based on node types.
  317. ```typescript
  318. // Node executor factory
  319. export class NodeExecutorFactory implements INodeExecutorFactory {
  320. private executors: Record<string, INodeExecutor> = {};
  321. // Register node executor
  322. public register(executor: INodeExecutor): void {
  323. this.executors[executor.type] = executor;
  324. }
  325. // Create node executor
  326. public create(type: string): INodeExecutor {
  327. const executor = this.executors[type];
  328. if (!executor) {
  329. throw new Error(`No executor registered for node type: ${type}`);
  330. }
  331. return executor;
  332. }
  333. }
  334. ```
  335. ### Strategy Pattern
  336. FlowGram Runtime uses the Strategy pattern to handle execution logic for different types of nodes, with each node type having a corresponding execution strategy.
  337. ```typescript
  338. // Node executor interface (strategy interface)
  339. export interface INodeExecutor {
  340. type: string;
  341. execute(context: ExecutionContext): Promise<ExecutionResult>;
  342. }
  343. // Concrete strategy implementation
  344. export class StartExecutor implements INodeExecutor {
  345. public type = 'start';
  346. public async execute(context: ExecutionContext): Promise<ExecutionResult> {
  347. // Start node execution logic
  348. }
  349. }
  350. export class EndExecutor implements INodeExecutor {
  351. public type = 'end';
  352. public async execute(context: ExecutionContext): Promise<ExecutionResult> {
  353. // End node execution logic
  354. }
  355. }
  356. ```
  357. ### Observer Pattern
  358. FlowGram Runtime uses the Observer pattern to implement the event system, allowing components to publish and subscribe to events.
  359. ```typescript
  360. // Event emitter
  361. export class EventEmitter implements IEventEmitter {
  362. private listeners: Record<string, Function[]> = {};
  363. // Subscribe to event
  364. public on(event: string, listener: Function): void {
  365. if (!this.listeners[event]) {
  366. this.listeners[event] = [];
  367. }
  368. this.listeners[event].push(listener);
  369. }
  370. // Publish event
  371. public emit(event: string, ...args: any[]): void {
  372. const eventListeners = this.listeners[event];
  373. if (eventListeners) {
  374. for (const listener of eventListeners) {
  375. listener(...args);
  376. }
  377. }
  378. }
  379. }
  380. ```
  381. ### Dependency Injection
  382. FlowGram Runtime uses Dependency Injection to manage dependencies between components, making components more loosely coupled and testable.
  383. ```typescript
  384. // Dependency injection container
  385. export class Container {
  386. private static _instance: Container;
  387. private registry: Map<any, any> = new Map();
  388. public static get instance(): Container {
  389. if (!Container._instance) {
  390. Container._instance = new Container();
  391. }
  392. return Container._instance;
  393. }
  394. // Register service
  395. public register<T>(token: any, instance: T): void {
  396. this.registry.set(token, instance);
  397. }
  398. // Get service
  399. public resolve<T>(token: any): T {
  400. const instance = this.registry.get(token);
  401. if (!instance) {
  402. throw new Error(`No instance registered for token: ${token}`);
  403. }
  404. return instance;
  405. }
  406. }
  407. ```