Files
langgraph/spec/JavaChannels.md
T
2025-03-01 18:51:13 -08:00

20 KiB

Java Channel Interfaces

This document defines the Java interfaces for the channel system of LangGraph, closely aligned with the Python implementation.

Channel Interface

The base interface for all channels, providing methods for getting values, applying updates, and checkpoint management.

package com.langgraph.channels;

/**
 * Interface for communication channels between nodes in a graph.
 */
public interface Channel {
    /**
     * Get the current value of the channel.
     *
     * @return Current value
     */
    Object getValue();
    
    /**
     * Update the channel with a new value.
     *
     * @param value New value
     * @return True if the update was applied, false otherwise
     */
    boolean update(Object value);
    
    /**
     * Get the value to save in a checkpoint.
     *
     * @return Checkpointed value
     */
    Object checkpoint();
    
    /**
     * Restore the channel from a checkpoint.
     *
     * @param value Checkpointed value
     */
    void fromCheckpoint(Object value);
}

Channel Implementations

LastValue Channel

A channel that stores a single value, replacing it with each update.

package com.langgraph.channels;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Channel that stores the last value set, rejecting multiple updates within a single step.
 * This is the default channel type for most use cases.
 */
public class LastValue implements Channel {
    private final AtomicReference<Object> value = new AtomicReference<>();
    private boolean updated = false;
    
    /**
     * Create a LastValue channel with an optional initial value.
     *
     * @param initialValue Optional initial value
     */
    public LastValue(Object initialValue) {
        value.set(initialValue);
    }
    
    /**
     * Create an empty LastValue channel.
     */
    public LastValue() {
        this(null);
    }
    
    @Override
    public Object getValue() {
        return value.get();
    }
    
    @Override
    public boolean update(Object newValue) {
        if (updated) {
            throw new IllegalStateException("LastValue channel cannot be updated multiple times in one step");
        }
        
        // Skip update if value hasn't changed
        if (Objects.equals(value.get(), newValue)) {
            return false;
        }
        
        value.set(newValue);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return value.get();
    }
    
    @Override
    public void fromCheckpoint(Object checkpointValue) {
        value.set(checkpointValue);
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
}

AnyValue Channel

A channel that accepts multiple updates within a step, storing only the last one.

package com.langgraph.channels;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Channel that accepts multiple updates within a step, storing only the last one.
 */
public class AnyValue implements Channel {
    private final AtomicReference<Object> value = new AtomicReference<>();
    private boolean updated = false;
    
    /**
     * Create an AnyValue channel with an optional initial value.
     *
     * @param initialValue Optional initial value
     */
    public AnyValue(Object initialValue) {
        value.set(initialValue);
    }
    
    /**
     * Create an empty AnyValue channel.
     */
    public AnyValue() {
        this(null);
    }
    
    @Override
    public Object getValue() {
        return value.get();
    }
    
    @Override
    public boolean update(Object newValue) {
        // Skip update if value hasn't changed
        if (Objects.equals(value.get(), newValue)) {
            return false;
        }
        
        value.set(newValue);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return value.get();
    }
    
    @Override
    public void fromCheckpoint(Object checkpointValue) {
        value.set(checkpointValue);
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
}

EphemeralValue Channel

A channel that clears its value after being read.

package com.langgraph.channels;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Channel that clears its value after being read.
 * Useful for temporary values that should only be processed once.
 */
public class EphemeralValue implements Channel {
    private final AtomicReference<Object> value = new AtomicReference<>();
    private boolean updated = false;
    
    /**
     * Create an EphemeralValue channel with an optional initial value.
     *
     * @param initialValue Optional initial value
     */
    public EphemeralValue(Object initialValue) {
        value.set(initialValue);
    }
    
    /**
     * Create an empty EphemeralValue channel.
     */
    public EphemeralValue() {
        this(null);
    }
    
    @Override
    public Object getValue() {
        Object currentValue = value.getAndSet(null);
        return currentValue;
    }
    
    @Override
    public boolean update(Object newValue) {
        // Skip update if value hasn't changed
        if (Objects.equals(value.get(), newValue)) {
            return false;
        }
        
        value.set(newValue);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return value.get();
    }
    
    @Override
    public void fromCheckpoint(Object checkpointValue) {
        value.set(checkpointValue);
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
}

UntrackedValue Channel

A channel that's excluded from checkpoints.

package com.langgraph.channels;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Channel that's excluded from checkpoints.
 * Useful for storing large temporary data that shouldn't be persisted.
 */
public class UntrackedValue implements Channel {
    private final AtomicReference<Object> value = new AtomicReference<>();
    private boolean updated = false;
    
    /**
     * Create an UntrackedValue channel with an optional initial value.
     *
     * @param initialValue Optional initial value
     */
    public UntrackedValue(Object initialValue) {
        value.set(initialValue);
    }
    
