PYAE1994's picture
Upload folder using huggingface_hub
dd480ef verified
/**
* Workflow Graph Engine (IR Layer) β€” UPGRADED
* Builds an internal graph representation BEFORE compiling to n8n JSON
* Provider-agnostic LLM Gateway β€” NO direct OpenAI dependency
* Validates all node types against the real Node Registry before returning
*/
import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
import type { WorkflowIntent, WorkflowArchitecturePlan, WorkflowGraph } from '../types/workflow';
import { GRAPH_ENGINE_PROMPT } from '../prompts/graphEngine';
import { isValidNodeType, getRegistryNodeList } from '../knowledge/nodeRegistry';
export class WorkflowGraphEngine {
private llm: LLMGateway;
constructor(llm: LLMGateway) {
this.llm = llm;
}
async buildGraph(
userRequest: string,
intent: WorkflowIntent,
plan: WorkflowArchitecturePlan,
): Promise<WorkflowGraph> {
const registryList = getRegistryNodeList();
const graph = await this.llm.completeJSON<WorkflowGraph>([
{ role: 'system', content: GRAPH_ENGINE_PROMPT },
{
role: 'user',
content: `Build a WorkflowGraph IR for this workflow:
REQUEST: ${userRequest}
INTENT:
${JSON.stringify(intent, null, 2)}
ARCHITECTURE PLAN:
${JSON.stringify(plan, null, 2)}
AVAILABLE NODE TYPES (USE ONLY THESE β€” NO OTHERS):
${registryList}
CRITICAL RULES:
- Every node MUST have n8nNodeType from the list above ONLY
- NEVER invent node types not in the list
- Every non-trigger node MUST have at least one incoming edge
- Every node MUST have meaningful parameters β€” NO empty nodes
- DataContracts must define what JSON fields flow between nodes
- Use real expressions: {{$json?.field ?? ""}} β€” NOT placeholder text
- Position nodes in clean left-to-right layout (x increases by 220 per step)
- Return a complete WorkflowGraph JSON`,
},
], {
temperature: 0.0,
retries: 3,
});
return this.validateAndOptimizeGraph(graph);
}
/**
* Validates all node types against registry, removes unknown nodes,
* optimises graph structure (orphan removal, position cleanup)
*/
private validateAndOptimizeGraph(graph: WorkflowGraph): WorkflowGraph {
const unknownNodes: string[] = [];
// Filter out any hallucinated node types
const validNodes = graph.nodes.filter((node) => {
if (!isValidNodeType(node.n8nNodeType)) {
unknownNodes.push(`${node.label} (${node.n8nNodeType})`);
return false;
}
return true;
});
if (unknownNodes.length > 0) {
console.warn(
`[GraphEngine] REJECTED ${unknownNodes.length} unknown node type(s): ${unknownNodes.join(', ')}`,
);
}
const validNodeIds = new Set(validNodes.map((n) => n.id));
// Remove edges that reference removed nodes
const validEdges = graph.edges.filter(
(e) => validNodeIds.has(e.sourceNodeId) && validNodeIds.has(e.targetNodeId),
);
// Remove orphaned non-trigger nodes
const optimizedNodes = validNodes.filter((node) => {
const hasIncoming = validEdges.some((e) => e.targetNodeId === node.id);
const hasOutgoing = validEdges.some((e) => e.sourceNodeId === node.id);
const isTrigger = node.layer === 'trigger';
return isTrigger || hasIncoming || hasOutgoing;
});
return {
...graph,
nodes: optimizedNodes,
edges: validEdges,
metadata: {
...graph.metadata,
version: '2.0.0',
createdAt: new Date().toISOString(),
unknownNodesRejected: unknownNodes,
},
};
}
}