Skip to content

Commit e0276b7

Browse files
committed
feat: Three-Tier Content-Aware Reasoning System
Implements a config-driven complexity scoring system that assigns LOW/MEDIUM/HIGH reasoning tiers to each COBOL chunk based on 19 regex indicators (nested IFs, PERFORM VARYING, EVALUATE/WHEN, etc.). Key changes: - AgentBase.cs: Per-tier reasoning effort and token multipliers - ResponsesApiClient.cs: Tier-aware token budget and effort routing - Settings.cs: New ThreeTierReasoning config section with overrides - appsettings.json: Default complexity indicators and tier thresholds - Program.cs: Wires three-tier settings into DI container - ai-config.env: Environment variable overrides for tier tuning - doctor.sh: Speed profile CLI (FAST/BALANCED/THOROUGH) - README.md: Speed profile documentation - CHANGELOG.md: Version 2.4.0 entry Stabilizes dual model strategy (chat for RE/chat-with-code, codex for conversion) with automatic fallback chain. Fixes #26
1 parent 43a0797 commit e0276b7

File tree

10 files changed

+898
-71
lines changed

10 files changed

+898
-71
lines changed

Agents/Infrastructure/AgentBase.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,88 @@ protected async Task<string> ExecuteChatCompletionAsync(
170170
var response = await ExecuteChatCompletionAsync(systemPrompt, userPrompt, contextIdentifier);
171171
return (response, false, null);
172172
}
173+
// ── Reasoning exhaustion catch (before transient error catch) ──
174+
catch (ReasoningExhaustionException rex) when (UseResponsesApi && ResponsesClient != null)
175+
{
176+
var profile = ResponsesClient.Profile;
177+
var maxExhaustionRetries = profile.ReasoningExhaustionMaxRetries;
178+
179+
Logger.LogWarning(
180+
"[{Agent}] Reasoning exhaustion for {Context}: {Message}",
181+
AgentName, contextIdentifier, rex.Message);
182+
183+
EnhancedLogger?.LogBehindTheScenes("REASONING_EXHAUSTION", "DETECTED",
184+
$"max_output_tokens={rex.MaxOutputTokens}, reasoning={rex.ReasoningTokens}, " +
185+
$"output={rex.ActualOutputTokens}, effort='{rex.ReasoningEffort}'", AgentName);
186+
187+
// Escalation loop: increase tokens and promote reasoning effort
188+
var currentMaxTokens = rex.MaxOutputTokens;
189+
var currentEffort = rex.ReasoningEffort;
190+
191+
for (int exhaustionRetry = 0; exhaustionRetry < maxExhaustionRetries; exhaustionRetry++)
192+
{
193+
// Double the output tokens
194+
currentMaxTokens = (int)(currentMaxTokens * profile.ReasoningExhaustionRetryMultiplier);
195+
// Cap at profile maximum
196+
currentMaxTokens = Math.Min(currentMaxTokens, profile.MaxOutputTokens);
197+
198+
// Promote reasoning effort: low → medium → high
199+
if (currentEffort == profile.LowReasoningEffort && currentEffort != profile.MediumReasoningEffort)
200+
currentEffort = profile.MediumReasoningEffort;
201+
else if (currentEffort == profile.MediumReasoningEffort && currentEffort != profile.HighReasoningEffort)
202+
currentEffort = profile.HighReasoningEffort;
203+
204+
// ACTION 3: Thrash guard — if already at max tokens AND max effort, don't burn another API call
205+
if (currentMaxTokens >= profile.MaxOutputTokens && currentEffort == profile.HighReasoningEffort
206+
&& exhaustionRetry > 0)
207+
{
208+
Logger.LogError(
209+
"[{Agent}] Thrash guard: already at max tokens ({Tokens}) and max effort ('{Effort}') " +
210+
"for {Context}. Failing fast — further retries are hopeless.",
211+
AgentName, currentMaxTokens, currentEffort, contextIdentifier);
212+
213+
EnhancedLogger?.LogBehindTheScenes("REASONING_EXHAUSTION", "THRASH_GUARD",
214+
$"Stopped retrying: tokens={currentMaxTokens} (max), effort='{currentEffort}' (max)", AgentName);
215+
216+
break;
217+
}
218+
219+
Logger.LogInformation(
220+
"[{Agent}] Reasoning exhaustion retry {Retry}/{MaxRetries} for {Context}: " +
221+
"max_output_tokens={Tokens}, effort='{Effort}'",
222+
AgentName, exhaustionRetry + 1, maxExhaustionRetries,
223+
contextIdentifier, currentMaxTokens, currentEffort);
224+
225+
try
226+
{
227+
var retryResponse = await ResponsesClient.GetResponseAsync(
228+
systemPrompt, userPrompt, currentMaxTokens, currentEffort);
229+
230+
EnhancedLogger?.LogBehindTheScenes("REASONING_EXHAUSTION_RECOVERED", "SUCCESS",
231+
$"Recovered on retry {exhaustionRetry + 1} with tokens={currentMaxTokens}, effort='{currentEffort}'",
232+
AgentName);
233+
234+
ChatLogger?.LogAIResponse(AgentName, contextIdentifier, retryResponse);
235+
return (retryResponse, false, null);
236+
}
237+
catch (ReasoningExhaustionException)
238+
{
239+
// Still exhausted, continue escalation loop
240+
Logger.LogWarning(
241+
"[{Agent}] Still exhausted after retry {Retry} with tokens={Tokens}",
242+
AgentName, exhaustionRetry + 1, currentMaxTokens);
243+
}
244+
}
245+
246+
// All exhaustion retries failed
247+
lastException = rex;
248+
Logger.LogError(
249+
"[{Agent}] All {MaxRetries} reasoning exhaustion retries failed for {Context}",
250+
AgentName, maxExhaustionRetries, contextIdentifier);
251+
252+
return (string.Empty, true, $"Reasoning exhaustion: all {maxExhaustionRetries} escalation retries failed");
253+
}
254+
// ── END Reasoning exhaustion ──
173255
catch (Exception ex) when (IsTransientError(ex) && attempt < maxRetries)
174256
{
175257
lastException = ex;

0 commit comments

Comments
 (0)