Files
AgenticCode/openspec/exports/migrate-to-semantic-kernel-spec.md
local d3300c7db9 feat: add extract-feature and export-spec portability skills
Two new OpenSpec skills for porting features to sandboxed codebases:
- /opsx:extract-feature generates minimal, printable code recipes
- /opsx:export-spec generates compact specs for AI-assisted reimplementation
Both support cumulative dependency analysis across archived changes.

Includes first export of migrate-to-semantic-kernel in all three formats:
code recipe (~120 lines), portable spec (~40 lines), OpenSpec variant (~25 lines).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:59:06 +01:00

64 lines
2.8 KiB
Markdown

# 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<ChatMessage> 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<string>)
### ChatRequest + ChatMessage: Shared/Models/
- ChatRequest: Messages(List<ChatMessage>)
- 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<ExtractionPlugin>()
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