Skip to content

Conversation

@SnakeO
Copy link

@SnakeO SnakeO commented Aug 28, 2025

Summary

This PR introduces string-based node keys to the Workflow system, allowing developers to use custom string identifiers for nodes instead of being limited to class names. This enables
workflows to have multiple instances of the same node class - a critical feature for building complex, reusable workflows.

Key Features

  • String-based node keys: Use descriptive string keys like 'add1', 'multiply_first' instead of class names
  • Multiple node instances: Instantiate the same node class multiple times with different parameters
  • Full backward compatibility: Existing workflows using class names continue to work without modification
  • Smart detection: Automatically detects whether nodes use string keys or class names
  • Enhanced Mermaid export: Diagram generation handles both string keys and class names

Technical Changes

  • Modified Workflow::getNodes() to detect and handle associative arrays with string keys
  • Updated Workflow::addNode() to accept an optional string key parameter (defaults to class name)
  • Enhanced Workflow::addNodes() to handle both indexed and associative arrays
  • Updated MermaidExporter to intelligently handle both class names and string keys
  • Added comprehensive test coverage in WorkflowStringKeysTest.php

Backward Compatibility

All existing workflows continue to function exactly as before. The system automatically detects the array format:

  • Indexed arrays (numeric keys): Uses class names as keys (existing behavior)
  • Associative arrays (string keys): Uses provided keys directly (new behavior)

Example Usage

Basic Calculator Workflow with String Keys

<?php

use NeuronAI\Workflow\Edge;
use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\Workflow;
use NeuronAI\Workflow\WorkflowState;

// Reusable node classes
class AddNode extends Node
{
    public function __construct(private int $value) {}

    public function run(WorkflowState $state): WorkflowState
    {
        $current = $state->get('value', 0);
        $state->set('value', $current + $this->value);
        return $state;
    }
}

class MultiplyNode extends Node
{
    public function __construct(private int $value) {}

    public function run(WorkflowState $state): WorkflowState
    {
        $current = $state->get('value', 0);
        $state->set('value', $current * $this->value);
        return $state;
    }
}

class SubtractNode extends Node
{
    public function __construct(private int $value) {}

    public function run(WorkflowState $state): WorkflowState
    {
        $current = $state->get('value', 0);
        $state->set('value', $current - $this->value);
        return $state;
    }
}

// Workflow that calculates: ((value + 1) * 3) * 3) - 1
class CalculatorWorkflow extends Workflow
{
    public function nodes(): array
    {
        return [
            'add1' => new AddNode(1),
            'multiply3_first' => new MultiplyNode(3),
            'multiply3_second' => new MultiplyNode(3),  // Same class, different instance!
            'sub1' => new SubtractNode(1),
            'finish' => new FinishNode()
        ];
    }

    public function edges(): array
    {
        return [
            new Edge('add1', 'multiply3_first'),
            new Edge('multiply3_first', 'multiply3_second'),
            new Edge('multiply3_second', 'sub1'),
            new Edge('sub1', 'finish')
        ];
    }

    protected function start(): string
    {
        return 'add1';
    }

    protected function end(): array
    {
        return ['finish'];
    }
}

Benefits

  1. Reusability: Use the same node class multiple times with different configurations
  2. Readability: String keys make workflow definitions more self-documenting
  3. No Breaking Changes: Existing workflows continue to work without modification

Testing

  • All 17 existing workflow tests pass unchanged
  • Added 6 new tests specifically for string-key functionality

Migration Guide

No migration needed! Existing workflows continue to work as-is. To adopt string keys:

  1. Change your nodes() method to return an associative array
  2. Use the string keys in your edges() definitions
  3. Reference nodes by their string keys in start() and end() methods

Related Issues

Addresses the limitation where workflows couldn't have multiple instances of the same node class

Enables workflows to use custom string keys for nodes instead of only class names,
allowing multiple instances of the same node class. Maintains full backward
compatibility with existing class-name-based approach.

Changes:
- Modified Workflow::getNodes() to detect and handle associative arrays
- Updated Workflow::addNode() to accept optional string key parameter
- Enhanced Workflow::addNodes() to handle both indexed and associative arrays
- Updated MermaidExporter to handle both class names and string keys
- Added comprehensive test coverage for new functionality

Benefits:
- Multiple instances of same node class can now be used in a workflow
- Cleaner, more readable edge definitions using descriptive keys
- Full backward compatibility - existing workflows continue to work unchanged
- Flexible usage in both declarative and programmatic workflows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@SnakeO
Copy link
Author

SnakeO commented Aug 28, 2025

@ilvalerione To get a feel for Workflows I wanted to make a simple calculator. I quickly realized how difficult it was to do because edges are defined by classname, we can't re-use nodes. This PR allows the node/edge definition to work with user-defined keys rather than hardcoded classnames. It's backwards compatible -- I hope this can be integrated into the package.

@ilvalerione
Copy link
Contributor

Hi @SnakeO
Thank you for posting this great proposal. I will merge it thanks to the backward compatibility.

But, consider we are about to release the Neuron v2 that will include a completely re-architected version of the Workflow module where the Edge class was definitely removed.

You can get an overview of what we are cooking here: https://github.com/inspector-apm/neuron-ai/tree/2.x/examples/workflow

The new Workflow is event driven, so you don't need to register nodes and edges separately, and support streaming. More in the next weeks.

@ilvalerione ilvalerione merged commit c855c22 into neuron-core:1.x Aug 30, 2025
@SnakeO
Copy link
Author

SnakeO commented Sep 3, 2025

@ilvalerione Thank you for merging in my PR. I would like to ask, after looking at Neuron v2 workflows: what inspired you to move to an event-based system?

For our use case, Neruon v1 Workflows are a better fit because we want to implement a drag-and-drop UI for building them out, and this is best accomplished with a finite state machine where the full flow is concretely defined. Would it be possible to have a feature where we can select between the two (event driven vs edge driven)?

@ilvalerione
Copy link
Contributor

ilvalerione commented Sep 4, 2025

The goal for the framework development path is to costantly improve the development experience. In this perspective int the graph-based solution there was too much overhead forcing developers to constantly switch between nodes and edges to orchestrate the execution flow.

Letting nodes accept and emit events is much more declarative, and helps you to keep all the logic into just the nodes.

Look at the examples below we were able to create in just a couple of days with this new architecture:

For what concern the creation of a drag-and-drop UI there are other examples of this kind of tools for similar architectures, like the llamaindex flow-maker: https://github.com/run-llama/flow-maker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants