Skip to content

Commit 95a1966

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 95a1966

File tree

10 files changed

+1307
-71
lines changed

10 files changed

+1307
-71
lines changed

Agents/Infrastructure/AgentBase.cs

Lines changed: 484 additions & 0 deletions
Large diffs are not rendered by default.

Agents/Infrastructure/ResponsesApiClient.cs

Lines changed: 235 additions & 50 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this repository are documented here.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.4.0] - 2026-02-16
9+
10+
### Added
11+
- **Speed Profile Selection** - New interactive prompt in `doctor.sh` lets you choose between four speed profiles before running migrations, reverse engineering, or conversion-only:
12+
- **TURBO** — Low reasoning on ALL files with no exceptions. 16K token cap, minimal multipliers. Designed for testing and smoke runs where speed matters more than quality.
13+
- **FAST** — Low reasoning on most files, medium only on the most complex ones. 32K token cap. Good for quick iterations and proof-of-concept runs.
14+
- **BALANCED** (default) — Uses the three-tier content-aware reasoning system. Simple files get low effort, complex files get high effort. Uses `appsettings.json` defaults.
15+
- **THOROUGH** — Maximum reasoning on all files regardless of complexity. Best for critical codebases where accuracy matters more than speed.
16+
- **Shared `select_speed_profile()` function** — Called from `run_migration()`, `run_reverse_engineering()`, and `run_conversion_only()`. Sets `CODEX_*` environment variables that are picked up by `Program.cs` `OverrideSettingsFromEnvironment()` at startup — no C# changes needed.
17+
- **Adaptive Re-Chunking on Output Exhaustion** — When reasoning exhaustion retries fail (all escalation attempts exhausted), `AgentBase` now automatically splits the COBOL source at the best semantic boundary (DIVISION > SECTION > paragraph > midpoint) and processes each half independently with 50-line overlap for context continuity. Results are merged with duplicate package/import/class removal and validated for truncation signals. This solves the TURBO/FAST paradox where small output token caps caused repeated exhaustion failures rather than triggering the existing input-size-based chunking.
18+
19+
### Changed
20+
- **README.md** — Added Speed Profile documentation with profile comparison table
21+
- **doctor.sh** — Added `select_speed_profile()` function and integrated into all three run commands
22+
823
## [2.3.1] - 2026-02-12
924

1025
### Fixed

Config/ai-config.env

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,38 @@ LOG_LEVEL="Information"
7878
ENABLE_CHAT_LOGGING="true"
7979
ENABLE_API_CALL_LOGGING="true"
8080

81+
# =============================================================================
82+
# 4. MODEL PROFILE SETTINGS (Three-Tier Reasoning)
83+
# =============================================================================
84+
# Override CodexProfile or ChatProfile values from appsettings.json at runtime.
85+
# All values are optional — defaults come from appsettings.json.
86+
87+
# --- Codex Profile (Responses API) ---
88+
# CODEX_LOW_REASONING_EFFORT="low"
89+
# CODEX_MEDIUM_REASONING_EFFORT="medium"
90+
# CODEX_HIGH_REASONING_EFFORT="high"
91+
# CODEX_MEDIUM_THRESHOLD="5"
92+
# CODEX_HIGH_THRESHOLD="15"
93+
# CODEX_LOW_MULTIPLIER="1.5"
94+
# CODEX_MEDIUM_MULTIPLIER="2.5"
95+
# CODEX_HIGH_MULTIPLIER="3.5"
96+
# CODEX_MIN_OUTPUT_TOKENS="32768"
97+
# CODEX_MAX_OUTPUT_TOKENS="100000"
98+
# CODEX_TIMEOUT_SECONDS="900"
99+
# CODEX_TOKENS_PER_MINUTE="500000"
100+
# CODEX_REQUESTS_PER_MINUTE="1000"
101+
# CODEX_PIC_DENSITY_FLOOR="0.25"
102+
# CODEX_LEVEL_DENSITY_FLOOR="0.30"
103+
# CODEX_ENABLE_AMPLIFIERS="true"
104+
# CODEX_EXHAUSTION_MAX_RETRIES="2"
105+
# CODEX_EXHAUSTION_RETRY_MULTIPLIER="2.0"
106+
107+
# --- Chat Profile (Chat Completions API) ---
108+
# CHAT_TIMEOUT_SECONDS="600"
109+
# CHAT_TOKENS_PER_MINUTE="300000"
110+
# CHAT_MIN_OUTPUT_TOKENS="16384"
111+
# CHAT_MAX_OUTPUT_TOKENS="65536"
112+
81113
# =============================================================================
82114
# Security Notes:
83115
# - Never commit real API keys.

