Skip to content

REST Tools Integration

SemanticKernel.Graph provides comprehensive integration capabilities for external REST APIs and tools, enabling you to seamlessly incorporate external services into your graph workflows. This guide covers the complete REST tools ecosystem including schema definition, validation, caching, and idempotency.

Overview

The REST tools integration system consists of several key components:

  • RestToolSchema: Defines the structure and behavior of REST API operations
  • RestToolGraphNode: Executable graph node that performs HTTP requests
  • IToolRegistry: Central registry for managing and discovering available tools
  • IToolSchemaConverter: Converts schemas into executable nodes
  • Built-in caching and idempotency: Performance optimization and request safety

Core Components

RestToolSchema

The RestToolSchema class defines the contract for a REST API operation:

public sealed class RestToolSchema
{
    public required string Id { get; init; }
    public required string Name { get; init; }
    public string Description { get; init; } = string.Empty;
    public required Uri BaseUri { get; init; }
    public required string Path { get; init; }
    public required HttpMethod Method { get; init; }
    public string? JsonBodyTemplate { get; init; }
    public Dictionary<string, string> QueryParameters { get; init; } = new();
    public Dictionary<string, string> Headers { get; init; } = new();
    public int TimeoutSeconds { get; init; } = 30;
    public bool CacheEnabled { get; init; } = true;
    public int CacheTtlSeconds { get; init; } = 60;
    public string? TelemetryDependencyName { get; init; }
}

Key Features: * Flexible parameter mapping: Map query parameters and headers to graph state variables * Template support: JSON body templates with variable substitution * Configurable timeouts: Per-request timeout settings * Built-in caching: Response caching with configurable TTL * Telemetry integration: Dependency tracking for monitoring

RestToolGraphNode

The RestToolGraphNode executes REST operations based on schema definitions:

public sealed class RestToolGraphNode : IGraphNode, ITypedSchemaNode
{
    public RestToolGraphNode(
        RestToolSchema schema, 
        HttpClient httpClient, 
        ILogger<RestToolGraphNode>? logger = null, 
        ISecretResolver? secretResolver = null, 
        IGraphTelemetry? telemetry = null);
}

Capabilities: * Input mapping: Automatically maps graph state to HTTP parameters * Response handling: Parses JSON responses and provides structured output * Error handling: Comprehensive error handling with telemetry * Schema validation: Implements ITypedSchemaNode for type safety * Secret resolution: Secure handling of API keys and sensitive data

Schema Definition

Basic REST Schema

Define a simple GET operation:

var weatherSchema = new RestToolSchema
{
    Id = "weather.get",
    Name = "Get Weather",
    Description = "Retrieve current weather information",
    BaseUri = new Uri("https://api.weatherapi.com"),
    Path = "/v1/current.json",
    Method = HttpMethod.Get,
    QueryParameters = 
    {
        ["q"] = "location",           // Maps to graph state "location"
        ["key"] = "secret:weather_key" // Resolved via secret resolver
    },
    Headers = 
    {
        ["User-Agent"] = ":SemanticKernel.Graph/1.0", // Literal value
        ["X-Correlation-ID"] = "correlation_id"        // Maps to graph state
    },
    CacheEnabled = true,
    CacheTtlSeconds = 300, // 5 minutes
    TimeoutSeconds = 10
};

POST with JSON Body

Define a POST operation with dynamic body content:

var orderSchema = new RestToolSchema
{
    Id = "orders.create",
    Name = "Create Order",
    Description = "Create a new order in the system",
    BaseUri = new Uri("https://api.store.com"),
    Path = "/v1/orders",
    Method = HttpMethod.Post,
    JsonBodyTemplate = """
    {
        "customer_id": "{{customer_id}}",
        "items": {{items_json}},
        "shipping_address": {
            "street": "{{street}}",
            "city": "{{city}}",
            "postal_code": "{{postal_code}}"
        }
    }
    """,
    Headers = 
    {
        ["Content-Type"] = ":application/json",
        ["Authorization"] = "secret:store_api_key",
        ["X-Request-ID"] = "request_id"
    },
    CacheEnabled = false, // POST operations typically not cached
    TimeoutSeconds = 30
};

