Skip to content

Retrieval Agent Example

This example demonstrates a question-answering agent that retrieves relevant context from a knowledge base and synthesizes an answer using retrieval-augmented generation (RAG).

Objective

Learn how to implement retrieval-augmented generation workflows in Semantic Kernel Graph to: * Create a linear retrieval QA pipeline with three steps * Implement question analysis and search query generation * Retrieve relevant context from a knowledge base * Synthesize comprehensive answers using retrieved context * Demonstrate RAG-style question answering capabilities

Prerequisites

  • .NET 8.0 or later
  • OpenAI API Key configured in appsettings.json
  • Semantic Kernel Graph package installed
  • Kernel Memory configured for knowledge base operations
  • Basic understanding of Graph Concepts and RAG Patterns
  • Familiarity with Memory and Retrieval

Key Components

Concepts and Techniques

  • Retrieval-Augmented Generation (RAG): Combining retrieval with generation for accurate answers
  • Question Analysis: Understanding and reformulating user questions for better retrieval
  • Context Retrieval: Finding relevant information from knowledge bases
  • Answer Synthesis: Generating comprehensive answers using retrieved context
  • Knowledge Base Management: Indexing and searching structured information

Core Classes

  • GraphExecutor: Executor for retrieval agent workflows
  • FunctionGraphNode: Nodes for question analysis, retrieval, and answer synthesis
  • KernelMemoryGraphProvider: Provider for knowledge base operations
  • ConditionalEdge: Graph edges for workflow control
  • GraphState: State management for retrieval results and context

Running the Example

Getting Started

This example demonstrates retrieval-augmented generation (RAG) patterns with the Semantic Kernel Graph package. The code snippets below show you how to implement this pattern in your own applications.

Step-by-Step Implementation

1. Knowledge Base Setup

The example starts by setting up a knowledge base with sample content.

// Create a lightweight in-memory provider and seed the collection with sample docs
var memoryProvider = new SimpleMemoryProvider();
var collection = "kb_general";

await SeedKnowledgeBaseAsync(memoryProvider, collection);

private static async Task SeedKnowledgeBaseAsync(SimpleMemoryProvider provider, string collection)
{
    // Add a few short documents to the in-memory knowledge base
    await provider.SaveInformationAsync(collection,
        "The Semantic Kernel Graph is a powerful extension to build complex workflows with graphs, enabling conditional routing, memory integration, and performance metrics.",
        "kb-001",
        "Project overview",
        "category:overview");

    await provider.SaveInformationAsync(collection,
        "Data privacy is handled through encryption at rest and in transit, with role-based access controls and audit logging for compliance with GDPR and other regulations.",
        "kb-002",
        "Data privacy",
        "category:security");

    await provider.SaveInformationAsync(collection,
        "The quarterly business report shows 25% improvement in system performance, 40% reduction in response times, and 15% increase in user satisfaction scores.",
        "kb-003",
        "Business report",
        "category:performance");

    Console.WriteLine("✅ Knowledge base seeded with sample content");
}

2. Creating the Retrieval Agent

The agent is built with a linear three-step pipeline: analyze, retrieve, and answer.

// Compose a simple linear graph: analyze -> retrieve -> synthesize
var executor = new GraphExecutor(kernel);

var analyze = new FunctionGraphNode(
    kernel.CreateFunctionFromMethod((KernelArguments args) =>
    {
        // Implementation provided below in the Analysis function snippet
        var question = args.TryGetValue("user_question", out var q) ? q?.ToString() ?? string.Empty : string.Empty;
        var searchQuery = question.ToLowerInvariant()
            .Replace("what", string.Empty)
            .Replace("how", string.Empty)
            .Replace("benefits", "benefits advantages features")
            .Trim();

        args["search_query"] = searchQuery;
        return $"Search query generated: {searchQuery}";
    }, functionName: "analyze_question", description: "Analyze the user question"),
    "analyze_question").StoreResultAs("search_query");

var retrieve = new FunctionGraphNode(
    kernel.CreateFunctionFromMethod(async (KernelArguments args) =>
    {
        // Implementation provided below in the Retrieval function snippet
        return "retrieved";
    }, functionName: "retrieve_context", description: "Retrieve relevant context"),
    "retrieve_context").StoreResultAs("retrieved_context");

var synth = new FunctionGraphNode(
    kernel.CreateFunctionFromMethod((KernelArguments args) =>
    {
        // Implementation provided below in the Synthesis snippet
        return "answer";
    }, functionName: "synthesize_answer", description: "Synthesize the final answer"),
    "synthesize_answer").StoreResultAs("final_answer");