Config/appsettings.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,73 @@
164164
"GenerateInterface": false
165165
}
166166
}
167+
},
168+
"CodexProfile": {
169+
"_description": "Model profile for Codex/Responses API models (gpt-5.1-codex-mini). Controls three-tier content-aware reasoning.",
170+
"LowReasoningEffort": "low",
171+
"MediumReasoningEffort": "medium",
172+
"HighReasoningEffort": "high",
173+
"MediumThreshold": 5,
174+
"HighThreshold": 15,
175+
"LowMultiplier": 1.5,
176+
"MediumMultiplier": 2.5,
177+
"HighMultiplier": 3.5,
178+
"MinOutputTokens": 32768,
179+
"MaxOutputTokens": 100000,
180+
"TimeoutSeconds": 900,
181+
"TokensPerMinute": 500000,
182+
"RequestsPerMinute": 1000,
183+
"PicDensityFloor": 0.25,
184+
"LevelDensityFloor": 0.30,
185+
"EnableAmplifiers": true,
186+
"CopyNearStorageBonus": 3,
187+
"ExecSqlDliBonus": 4,
188+
"ReasoningExhaustionMaxRetries": 2,
189+
"ReasoningExhaustionRetryMultiplier": 2.0,
190+
"ComplexityIndicators": [
191+
{ "Pattern": "EXEC\\s+SQL", "Weight": 3 },
192+
{ "Pattern": "EXEC\\s+CICS", "Weight": 4 },
193+
{ "Pattern": "EXEC\\s+DLI", "Weight": 4 },
194+
{ "Pattern": "PERFORM\\s+VARYING", "Weight": 2 },
195+
{ "Pattern": "PERFORM\\s+UNTIL", "Weight": 1 },
196+
{ "Pattern": "EVALUATE\\s+TRUE", "Weight": 2 },
197+
{ "Pattern": "SEARCH\\s+ALL", "Weight": 2 },
198+
{ "Pattern": "REDEFINES", "Weight": 2 },
199+
{ "Pattern": "OCCURS\\s+\\d+.*DEPENDING", "Weight": 3 },
200+
{ "Pattern": "OCCURS\\s+\\d+", "Weight": 1 },
201+
{ "Pattern": "COMPUTE\\b", "Weight": 1 },
202+
{ "Pattern": "INSPECT\\b", "Weight": 1 },
203+
{ "Pattern": "STRING\\b", "Weight": 1 },
204+
{ "Pattern": "UNSTRING\\b", "Weight": 2 },
205+
{ "Pattern": "CALL\\s+'[^']+'", "Weight": 2 },
206+
{ "Pattern": "ALTER\\b", "Weight": 3 },
207+
{ "Pattern": "GO\\s+TO\\s+DEPENDING", "Weight": 3 },
208+
{ "Pattern": "COPY\\b", "Weight": 1 },
209+
{ "Pattern": "REPLACE\\b", "Weight": 2 }
210+
]
211+
},
212+
"ChatProfile": {
213+
"_description": "Model profile for Chat Completions API models (gpt-5.2-chat). Chat doesn't support 'low' reasoning.",
214+
"LowReasoningEffort": "medium",
215+
"MediumReasoningEffort": "medium",
216+
"HighReasoningEffort": "high",
217+
"MediumThreshold": 5,
218+
"HighThreshold": 15,
219+
"LowMultiplier": 1.5,
220+
"MediumMultiplier": 2.0,
221+
"HighMultiplier": 2.5,
222+
"MinOutputTokens": 16384,
223+
"MaxOutputTokens": 65536,
224+
"TimeoutSeconds": 600,
225+
"TokensPerMinute": 300000,
226+
"RequestsPerMinute": 1000,
227+
"PicDensityFloor": 0.25,
228+
"LevelDensityFloor": 0.30,
229+
"EnableAmplifiers": false,
230+
"CopyNearStorageBonus": 0,
231+
"ExecSqlDliBonus": 0,
232+
"ReasoningExhaustionMaxRetries": 1,
233+
"ReasoningExhaustionRetryMultiplier": 1.5,
234+
"ComplexityIndicators": []
167235
}
168236
}