Advanced Schema with Route Parameters

Define operations with dynamic path segments:

var userSchema = new RestToolSchema
{
    Id = "users.get",
    Name = "Get User",
    Description = "Retrieve user information by ID",
    BaseUri = new Uri("https://api.users.com"),
    Path = "/v1/users/{user_id}/profile", // Route parameter
    Method = HttpMethod.Get,
    QueryParameters = 
    {
        ["include"] = "include_fields",
        ["format"] = ":json"
    },
    Headers = 
    {
        ["Accept"] = ":application/json",
        ["Authorization"] = "secret:users_api_key"
    }
};

Parameter Mapping

Query Parameters

Map graph state variables to HTTP query parameters:

var schema = new RestToolSchema
{
    // ... other properties
    QueryParameters = 
    {
        ["search"] = "query_text",        // ?search=value_from_state
        ["page"] = "page_number",         // &page=value_from_state
        ["limit"] = "max_results",        // &limit=value_from_state
        ["sort"] = ":name_asc"            // &sort=name_asc (literal)
    }
};

// Graph state
var args = new KernelArguments
{
    ["query_text"] = "semantic kernel",
    ["page_number"] = 1,
    ["max_results"] = 20
};

// Results in: ?search=semantic%20kernel&page=1&limit=20&sort=name_asc

Headers

Map graph state variables to HTTP headers:

var schema = new RestToolSchema
{
    // ... other properties
    Headers = 
    {
        ["Authorization"] = "secret:api_key",           // Resolved via secret resolver
        ["X-Correlation-ID"] = "correlation_id",        // From graph state
        ["User-Agent"] = ":MyApp/1.0",                  // Literal value
        ["Accept-Language"] = "language_code",          // From graph state
        ["X-Tenant"] = "tenant_id"                      // From graph state
    }
};

Header Value Types: * Literal values: Prefixed with : (e.g., ":application/json") * Secret references: Prefixed with secret: (e.g., "secret:api_key") * State variables: No prefix (e.g., "correlation_id")

JSON Body Templates

Use Handlebars-style templates for dynamic request bodies:

var schema = new RestToolSchema
{
    // ... other properties
    JsonBodyTemplate = """
    {
        "user": {
            "id": "{{user_id}}",
            "name": "{{user_name}}",
            "email": "{{user_email}}"
        },
        "preferences": {{preferences_json}},
        "metadata": {
            "created_at": "{{timestamp}}",
            "source": "{{source}}"
        }
    }
    """
};

Template Features: * Variable substitution: {{variable_name}} replaced with graph state values * JSON embedding: {{json_variable}} for complex objects * Conditional logic: Support for basic Handlebars expressions * Escape handling: Automatic JSON escaping for safe output

Tool Registry

IToolRegistry Interface

The IToolRegistry provides centralized tool management:

public interface IToolRegistry
{
    Task<bool> RegisterAsync(ToolMetadata metadata, Func<IServiceProvider, IGraphNode> factory);
    Task<bool> UnregisterAsync(string toolId);
    Task<ToolMetadata?> GetAsync(string toolId);
    Task<IGraphNode?> CreateNodeAsync(string toolId, IServiceProvider serviceProvider);
    Task<IReadOnlyList<ToolMetadata>> ListAsync(ToolSearchCriteria? criteria = null);
}

Tool Registration

Register REST tools in the registry:

// Create tool metadata
var metadata = new ToolMetadata
{
    Id = "weather.api",
    Name = "Weather API",
    Description = "Weather information services",
    Type = ToolType.Rest,
    Tags = new HashSet<string> { "weather", "external", "api" },
    Version = "1.0.0"
};