    /**
     * Create an empty UntrackedValue channel.
     */
    public UntrackedValue() {
        this(null);
    }
    
    @Override
    public Object getValue() {
        return value.get();
    }
    
    @Override
    public boolean update(Object newValue) {
        // Skip update if value hasn't changed
        if (Objects.equals(value.get(), newValue)) {
            return false;
        }
        
        value.set(newValue);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        // Return null for checkpoint as this value is not tracked
        return null;
    }
    
    @Override
    public void fromCheckpoint(Object checkpointValue) {
        // No-op as this channel isn't tracked in checkpoints
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
}

Topic Channel

A publish-subscribe channel supporting multiple values and subscribers.

package com.langgraph.channels;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Publish-subscribe channel supporting multiple values and subscribers.
 * Each subscriber receives all published values.
 */
public class Topic implements Channel {
    private final List<Object> values = new CopyOnWriteArrayList<>();
    private boolean updated = false;
    
    @Override
    public Object getValue() {
        List<Object> result = new ArrayList<>(values);
        values.clear();
        return result;
    }
    
    @Override
    public boolean update(Object value) {
        values.add(value);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return new ArrayList<>(values);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void fromCheckpoint(Object checkpointValue) {
        values.clear();
        if (checkpointValue != null) {
            values.addAll((List<Object>) checkpointValue);
        }
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
}

BinaryOperatorAggregate Channel

A channel that aggregates values using a binary operator.

package com.langgraph.channels;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;

/**
 * Channel that aggregates values using a binary operator.
 *
 * @param <T> Type of values to aggregate
 */
public class BinaryOperatorAggregate<T> implements Channel {
    private final AtomicReference<T> value = new AtomicReference<>();
    private final BinaryOperator<T> operator;
    private boolean updated = false;
    
    /**
     * Create a BinaryOperatorAggregate channel with an operator and optional initial value.
     *
     * @param operator Binary operator for combining values
     * @param initialValue Optional initial value
     */
    public BinaryOperatorAggregate(BinaryOperator<T> operator, T initialValue) {
        this.operator = operator;
        value.set(initialValue);
    }
    
    /**
     * Create a BinaryOperatorAggregate channel with an operator.
     *
     * @param operator Binary operator for combining values
     */
    public BinaryOperatorAggregate(BinaryOperator<T> operator) {
        this(operator, null);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public T getValue() {
        return value.get();
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public boolean update(Object newValue) {
        T typedValue = (T) newValue;
        T currentValue = value.get();
        
        if (currentValue == null) {
            value.set(typedValue);
            updated = true;
            return true;
        }
        
        // Apply the binary operator to combine values
        T combinedValue = operator.apply(currentValue, typedValue);
        
        // Skip update if value hasn't changed
        if (Objects.equals(currentValue, combinedValue)) {
            return false;
        }
        
        value.set(combinedValue);
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return value.get();
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void fromCheckpoint(Object checkpointValue) {
        value.set((T) checkpointValue);
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
    
    /**
     * Factory for creating sum aggregates.
     *
     * @param <T> Type of values to sum
     * @return A channel that sums values
     */
    public static <T extends Number> BinaryOperatorAggregate<T> sum() {
        return new BinaryOperatorAggregate<>((a, b) -> {
            if (a instanceof Integer) {
                return (T) Integer.valueOf(((Integer) a) + ((Integer) b));
            } else if (a instanceof Long) {
                return (T) Long.valueOf(((Long) a) + ((Long) b));
            } else if (a instanceof Double) {
                return (T) Double.valueOf(((Double) a) + ((Double) b));
            } else if (a instanceof Float) {
                return (T) Float.valueOf(((Float) a) + ((Float) b));
            } else {
                throw new IllegalArgumentException("Unsupported number type: " + a.getClass());
            }
        });
    }
}

NamedBarrierValue Channel

A synchronization mechanism requiring all named values to be received.

package com.langgraph.channels;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Synchronization channel requiring all named values to be received.
 * Triggers when all expected names have provided values.
 */
public class NamedBarrierValue implements Channel {
    private final Set<String> expectedNames;
    private final Map<String, Object> values = new ConcurrentHashMap<>();
    private boolean updated = false;
    
    /**
     * Create a NamedBarrierValue channel with expected names.
     *
     * @param expectedNames Set of names expected to provide values
     */
    public NamedBarrierValue(Set<String> expectedNames) {
        this.expectedNames = new HashSet<>(expectedNames);
    }
    
    @Override
    public Object getValue() {
        // Return the map of collected values if all expected names have provided values
        if (values.keySet().containsAll(expectedNames)) {
            Map<String, Object> result = new HashMap<>(values);
            values.clear();
            return result;
        }
        
        // Return null if the barrier is not satisfied
        return null;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public boolean update(Object value) {
        if (!(value instanceof Map)) {
            throw new IllegalArgumentException("NamedBarrierValue requires a Map<String, Object> update");
        }
        
        Map<String, Object> update = (Map<String, Object>) value;
        if (update.size() != 1) {
            throw new IllegalArgumentException("NamedBarrierValue update must contain exactly one entry");
        }
        
        String name = update.keySet().iterator().next();
        if (!expectedNames.contains(name)) {
            throw new IllegalArgumentException("Unexpected name in NamedBarrierValue update: " + name);
        }
        
        values.put(name, update.get(name));
        updated = true;
        return true;
    }
    
    @Override
    public Object checkpoint() {
        return new HashMap<>(values);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void fromCheckpoint(Object checkpointValue) {
        values.clear();
        if (checkpointValue != null) {
            values.putAll((Map<String, Object>) checkpointValue);
        }
        updated = false;
    }
    
    /**
     * Reset the update flag at the end of a superstep.
     */
    public void resetUpdated() {
        updated = false;
    }
    
    /**
     * Check if the channel was updated in the current step.
     *
     * @return True if updated, false otherwise
     */
    public boolean wasUpdated() {
        return updated;
    }
    
    /**
     * Check if all expected names have provided values.
     *
     * @return True if the barrier is satisfied, false otherwise
     */
    public boolean isBarrierSatisfied() {
        return values.keySet().containsAll(expectedNames);
    }
}

Channel Factory

A factory class for creating channels.

package com.langgraph.channels;

import java.util.Set;
import java.util.function.BinaryOperator;

/**
 * Factory for creating channels.
 */
public final class Channels {
    private Channels() {}
    
    /**
     * Create a LastValue channel.
     *
     * @param initialValue Optional initial value
     * @return LastValue channel
     */
    public static LastValue lastValue(Object initialValue) {
        return new LastValue(initialValue);
    }
    
    /**
     * Create an empty LastValue channel.
     *
     * @return LastValue channel
     */
    public static LastValue lastValue() {
        return new LastValue();
    }
    
    /**
     * Create an AnyValue channel.
     *
     * @param initialValue Optional initial value
     * @return AnyValue channel
     */
    public static AnyValue anyValue(Object initialValue) {
        return new AnyValue(initialValue);
    }
    
    /**
     * Create an empty AnyValue channel.
     *
     * @return AnyValue channel
     */
    public static AnyValue anyValue() {
        return new AnyValue();
    }
    
    /**
     * Create an EphemeralValue channel.
     *
     * @param initialValue Optional initial value
     * @return EphemeralValue channel
     */
    public static EphemeralValue ephemeralValue(Object initialValue) {
        return new EphemeralValue(initialValue);
    }
    
    /**
     * Create an empty EphemeralValue channel.
     *
     * @return EphemeralValue channel
     */
    public static EphemeralValue ephemeralValue() {
        return new EphemeralValue();
    }
    
    /**
     * Create an UntrackedValue channel.
     *
     * @param initialValue Optional initial value
     * @return UntrackedValue channel
     */
    public static UntrackedValue untrackedValue(Object initialValue) {
        return new UntrackedValue(initialValue);
    }
    
    /**
     * Create an empty UntrackedValue channel.
     *
     * @return UntrackedValue channel
     */
    public static UntrackedValue untrackedValue() {
        return new UntrackedValue();
    }
    
    /**
     * Create a Topic channel.
     *
     * @return Topic channel
     */
    public static Topic topic() {
        return new Topic();
    }
    
    /**
     * Create a BinaryOperatorAggregate channel.
     *
     * @param operator Binary operator for combining values
     * @param initialValue Optional initial value
     * @param <T> Type of values to aggregate
     * @return BinaryOperatorAggregate channel
     */
    public static <T> BinaryOperatorAggregate<T> binaryOperatorAggregate(
            BinaryOperator<T> operator, T initialValue) {
        return new BinaryOperatorAggregate<>(operator, initialValue);
    }
    
    /**
     * Create a BinaryOperatorAggregate channel.
     *
     * @param operator Binary operator for combining values
     * @param <T> Type of values to aggregate
     * @return BinaryOperatorAggregate channel
     */
    public static <T> BinaryOperatorAggregate<T> binaryOperatorAggregate(BinaryOperator<T> operator) {
        return new BinaryOperatorAggregate<>(operator);
    }
    
    /**
     * Create a NamedBarrierValue channel.
     *
     * @param expectedNames Set of names expected to provide values
     * @return NamedBarrierValue channel
     */
    public static NamedBarrierValue namedBarrierValue(Set<String> expectedNames) {
        return new NamedBarrierValue(expectedNames);
    }
}