State Management Tutorial¶
This tutorial will teach you how to work with state in SemanticKernel.Graph. You'll learn how to use KernelArguments
and GraphState
, manage data flow between nodes, and leverage the powerful state management features.
What You'll Learn¶
- How state flows through your graph
- Using
KernelArguments
for input and output - Working with
GraphState
for enhanced state management - State validation and versioning
- Best practices for state design
Prerequisites¶
Before starting, ensure you have: * Completed the First Graph Tutorial * Basic understanding of SemanticKernel.Graph concepts * A configured LLM provider
Understanding State in Graphs¶
What is State?¶
State in SemanticKernel.Graph represents the data that flows through your workflow. It's like a backpack that gets passed from node to node, collecting and carrying information throughout the execution.
State Components¶
- Input State: Initial data when the graph starts
- Intermediate State: Data modified by each node during execution
- Output State: Final state containing all results and intermediate data
Basic State Management¶
Creating Input State¶
using Microsoft.SemanticKernel;
using SemanticKernel.Graph.Extensions;
// Basic state with simple values
var state = new KernelArguments
{
["userName"] = "Alice",
["userAge"] = 30,
["preferences"] = new[] { "AI", "Machine Learning", "Graphs" }
};
// State with complex objects
var userProfile = new
{
Name = "Bob",
Department = "Engineering",
Skills = new[] { "C#", ".NET", "AI" }
};
var stateWithObjects = new KernelArguments
{
["userProfile"] = userProfile,
["requestId"] = Guid.NewGuid().ToString(),
["timestamp"] = DateTimeOffset.UtcNow
};
Reading State in Nodes¶
var analysisNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Analyze this user profile:
Name: {{$userProfile.Name}}
Department: {{$userProfile.Department}}
Skills: {{$userProfile.Skills}}
Provide insights about their technical background and suggest learning paths.
")
);
Writing to State¶
var processingNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Process the user profile and provide:
1. Technical assessment
2. Learning recommendations
3. Career suggestions
Format as JSON with keys: assessment, recommendations, career
")
);
// Store the result in a specific key
processingNode.StoreResultAs("analysis");
Enhanced State with GraphState¶
What is GraphState?¶
GraphState
is an enhanced wrapper around KernelArguments
that provides additional features like:
* State versioning and validation
* Execution history tracking
* Metadata management
* Serialization capabilities
Using GraphState¶
using SemanticKernel.Graph.State;
// Create enhanced state
var graphState = new GraphState(new KernelArguments
{
["input"] = "Hello World",
["metadata"] = new Dictionary<string, object>
{
["source"] = "tutorial",
["version"] = "1.0"
}
});
// Access state with enhanced features
var input = graphState.GetValue<string>("input");
var metadata = graphState.GetValue<Dictionary<string, object>>("metadata");
// Add execution metadata
graphState.AddExecutionStep("NodeName", "Processing input", DateTimeOffset.UtcNow);
State Flow Between Nodes¶
Building a State-Aware Graph¶
using Microsoft.SemanticKernel;
using SemanticKernel.Graph;
using SemanticKernel.Graph.Extensions;
using SemanticKernel.Graph.Nodes;
class Program
{
static async Task Main(string[] args)
{
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4", Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
builder.AddGraphSupport();
var kernel = builder.Build();
// Create nodes that work with state
var inputNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Analyze this text: {{$input}}
Provide:
1. Sentiment (positive/negative/neutral)
2. Key topics
3. Word count
Format as JSON with keys: sentiment, topics, wordCount
")
).StoreResultAs("analysis");
var decisionNode = new ConditionalGraphNode("RouteBySentiment",
state => state.GetValueOrDefault("analysis", "").ToString().Contains("positive"));
var positiveNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
The analysis shows positive sentiment: {{$analysis}}
Generate an encouraging response with suggestions for next steps.
")
).StoreResultAs("response");
var negativeNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
The analysis shows negative sentiment: {{$analysis}}
Provide empathetic support and constructive feedback.
")
).StoreResultAs("response");
var summaryNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Create a summary of this interaction:
Input: {{$input}}
Analysis: {{$analysis}}
Response: {{$response}}
Format as a concise summary paragraph.
")
).StoreResultAs("summary");
// Build the graph
var graph = new GraphExecutor("StateManagementExample");
graph.AddNode(inputNode);
graph.AddNode(decisionNode);
graph.AddNode(positiveNode);
graph.AddNode(negativeNode);
graph.AddNode(summaryNode);
// Connect with conditional routing
graph.Connect(inputNode, decisionNode);
graph.Connect(decisionNode, positiveNode,
edge => edge.When(state => state.GetValueOrDefault("analysis", "").ToString().Contains("positive")));
graph.Connect(decisionNode, negativeNode,
edge => edge.When(state => !state.GetValueOrDefault("analysis", "").ToString().Contains("positive")));
graph.Connect(positiveNode, summaryNode);
graph.Connect(negativeNode, summaryNode);
graph.SetStartNode(inputNode);
// Execute with different inputs
var testInputs = new[]
{
"I love working with AI and machine learning!",
"I'm struggling with this programming problem.",
"The weather is beautiful today and I feel great!"
};
foreach (var input in testInputs)
{
Console.WriteLine($"\n=== Testing with: {input} ===");
var state = new KernelArguments { ["input"] = input };
var result = await graph.ExecuteAsync(state);
Console.WriteLine($"Sentiment: {result.GetValueOrDefault("analysis", "No analysis")}");
Console.WriteLine($"Response: {result.GetValueOrDefault("response", "No response")}");
Console.WriteLine($"Summary: {result.GetValueOrDefault("summary", "No summary")}");
}
}
}
State Validation and Type Safety¶
Type-Safe State Access¶
// Use extension methods for type-safe access
var state = new KernelArguments
{
["count"] = 42,
["name"] = "Alice",
["isActive"] = true
};
// Type-safe retrieval with defaults
var count = state.GetValueOrDefault<int>("count", 0);
var name = state.GetValueOrDefault<string>("name", "Unknown");
var isActive = state.GetValueOrDefault<bool>("isActive", false);
// Try to get values with type checking
if (state.TryGetValue<int>("count", out var safeCount))
{
Console.WriteLine($"Count is: {safeCount}");
}
State Validation¶
var validationNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Validate this user data:
Name: {{$userName}}
Age: {{$userAge}}
Email: {{$userEmail}}
Return JSON with validation results:
{
"isValid": boolean,
"errors": [string],
"warnings": [string]
}
")
).StoreResultAs("validation");
// Add validation logic
var decisionNode = new ConditionalGraphNode("IsValidUser",
state =>
{
var validation = state.GetValueOrDefault("validation", "");
return validation.ToString().Contains("\"isValid\": true");
});
Advanced State Patterns¶
State Accumulation¶
var accumulationNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Based on the current conversation history: {{$conversationHistory}}
And the new input: {{$newInput}}
Update the conversation with the new interaction.
Return the updated conversation history.
")
).StoreResultAs("conversationHistory");
// Initialize with empty history
var state = new KernelArguments
{
["conversationHistory"] = new List<string>(),
["newInput"] = "Hello, I need help with AI"
};
State Transformation¶
var transformNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Transform this data structure: {{$rawData}}
Extract and structure the following information:
- Key insights
- Action items
- Priority levels
Return as structured JSON.
")
).StoreResultAs("structuredData");
State Persistence and Checkpointing¶
Basic Checkpointing¶
// Enable checkpointing in your graph options
builder.AddGraphSupport(options =>
{
options.Checkpointing.Enabled = true;
options.Checkpointing.Interval = 3; // Checkpoint every 3 nodes
});
// State is automatically saved and can be restored
var checkpointManager = kernel.GetRequiredService<ICheckpointManager>();
var savedState = await checkpointManager.SaveCheckpointAsync(graphId, executionId, state);
State Serialization¶
// State can be serialized for external storage
var serializedState = JsonSerializer.Serialize(state, new JsonSerializerOptions
{
WriteIndented = true
});
// And deserialized back
var restoredState = JsonSerializer.Deserialize<KernelArguments>(serializedState);
Best Practices¶
1. Use Descriptive Keys¶
// Good
state["userProfile"] = userData;
state["analysisResults"] = analysis;
// Avoid
state["data"] = userData;
state["result"] = analysis;
2. Validate State at Key Points¶
var validationNode = new ConditionalGraphNode("ValidateState",
state =>
{
// Check required fields exist
var required = new[] { "userProfile", "analysisResults" };
return required.All(key => state.ContainsKey(key));
});
3. Use Type-Safe Access¶
// Use extension methods for safety
var count = state.GetValueOrDefault<int>("count", 0);
var name = state.GetValueOrDefault<string>("name", "Unknown");
4. Structure Complex State¶
// Group related data
state["user"] = new
{
Profile = userProfile,
Preferences = userPreferences,
History = userHistory
};
5. Handle State Errors Gracefully¶
var errorHandlerNode = new FunctionGraphNode(
kernel.CreateFunctionFromPrompt(@"
Handle this error gracefully: {{$error}}
Provide a user-friendly message and recovery suggestions.
")
).StoreResultAs("errorResponse");
Common State Patterns¶
Configuration State¶
var configState = new KernelArguments
{
["maxRetries"] = 3,
["timeout"] = TimeSpan.FromSeconds(30),
["logLevel"] = "Information"
};
Session State¶
var sessionState = new KernelArguments
{
["sessionId"] = Guid.NewGuid().ToString(),
["startTime"] = DateTimeOffset.UtcNow,
["userContext"] = userContext
};
Workflow State¶
var workflowState = new KernelArguments
{
["currentStep"] = "analysis",
["completedSteps"] = new[] { "input", "validation" },
["nextSteps"] = new[] { "processing", "output" }
};
Troubleshooting State Issues¶
Common Problems¶
State Not Persisting Between Nodes¶
System.Collections.Generic.KeyNotFoundException: The given key 'result' was not present in the dictionary.
StoreResultAs()
or manual state assignment.
Type Conversion Errors¶
Solution: Use type-safe access methods likeGetValueOrDefault<T>()
or validate types before casting.
State Corruption¶
Solution: Check state serialization/deserialization logic and ensure all objects are serializable.Next Steps¶
Now that you understand state management, explore these advanced topics:
- Conditional Nodes Guide: Master conditional logic and routing
- Checkpointing Tutorial: Learn about state persistence and recovery
- Error Handling Guide: Handle state errors and exceptions
- Advanced Patterns: Discover complex state management patterns
Concepts and Techniques¶
This tutorial covers several key concepts:
- State: Data that flows through the graph, maintaining context across execution steps
- KernelArguments: The base state container that holds key-value pairs
- GraphState: Enhanced state wrapper with versioning, validation, and metadata
- State Flow: How data moves between nodes and transforms during execution
- State Validation: Ensuring data integrity and type safety throughout the workflow
Prerequisites and Minimum Configuration¶
To complete this tutorial, you need: * SemanticKernel.Graph package installed and configured * LLM Provider with valid API keys * Basic understanding of graph concepts from the first tutorial * .NET 8.0+ runtime environment
See Also¶
- First Graph Tutorial: Build your first complete graph workflow
- Core Concepts: Understanding graphs, nodes, and execution
- API Reference: Complete API documentation
- Examples: Real-world usage patterns and implementations