// Create factory function
Func<IServiceProvider, IGraphNode> factory = (services) =>
{
    var httpClient = services.GetRequiredService<HttpClient>();
    var logger = services.GetService<ILogger<RestToolGraphNode>>();
    var secretResolver = services.GetService<ISecretResolver>();
    var telemetry = services.GetService<IGraphTelemetry>();

    return new RestToolGraphNode(weatherSchema, httpClient, logger, secretResolver, telemetry);
};

// Register the tool
await toolRegistry.RegisterAsync(metadata, factory);

Tool Discovery

Discover and list available tools:

// List all tools
var allTools = await toolRegistry.ListAsync();

// Search by criteria
var searchCriteria = new ToolSearchCriteria
{
    SearchText = "weather",
    Type = ToolType.Rest,
    Tags = new HashSet<string> { "api" }
};

var weatherTools = await toolRegistry.ListAsync(searchCriteria);

// Get specific tool
var toolMetadata = await toolRegistry.GetAsync("weather.api");
if (toolMetadata != null)
{
    var toolNode = await toolRegistry.CreateNodeAsync("weather.api", serviceProvider);
    // Use the tool node in your graph
}

Schema Conversion

IToolSchemaConverter

Convert schemas into executable nodes:

public interface IToolSchemaConverter
{
    IGraphNode CreateNode(RestToolSchema schema);
}

RestToolSchemaConverter

Default implementation for REST tools:

public sealed class RestToolSchemaConverter : IToolSchemaConverter
{
    public RestToolSchemaConverter(
        HttpClient httpClient, 
        ILogger<RestToolSchemaConverter>? logger = null, 
        ISecretResolver? secretResolver = null, 
        IGraphTelemetry? telemetry = null);

    public IGraphNode CreateNode(RestToolSchema schema);
}

Usage:

var converter = new RestToolSchemaConverter(
    httpClient: httpClient,
    logger: logger,
    secretResolver: secretResolver,
    telemetry: telemetry
);

var restNode = converter.CreateNode(weatherSchema);

Caching and Performance

Response Caching

Enable caching for improved performance:

var schema = new RestToolSchema
{
    // ... other properties
    CacheEnabled = true,
    CacheTtlSeconds = 600 // 10 minutes
};

Cache Behavior: * Cache key: Generated from HTTP method, URL, and request body hash * TTL management: Automatic expiration based on schema configuration * Memory efficient: Uses concurrent dictionary with expiration tracking * Thread safe: Concurrent access support for high-performance scenarios

Cache Configuration

Configure caching behavior:

// Enable caching with custom TTL
var schema = new RestToolSchema
{
    CacheEnabled = true,
    CacheTtlSeconds = 3600, // 1 hour
    // ... other properties
};

// Disable caching for dynamic content
var dynamicSchema = new RestToolSchema
{
    CacheEnabled = false,
    // ... other properties
};

Idempotency

Request Idempotency

Ensure safe retry behavior with idempotency keys:

// The GraphRestApi automatically handles idempotency
var request = new ExecuteGraphRequest
{
    GraphName = "weather-workflow",
    Arguments = new KernelArguments { ["location"] = "New York" },
    IdempotencyKey = "weather-ny-2025-08-15" // Unique key for this operation
};

var response = await graphApi.ExecuteGraphAsync(request, cancellationToken);

Idempotency Features: * Automatic handling: Built into the GraphRestApi service * Request deduplication: Prevents duplicate executions * Hash validation: Ensures request consistency * Configurable window: Adjustable idempotency time window

Validation and Error Handling

Input Validation

Validate inputs before execution:

var node = new RestToolGraphNode(schema, httpClient);

// Validate execution arguments
var validationResult = node.ValidateExecution(arguments);
if (!validationResult.IsValid)
{
    foreach (var error in validationResult.Errors)
    {
        Console.WriteLine($"Validation error: {error.Message}");
    }
}

Schema Validation

Validate schema integrity:

// Validate schema configuration
if (string.IsNullOrEmpty(schema.Id))
    throw new ArgumentException("Schema ID is required");

