Skip to content

Node

A node is the building block of the graph. It starts with a message to be sent to the user (action), then either it auto-advances to the next node, or waits for user input which can be validated and used to update the state before advancing to the next node.

Structure

Every node has four key properties:

  • id: A unique identifier for the node
  • action: What the node does (sends a message, updates state) — see Action
  • validate (optional): How to handle user input — see Validate
  • autoAdvance (optional): Whether to skip waiting for user input

Two Types of Nodes

1. Nodes That Wait for User Input

These nodes send a message and wait for the user to respond. The response is validated before advancing.

typescript
{
  id: 'ask_name',
  action: { message: "What's your name?" },
  validate: {
    rules: [{ regex: '\\w+', errorMessage: 'Please enter a valid name' }],
    answerKey: 'name'
  }
}

2. Nodes That Auto-Advance

These nodes execute their action and immediately move to the next node without waiting for input.

typescript
{
  id: 'welcome',
  action: (state) => ({
    messages: [`Welcome to our service, ${state.name}!`]
  }),
  autoAdvance: true
}

Complete Examples

Example 1: Simple Q&A Node

typescript
{
  id: 'ask_email',
  action: { message: 'What is your email address?' },
  validate: {
    rules: [
      {
        regex: '\\S+@\\S+\\.\\S+',
        errorMessage: 'Please enter a valid email address'
      }
    ],
    answerKey: 'email'
  }
}

Example 2: Dynamic Action with Function

typescript
{
  id: 'gold_price',
  action: async (state) => {
    const price = await getCurrentGoldPrice(); // could be an API call or some logic to fetch the price
    return {
      messages: [`Hello, ${state.name}! The current gold price is $${price}.`]
    };
  },
  autoAdvance: true
}

Example 3: Custom Function Validation

typescript
{
  id: 'ask_age',
  action: { message: 'How old are you?' },
  validate: (state, event) => {
    const age = Number(event.userMessage);

    if (!Number.isFinite(age) || age < 0) {
      return {
        isValid: false,
        errorMessage: 'Please enter a valid age'
      };
    }

    if (age < 18) {
      return {
        isValid: false,
        errorMessage: 'You must be 18 or older'
      };
    }

    return {
      isValid: true,
      state: { age, isAdult: true }
    };
  }
}

Example 4: Multi-Step Flow

typescript
const flow = new ChatGraph({
  id: 'user_onboarding',
  schema: UserSchema,
  nodes: [
    {
      id: 'greet',
      action: { message: "Hi! What's your name?" },
      validate: {
        rules: [{ regex: '\\w+', errorMessage: 'Enter a valid name' }],
        answerKey: 'name',
      },
    },
    {
      id: 'personalized_message',
      action: { message: 'Great to meet you, {{name}}!' },
      autoAdvance: true,
    },
    {
      id: 'ask_email',
      action: { message: 'What is your email?' },
      validate: {
        rules: [
          {
            regex: '\\S+@\\S+\\.\\S+',
            errorMessage: 'Enter a valid email',
          },
        ],
        answerKey: 'email',
      },
    },
    {
      id: 'confirmation',
      action: async (state) => {
        await sendEmail(state.email); // some async operation to send an email
        return {
          messages: [
            `Perfect! We have sent a confirmation email to ${state.email}.`,
          ],
        };
      },
      autoAdvance: true,
    },
  ],
  edges: [
    { from: START, to: 'greet' },
    { from: 'greet', to: 'personalized_message' },
    { from: 'personalized_message', to: 'ask_email' },
    { from: 'ask_email', to: 'confirmation' },
    { from: 'confirmation', to: END },
  ],
});

Example 5: Complex State Updates

typescript
{
  id: 'process_order',
  action: (state, event) => {
    const items = state.cartItems || [];
    const total = items.reduce((sum, item) => sum + item.price, 0);

    return {
      messages: [
        `Your order total is $${total.toFixed(2)}`,
        'Confirm with "yes" or "no"'
      ],
      orderTotal: total,
      orderDate: new Date().toISOString()
    };
  },
  validate: (state, event) => {
    const answer = event.userMessage.toLowerCase();

    if (answer === 'yes') {
      return {
        isValid: true,
        state: { orderConfirmed: true }
      };
    } else if (answer === 'no') {
      return {
        isValid: true,
        state: { orderConfirmed: false }
      };
    }

    return {
      isValid: false,
      errorMessage: 'Please answer "yes" or "no"'
    };
  }
}

Best Practices

  1. Use descriptive IDs: Choose clear, meaningful node identifiers
  2. Keep actions focused: Each node should do one thing well
  3. Validate thoroughly: Don't assume user input will be in the expected format
  4. Provide clear error messages: Help users understand what went wrong
  5. Use autoAdvance for info messages: If no input is needed, set autoAdvance: true
  6. Store validated data: Use answerKey or return state updates to persist user input

See Also

  • Action — Deep dive into node actions
  • Validate — Complete validation guide
  • Graph — How to connect nodes with edges