# Feature: SK Chat Endpoint with Tool Calling ## Target: ApplicationX (ASP.NET Core + Blazor WASM + MudBlazor) **Included**: wire-responses-api (superseded — SK version used), migrate-to-semantic-kernel **Lines to type**: ~40 | **Code equivalent**: ~120 lines | **Compression**: 3x ## Packages - Microsoft.SemanticKernel 1.74.0 - Microsoft.SemanticKernel.Connectors.OpenAI 1.74.0 ## Architecture POST /api/chat accepts conversation messages, runs them through Semantic Kernel's chat completion with auto tool calling, streams response as SSE. An ExtractionPlugin lets the LLM validate structured data it extracts from natural language, retrying autonomously before escalating to the user. ## Components ### ChatController: Controllers/ChatController.cs - [ApiController] POST endpoint, injects Kernel via DI - Converts List to SK ChatHistory (user/assistant roles) - Imports ExtractionPlugin per-request via _kernel.ImportPluginFromObject - Uses OpenAIPromptExecutionSettings with FunctionChoiceBehavior.Auto() - Streams via GetStreamingChatMessageContentsAsync, skips empty chunks - SSE output: `data: {"text":"..."}\n\n` per chunk, `data: [DONE]\n\n` at end - Error: `data: {"error":"..."}\n\n` then [DONE] - Catches TaskCanceledException silently (client disconnect) ### ExtractionPlugin: Plugins/ExtractionPlugin.cs - [KernelFunction("validate_extracted_fields")] - [Description] tells LLM: validates extracted fields against required schema - Accepts string fieldsJson, deserializes to ExtractedFields - Checks required fields non-null/non-empty, decimals > 0 - Returns JSON: {"IsValid": bool, "Errors": ["..."]} ### ExtractedFields: Shared/Models/ExtractedFields.cs - Required: Client(string?), Project(string?), Hours(decimal?), Rate(decimal?), Currency(string?), Date(string?) - Optional: Description(string?), PoNumber(string?) ### ValidationResult: Shared/Models/ValidationResult.cs - IsValid(bool), Errors(List) ### ChatRequest + ChatMessage: Shared/Models/ - ChatRequest: Messages(List) - ChatMessage: Role(string), Content(string), Timestamp(DateTime) ## Wiring (Program.cs, after AddControllers) 1. `using Microsoft.SemanticKernel;` at top (required for extension methods) 2. Read BaseUrl and Model from config section "ResponsesApi" 3. AddOpenAIChatCompletion(modelId, endpoint: new Uri(baseUrl), apiKey) 4. AddKernel() 5. AddSingleton() 6. CORS policy if Blazor client on different port ## Config (appsettings.json) - ResponsesApi:BaseUrl = "http://localhost:8317/v1" - ResponsesApi:Model = "claude-sonnet-4-6" ## Gotchas - Base URL MUST include /v1 — OpenAI SDK appends chat/completions directly - Plugin imported per-request, not at startup (avoids kernel state leaks) - SK has built-in auto-invoke limit — no need to set max retries - JsonSerializerOptions needs PropertyNameCaseInsensitive = true for deserialization