| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- ---
- title: Source Code Guide
- description: Analysis of FlowGram Runtime source code structure and implementation details
- ---
- # FlowGram Runtime Source Code Guide
- 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.
- ## Project Structure Overview
- ### Directory Structure
- The FlowGram Runtime JS project has the following directory structure:
- ```
- packages/runtime
- ├── js-core/ # Core runtime library
- │ ├── src/
- │ │ ├── application/ # Application layer, API implementation
- │ │ ├── domain/ # Domain layer, core business logic
- │ │ ├── infrastructure/ # Infrastructure layer, technical support
- │ │ ├── nodes/ # Node executor implementations
- │ │ └── index.ts # Entry point
- │ ├── package.json
- │ └── tsconfig.json
- ├── interface/ # Interface definitions
- │ ├── src/
- │ │ ├── api/ # API interface definitions
- │ │ ├── domain/ # Domain model interface definitions
- │ │ ├── engine/ # Engine interface definitions
- │ │ ├── node/ # Node interface definitions
- │ │ └── index.ts # Entry point
- │ ├── package.json
- │ └── tsconfig.json
- └── nodejs/ # NodeJS service implementation
- ├── src/
- │ ├── api/ # HTTP API implementation
- │ ├── server/ # Server implementation
- │ └── index.ts # Entry point
- ├── package.json
- └── tsconfig.json
- ```
- ### Module Organization
- FlowGram Runtime JS employs a modular design, primarily divided into three core modules:
- 1. **interface**: Defines the system's interfaces and data structures, serving as the foundation for other modules
- 2. **js-core**: Implements the core functionality of the workflow engine, including workflow parsing, node execution, state management, etc.
- 3. **nodejs**: Provides an HTTP API service based on NodeJS, allowing the workflow engine to be called via HTTP interfaces
- ### Dependencies
- The dependencies between modules are as follows:
- ```mermaid
- graph TD
- nodejs --> js-core
- js-core --> interface
- nodejs -.-> interface
- ```
- - **interface** is the foundational module with no dependencies on other modules
- - **js-core** depends on interfaces defined in the interface module
- - **nodejs** depends on functionality provided by the js-core module, while also using interface definitions from the interface module
- Key external dependencies include:
- - **TypeScript**: Provides type safety and object-oriented programming support
- - **LangChain**: Used for integrating large language models
- - **OpenAI API**: Provides the default implementation for LLM nodes
- - **fastify**: Used to implement HTTP API services
- - **tRPC**: Used for type-safe API definitions and calls
- ## Core Module Analysis
- ### js-core Module
- 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.
- #### Application Layer
- The application layer is responsible for coordinating domain objects and implementing system use cases. Key files:
- - `application/workflow.ts`: Workflow application service, implementing workflow validation, execution, cancellation, querying, etc.
- - `application/api.ts`: API implementation, including TaskValidate, TaskRun, TaskResult, TaskReport, TaskCancel, etc.
- #### Domain Layer
- The domain layer contains core business logic and domain models. Key directories and files:
- - `domain/engine/`: Workflow execution engine, responsible for workflow parsing and execution
- - `engine.ts`: Workflow engine implementation, containing core logic for node execution, state management, etc.
- - `validator.ts`: Workflow validator, checking the validity of workflow definitions
- - `domain/document/`: Workflow document model, representing the structure of workflows
- - `workflow.ts`: Workflow definition model
- - `node.ts`: Node definition model
- - `edge.ts`: Edge definition model
- - `domain/executor/`: Node executors, responsible for executing specific node logic
- - `executor.ts`: Node executor base class and factory
- - `domain/variable/`: Variable management, handling variable storage and references in workflows
- - `manager.ts`: Variable manager, responsible for variable storage, retrieval, and parsing
- - `store.ts`: Variable storage, providing variable persistence
- - `domain/status/`: Status management, tracking the execution status of workflows and nodes
- - `center.ts`: Status center, managing workflow and node statuses
- - `domain/snapshot/`: Snapshot management, recording intermediate states of workflow execution
- - `center.ts`: Snapshot center, managing node execution snapshots
- - `domain/report/`: Report generation, collecting detailed information on workflow execution
- - `center.ts`: Report center, generating workflow execution reports
- #### Infrastructure Layer
- The infrastructure layer provides technical support, including logging, events, containers, etc. Key files:
- - `infrastructure/logger.ts`: Logging service, providing logging functionality
- - `infrastructure/event.ts`: Event service, providing event publishing and subscription functionality
- - `infrastructure/container.ts`: Dependency injection container, managing object creation and lifecycle
- - `infrastructure/error.ts`: Error handling, defining error types and handling methods in the system
- #### Node Executors
- The nodes directory contains executor implementations for various node types. Key files:
- - `nodes/start.ts`: Start node executor
- - `nodes/end.ts`: End node executor
- - `nodes/llm.ts`: LLM node executor, integrating large language models
- - `nodes/condition.ts`: Condition node executor, implementing conditional branching
- - `nodes/loop.ts`: Loop node executor, implementing loop logic
- ### interface Module
- The interface module defines the system's interfaces and data structures, serving as the foundation for other modules. Key directories and files:
- - `api/`: API interface definitions
- - `api.ts`: Defines the API interfaces provided by the system
- - `types.ts`: API-related data type definitions
- - `domain/`: Domain model interface definitions
- - `document.ts`: Workflow document-related interfaces
- - `engine.ts`: Workflow engine-related interfaces
- - `executor.ts`: Node executor-related interfaces
- - `variable.ts`: Variable management-related interfaces
- - `status.ts`: Status management-related interfaces
- - `snapshot.ts`: Snapshot management-related interfaces
- - `report.ts`: Report generation-related interfaces
- - `engine/`: Engine interface definitions
- - `types.ts`: Engine-related data type definitions
- - `node/`: Node interface definitions
- - `types.ts`: Node-related data type definitions
- ### nodejs Module
- 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:
- - `api/`: HTTP API implementation
- - `router.ts`: API route definitions
- - `handlers.ts`: API handler functions
- - `server/`: Server implementation
- - `server.ts`: HTTP server implementation
- - `config.ts`: Server configuration
- ## Key Implementation Details
- ### Workflow Engine
- 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`.
- The main functions of the workflow engine include:
- 1. **Workflow Parsing**: Converting workflow definitions into internal models
- 2. **Node Scheduling**: Determining the execution order of nodes based on edges defined in the workflow
- 3. **Node Execution**: Calling node executors to execute node logic
- 4. **State Management**: Tracking the execution status of workflows and nodes
- 5. **Variable Management**: Handling data transfer between nodes
- 6. **Error Handling**: Managing exceptions during execution
- Key code snippet:
- ```typescript
- // Core method for workflow execution
- public async run(params: RunParams): Promise<RunResult> {
- const { schema, inputs, options } = params;
- // Create workflow context
- const context = this.createContext(schema, inputs, options);
- try {
- // Initialize workflow
- await this.initialize(context);
- // Execute workflow
- await this.execute(context);
- // Get workflow result
- const result = await this.getResult(context);
- return {
- status: 'success',
- outputs: result
- };
- } catch (error) {
- // Error handling
- return {
- status: 'fail',
- error: error.message
- };
- }
- }
- // Execute workflow
- private async execute(context: IContext): Promise<void> {
- // Get start node
- const startNode = context.workflow.getStartNode();
- // Start execution from the start node
- await this.executeNode({ context, node: startNode });
- // Wait for all nodes to complete execution
- await this.waitForCompletion(context);
- }
- // Execute node
- public async executeNode(params: { context: IContext; node: INode }): Promise<void> {
- const { context, node } = params;
- // Get node executor
- const executor = this.getExecutor(node.type);
- // Prepare node inputs
- const inputs = await this.prepareInputs(context, node);
- // Execute node
- const result = await executor.execute({
- node,
- inputs,
- context
- });
- // Process node outputs
- await this.processOutputs(context, node, result.outputs);
- // Schedule next nodes
- await this.scheduleNextNodes(context, node);
- }
- ```
- ### Node Executors
- 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.
- The basic interface for node executors is defined in `interface/src/domain/executor.ts`:
- ```typescript
- export interface INodeExecutor {
- type: string;
- execute(context: ExecutionContext): Promise<ExecutionResult>;
- }
- ```
- Taking the LLM node executor as an example, its implementation is in `js-core/src/nodes/llm.ts`:
- ```typescript
- export class LLMExecutor implements INodeExecutor {
- public type = 'llm';
- public async execute(context: ExecutionContext): Promise<ExecutionResult> {
- const inputs = context.inputs as LLMExecutorInputs;
- // Create LLM provider
- const provider = this.createProvider(inputs);
- // Prepare prompts
- const systemPrompt = inputs.systemPrompt || '';
- const userPrompt = inputs.prompt || '';
- // Call LLM
- const result = await provider.call({
- systemPrompt,
- userPrompt,
- options: {
- temperature: inputs.temperature
- }
- });
- // Return result
- return {
- outputs: {
- result: result.content
- }
- };
- }
- private createProvider(inputs: LLMExecutorInputs): ILLMProvider {
- // Create different providers based on model name
- if (inputs.modelName.startsWith('gpt-')) {
- return new OpenAIProvider({
- apiKey: inputs.apiKey,
- apiHost: inputs.apiHost,
- modelName: inputs.modelName
- });
- }
- throw new Error(`Unsupported model: ${inputs.modelName}`);
- }
- }
- ```
- ### Variable Management
- 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.
- The core of variable management is the variable manager and variable storage:
- - **Variable Manager**: Responsible for parsing, getting, and setting variables
- - **Variable Storage**: Provides persistent storage for variables
- Key code snippet:
- ```typescript
- // Variable manager
- export class VariableManager implements IVariableManager {
- constructor(private store: IVariableStore) {}
- // Resolve variable references
- public async resolve(ref: ValueSchema, scope?: string): Promise<any> {
- if (ref.type === 'constant') {
- return ref.content;
- } else if (ref.type === 'ref') {
- const path = ref.content as string[];
- return this.get(path, scope);
- }
- throw new Error(`Unsupported value type: ${ref.type}`);
- }
- // Get variable value
- public async get(path: string[], scope?: string): Promise<any> {
- const [nodeID, key, ...rest] = path;
- const value = await this.store.get(nodeID, key, scope);
- if (rest.length === 0) {
- return value;
- }
- // Handle nested properties
- return this.getNestedProperty(value, rest);
- }
- // Set variable value
- public async set(nodeID: string, key: string, value: any, scope?: string): Promise<void> {
- await this.store.set(nodeID, key, value, scope);
- }
- }
- ```
- ### State Storage
- 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.
- The core components of state management include:
- - **Status Center**: Manages the status of workflows and nodes
- - **Snapshot Center**: Records snapshots of node execution
- - **Report Center**: Generates workflow execution reports
- Key code snippet:
- ```typescript
- // Status center
- export class StatusCenter implements IStatusCenter {
- private workflowStatus: Record<string, WorkflowStatus> = {};
- private nodeStatus: Record<string, Record<string, NodeStatus>> = {};
- // Set workflow status
- public setWorkflowStatus(workflowID: string, status: WorkflowStatus): void {
- this.workflowStatus[workflowID] = status;
- }
- // Get workflow status
- public getWorkflowStatus(workflowID: string): WorkflowStatus {
- return this.workflowStatus[workflowID] || 'idle';
- }
- // Set node status
- public setNodeStatus(workflowID: string, nodeID: string, status: NodeStatus): void {
- if (!this.nodeStatus[workflowID]) {
- this.nodeStatus[workflowID] = {};
- }
- this.nodeStatus[workflowID][nodeID] = status;
- }
- // Get node status
- public getNodeStatus(workflowID: string, nodeID: string): NodeStatus {
- return this.nodeStatus[workflowID]?.[nodeID] || 'idle';
- }
- }
- ```
- ## Design Patterns and Architectural Decisions
- ### Domain-Driven Design
- 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.
- Key domain concepts include:
- - **Workflow**: Represents a complete workflow definition
- - **Node**: Basic execution unit in a workflow
- - **Edge**: Line connecting nodes, representing execution flow
- - **Execution Context**: Environment for workflow execution
- - **Variable**: Data in the workflow execution process
- ### Factory Pattern
- FlowGram Runtime uses the Factory pattern to create node executors, enabling the system to dynamically create corresponding executors based on node types.
- ```typescript
- // Node executor factory
- export class NodeExecutorFactory implements INodeExecutorFactory {
- private executors: Record<string, INodeExecutor> = {};
- // Register node executor
- public register(executor: INodeExecutor): void {
- this.executors[executor.type] = executor;
- }
- // Create node executor
- public create(type: string): INodeExecutor {
- const executor = this.executors[type];
- if (!executor) {
- throw new Error(`No executor registered for node type: ${type}`);
- }
- return executor;
- }
- }
- ```
- ### Strategy Pattern
- FlowGram Runtime uses the Strategy pattern to handle execution logic for different types of nodes, with each node type having a corresponding execution strategy.
- ```typescript
- // Node executor interface (strategy interface)
- export interface INodeExecutor {
- type: string;
- execute(context: ExecutionContext): Promise<ExecutionResult>;
- }
- // Concrete strategy implementation
- export class StartExecutor implements INodeExecutor {
- public type = 'start';
- public async execute(context: ExecutionContext): Promise<ExecutionResult> {
- // Start node execution logic
- }
- }
- export class EndExecutor implements INodeExecutor {
- public type = 'end';
- public async execute(context: ExecutionContext): Promise<ExecutionResult> {
- // End node execution logic
- }
- }
- ```
- ### Observer Pattern
- FlowGram Runtime uses the Observer pattern to implement the event system, allowing components to publish and subscribe to events.
- ```typescript
- // Event emitter
- export class EventEmitter implements IEventEmitter {
- private listeners: Record<string, Function[]> = {};
- // Subscribe to event
- public on(event: string, listener: Function): void {
- if (!this.listeners[event]) {
- this.listeners[event] = [];
- }
- this.listeners[event].push(listener);
- }
- // Publish event
- public emit(event: string, ...args: any[]): void {
- const eventListeners = this.listeners[event];
- if (eventListeners) {
- for (const listener of eventListeners) {
- listener(...args);
- }
- }
- }
- }
- ```
- ### Dependency Injection
- FlowGram Runtime uses Dependency Injection to manage dependencies between components, making components more loosely coupled and testable.
- ```typescript
- // Dependency injection container
- export class Container {
- private static _instance: Container;
- private registry: Map<any, any> = new Map();
- public static get instance(): Container {
- if (!Container._instance) {
- Container._instance = new Container();
- }
- return Container._instance;
- }
- // Register service
- public register<T>(token: any, instance: T): void {
- this.registry.set(token, instance);
- }
- // Get service
- public resolve<T>(token: any): T {
- const instance = this.registry.get(token);
- if (!instance) {
- throw new Error(`No instance registered for token: ${token}`);
- }
- return instance;
- }
- }
- ```
|