Models/Settings.cs

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ public class AppSettings
6060
/// Controls how converted code is organized into files, packages/namespaces, and classes.
6161
/// </summary>
6262
public AssemblySettings AssemblySettings { get; set; } = new AssemblySettings();
63+
64+
/// <summary>
65+
/// Gets or sets the model profile for Codex/Responses API models.
66+
/// Loaded from appsettings.json "CodexProfile" section.
67+
/// </summary>
68+
public ModelProfileSettings CodexProfile { get; set; } = new ModelProfileSettings();
69+
70+
/// <summary>
71+
/// Gets or sets the model profile for Chat Completions API models.
72+
/// Loaded from appsettings.json "ChatProfile" section.
73+
/// </summary>
74+
public ModelProfileSettings ChatProfile { get; set; } = new ModelProfileSettings();
6375
}
6476

6577
/// <summary>
@@ -84,20 +96,21 @@ public class AISettings
8496

8597
/// <summary>
8698
/// Gets or sets the model ID for general use.
99+
/// Must be configured in appsettings.json or env vars — no default model name.
87100
/// </summary>
88-
public string ModelId { get; set; } = "gpt-4.1";
101+
public string ModelId { get; set; } = string.Empty;
89102

90103
/// <summary>
91104
/// Gets or sets the model ID for the COBOL analyzer.
105+
/// Falls back to ModelId when empty.
92106
/// </summary>
93-
public string CobolAnalyzerModelId { get; set; } = "gpt-4.1";
107+
public string CobolAnalyzerModelId { get; set; } = string.Empty;
94108

95109
/// <summary>
96110
/// Gets or sets the model ID for the Java converter.
97-
/// <summary>
98-
/// Gets or sets the model ID for the Java converter.
111+
/// Falls back to ModelId when empty.
99112
/// </summary>
100-
public string JavaConverterModelId { get; set; } = "gpt-4.1";
113+
public string JavaConverterModelId { get; set; } = string.Empty;
101114

102115
/// <summary>
103116
/// Gets or sets the model ID for the dependency mapper.
@@ -106,13 +119,15 @@ public class AISettings
106119

107120
/// <summary>
108121
/// Gets or sets the model ID for the unit test generator.
122+
/// Falls back to ModelId when empty.
109123
/// </summary>
110-
public string UnitTestModelId { get; set; } = "gpt-4.1";
124+
public string UnitTestModelId { get; set; } = string.Empty;
111125

112126
/// <summary>
113127
/// Gets or sets the deployment name for Azure OpenAI.
128+
/// Must be configured in appsettings.json or env vars — no default deployment name.
114129
/// </summary>
115-
public string DeploymentName { get; set; } = "gpt-4.1";
130+
public string DeploymentName { get; set; } = string.Empty;
116131