if (schema.BaseUri == null)
    throw new ArgumentException("Base URI is required");

if (schema.TimeoutSeconds <= 0)
    throw new ArgumentException("Timeout must be positive");

if (schema.CacheTtlSeconds <= 0 && schema.CacheEnabled)
    throw new ArgumentException("Cache TTL must be positive when caching is enabled");

Error Handling

Comprehensive error handling in REST operations:

try
{
    var result = await restNode.ExecuteAsync(kernel, arguments, cancellationToken);

    // Check HTTP status
    var statusCode = result.GetValue<int>("status_code");
    if (statusCode >= 400)
    {
        // Handle HTTP errors
        var errorBody = result.GetValue<string>("response_body");
        _logger.LogError("HTTP error {StatusCode}: {ErrorBody}", statusCode, errorBody);
    }
}
catch (OperationCanceledException)
{
    // Handle timeout or cancellation
    _logger.LogWarning("Request was cancelled or timed out");
}
catch (HttpRequestException ex)
{
    // Handle HTTP request errors
    _logger.LogError(ex, "HTTP request failed");
}
catch (Exception ex)
{
    // Handle other errors
    _logger.LogError(ex, "Unexpected error during REST operation");
}

Telemetry and Monitoring

Dependency Tracking

Track external API dependencies:

var schema = new RestToolSchema
{
    // ... other properties
    TelemetryDependencyName = "Weather API Service"
};

// Telemetry is automatically emitted when IGraphTelemetry is available

Telemetry Data: * Dependency type: HTTP * Target: API hostname * Operation: HTTP method + path * Duration: Request execution time * Success: HTTP status code range * Properties: Node ID, graph name, URI

Performance Monitoring

Monitor REST tool performance:

// Access performance metrics
var metrics = restNode.GetPerformanceMetrics();
Console.WriteLine($"Total requests: {metrics.TotalExecutions}");
Console.WriteLine($"Average duration: {metrics.AverageExecutionTime}");
Console.WriteLine($"Cache hit rate: {metrics.CacheHitRate:P2}");

Security Features

Secret Resolution

Secure handling of sensitive data:

public interface ISecretResolver
{
    Task<string?> ResolveSecretAsync(string secretName, CancellationToken cancellationToken = default);
}

// Configure secret resolver
var secretResolver = new AzureKeyVaultSecretResolver(keyVaultClient);
var restNode = new RestToolGraphNode(schema, httpClient, secretResolver: secretResolver);

Secret Types: * API keys: "secret:weather_api_key" * Authentication tokens: "secret:bearer_token" * Connection strings: "secret:database_connection"

Data Sanitization

Automatic sanitization of sensitive data:

// Sensitive data is automatically sanitized in logs and telemetry
var sanitizer = new SensitiveDataSanitizer(new SensitiveDataPolicy
{
    SanitizeApiKeys = true,
    SanitizeTokens = true,
    SanitizeUrls = false
});

Usage Examples

Weather API Integration

Complete example of integrating a weather API:

// 1. Define the schema
var weatherSchema = new RestToolSchema
{
    Id = "weather.current",
    Name = "Current Weather",
    Description = "Get current weather for a location",
    BaseUri = new Uri("https://api.weatherapi.com"),
    Path = "/v1/current.json",
    Method = HttpMethod.Get,
    QueryParameters = 
    {
        ["q"] = "location",
        ["key"] = "secret:weather_api_key"
    },
    CacheEnabled = true,
    CacheTtlSeconds = 1800, // 30 minutes
    TimeoutSeconds = 10
};

// 2. Create the node
var httpClient = new HttpClient();
var secretResolver = new EnvironmentSecretResolver();
var weatherNode = new RestToolGraphNode(weatherSchema, httpClient, secretResolver: secretResolver);

// 3. Use in a graph
var graph = new GraphExecutor("weather-graph");
graph.AddNode(weatherNode).SetStartNode("weather.current");

// 4. Execute with location
var args = new KernelArguments { ["location"] = "London" };
var result = await graph.ExecuteAsync(kernel, args);