executor.AddNode(analyze);
executor.AddNode(retrieve);
executor.AddNode(synth);

executor.SetStartNode(analyze.NodeId);
executor.AddEdge(ConditionalEdge.CreateUnconditional(analyze, retrieve));
executor.AddEdge(ConditionalEdge.CreateUnconditional(retrieve, synth));

return executor;

3. Question Analysis Function

The analysis function understands user questions and generates focused search queries.

// Analysis function: converts a free-form question into a compact search query
private static KernelFunction CreateAnalyzeQuestionFunction(Kernel kernel)
{
    return KernelFunctionFactory.CreateFromMethod(
        (KernelArguments args) =>
        {
            var question = args.TryGetValue("user_question", out var q) ? q?.ToString() ?? string.Empty : string.Empty;

            // Simplify question text and expand some terms to improve retrieval
            var searchQuery = question.ToLowerInvariant()
                .Replace("what", string.Empty)
                .Replace("how", string.Empty)
                .Replace("benefits", "benefits advantages features")
                .Replace("handled", "handled managed implemented")
                .Replace("improvements", "improvements enhancements progress")
                .Trim();

            if (question.Contains("semantic kernel graph", StringComparison.OrdinalIgnoreCase))
            {
                searchQuery += " semantic kernel graph workflow";
            }

            if (question.Contains("data privacy", StringComparison.OrdinalIgnoreCase) || question.Contains("encryption", StringComparison.OrdinalIgnoreCase))
            {
                searchQuery += " data privacy encryption security compliance";
            }

            args["search_query"] = searchQuery;
            return $"Search query generated: {searchQuery}";
        },
        functionName: "analyze_question",
        description: "Analyzes user questions and generates focused search queries"
    );
}

4. Context Retrieval Function

The retrieval function searches the knowledge base for relevant information.

// Retrieval function: queries the knowledge base and formats results for synthesis
private static KernelFunction CreateRetrieveContextFunction(Kernel kernel, SimpleMemoryProvider memoryProvider, string collection)
{
    return KernelFunctionFactory.CreateFromMethod(
        async (KernelArguments args) =>
        {
            var query = args.TryGetValue("search_query", out var q) ? q?.ToString() ?? string.Empty : string.Empty;
            var topK = args.TryGetValue("top_k", out var tk) && tk is int k ? k : 5;
            var minScore = args.TryGetValue("min_score", out var ms) && ms is double s ? s : 0.0;

            // Retrieve relevant context from the in-memory provider
            var results = await memoryProvider.SearchAsync(collection, query, topK, minScore);

            if (!results.Any())
            {
                args["retrieved_context"] = "No relevant context found for the query.";
                return "No relevant context retrieved";
            }

            // Format retrieved context for answer synthesis
            var context = string.Join("\n\n", results.Select(r =>
                $"Source: {r.Metadata.GetValueOrDefault("source", "Unknown")}\nContent: {r.Text}"));

            args["retrieved_context"] = context;
            args["retrieval_count"] = results.Count;
            args["retrieval_score"] = results.Max(r => r.Score);

            return $"Retrieved {results.Count} relevant context items with max score {results.Max(r => r.Score):F3}";
        },
        functionName: "retrieve_context",
        description: "Retrieves relevant context from the knowledge base"
    );
}

5. Answer Synthesis Function

The synthesis function combines retrieved context into comprehensive answers.

// Synthesis function: combine retrieved context into a readable answer
private static KernelFunction CreateSynthesizeAnswerFunction(Kernel kernel)
{
    return KernelFunctionFactory.CreateFromMethod(
        (KernelArguments args) =>
        {
            var question = args.TryGetValue("user_question", out var q) ? q?.ToString() ?? string.Empty : string.Empty;
            var context = args.TryGetValue("retrieved_context", out var c) ? c?.ToString() ?? string.Empty : string.Empty;
            var retrievalCount = args.TryGetValue("retrieval_count", out var rc) && rc is int count ? count : 0;
            var retrievalScore = args.TryGetValue("retrieval_score", out var rs) && rs is double score ? score : 0.0;

            if (string.IsNullOrEmpty(context) || context.Contains("No relevant context found"))
            {
                return "I don't have enough information to answer that question accurately. Please try rephrasing.";
            }

            // Build a concise answer using retrieved context
            var answer = $"Based on the available information:\n\n{context}\n\n" +
                        $"This answer was synthesized from {retrievalCount} relevant sources (confidence: {retrievalScore:F2}).";

            args["final_answer"] = answer;
            return answer;
        },
        functionName: "synthesize_answer",
        description: "Synthesizes final answers using retrieved context"
    );
}

6. Question Processing

The example processes multiple questions to demonstrate the retrieval capabilities.

