feat: migrate chat backend to Semantic Kernel with tool calling support
Replace manual HTTP proxy in ChatController with Semantic Kernel's OpenAI chat completion service pointed at CLIProxyAPI. Add extraction plugin with validation function for structured field extraction from natural language, enabling an agentic loop with auto-retry and human-in-the-loop escalation. - Add Microsoft.SemanticKernel 1.74.0 with OpenAI connector - Create ExtractedFields schema and ValidationResult models - Create ExtractionPlugin with [KernelFunction] validation - Rewrite ChatController to use IChatCompletionService streaming - Configure FunctionChoiceBehavior.Auto() for tool calling - Preserve existing SSE contract (client unchanged) - Update tests to mock SK services, add plugin and integration tests - Archive multi-turn-conversations and migrate-to-semantic-kernel changes - Sync specs for agent-extraction, semantic-kernel-integration, chat-streaming Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
// Program.cs -- ASP.NET Core Web API entry point for ChatAgent.
|
||||
//
|
||||
// These using directives bring in the Semantic Kernel extension methods for DI registration.
|
||||
// Without them, the AddOpenAIChatCompletion() and AddKernel() methods won't be found.
|
||||
using Microsoft.SemanticKernel;
|
||||
//
|
||||
// This is the backend server. In Phase 1, it only serves a health check endpoint.
|
||||
// In later phases, it will proxy OpenAI API calls (keeping the API key server-side)
|
||||
// and manage JSON file storage for conversation persistence.
|
||||
@@ -16,14 +20,43 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// for explicit structure -- each controller is a separate file with clear routing (D-05).
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// Register a named HttpClient for proxying requests to the Responses API.
|
||||
// The base URL comes from appsettings.json (server-side config, not exposed to the browser).
|
||||
// IHttpClientFactory manages the underlying HttpMessageHandler lifetime.
|
||||
builder.Services.AddHttpClient("ResponsesApi", client =>
|
||||
{
|
||||
var baseUrl = builder.Configuration["ResponsesApi:BaseUrl"] ?? "http://localhost:8317";
|
||||
client.BaseAddress = new Uri(baseUrl);
|
||||
});
|
||||
// --- Semantic Kernel Setup ---
|
||||
//
|
||||
// Semantic Kernel (SK) is an AI orchestration framework from Microsoft. It provides:
|
||||
// - Chat completion connectors (OpenAI, Azure OpenAI, etc.)
|
||||
// - Plugin system for exposing C# methods as tools the LLM can call
|
||||
// - Automatic function calling (the LLM decides when to invoke tools)
|
||||
// - Streaming support for token-by-token delivery
|
||||
//
|
||||
// The "Kernel" is the central object: it holds the AI service, plugins, and configuration.
|
||||
// We register it in DI so controllers can inject it.
|
||||
|
||||
// Read the CLIProxyAPI proxy URL and model from appsettings.json.
|
||||
// The OpenAI connector works with any OpenAI-compatible API endpoint,
|
||||
// so we point it at our local CLIProxyAPI proxy rather than OpenAI directly.
|
||||
// IMPORTANT: The base URL must include "/v1" because the OpenAI SDK appends
|
||||
// "chat/completions" directly to the base URL. Without "/v1", requests would
|
||||
// hit "/chat/completions" instead of "/v1/chat/completions" and get a 404.
|
||||
var responsesApiBaseUrl = builder.Configuration["ResponsesApi:BaseUrl"] ?? "http://localhost:8317/v1";
|
||||
var model = builder.Configuration["ResponsesApi:Model"] ?? "claude-sonnet-4-6";
|
||||
|
||||
// AddOpenAIChatCompletion registers an IChatCompletionService in DI.
|
||||
// The "endpoint" parameter lets us target any OpenAI-compatible API (here: CLIProxyAPI).
|
||||
// The "apiKey" is required by the connector but CLIProxyAPI may not check it,
|
||||
// so we use a placeholder. In production, this would be a real API key.
|
||||
builder.Services.AddOpenAIChatCompletion(
|
||||
modelId: model,
|
||||
endpoint: new Uri(responsesApiBaseUrl),
|
||||
apiKey: builder.Configuration["ResponsesApi:ApiKey"] ?? "not-needed");
|
||||
|
||||
// AddKernel() registers the Kernel class itself in DI. It automatically picks up
|
||||
// any AI services (like the chat completion above) that are already registered.
|
||||
builder.Services.AddKernel();
|
||||
|
||||
// Register the ExtractionPlugin so the Kernel can expose its [KernelFunction] methods
|
||||
// as tools. When the LLM sees these tools, it can decide to call them during a conversation
|
||||
// to validate extracted data. The plugin is registered as a singleton via DI.
|
||||
builder.Services.AddSingleton<ChatAgent.Api.Plugins.ExtractionPlugin>();
|
||||
|
||||
// AddCors() registers Cross-Origin Resource Sharing services.
|
||||
// CORS is REQUIRED because the Blazor WASM client runs on a different origin
|
||||
|
||||
Reference in New Issue
Block a user