// 5. Process results
var weatherData = result.GetValue<object>("response_json");
var statusCode = result.GetValue<int>("status_code");

E-commerce API Integration

Example of integrating an e-commerce system:

// Product search schema
var productSearchSchema = new RestToolSchema
{
    Id = "products.search",
    Name = "Search Products",
    BaseUri = new Uri("https://api.store.com"),
    Path = "/v1/products/search",
    Method = HttpMethod.Get,
    QueryParameters = 
    {
        ["q"] = "search_query",
        ["category"] = "category_id",
        ["price_min"] = "min_price",
        ["price_max"] = "max_price",
        ["sort"] = "sort_order"
    },
    Headers = 
    {
        ["Authorization"] = "secret:store_api_key",
        ["Accept-Language"] = "language"
    },
    CacheEnabled = true,
    CacheTtlSeconds = 300
};

// Order creation schema
var orderCreateSchema = new RestToolSchema
{
    Id = "orders.create",
    Name = "Create Order",
    BaseUri = new Uri("https://api.store.com"),
    Path = "/v1/orders",
    Method = HttpMethod.Post,
    JsonBodyTemplate = """
    {
        "customer_id": "{{customer_id}}",
        "items": {{items_json}},
        "shipping_address": {
            "street": "{{street}}",
            "city": "{{city}}",
            "postal_code": "{{postal_code}}"
        }
    }
    """,
    Headers = 
    {
        ["Content-Type"] = ":application/json",
        ["Authorization"] = "secret:store_api_key"
    },
    CacheEnabled = false,
    TimeoutSeconds = 30
};

Best Practices

1. Schema Design

  • Use descriptive IDs: Clear, hierarchical naming (e.g., "api.weather.current")
  • Provide descriptions: Help developers understand tool purpose
  • Set appropriate timeouts: Balance responsiveness with reliability
  • Configure caching: Enable for read operations, disable for mutations

2. Security

  • Use secret resolver: Never hardcode API keys
  • Validate inputs: Sanitize user-provided data
  • Set timeouts: Prevent hanging requests
  • Monitor usage: Track API consumption and errors

3. Performance

  • Enable caching: Cache responses for read operations
  • Set TTL appropriately: Balance freshness with performance
  • Use connection pooling: Reuse HttpClient instances
  • Monitor metrics: Track response times and error rates

4. Error Handling

  • Handle HTTP errors: Check status codes and error responses
  • Implement retries: Use exponential backoff for transient failures
  • Log failures: Provide context for debugging
  • Graceful degradation: Continue execution when possible

5. Testing

  • Mock external APIs: Use test doubles for development
  • Validate schemas: Ensure schema definitions are correct
  • Test error scenarios: Verify error handling behavior
  • Performance testing: Validate caching and timeout behavior

Troubleshooting

Common Issues

Timeout errors: * Check network connectivity * Verify API endpoint availability * Adjust timeout settings in schema * Monitor API response times

Authentication failures: * Verify secret resolver configuration * Check API key validity * Ensure proper header formatting * Validate authorization scopes

Cache issues: * Verify cache configuration * Check TTL settings * Monitor memory usage * Validate cache key generation

Schema validation errors: * Check required properties * Verify URI format * Validate HTTP method * Ensure parameter mappings

Debug Information

Enable detailed logging for troubleshooting:

// Enable debug logging
var logger = LoggerFactory.Create(builder => 
    builder.SetMinimumLevel(LogLevel.Debug)
).CreateLogger<RestToolGraphNode>();

var restNode = new RestToolGraphNode(schema, httpClient, logger: logger);

// Check execution details
var result = await restNode.ExecuteAsync(kernel, arguments);
Console.WriteLine($"Status: {result.GetValue<int>("status_code")}");
Console.WriteLine($"Response: {result.GetValue<string>("response_body")}");
Console.WriteLine($"From cache: {result.GetValue<bool>("from_cache")}");

See Also

Examples