117132
// Optional chat-specific settings (used for portal/chat/report); falls back to DeploymentName/Endpoint/ApiKey when not set
118133
public string ChatDeploymentName { get; set; } = string.Empty;
@@ -226,3 +241,119 @@ public class Neo4jSettings
226241
/// </summary>
227242
public string Database { get; set; } = "neo4j";
228243
}
244+
245+
// ============================================================================
246+
// Three-Tier Content-Aware Reasoning — Model Profile Settings
247+
// ============================================================================
248+
249+
/// <summary>
250+
/// A single complexity indicator: a regex pattern with a weight.
251+
/// Matched against COBOL source to calculate a complexity score.
252+
/// Loaded from appsettings.json CodexProfile.ComplexityIndicators array.
253+
/// </summary>
254+
public class ComplexityIndicator
255+
{
256+
/// <summary>
257+
/// Regex pattern to match in the COBOL source (case-insensitive).
258+
/// Example: "EXEC\\s+SQL", "EXEC\\s+CICS", "PERFORM\\s+VARYING"
259+
/// </summary>
260+
public string Pattern { get; set; } = string.Empty;
261+
262+
/// <summary>
263+
/// Weight added to complexity score per match.
264+
/// Typical range: 1-5. Higher = more complex.
265+
/// </summary>
266+
public int Weight { get; set; } = 1;
267+
}
268+
269+
/// <summary>
270+
/// Model profile settings for content-aware reasoning effort and token management.
271+
/// One instance per API type (Codex vs Chat). No hardcoded model names —
272+
/// all tuning comes from appsettings.json / env vars.
273+
///
274+
/// C# defaults are a conservative safety net. appsettings.json provides
275+
/// the actual model-appropriate values.
276+
/// </summary>
277+
public class ModelProfileSettings
278+
{
279+
// ── Reasoning effort labels ──────────────────────────────────────────
280+
/// <summary>Reasoning effort string for LOW complexity. e.g. "low" or "medium".</summary>
281+
public string LowReasoningEffort { get; set; } = "medium";
282+
283+
/// <summary>Reasoning effort string for MEDIUM complexity.</summary>
284+
public string MediumReasoningEffort { get; set; } = "medium";
285+
286+
/// <summary>Reasoning effort string for HIGH complexity.</summary>
287+
public string HighReasoningEffort { get; set; } = "high";
288+
289+
// ── Complexity score thresholds ──────────────────────────────────────
290+
/// <summary>Score at or above which we jump from low → medium tier.</summary>
291+
public int MediumThreshold { get; set; } = 5;
292+
293+
/// <summary>Score at or above which we jump from medium → high tier.</summary>
294+
public int HighThreshold { get; set; } = 15;
295+
296+
// ── Token multipliers per tier ───────────────────────────────────────
297+
/// <summary>Output-token multiplier for LOW complexity (relative to estimated input).</summary>
298+
public double LowMultiplier { get; set; } = 1.5;
299+
300+
/// <summary>Output-token multiplier for MEDIUM complexity.</summary>
301+
public double MediumMultiplier { get; set; } = 2.0;
302+
303+
/// <summary>Output-token multiplier for HIGH complexity.</summary>
304+
public double HighMultiplier { get; set; } = 3.0;
305+
306+
// ── Token limits ─────────────────────────────────────────────────────
307+
/// <summary>Minimum max_output_tokens regardless of estimation.</summary>
308+
public int MinOutputTokens { get; set; } = 16384;
309+
310+
/// <summary>Maximum max_output_tokens cap.</summary>
311+
public int MaxOutputTokens { get; set; } = 65536;
312+
313+
// ── Operational limits ───────────────────────────────────────────────
314+
/// <summary>HTTP timeout in seconds for this model profile.</summary>
315+
public int TimeoutSeconds { get; set; } = 600;
316+
317+
/// <summary>Tokens-per-minute rate limit.</summary>
318+
public int TokensPerMinute { get; set; } = 300_000;
319+
320+
/// <summary>Requests-per-minute rate limit.</summary>
321+
public int RequestsPerMinute { get; set; } = 1_000;
322+
323+
// ── Structural baseline floors ───────────────────────────────────────
324+
/// <summary>
325+
/// PIC density floor: if (PIC count / meaningful lines) exceeds this,
326+
/// add +3 to complexity score. Catches data-heavy programs.
327+
/// </summary>
328+
public double PicDensityFloor { get; set; } = 0.25;
329+
330+
/// <summary>
331+
/// Level-number density floor: if (level-number count / meaningful lines) exceeds this,
332+
/// add +2 to complexity score.
333+
/// </summary>
334+
public double LevelDensityFloor { get; set; } = 0.30;
335+
336+
// ── COPY/EXEC amplifiers ─────────────────────────────────────────────
337+
/// <summary>Enable COPY/EXEC amplifier bonuses.</summary>
338+
public bool EnableAmplifiers { get; set; } = true;
339+
340+
/// <summary>Bonus added when COPY appears near WORKING-STORAGE/LINKAGE data.</summary>
341+
public int CopyNearStorageBonus { get; set; } = 3;
342+
343+
/// <summary>Bonus added for EXEC SQL or EXEC DLI presence.</summary>
344+
public int ExecSqlDliBonus { get; set; } = 4;
345+
346+
// ── Reasoning exhaustion retry ───────────────────────────────────────
347+
/// <summary>Max retries when reasoning exhaustion is detected.</summary>
348+
public int ReasoningExhaustionMaxRetries { get; set; } = 2;
349+
350+
/// <summary>Multiplier for max_output_tokens on each retry (e.g. 2.0 = double).</summary>
351+
public double ReasoningExhaustionRetryMultiplier { get; set; } = 2.0;
352+
353+
// ── Complexity indicators (config-driven regex) ──────────────────────
354+
/// <summary>
355+
/// List of regex patterns + weights for complexity scoring.
356+
/// Loaded from appsettings.json. Empty list = no content-based scoring (baseline only).
357+
/// </summary>
358+
public List<ComplexityIndicator> ComplexityIndicators { get; set; } = new();
359+
}

0 commit comments

Comments
 (0)