// Run a few sample questions through the retrieval agent to demonstrate behavior
var questions = new[]
{
    "What benefits does the Semantic Kernel Graph provide?",
    "How are data privacy and encryption handled?",
    "What improvements were reported in the quarterly business report?"
};

foreach (var q in questions)
{
    Console.WriteLine($"🧑‍💻 User: {q}");
    var args = new KernelArguments
    {
        ["user_question"] = q,
        ["top_k"] = 5,
        ["min_score"] = 0.0
    };

    var result = await executor.ExecuteAsync(kernel, args);
    var answer = result.GetValue<string>() ?? "No answer produced";
    Console.WriteLine($"🤖 Agent: {answer}\n");
    await Task.Delay(200);
}

7. Enhanced Knowledge Base Content

The example includes more comprehensive knowledge base content for better retrieval.

private static async Task SeedKnowledgeBaseAsync(KernelMemoryGraphProvider provider, string collection)
{
    var documents = new[]
    {
        new
        {
            Content = "The Semantic Kernel Graph is a powerful extension to build complex workflows with graphs, enabling conditional routing, memory integration, and performance metrics. It provides benefits such as improved workflow management, better error handling, and enhanced observability.",
            Id = "kb-001",
            Source = "Project overview",
            Tags = "category:overview,benefits,workflow"
        },
        new
        {
            Content = "Data privacy is handled through encryption at rest and in transit, with role-based access controls and audit logging for compliance with GDPR and other regulations. The system implements end-to-end encryption and provides granular permission management.",
            Id = "kb-002",
            Source = "Data privacy",
            Tags = "category:security,encryption,compliance"
        },
        new
        {
            Content = "The quarterly business report shows 25% improvement in system performance, 40% reduction in response times, and 15% increase in user satisfaction scores. These improvements were achieved through optimization efforts and infrastructure upgrades.",
            Id = "kb-003",
            Source = "Business report",
            Tags = "category:performance,metrics,improvements"
        },
        new
        {
            Content = "Semantic Kernel Graph features include advanced routing capabilities, checkpointing for long-running workflows, streaming execution support, and comprehensive monitoring and debugging tools.",
            Id = "kb-004",
            Source = "Feature overview",
            Tags = "category:features,routing,checkpointing"
        },
        new
        {
            Content = "Security measures include multi-factor authentication, encrypted communication channels, regular security audits, and compliance with industry standards like SOC 2 and ISO 27001.",
            Id = "kb-005",
            Source = "Security overview",
            Tags = "category:security,authentication,compliance"
        }
    };

    foreach (var doc in documents)
    {
        await provider.SaveInformationAsync(collection, doc.Content, doc.Id, doc.Source, doc.Tags);
    }

    Console.WriteLine($"✅ Knowledge base seeded with {documents.Length} documents");
}

8. Advanced Retrieval Options

The example supports configurable retrieval parameters for different use cases.

// Configure retrieval parameters based on question complexity
var retrievalParams = question.ToLowerInvariant() switch
{
    var q when q.Contains("benefits") || q.Contains("features") => 
        new { TopK = 3, MinScore = 0.4 }, // Focused retrieval for specific topics
    var q when q.Contains("how") || q.Contains("process") => 
        new { TopK = 5, MinScore = 0.35 }, // Broader retrieval for process questions
    var q when q.Contains("what") && q.Contains("improvements") => 
        new { TopK = 4, MinScore = 0.3 }, // Comprehensive retrieval for improvement questions
    _ => new { TopK = 5, MinScore = 0.35 } // Default parameters
};

var args = new KernelArguments
{
    ["user_question"] = question,
    ["top_k"] = retrievalParams.TopK,
    ["min_score"] = retrievalParams.MinScore
};

Expected Output

The example produces comprehensive output showing:

  • 🧑‍💻 User questions and search query analysis
  • 🔍 Context retrieval from knowledge base
  • 📊 Retrieval scores and result counts
  • 🤖 Synthesized answers using retrieved context
  • ✅ Complete RAG workflow execution
  • 📚 Knowledge base content and retrieval quality

Troubleshooting

Common Issues

  1. Knowledge Base Connection Failures: Ensure Kernel Memory is properly configured
  2. Retrieval Quality Issues: Adjust top_k and min_score parameters for better results
  3. Context Insufficiency: Verify knowledge base content and search query generation
  4. Answer Synthesis Failures: Check context formatting and synthesis logic

Debugging Tips

  • Monitor search query generation and refinement
  • Verify knowledge base content indexing and search functionality
  • Check retrieval parameters and scoring thresholds
  • Monitor answer synthesis quality and context utilization

See Also