Fix AI price check truncation and JSON parse errors
Root cause: MaxTokens=4096 was too low — 25 items at ~250 tokens each hit the limit mid-array (logged error showed Path: $[17]). - MaxTokens: 4096 → 8192 - BatchSize: 25 → 15 items (keeps each response well under the limit) - StripJsonFences → ExtractJsonArray: now also handles prose before/after the JSON array, and recovers truncated responses by finding the last complete object and closing the array — so partial batches return whatever Claude finished rather than nothing - GET action: added try-catch around ResultsJson deserialization so a bad DB row shows a friendly "re-run" warning instead of a raw error page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService
|
||||
private readonly ILogger<AiCatalogPriceCheckService> _logger;
|
||||
|
||||
private const string Model = "claude-sonnet-4-6";
|
||||
private const int BatchSize = 25;
|
||||
private const int BatchSize = 15; // 25 items at ~250 tokens each exceeds 4096 output tokens
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
@@ -37,18 +37,44 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips optional ```json ... ``` fences that Claude sometimes adds despite instructions.
|
||||
/// Extracts a JSON array from Claude's response, handling three common failure modes:
|
||||
/// (1) ```json ... ``` fences wrapping the array,
|
||||
/// (2) prose text before or after the JSON array,
|
||||
/// (3) truncated responses where the closing ] is missing — in that case we close any
|
||||
/// open string and append ]} to produce a parseable (though incomplete) array so
|
||||
/// we recover whatever items Claude did finish.
|
||||
/// </summary>
|
||||
private static string StripJsonFences(string text)
|
||||
private static string ExtractJsonArray(string text)
|
||||
{
|
||||
var trimmed = text.Trim();
|
||||
|
||||
// Strip code fences
|
||||
if (trimmed.StartsWith("```"))
|
||||
{
|
||||
var firstNewline = trimmed.IndexOf('\n');
|
||||
if (firstNewline >= 0) trimmed = trimmed[(firstNewline + 1)..];
|
||||
if (trimmed.EndsWith("```")) trimmed = trimmed[..^3];
|
||||
trimmed = trimmed.Trim();
|
||||
}
|
||||
return trimmed.Trim();
|
||||
|
||||
// Find the outermost [ ... ] even when Claude adds prose around it
|
||||
var arrayStart = trimmed.IndexOf('[');
|
||||
if (arrayStart < 0) return "[]";
|
||||
trimmed = trimmed[arrayStart..];
|
||||
|
||||
var arrayEnd = trimmed.LastIndexOf(']');
|
||||
if (arrayEnd >= 0)
|
||||
return trimmed[..(arrayEnd + 1)];
|
||||
|
||||
// No closing bracket — response was truncated. Patch it so we can recover
|
||||
// whatever complete objects Claude did return.
|
||||
// Strategy: find the last complete }, and close the array after it.
|
||||
var lastComplete = trimmed.LastIndexOf("},");
|
||||
if (lastComplete < 0) lastComplete = trimmed.LastIndexOf('}');
|
||||
if (lastComplete >= 0)
|
||||
return trimmed[..(lastComplete + 1)] + "]";
|
||||
|
||||
return "[]";
|
||||
}
|
||||
|
||||
private static async Task<MessageResponse> SendAsync(AnthropicClient client, MessageParameters parameters)
|
||||
@@ -98,7 +124,7 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService
|
||||
var parameters = new MessageParameters
|
||||
{
|
||||
Model = Model,
|
||||
MaxTokens = 4096,
|
||||
MaxTokens = 8192,
|
||||
SystemMessage = systemPrompt,
|
||||
Messages = new List<Message>
|
||||
{
|
||||
@@ -110,7 +136,7 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService
|
||||
{
|
||||
var response = await SendAsync(client, parameters);
|
||||
var raw = response.Content.OfType<TextContent>().FirstOrDefault()?.Text ?? "[]";
|
||||
var json = StripJsonFences(raw);
|
||||
var json = ExtractJsonArray(raw);
|
||||
|
||||
var claudeItems = JsonSerializer.Deserialize<List<ClaudePriceCheckItem>>(json, JsonOpts) ?? new();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user