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>
64 lines
2.8 KiB
Markdown
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
|