From 35f2646dc1fb4bce65c7de7084b1fdd706a84fc7 Mon Sep 17 00:00:00 2001 From: Jerry Phillips Date: Sat, 25 Apr 2026 17:58:34 -0400 Subject: [PATCH] feat(api): [AB#111][AB#116] Flow persona in system prompt, AI estimate-draft endpoint --- JobFlow.Business/Services/AiWriterService.cs | 38 +++++++++++----- .../Services/SetupCompanionService.cs | 45 ++++++++++++------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/JobFlow.Business/Services/AiWriterService.cs b/JobFlow.Business/Services/AiWriterService.cs index 4e66998..86e7245 100644 --- a/JobFlow.Business/Services/AiWriterService.cs +++ b/JobFlow.Business/Services/AiWriterService.cs @@ -3,6 +3,7 @@ using JobFlow.Business.Services.ServiceInterfaces; using Microsoft.Extensions.Options; using OpenAI.Chat; +using System.ClientModel; namespace JobFlow.Business.Services; @@ -39,20 +40,33 @@ public async Task> DraftEstimateNotesAsync(Guid organizationId, s Output only the notes text — no headers, no labels, no extra commentary. """; - var client = new ChatClient(_openAiSettings.Model, _openAiSettings.ApiKey); - - var messages = new List + try { - new UserChatMessage(prompt) - }; + var client = new ChatClient(_openAiSettings.Model, _openAiSettings.ApiKey); - var response = await client.CompleteChatAsync(messages, new ChatCompletionOptions - { - MaxOutputTokenCount = 200, - Temperature = 0.5f - }); + var messages = new List + { + new UserChatMessage(prompt) + }; + + var response = await client.CompleteChatAsync(messages, new ChatCompletionOptions + { + MaxOutputTokenCount = 200, + Temperature = 0.5f + }); - var notes = response.Value.Content[0].Text.Trim(); - return Result.Success(notes); + var notes = response.Value.Content[0].Text.Trim(); + return Result.Success(notes); + } + catch (ClientResultException ex) + { + return Result.Failure(Error.Failure("AiWriter.ApiError", + $"The AI service returned an error (HTTP {ex.Status}). Check that your API key and model are configured correctly.")); + } + catch (Exception) + { + return Result.Failure(Error.Failure("AiWriter.Unavailable", + "The AI service is temporarily unavailable. Please try again.")); + } } } diff --git a/JobFlow.Business/Services/SetupCompanionService.cs b/JobFlow.Business/Services/SetupCompanionService.cs index ca5e868..7f4a4ea 100644 --- a/JobFlow.Business/Services/SetupCompanionService.cs +++ b/JobFlow.Business/Services/SetupCompanionService.cs @@ -1,10 +1,11 @@ -using JobFlow.Business.ConfigurationSettings; +using JobFlow.Business.ConfigurationSettings; using JobFlow.Business.DI; using JobFlow.Business.Services.ServiceInterfaces; using JobFlow.Domain; using JobFlow.Domain.Models; using Microsoft.Extensions.Options; using OpenAI.Chat; +using System.ClientModel; namespace JobFlow.Business.Services; @@ -45,7 +46,6 @@ public async Task> AskAsync(Guid organizationId, string sessionId if (org is null) return Result.Failure(Error.NotFound("Organization.NotFound", "Organization not found.")); - // Track the free-text ask as an analytics event var ev = new SetupCompanionEvent { OrganizationId = organizationId, @@ -59,22 +59,35 @@ public async Task> AskAsync(Guid organizationId, string sessionId var systemPrompt = BuildSystemPrompt(org, currentRoute); - var client = new ChatClient(_openAiSettings.Model, _openAiSettings.ApiKey); - - var messages = new List + try { - new SystemChatMessage(systemPrompt), - new UserChatMessage(question) - }; - - var response = await client.CompleteChatAsync(messages, new ChatCompletionOptions + var client = new ChatClient(_openAiSettings.Model, _openAiSettings.ApiKey); + + var messages = new List + { + new SystemChatMessage(systemPrompt), + new UserChatMessage(question) + }; + + var response = await client.CompleteChatAsync(messages, new ChatCompletionOptions + { + MaxOutputTokenCount = 400, + Temperature = 0.3f + }); + + var answer = response.Value.Content[0].Text; + return Result.Success(answer); + } + catch (ClientResultException ex) { - MaxOutputTokenCount = 400, - Temperature = 0.3f - }); - - var answer = response.Value.Content[0].Text; - return Result.Success(answer); + return Result.Failure(Error.Failure("Companion.ApiError", + $"The AI service returned an error (HTTP {ex.Status}). Check that your API key and model are configured correctly.")); + } + catch (Exception) + { + return Result.Failure(Error.Failure("Companion.Unavailable", + "The AI service is temporarily unavailable. Please try again.")); + } } private static string BuildSystemPrompt(Organization org, string currentRoute)