Graph Concepts¶
This guide explains the fundamental concepts of SemanticKernel.Graph, including nodes, conditional edges, execution paths, and controlled cycles.
Overview¶
SemanticKernel.Graph extends the Semantic Kernel with a graph-based execution model that allows you to create complex workflows by connecting nodes with conditional logic. Unlike traditional linear execution, graphs enable branching, looping, and dynamic routing based on execution state.
Core Components¶
Nodes¶
Nodes are the fundamental building blocks of a graph. Each node represents a unit of work that can be executed, and they communicate through shared state.
Key Properties: * NodeId: Unique identifier for the node * Name: Human-readable name for debugging and visualization * Description: What the node does * IsExecutable: Whether the node can be executed * InputParameters: Expected input parameters * OutputParameters: Produced output parameters
Lifecycle Methods:
* OnBeforeExecuteAsync
: Setup and validation before execution
* ExecuteAsync
: Main execution logic
* OnAfterExecuteAsync
: Cleanup and post-processing
* OnExecutionFailedAsync
: Error handling and recovery
Conditional Edges¶
Edges define the connections between nodes and control the flow of execution. They can be unconditional (always taken) or conditional (taken only when specific conditions are met).
Edge Types: * Unconditional: Always traversed after the source node executes * Parameter-based: Evaluates against specific argument values * State-based: Evaluates against the entire graph state * Template-based: Uses Handlebars-like templates for complex conditions
Edge Properties: * SourceNode: The origin node * TargetNode: The destination node * Condition: Predicate function that determines traversal * Metadata: Additional information for routing and visualization * MergeConfiguration: How to handle state when multiple paths converge
Execution Model¶
Execution Flow¶
The graph executor follows a systematic approach to navigate and execute nodes:
- Start Node: Execution begins at the designated start node
- Node Execution: The current node executes with access to shared state
- Next Node Selection: The executor determines which nodes to execute next
- State Propagation: Results and state changes flow to subsequent nodes
- Termination: Execution stops when no more nodes are available
Navigation Logic¶
Each node implements GetNextNodes()
to specify which nodes should execute next:
public IEnumerable<IGraphNode> GetNextNodes(FunctionResult? executionResult, GraphState graphState)
{
// Return the next nodes based on execution result and current state
if (executionResult?.GetValue<bool>("should_continue") == true)
{
return new[] { nextNode };
}
return Enumerable.Empty<IGraphNode>(); // Terminate execution
}
Conditional Routing¶
Conditional edges enable dynamic routing based on execution state:
// Create an edge that's only taken when a condition is met
var edge = new ConditionalEdge(
sourceNode,
targetNode,
state => state.GetValue<int>("score") >= 80
);
// Or use factory methods for common patterns
var edge = ConditionalEdge.CreateParameterEquals(
sourceNode,
targetNode,
"status",
"success"
);
Execution Paths¶
Linear Paths¶
Simple workflows follow a linear sequence:
Branching Paths¶
Conditional logic creates branching execution:
Parallel Paths¶
Multiple paths can execute concurrently and then converge:
Loop Paths¶
Nodes can create loops for iterative processing:
Controlled Cycles¶
Loop Prevention¶
The graph executor includes built-in safeguards to prevent infinite loops:
- Maximum Iterations: Configurable limits on loop execution
- Execution Depth: Tracking of execution depth to detect excessive nesting
- Timeout Controls: Configurable timeouts for long-running operations
- Circuit Breakers: Automatic termination of problematic execution paths
Loop Control¶
Nodes can implement loop control logic:
public bool ShouldExecute(GraphState graphState)
{
var iterationCount = graphState.GetValue<int>("iteration_count", 0);
var maxIterations = graphState.GetValue<int>("max_iterations", 10);
return iterationCount < maxIterations;
}
State Management in Loops¶
Loop nodes maintain state across iterations:
// Store iteration count
graphState.SetValue("iteration_count",
graphState.GetValue<int>("iteration_count", 0) + 1);
// Check for termination conditions
if (goalAchieved || maxIterationsReached)
{
graphState.SetValue("loop_terminated", true);
}
State Management¶
Shared State¶
All nodes share a common GraphState
that wraps KernelArguments
:
// Set values in state
graphState.SetValue("user_input", "Hello, world!");
graphState.SetValue("processing_step", 1);
// Retrieve values from state
var input = graphState.GetValue<string>("user_input");
var step = graphState.GetValue<int>("processing_step");
State Persistence¶
Graph state can be serialized and persisted:
// Save state to checkpoint
var checkpoint = await stateHelpers.SaveCheckpointAsync(graphState);
// Restore state from checkpoint
var restoredState = await stateHelpers.RestoreCheckpointAsync(checkpoint);
State Validation¶
Nodes can validate state before execution:
public ValidationResult ValidateExecution(KernelArguments arguments)
{
var result = new ValidationResult();
if (!arguments.ContainsName("required_parameter"))
{
result.AddError("Required parameter 'required_parameter' is missing");
}
return result;
}
Advanced Patterns¶
ReAct Pattern¶
The ReAct (Reasoning + Acting) pattern implements iterative problem-solving:
var reactNode = new ReActLoopGraphNode()
.ConfigureNodes(
reasoningNode, // Analyze and plan
actionNode, // Execute actions
observationNode // Observe results
);
Multi-Agent Coordination¶
Multiple agents can work together with shared state:
var coordinator = new MultiAgentCoordinator();
coordinator.AddAgent("researcher", researchAgent);
coordinator.AddAgent("analyzer", analysisAgent);
coordinator.AddAgent("summarizer", summaryAgent);
Human-in-the-Loop¶
Graphs can pause execution for human approval:
var approvalNode = new HumanApprovalGraphNode(
"approval_required",
"Requires human approval to proceed"
);
Best Practices¶
Node Design¶
- Single Responsibility: Each node should have one clear purpose
- State Validation: Always validate inputs before execution
- Error Handling: Implement robust error handling and recovery
- Metadata: Use metadata to provide context for debugging
Edge Design¶
- Clear Conditions: Make conditional logic explicit and readable
- Performance: Keep condition evaluation fast and side-effect free
- Documentation: Document complex routing logic
State Management¶
- Immutable Updates: Avoid modifying existing state directly
- Validation: Validate state changes and provide meaningful error messages
- Serialization: Consider serialization requirements when designing state
Performance¶
- Lazy Evaluation: Only evaluate conditions when necessary
- Caching: Cache expensive computations when possible
- Parallelization: Use parallel execution for independent paths
See Also¶
- Execution Model - Detailed execution lifecycle
- Node Types - Available node implementations
- State Management - Advanced state handling
- Routing Strategies - Dynamic routing techniques
- Examples - Practical examples and use cases