From 9370fcdd8f36fcb395f51751b2f991cfcb137eda Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Sat, 25 Apr 2026 19:57:23 -0400 Subject: [PATCH] Reduce batch size to 10 and tighten AI price check prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Still seeing stubs despite MaxTokens=8192 — smaller batches and explicit word limits in the prompt eliminate any remaining truncation risk. - BatchSize: 15 → 10 (~1200 output tokens per batch vs. potential 3000+) - Prompt: added 20-word cap on assumptions, 25-word cap on reasoning - Prompt: strengthened "nothing before or after the '['" instruction - Error log: now includes item IDs and first 300 chars of raw response so the next failure tells us exactly what Claude returned - JS timing: updated batch divisor from 25 → 10 to match actual batch size Co-Authored-By: Claude Sonnet 4.6 --- .../Services/AiCatalogPriceCheckService.cs | 19 ++++++++++++------- .../wwwroot/js/catalog-price-check.js | 6 +++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs b/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs index da7b471..cdaae80 100644 --- a/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs +++ b/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs @@ -20,7 +20,7 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService private readonly ILogger _logger; private const string Model = "claude-sonnet-4-6"; - private const int BatchSize = 15; // 25 items at ~250 tokens each exceeds 4096 output tokens + private const int BatchSize = 10; // keep responses predictably short; 10 items × ~120 tokens ≈ 1200 output tokens private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true }; @@ -132,10 +132,11 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService } }; + var raw = string.Empty; try { var response = await SendAsync(client, parameters); - var raw = response.Content.OfType().FirstOrDefault()?.Text ?? "[]"; + raw = response.Content.OfType().FirstOrDefault()?.Text ?? "[]"; var json = ExtractJsonArray(raw); var claudeItems = JsonSerializer.Deserialize>(json, JsonOpts) ?? new(); @@ -164,8 +165,10 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService } catch (Exception ex) { - _logger.LogError(ex, "AI catalog price check batch failed"); - // Return stub verdicts so the rest of the report still renders + var preview = raw.Length > 300 ? raw[..300] + "…" : raw; + _logger.LogError(ex, + "AI price check batch failed for items [{ItemIds}]. Raw response preview: {RawPreview}", + string.Join(", ", batch.Select(b => b.Id)), preview); return batch.Select(item => new CatalogItemPriceVerdict { CatalogItemId = item.Id, @@ -220,10 +223,12 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService sb.AppendLine(); sb.AppendLine("If the item already has an ApproximateArea or EstimatedMinutes, use those instead of guessing."); sb.AppendLine(); - sb.AppendLine("Return ONLY a JSON array, no prose, no markdown fences. Use this exact schema for each element:"); + sb.AppendLine("IMPORTANT: Keep responses concise to avoid truncation. Limit assumptions to 20 words max. Limit reasoning to 25 words max."); + sb.AppendLine(); + sb.AppendLine("Return ONLY a JSON array — no prose, no markdown fences, nothing before or after the '['. Use this exact schema for each element:"); sb.AppendLine(@"{ ""catalogItemId"": , - ""assumptions"": """", + ""assumptions"": ""<≤20 words>"", ""estimatedSqFtMin"": , ""estimatedSqFtMax"": , ""estimatedMinutesMin"": , @@ -233,7 +238,7 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService ""suggestedPriceMin"": , ""suggestedPriceMax"": , ""confidence"": ""high|medium|low"", - ""reasoning"": ""<1-2 sentence explanation>"" + ""reasoning"": ""<≤25 words>"" }"); return sb.ToString(); } diff --git a/src/PowderCoating.Web/wwwroot/js/catalog-price-check.js b/src/PowderCoating.Web/wwwroot/js/catalog-price-check.js index af9bcb9..6d994c1 100644 --- a/src/PowderCoating.Web/wwwroot/js/catalog-price-check.js +++ b/src/PowderCoating.Web/wwwroot/js/catalog-price-check.js @@ -12,14 +12,14 @@ // Estimate total seconds based on item count (roughly 12s per batch of 25, min 15s). function estimateDuration(itemCount) { - var batches = Math.max(1, Math.ceil(itemCount / 25)); - return Math.max(15, batches * 12); + var batches = Math.max(1, Math.ceil(itemCount / 10)); + return Math.max(15, batches * 10); } // Messages keyed to approximate progress milestones (0–100). function messageAt(pct, batchCount) { if (pct < 10) return 'Loading catalog items…'; - if (pct < 20) return 'Sending items to Claude…'; + if (pct < 20) return 'Sending items for analysis…'; if (batchCount <= 1) { if (pct < 75) return 'Analyzing your catalog with AI…'; if (pct < 92) return 'Reviewing pricing data…';