feat: auto-apply agent hyperparameter recommendations (#460) - #828
Conversation
Add data classes, parser, registry, interface, and applicator service for the agent hyperparameter auto-apply feature. This includes: - HyperparameterApplicationResult for tracking applied/skipped/failed params - HyperparameterDefinition and HyperparameterValidationResult data classes - HyperparameterRegistry mapping ModelType -> LLM param names -> C# properties - HyperparameterResponseParser with multi-strategy LLM output extraction - IConfigurableModel<T> interface for post-construction hyperparameter access - AgentHyperparameterApplicator<T> service for reflection-based application Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add EnableAutoApplyHyperparameters option to AgentAssistanceOptions - Update Comprehensive preset to include auto-apply - Add Enable/Disable methods to AgentAssistanceOptionsBuilder - Add HyperparameterApplicationResult property to AgentRecommendation - Replace placeholder hyperparameter parsing with HyperparameterResponseParser - Replace stub in ApplyAgentRecommendationsCore with real application logic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ons hierarchy (#460) - Add IConfigurableModel<T> interface + GetOptions() to all 11 model base classes (RegressionBase, NonLinearRegressionBase, DecisionTreeRegressionBase, AsyncDecisionTreeRegressionBase, TimeSeriesModelBase, ClassifierBase, ClusteringBase, ReinforcementLearningAgentBase, MetaLearnerBase, NeuralNetworkBase, DiffusionModelBase) - Add protected ModelOptions Options property to NeuralNetworkBase - Create base options hierarchy: NeuralNetworkOptions, AudioNeuralNetworkOptions, DocumentNeuralNetworkOptions, FinancialNeuralNetworkOptions, ForecastingModelOptions, FinancialNLPOptions, PhysicsInformedOptions - Update PortfolioOptimizerOptions and RiskModelOptions to inherit from FinancialNeuralNetworkOptions - Set domain-specific Options in intermediate NN base class constructors (AudioNeuralNetworkBase, DocumentNeuralNetworkBase, FinancialModelBase, ForecastingModelBase, PortfolioOptimizerBase, RiskModelBase, FinancialNLPModelBase) - Standardize ClusteringOptions to inherit from ModelOptions (RandomState wraps Seed) - Standardize ReinforcementLearningOptions to inherit from ModelOptions - Standardize all 17 MetaLearning Options to inherit from ModelOptions (RandomSeed wraps Seed for backward compatibility) - Standardize SpiralNetOptions, MeshCNNOptions, MixtureOfExpertsOptions to inherit from NeuralNetworkOptions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Options = _options in all 28 financial model constructors (56 constructors total - ONNX + native mode each) so GetOptions() returns the model-specific options with all hyperparameters - Fix 4 options classes to inherit from ModelOptions: FactorVAEOptions, FactorTransformerOptions, AlphaFactorOptions, FinBERTOptions Models updated: RealizedVolatilityTransformer, NeuralGARCH, CSDI, MTGNN, DCRNN, TemporalGCN, TSDiff, FactorVAE, TabTransformer, GraphWaveNet, RelationalGCN, STGNN, TimeGrad, FactorTransformer, TabNet, AttentionAllocation, FinBERT, BlackLittermanNeural, AlphaFactorModel, ScoreGrad, DiffusionTS, SAINT, HierarchicalRiskParity, NeuralCVaR, NeuralStressTest, Hippo, S4, TimeMachine Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up Options = _options in TimeSeries deep learning models, Audio models, PhysicsInformed models, and core NN models. Fix supporting options classes to inherit from ModelOptions where needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 98 unit tests covering: - HyperparameterResponseParser: JSON, markdown bold, colon-separated parsing, type inference, strategy priority, edge cases - HyperparameterRegistry: property name lookups for all model families, alias normalization, validation ranges, custom registration, shared params - AgentHyperparameterApplicator: application of known/unknown params, type conversion, validation warnings, case-insensitive matching, result summary reporting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
) Add 32 integration tests exercising the full pipeline from LLM response parsing through registry lookup to application on real model classes: - Full pipeline tests with DecisionTree, GradientBoosting, RandomForest, KNN, SVR - GetOptions type verification (explicit and null constructor scenarios) - Bug detection tests for null-options constructor pattern - Bug detection test for colon parser header line issue - Validation warning propagation through pipeline - Mixed known/unknown parameter handling - Type conversion (int<->double, scientific notation) - Realistic verbose LLM response formats (JSON, markdown, colon-separated) - AgentAssistanceOptions and AgentRecommendation integration - Registry-to-options property name verification against real options classes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
WalkthroughAdds LLM hyperparameter parsing, a HyperparameterRegistry and HyperparameterDefinition, and a reflection-based generic AgentHyperparameterApplicator to apply suggested hyperparameters to model options. Exposes IConfigurableModel.GetOptions(), wires auto-apply into AiModelBuilder, and adds many ModelOptions-derived option types while renaming RandomState → Seed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Agent as AI Agent
participant Builder as AiModelBuilder
participant Parser as HyperparameterResponseParser
participant Registry as HyperparameterRegistry
participant Applicator as AgentHyperparameterApplicator<T>
participant Model as IConfigurableModel
Agent->>Builder: send recommendation (text)
Builder->>Parser: Parse(llmResponse)
Parser-->>Builder: Dictionary<string, object>
Builder->>Registry: Resolve definitions / aliases
Builder->>Applicator: new Applicator(registry)
alt EnableAutoApplyHyperparameters && model implements IConfigurableModel
Builder->>Applicator: Apply(model, modelType, params)
Applicator->>Registry: GetDefinition(modelType, paramName)
Registry-->>Applicator: HyperparameterDefinition
Applicator->>Applicator: Validate & ConvertValue (handles nullable/enums/primitives)
Applicator->>Model: Model.GetOptions() -> set property via reflection
Applicator-->>Builder: HyperparameterApplicationResult (Applied/Skipped/Failed/Warnings)
else
Builder-->>Builder: Skip auto-apply
end
Builder-->>Agent: return AgentRecommendation with HyperparameterApplicationResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Blocking issues / attention items
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
Add 31 integration tests that exercise the full pipeline: LLM response parsing -> registry lookup -> applicator -> real model options. Tests cover DecisionTree, GradientBoosting, RandomForest, KNN, and SVR models. Fix bug in HyperparameterResponseParser.TryParseColonSeparated where the regex \s* after [:=] could span newlines, causing a header line like "Settings:" to consume the first token of the next line as its value. Fixed by using [^\S\n]* to match only same-line whitespace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Implements Issue #460 by parsing agent/LLM hyperparameter recommendations and auto-applying them to configured models (opt-in), using a registry-backed name mapping and reflection to mutate live model options via a new IConfigurableModel<T> interface.
Changes:
- Added core hyperparameter infrastructure: response parser, registry/definitions + validation, reflection-based applicator, and application-result reporting.
- Standardized options access across model families by introducing
IConfigurableModel<T>and aligning many option types underModelOptions/NeuralNetworkOptions(plus related base option hierarchies). - Wired the pipeline into
AiModelBuilderand added extensive unit + integration test coverage.
Reviewed changes
Copilot reviewed 118 out of 118 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/AiDotNet.Tests/UnitTests/Agents/HyperparameterResponseParserTests.cs | Unit tests for multi-strategy LLM hyperparameter parsing and type inference. |
| tests/AiDotNet.Tests/UnitTests/Agents/HyperparameterRegistryTests.cs | Unit tests for registry mapping, alias normalization, and validation warnings. |
| tests/AiDotNet.Tests/UnitTests/Agents/AgentHyperparameterApplicatorTests.cs | Unit tests for reflection-based application, conversion, warnings, and summaries. |
| tests/AiDotNet.Tests/IntegrationTests/Agents/HyperparameterAutoApplyIntegrationTests.cs | End-to-end integration tests across parser → registry → applicator → real model options. |
| src/Agents/AgentHyperparameterApplicator.cs | Applies parsed hyperparameters to ModelOptions via reflection + registry mapping. |
| src/Agents/HyperparameterDefinition.cs | Defines alias-to-property mapping metadata and normalization. |
| src/Agents/HyperparameterRegistry.cs | Central registry for model-type-specific + shared hyperparameter mappings and validation. |
| src/Agents/HyperparameterResponseParser.cs | Extracts hyperparameters from JSON/markdown/colon formats with type inference. |
| src/Agents/HyperparameterValidationResult.cs | Validation result model used by the registry. |
| src/Interfaces/IConfigurableModel.cs | New interface exposing live ModelOptions for post-construction configuration. |
| src/Models/HyperparameterApplicationResult.cs | Captures applied/skipped/failed/warning results and produces a summary string. |
| src/Models/AgentRecommendation.cs | Stores hyperparameter application results on the agent recommendation. |
| src/Models/AgentAssistanceOptions.cs | Adds EnableAutoApplyHyperparameters flag (default false) + preset/clone propagation. |
| src/Models/AgentAssistanceOptionsBuilder.cs | Builder helpers to enable/disable hyperparameter auto-apply (enables tuning too). |
| src/AiModelBuilder.cs | Parses hyperparameters from LLM output and auto-applies them when enabled. |
| src/Regression/RegressionBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Regression/NonLinearRegressionBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Regression/DecisionTreeRegressionBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Regression/DecisionTreeAsyncRegressionBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Classification/ClassifierBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Clustering/Base/ClusteringBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/Clustering/Options/ClusteringOptions.cs | Standardizes clustering options under ModelOptions and delegates random state to Seed. |
| src/ReinforcementLearning/Agents/ReinforcementLearningAgentBase.cs | Implements IConfigurableModel<T>; RL options now derive from ModelOptions for Seed. |
| src/MetaLearning/MetaLearnerBase.cs | Implements IConfigurableModel<T> and exposes options via GetOptions(). |
| src/MetaLearning/Options/ANILOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/BOILOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/CNAPOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/GNNMetaOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/iMAMLOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/LEOOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/MAMLOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/MANNOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/MatchingNetworksOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/MetaOptNetOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/MetaSGDOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/NTMOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/ProtoNetsOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/RelationNetworkOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/ReptileOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/SEALOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/MetaLearning/Options/TADAMOptions.cs | Meta-learning options now derive from ModelOptions and delegate RandomSeed to Seed. |
| src/TimeSeries/TimeSeriesModelBase.cs | Implements IConfigurableModel<T> and exposes mutable options via GetOptions(). |
| src/TimeSeries/TemporalFusionTransformer.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/NHiTSModel.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/NBEATSModel.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/InformerModel.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/DeepARModel.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/ChronosFoundationModel.cs | Ensures base Options points at derived _options instance. |
| src/TimeSeries/AutoformerModel.cs | Ensures base Options points at derived _options instance. |
| src/NeuralNetworks/NeuralNetworkBase.cs | Adds standardized ModelOptions Options + IConfigurableModel<T> implementation. |
| src/NeuralNetworks/MixtureOfExpertsNeuralNetwork.cs | Wires derived options into base Options for consistent access. |
| src/NeuralNetworks/MeshCNN.cs | Wires derived options into base Options for consistent access. |
| src/NeuralNetworks/Diffusion/DiffusionModelBase.cs | Implements IConfigurableModel<T> and exposes diffusion options as ModelOptions. |
| src/PhysicsInformed/PINNs/MultiScalePINN.cs | Wires training options into base Options for configurability. |
| src/PhysicsInformed/PINNs/InverseProblemPINN.cs | Wires training options into base Options for configurability. |
| src/PhysicsInformed/Interfaces/IMultiScalePDE.cs | Makes multi-scale training options derive from ModelOptions. |
| src/PhysicsInformed/Interfaces/IInverseProblem.cs | Makes inverse-problem training options derive from ModelOptions. |
| src/Models/Options/NeuralNetworkOptions.cs | New base options type for NN models, inheriting Seed from ModelOptions. |
| src/Models/Options/PhysicsInformedOptions.cs | New base options type for PINN/physics-informed models. |
| src/Models/Options/FinancialNeuralNetworkOptions.cs | New base options type for financial NN models. |
| src/Models/Options/ForecastingModelOptions.cs | New base options type for financial forecasting models. |
| src/Models/Options/FinancialNLPOptions.cs | New base options type for financial NLP models. |
| src/Models/Options/DocumentNeuralNetworkOptions.cs | New base options type for document NN models. |
| src/Models/Options/AudioNeuralNetworkOptions.cs | New base options type for audio NN models. |
| src/Models/Options/MixtureOfExpertsOptions.cs | Makes MoE options derive from NeuralNetworkOptions and maps RandomSeed to Seed. |
| src/Models/Options/MeshCNNOptions.cs | Makes MeshCNN options derive from NeuralNetworkOptions. |
| src/Models/Options/SpiralNetOptions.cs | Makes SpiralNet options derive from NeuralNetworkOptions. |
| src/Models/Options/RiskModelOptions.cs | Aligns risk model options under financial NN options hierarchy. |
| src/Models/Options/PortfolioOptimizerOptions.cs | Aligns portfolio optimizer options under financial NN options hierarchy. |
| src/Models/Options/FinBERTOptions.cs | Standardizes FinBERT options to derive from ModelOptions. |
| src/Models/Options/AlphaFactorOptions.cs | Standardizes AlphaFactor options to derive from ModelOptions. |
| src/Models/Options/FactorTransformerOptions.cs | Standardizes FactorTransformer options to derive from ModelOptions. |
| src/Models/Options/FactorVAEOptions.cs | Standardizes FactorVAE options to derive from ModelOptions (but see review comments on Seed). |
| src/Audio/AudioNeuralNetworkBase.cs | Sets standardized base Options for audio NN models. |
| src/Audio/AudioLDM/AudioLDMOptions.cs | Makes AudioLDM options derive from ModelOptions and removes duplicate seed. |
| src/Audio/AudioLDM/AudioLDMModel.cs | Wires AudioLDM options into base Options. |
| src/Audio/StableAudio/StableAudioOptions.cs | Makes StableAudio options derive from ModelOptions and removes duplicate seed. |
| src/Audio/StableAudio/StableAudioModel.cs | Wires StableAudio options into base Options. |
| src/Audio/MusicGen/MusicGenOptions.cs | Makes MusicGen options derive from ModelOptions and removes duplicate seed. |
| src/Audio/MusicGen/MusicGenModel.cs | Wires MusicGen options into base Options. |
| src/Audio/LanguageIdentification/LanguageIdentifierOptions.cs | Makes language ID options derive from ModelOptions. |
| src/Audio/LanguageIdentification/Wav2Vec2LanguageIdentifier.cs | Wires language ID options into base Options. |
| src/Audio/LanguageIdentification/VoxLingua107Identifier.cs | Wires language ID options into base Options. |
| src/Audio/LanguageIdentification/ECAPATDNNLanguageIdentifier.cs | Wires language ID options into base Options. |
| src/Document/DocumentNeuralNetworkBase.cs | Sets standardized base Options for document NN models. |
| src/Finance/Base/FinancialModelBase.cs | Sets standardized base Options for financial NN base models. |
| src/Finance/Base/ForecastingModelBase.cs | Sets standardized base Options for forecasting model base. |
| src/Finance/Base/PortfolioOptimizerBase.cs | Sets standardized base Options for portfolio optimizer base. |
| src/Finance/Base/RiskModelBase.cs | Sets standardized base Options for risk model base. |
| src/Finance/Base/FinancialNLPModelBase.cs | Sets standardized base Options for financial NLP base. |
| src/Finance/NLP/FinBERT.cs | Wires FinBERT options into base Options. |
| src/Finance/Trading/Factors/FactorVAE.cs | Wires FactorVAE options into base Options. |
| src/Finance/Trading/Factors/FactorTransformer.cs | Wires FactorTransformer options into base Options. |
| src/Finance/Trading/Factors/AlphaFactorModel.cs | Wires AlphaFactor options into base Options. |
| src/Finance/Volatility/NeuralGARCH.cs | Wires NeuralGARCH options into base Options. |
| src/Finance/Volatility/RealizedVolatilityTransformer.cs | Wires realized-volatility transformer options into base Options. |
| src/Finance/Risk/TabTransformer.cs | Wires TabTransformer options into base Options. |
| src/Finance/Risk/TabNet.cs | Wires TabNet options into base Options. |
| src/Finance/Risk/SAINT.cs | Wires SAINT options into base Options. |
| src/Finance/Risk/NeuralStressTest.cs | Wires NeuralStressTest options into base Options. |
| src/Finance/Risk/NeuralCVaR.cs | Wires NeuralCVaR options into base Options. |
| src/Finance/Portfolio/HierarchicalRiskParity.cs | Wires HRP options into base Options. |
| src/Finance/Portfolio/BlackLittermanNeural.cs | Wires Black-Litterman options into base Options. |
| src/Finance/Portfolio/AttentionAllocation.cs | Wires attention allocation options into base Options. |
| src/Finance/Graph/TemporalGCN.cs | Wires TemporalGCN options into base Options. |
| src/Finance/Graph/STGNN.cs | Wires STGNN options into base Options. |
| src/Finance/Graph/RelationalGCN.cs | Wires RelationalGCN options into base Options. |
| src/Finance/Graph/MTGNN.cs | Wires MTGNN options into base Options. |
| src/Finance/Graph/GraphWaveNet.cs | Wires GraphWaveNet options into base Options. |
| src/Finance/Graph/DCRNN.cs | Wires DCRNN options into base Options. |
| src/Finance/Forecasting/StateSpace/TimeMachine.cs | Wires TimeMachine options into base Options. |
| src/Finance/Forecasting/StateSpace/S4.cs | Wires S4 options into base Options. |
| src/Finance/Forecasting/StateSpace/Hippo.cs | Wires Hippo options into base Options. |
| src/Finance/Probabilistic/TimeGrad.cs | Wires TimeGrad options into base Options. |
| src/Finance/Probabilistic/TSDiff.cs | Wires TSDiff options into base Options. |
| src/Finance/Probabilistic/ScoreGrad.cs | Wires ScoreGrad options into base Options. |
| src/Finance/Probabilistic/DiffusionTS.cs | Wires DiffusionTS options into base Options. |
| src/Finance/Probabilistic/CSDI.cs | Wires CSDI options into base Options. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| catch (Exception ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
To fix the problem, narrow the generic catch (Exception ex) in Apply to only catch the exception types that are expected and reasonably recoverable when applying a single hyperparameter. For example, reflection-related exceptions (TargetInvocationException, TargetException, ArgumentException), invalid cast or conversion issues (InvalidCastException, FormatException, OverflowException), and perhaps KeyNotFoundException or ArgumentNullException if they can arise from the registry or dictionary usage. More severe runtime exceptions should be allowed to propagate so they are not silently converted into a simple failure message.
The best minimal-impact change is to replace the single generic catch (Exception ex) block around ApplyParameter with several specific catch blocks that map to the same failure recording behavior. This preserves the existing functional behavior for expected failures (they are still reported in result.Failed[paramName]) while avoiding swallowing arbitrary exceptions. Concretely, in src/Agents/AgentHyperparameterApplicator.cs, lines 59–66 inside the foreach loop of Apply should be updated to multiple typed catch clauses. No new methods are required. We can do this using only types from System and System.Reflection, which are already available (System.Reflection is imported at the top), so no additional imports are needed.
| @@ -60,10 +60,26 @@ | ||
| { | ||
| ApplyParameter(options, modelType, paramName, paramValue, result); | ||
| } | ||
| catch (Exception ex) | ||
| catch (ArgumentException ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } | ||
| catch (InvalidCastException ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } | ||
| catch (FormatException ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } | ||
| catch (OverflowException ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } | ||
| catch (TargetInvocationException ex) | ||
| { | ||
| result.Failed[paramName] = $"Unexpected error: {ex.Message}"; | ||
| } | ||
| } | ||
|
|
||
| return result; |
| catch (Exception ex) | ||
| { | ||
| result.Failed[paramName] = $"Failed to set property: {ex.Message}"; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
General approach: Replace the generic catch (Exception ex) with one or more specific catch blocks for the exceptions that are realistically expected from property.SetValue(options, convertedValue);, such as ArgumentException, TargetException, TargetParameterCountException, MethodAccessException, InvalidOperationException, and TargetInvocationException. Let all other exceptions propagate so they can be logged/handled at a higher level.
Best concrete fix here:
- Leave the
tryblock and success path unchanged. - Add a first
catch (TargetInvocationException ex)that records the inner exception message when available, becauseSetValuewraps exceptions thrown by the property setter inTargetInvocationException. - Add a second
catch (Exception ex) when (ex is ArgumentException || ex is TargetException || ex is TargetParameterCountException || ex is MethodAccessException || ex is InvalidOperationException)to handle other expected reflection/configuration errors while still avoiding a fully generic catch. - In both catches, continue to populate
result.Failed[paramName]with a similar message so existing behavior for configuration failures is preserved. - Do not add new imports:
TargetInvocationException,TargetException, andTargetParameterCountExceptionlive inSystem.Reflection, which is already imported.
All changes are within src/Agents/AgentHyperparameterApplicator.cs around lines 112–120; no additional methods or dependencies are required.
| @@ -114,8 +114,17 @@ | ||
| property.SetValue(options, convertedValue); | ||
| result.Applied[paramName] = paramValue; | ||
| } | ||
| catch (Exception ex) | ||
| catch (TargetInvocationException ex) | ||
| { | ||
| var message = ex.InnerException?.Message ?? ex.Message; | ||
| result.Failed[paramName] = $"Failed to set property: {message}"; | ||
| } | ||
| catch (Exception ex) when (ex is ArgumentException | ||
| || ex is TargetException | ||
| || ex is TargetParameterCountException | ||
| || ex is MethodAccessException | ||
| || ex is InvalidOperationException) | ||
| { | ||
| result.Failed[paramName] = $"Failed to set property: {ex.Message}"; | ||
| } | ||
| } |
| foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | ||
| { | ||
| if (prop.CanWrite && HyperparameterDefinition.NormalizeName(prop.Name) == normalized) | ||
| { | ||
| return prop; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix this kind of issue you replace a foreach that iterates an entire sequence and conditionally skips elements using if with a LINQ Where (and possibly FirstOrDefault, Any, etc.) that filters the sequence before iteration or selection. This makes the filtering explicit and improves readability.
Here, in FindProperty, the second phase tries to find a property whose normalized name matches the normalized target name and that is writable. Instead of:
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (prop.CanWrite && HyperparameterDefinition.NormalizeName(prop.Name) == normalized)
{
return prop;
}
}we can use LINQ to filter properties and then pick the first match:
using System.Linq; // needed at top of file
// ...
var property = type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(prop =>
prop.CanWrite &&
HyperparameterDefinition.NormalizeName(prop.Name) == normalized);
return property;This preserves behavior: if a matching property is found, it is returned; otherwise null is returned. We’ll also need to add using System.Linq; at the top of AgentHyperparameterApplicator.cs so FirstOrDefault is available, without modifying other imports or code outside the shown snippet.
| @@ -1,4 +1,5 @@ | ||
| using System.Reflection; | ||
| using System.Linq; | ||
| using AiDotNet.Enums; | ||
| using AiDotNet.Interfaces; | ||
| using AiDotNet.Models; | ||
| @@ -133,15 +134,13 @@ | ||
|
|
||
| // Try normalized match (remove underscores, case-insensitive) | ||
| var normalized = HyperparameterDefinition.NormalizeName(propertyName); | ||
| foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | ||
| { | ||
| if (prop.CanWrite && HyperparameterDefinition.NormalizeName(prop.Name) == normalized) | ||
| { | ||
| return prop; | ||
| } | ||
| } | ||
| property = type | ||
| .GetProperties(BindingFlags.Public | BindingFlags.Instance) | ||
| .FirstOrDefault(prop => | ||
| prop.CanWrite && | ||
| HyperparameterDefinition.NormalizeName(prop.Name) == normalized); | ||
|
|
||
| return null; | ||
| return property; | ||
| } | ||
|
|
||
| /// <summary> |
| catch | ||
| { | ||
| return null; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, the fix is to replace the bare catch with one or more specific exception types that are expected from type conversion, and let other exceptions bubble up. For numeric and general Convert.* calls, the common expected exceptions are InvalidCastException, FormatException, and OverflowException. For Convert.ChangeType, the same set applies. Narrowing to these maintains the intended behavior—returning null when conversion cannot be performed for normal reasons—while not swallowing unrelated or critical runtime exceptions.
The best concrete fix here is:
- Replace the generic
catchat lines 204–207 with acatchthat lists the expected conversion exceptions and returnsnullin that case. - Add a second
catch (Exception)only if you want to preserve the “never throw” contract. However, this would still be a generic catch and likely keep the CodeQL warning. To both satisfy CodeQL and improve robustness, we should only catch the specific conversion exceptions and not have a generic catch at all. - No additional imports are needed, since
InvalidCastException,FormatException, andOverflowExceptionare inSystem, which is implicitly available.
Concretely, in src/Agents/AgentHyperparameterApplicator.cs, within ConvertValue, replace the catch block starting at line 204 with a multi-type catch:
catch (InvalidCastException)
{
return null;
}
catch (FormatException)
{
return null;
}
catch (OverflowException)
{
return null;
}This preserves the original behavior for expected conversion problems while allowing any other unexpected exceptions to surface.
| @@ -201,9 +201,17 @@ | ||
| // Try using Convert.ChangeType as a last resort | ||
| return Convert.ChangeType(value, targetType); | ||
| } | ||
| catch | ||
| catch (InvalidCastException) | ||
| { | ||
| return null; | ||
| } | ||
| catch (FormatException) | ||
| { | ||
| return null; | ||
| } | ||
| catch (OverflowException) | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } |
| foreach (var def in definitions) | ||
| { | ||
| if (def.NormalizedAliases.Contains(normalized)) | ||
| { | ||
| return def.PropertyName; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix this kind of issue you replace a foreach loop that performs an if check on each element (implicitly filtering) with an explicit LINQ filter (e.g., .Where(...), or directly .FirstOrDefault(...) / .SingleOrDefault(...)), then operate only on the filtered sequence or matching element. This makes the existence of a filter explicit and often reduces nesting.
Here, in GetPropertyName, the loop:
if (_registry.TryGetValue(modelType, out var definitions))
{
foreach (var def in definitions)
{
if (def.NormalizedAliases.Contains(normalized))
{
return def.PropertyName;
}
}
}is logically “return the PropertyName for the first definition whose NormalizedAliases contains normalized”. The cleanest LINQ equivalent is:
if (_registry.TryGetValue(modelType, out var definitions))
{
var match = definitions.FirstOrDefault(d => d.NormalizedAliases.Contains(normalized));
if (match != null)
{
return match.PropertyName;
}
}This preserves early‑exit semantics and does not alter behavior. It also aligns with the rule’s recommendation by making the filter predicate explicit. We should similarly consider the _sharedDefinitions loop; however, that loop also just returns on first match and is structurally identical, so the same pattern could be applied. The alert is specifically on the first loop, so we will minimally change that loop only, to stay within the reported scope.
To implement this, we only need LINQ extension methods. We can safely add using System.Linq; at the top of HyperparameterRegistry.cs (if not already present in unseen code) because it’s a standard BCL namespace and introduces no behavioral changes to existing code. All other functionality remains the same.
| @@ -1,4 +1,5 @@ | ||
| using AiDotNet.Enums; | ||
| using System.Linq; | ||
|
|
||
| namespace AiDotNet.Agents; | ||
|
|
||
| @@ -43,12 +44,10 @@ | ||
| // Check model-specific definitions first | ||
| if (_registry.TryGetValue(modelType, out var definitions)) | ||
| { | ||
| foreach (var def in definitions) | ||
| var match = definitions.FirstOrDefault(def => def.NormalizedAliases.Contains(normalized)); | ||
| if (match != null) | ||
| { | ||
| if (def.NormalizedAliases.Contains(normalized)) | ||
| { | ||
| return def.PropertyName; | ||
| } | ||
| return match.PropertyName; | ||
| } | ||
| } | ||
|
|
| foreach (var def in _sharedDefinitions) | ||
| { | ||
| if (def.NormalizedAliases.Contains(normalized)) | ||
| { | ||
| return def; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix this kind of issue you replace an explicit foreach loop that checks a condition on each element with a LINQ query that filters the sequence (Where) and then selects the first matching element (FirstOrDefault). This makes the intent (“find the first definition whose aliases contain this normalized name”) explicit and removes manual loop control.
For this file, the best approach is:
- In
GetPropertyName, replace the two loops overdefinitionsand_sharedDefinitionswith LINQ calls that:- Filter each collection by
def.NormalizedAliases.Contains(normalized). - Take the first matching element (or
nullif none). - Return its
PropertyNameif found; otherwise fall back to the shared list, thennull.
- Filter each collection by
- In
GetDefinition, replace the two loops with LINQ calls that:- Directly return the first matching
HyperparameterDefinitionfrom the model-specific list, or from_sharedDefinitionsif none match. - Preserve the
nullreturn when no definitions match.
- Directly return the first matching
- To use LINQ extension methods on
List<T>, addusing System.Linq;at the top of this file. - We do not change anything else in the class;
Validateand other members keep working as before.
These changes must be done in src/Agents/HyperparameterRegistry.cs:
- Add the required
using System.Linq;. - Replace the body of
GetPropertyName. - Replace the body of
GetDefinition.
| catch | ||
| { | ||
| result = 0; | ||
| return false; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix a generic catch clause you should replace catch with one or more catch blocks that target the specific, expected exception types, and allow unexpected exceptions to propagate. This avoids inadvertently swallowing critical errors and makes behavior clearer.
Here, TryConvertToDouble calls Convert.ToDouble(object), which can throw several documented exceptions for invalid conversion scenarios: commonly FormatException, InvalidCastException, OverflowException, and ArgumentNullException. We want TryConvertToDouble to return false (and set result to a default) for these “normal conversion failure” cases, while letting other exceptions bubble up.
The single best fix is to change the bare catch block in TryConvertToDouble to a catch that explicitly lists the expected exceptions using a C# exception filter. This preserves existing semantics (return false and set result = 0 whenever the conversion fails for normal reasons) without adding logging or changing callers. No new imports are needed because these exception types are in System, which is already implicitly available in C# projects; we are not modifying the using directives. The changes are confined to the TryConvertToDouble method in src/Agents/HyperparameterRegistry.cs, around lines 143–155.
| @@ -147,7 +147,10 @@ | ||
| result = Convert.ToDouble(value); | ||
| return true; | ||
| } | ||
| catch | ||
| catch (Exception ex) when (ex is FormatException | ||
| || ex is InvalidCastException | ||
| || ex is OverflowException | ||
| || ex is ArgumentNullException) | ||
| { | ||
| result = 0; | ||
| return false; |
| foreach (Match match in matches) | ||
| { | ||
| if (TryParseJsonObject(match.Groups[1].Value.Trim(), result)) | ||
| { | ||
| return result; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Copilot Autofix
AI 8 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| foreach (Match match in matches) | ||
| { | ||
| if (TryParseJsonObject(match.Value.Trim(), result)) | ||
| { | ||
| return result; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Copilot Autofix
AI 8 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| catch | ||
| { | ||
| return false; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix a generic catch clause, you replace catch { ... } with one or more specific catch (SomeExceptionType) blocks that cover only the expected failure modes. Unexpected exceptions are then allowed to propagate, making debugging easier and avoiding masking serious issues.
For this code, the best minimal change is to narrow the catch in TryParseJsonObject to JSON/parsing-related exceptions thrown by Newtonsoft.Json. The main candidate is JsonReaderException (the typical exception for malformed JSON), and more broadly JsonException, both from Newtonsoft.Json. This preserves the existing behavior—returning false when the JSON cannot be parsed or interpreted—while no longer swallowing unrelated exceptions. Concretely:
- Add an appropriate
using Newtonsoft.Json;at the top ofHyperparameterResponseParser.csso thatJsonException/JsonReaderExceptionare available without fully qualified names. - Replace the generic
catchblock (lines 104–107) inTryParseJsonObjectwith specificcatchblocks forJsonReaderExceptionandJsonException. These should still justreturn false;to maintain the current external behavior. - Do not otherwise alter logic or signatures.
All changes are confined to src/Agents/HyperparameterResponseParser.cs within the provided snippet.
| @@ -1,5 +1,6 @@ | ||
| using System.Text.RegularExpressions; | ||
| using Newtonsoft.Json.Linq; | ||
| using Newtonsoft.Json; | ||
|
|
||
| namespace AiDotNet.Agents; | ||
|
|
||
| @@ -101,10 +102,14 @@ | ||
| } | ||
| return result.Count > 0; | ||
| } | ||
| catch | ||
| catch (JsonReaderException) | ||
| { | ||
| return false; | ||
| } | ||
| catch (JsonException) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/Clustering/Base/ClusteringBase.cs (1)
21-39:⚠️ Potential issue | 🟠 MajorNew public GetOptions/IConfigurableModel expands facade surface
Lines 21-39: Adding a public interface plus public GetOptions introduces a new user-facing entry point outside AiModelBuilder/AiModelResult. If this is only for internal hyperparameter plumbing, consider making the interface internal and/or implementing it explicitly to reduce surface area.
🔧 Suggested adjustment to hide GetOptions from the public surface
- public virtual ModelOptions GetOptions() => Options; + ModelOptions IConfigurableModel<T>.GetOptions() => Options;As per coding guidelines, "Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API".src/Regression/DecisionTreeAsyncRegressionBase.cs (1)
27-51:⚠️ Potential issue | 🟠 MajorAvoid expanding the public API with
GetOptions()on base types.If this is only for internal hyperparameter plumbing, consider explicit interface implementation to keep the method off the public surface (or make the interface internal).
💡 Suggested change
-public virtual ModelOptions GetOptions() => Options; +ModelOptions IConfigurableModel<T>.GetOptions() => Options;As per coding guidelines, "Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs" and "Prefer internal over public for all classes, methods, and properties unless they are part of the facade API".
src/Classification/ClassifierBase.cs (1)
31-69:⚠️ Potential issue | 🟠 MajorAvoid adding new public API on base types; consider explicit interface implementation.
If
GetOptions()is only needed by internal hyperparameter plumbing, keep it off the public surface via explicit interface implementation (or make the interface internal).💡 Suggested change
-public virtual ModelOptions GetOptions() => Options; +ModelOptions IConfigurableModel<T>.GetOptions() => Options;As per coding guidelines, "Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs" and "Prefer internal over public for all classes, methods, and properties unless they are part of the facade API".
src/ReinforcementLearning/Agents/ReinforcementLearningAgentBase.cs (1)
34-88:⚠️ Potential issue | 🟠 MajorHide
GetOptions()from the public surface unless this is an intentional API expansion.This adds a public member to a non‑facade type. If external callers shouldn’t access options directly, implement
IConfigurableModel<T>.GetOptions()explicitly (or make the interface/internal accessor internal) so it’s only reachable via the interface.🔐 Example explicit implementation
- /// <inheritdoc/> - public virtual ModelOptions GetOptions() => Options; + /// <inheritdoc/> + ModelOptions IConfigurableModel<T>.GetOptions() => Options;As per coding guidelines, “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicunless part of the facade API.”
🤖 Fix all issues with AI agents
In `@src/Agents/AgentHyperparameterApplicator.cs`:
- Around line 26-49: The AgentHyperparameterApplicator<T> class is public but
should be internal to avoid expanding the public API; change the type
declaration from public to internal (and ensure any related members like the
constructor AgentHyperparameterApplicator(HyperparameterRegistry) and the Apply
method remain accessible within the assembly), and, if external usage is
required, expose the functionality only via the facade (e.g., AiModelBuilder)
rather than making this class public.
- Around line 46-69: Add explicit null-argument validation at the start of
Apply: validate that the model and hyperparameters parameters are not null and
throw ArgumentNullException for each (e.g., check model and hyperparameters in
the Apply method), then retrieve options via model.GetOptions() and guard
against null (throw ArgumentNullException or InvalidOperationException with a
clear message if options is null) before iterating; this prevents downstream
NullReferenceExceptions from ApplyParameter and makes failures explicit.
In `@src/Agents/HyperparameterDefinition.cs`:
- Around line 19-71: Make HyperparameterDefinition an internal class and stop
exposing a mutable NormalizedAliases HashSet; change the class accessibility to
internal, make the NormalizedAliases field private (or private readonly) and
expose it only via a read-only view or method (e.g., a public
IReadOnlyCollection<string> GetNormalizedAliases() or a property
IReadOnlyCollection<string> NormalizedAliases { get; }) so callers cannot mutate
it; ensure BuildNormalizedAliases still populates the private set using
NormalizeName(PropertyName) and NormalizeName(alias) from Aliases and keep
PropertyName, Aliases, ValueType, MinValue, MaxValue unchanged.
In `@src/Agents/HyperparameterRegistry.cs`:
- Around line 20-141: Change the public surface of the internal utility by
making the HyperparameterRegistry class internal (replace "public class
HyperparameterRegistry" with "internal class HyperparameterRegistry"); keep all
members as-is (they can remain public/private as needed) and rebuild to surface
any external references—if external assemblies need access, add an
InternalsVisibleTo attribute instead of making the class public. Ensure the
constructor HyperparameterRegistry() and usages like GetPropertyName,
GetDefinition, Validate, and Register still compile after the visibility change.
In `@src/AiModelBuilder.cs`:
- Around line 5865-5883: When auto-applying hyperparameters, don't fall back to
ModelType.SimpleRegression when `_model` is already configured; instead derive
the modelType from the actual configured model and only proceed if it can be
resolved—use `_model`'s concrete type or an explicit mapping to determine
`modelType` before calling `AgentHyperparameterApplicator<T>.Apply`; if the
model type cannot be resolved, skip auto-apply and leave
`recommendation.HyperparameterApplicationResult` unset or marked as skipped.
Locate the block gated by `_agentOptions.EnableAutoApplyHyperparameters` that
checks `_model is IConfigurableModel<T> configurableModel` and
`recommendation.SuggestedHyperparameters`, replace the unconditional
`recommendation.SuggestedModelType ?? ModelType.SimpleRegression` with
resolution logic that prefers `recommendation.SuggestedModelType` or derives
from `configurableModel` (or aborts), then call `applicator.Apply` only when a
valid `modelType` is available; keep `HyperparameterRegistry` and
`AgentHyperparameterApplicator` internal as implementation details per the API
guidelines.
In `@src/Interfaces/IConfigurableModel.cs`:
- Around line 22-28: Change the visibility of the IConfigurableModel<T>
interface from public to internal to hide internal configuration mechanics from
consumers; update its declaration (IConfigurableModel<T>) so the GetOptions()
method and returned ModelOptions remain internal-only, ensuring the public
facade surface still exposes only AiModelBuilder and AiModelResult; run
build/tests to confirm no external usages break and adjust any internal
consumers or tests to use the internal interface.
In `@src/MetaLearning/Options/MANNOptions.cs`:
- Line 28: MANNOptions<T, TInput, TOutput> currently inherits from ModelOptions
which exposes ModelOptions' members (e.g., Seed) in the public API; change this
by either making MANNOptions internal or removing the public inheritance from
ModelOptions and instead surface any required configuration through the facade
(AiModelBuilder / AiModelResult) or an internal wrapper; specifically update the
declaration of MANNOptions<T, TInput, TOutput> (and any related
constructors/properties) so it does not publicly expose ModelOptions, and ensure
any code that relied on ModelOptions members accesses them via AiModelBuilder or
an internal API instead.
In `@src/MetaLearning/Options/ReptileOptions.cs`:
- Line 31: ReptileOptions<T, TInput, TOutput> currently publicly inherits
ModelOptions which widens the public API; to fix, remove the public inheritance
from ModelOptions and either make ModelOptions internal or refactor
ReptileOptions to use composition (hold an internal ModelOptions instance) and
implement only IMetaLearnerOptions<T>, exposing only the minimal properties
needed by AiModelBuilder.cs/AiModelResult.cs; ensure no unintended ModelOptions
members are public on ReptileOptions and update references to use the internal
ModelOptions helper where necessary.
In `@src/Models/HyperparameterApplicationResult.cs`:
- Around line 22-47: The HyperparameterApplicationResult type is public but
should be internal per the facade-only API rule; change the class declaration
for HyperparameterApplicationResult from public to internal (and, if desired,
make its properties internal or keep them public if used across the same
assembly) and update any usages in AiModelResult or AiModelBuilder to ensure
they reference the internal type in the same assembly (adjust test/access
modifiers or move/type-forward if needed) so the class is not part of the public
surface anymore.
In `@src/Models/Options/AudioNeuralNetworkOptions.cs`:
- Line 6: The new public class AudioNeuralNetworkOptions expands the public API
surface; if it isn’t intended for direct consumption, change its visibility from
public to internal (AudioNeuralNetworkOptions) and ensure any consumers access
it through the existing facade (AiModelBuilder/AiModelResult) instead—update
AiModelBuilder to accept or construct AudioNeuralNetworkOptions internally (or
return a facade type) and keep NeuralNetworkOptions minimal/public only if it is
part of the documented facade; also review any properties or methods on
AudioNeuralNetworkOptions and make them internal or expose equivalent
configuration methods on AiModelBuilder so no new public types leak from the
assembly.
In `@src/Models/Options/DocumentNeuralNetworkOptions.cs`:
- Around line 1-7: The new options class DocumentNeuralNetworkOptions is public
but should be internal per the API-surface guideline; change its accessibility
from public to internal for DocumentNeuralNetworkOptions and ensure its base
type NeuralNetworkOptions has at least internal visibility so wiring still
compiles, or alternatively expose this type only through the facade
(AiModelBuilder/AiModelResult) if it must remain public.
In `@src/Models/Options/FinancialNLPOptions.cs`:
- Around line 1-7: The FinancialNLPOptions class is declared public but is
intended for internal model wiring only; change its accessibility to internal by
updating the class declaration for FinancialNLPOptions (which inherits
FinancialNeuralNetworkOptions) so it is internal instead of public, ensuring
only the facade types (AiModelBuilder and AiModelResult) remain public.
In `@src/Models/Options/NeuralNetworkOptions.cs`:
- Around line 17-18: NeuralNetworkOptions is declared public but is not part of
the facade API; change its accessibility to internal by replacing "public class
NeuralNetworkOptions : ModelOptions" with "internal class NeuralNetworkOptions :
ModelOptions". Verify usages of NeuralNetworkOptions (and any constructors or
factory methods) are internal or within the same assembly and update any public
surface that accidentally exposes this type (ensure AiModelBuilder and
AiModelResult remain the only public-facing model types). If this class truly
needs to be public for external consumers, leave it public but add a comment
documenting why it’s part of the facade.
In `@src/NeuralNetworks/NeuralNetworkBase.cs`:
- Line 25: NeuralNetworkBase<T> currently exposes GetOptions via the public
IConfigurableModel<T> implementation, which makes it part of the non‑facade
public API; change the implementation of GetOptions on NeuralNetworkBase<T> to
be explicit (i.e., implement IConfigurableModel<T>.GetOptions explicitly) or
make the member internal so it is not publicly visible, and apply the same
change to the related members referenced around lines 106-116 to ensure all
configurable/options accessors are not public on the base class.
In `@src/PhysicsInformed/Interfaces/IInverseProblem.cs`:
- Line 262: InverseProblemOptions<T> was made public and now inherits
AiDotNet.Models.Options.ModelOptions which widens the public API surface; to
fix, either make InverseProblemOptions<T> internal (reverting its visibility) or
keep it public but remove/avoid inheriting ModelOptions and instead encapsulate
only the required settings, ensuring users still only interact via
AiModelBuilder.cs and AiModelResult.cs; update the declaration of
InverseProblemOptions<T> (or its base) accordingly and run
serialization/inheritance tests to confirm no breaking changes.
In `@src/Regression/NonLinearRegressionBase.cs`:
- Around line 29-30: NonLinearRegressionBase<T> currently exposes
IConfigurableModel<T> and a public GetOptions(), expanding the public API;
implement IConfigurableModel<T> explicitly instead: remove public
IConfigurableModel<T> from the class signature (keep INonLinearRegression<T>),
move the public GetOptions() into an explicit interface implementation
(IConfigurableModel<T>.GetOptions) so it isn’t accessible from the concrete
type, and ensure any callers in internal plumbing access it via the interface;
alternatively, if the interface is truly internal-only, change
IConfigurableModel<T> to internal to keep the configuration surface off the
facade.
In `@src/TimeSeries/TimeSeriesModelBase.cs`:
- Line 43: The TimeSeriesModelBase<T> currently exposes IConfigurableModel<T>
and its GetOptions publicly; change IConfigurableModel<T> to internal (or keep
it public but implement it explicitly on TimeSeriesModelBase<T>) so GetOptions
is not part of the public surface of TimeSeriesModelBase<T>; update the class
declaration in TimeSeriesModelBase<T> to remove the public interface surface
(implement IConfigurableModel<T>.GetOptions as an explicit interface
implementation) and ensure AiModelBuilder can still access the interface by
keeping AiModelBuilder in the same assembly or using InternalsVisibleTo as
needed; apply the same explicit-implementation/internal approach to the other
affected base types referenced around lines 63-66.
In
`@tests/AiDotNet.Tests/IntegrationTests/Agents/HyperparameterAutoApplyIntegrationTests.cs`:
- Around line 245-272: The test currently uses conditional assertions that never
fail; update the test for GetOptions on GradientBoostingRegression to enforce a
single expected outcome instead of branching: decide whether the desired
behavior is that GetOptions returns GradientBoostingRegressionOptions or
DecisionTreeOptions, then replace the conditional isCorrectType block with a
direct Assert.IsType<...>(returnedOptions) for the chosen type (or mark the test
with a skip/Explicit attribute and include a short known-issue message if this
is intentionally unresolved). Modify the test referencing
GradientBoostingRegression, IConfigurableModel.GetOptions,
GradientBoostingRegressionOptions and DecisionTreeOptions accordingly (and apply
the same change to the similar test at the later block) so the test fails when
behavior diverges from the decided expectation.
🧹 Nitpick comments (12)
src/PhysicsInformed/Interfaces/IMultiScalePDE.cs (1)
181-181: Consider whether ModelOptions members should be exposed here.Inheriting ModelOptions adds its public members to a public interface-layer options type. If those members aren’t meant to be part of the facade surface, consider making this type internal or relocating it outside Interfaces. As per coding guidelines: Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs; Prefer internal over public for all classes, methods, and properties unless they are part of the facade API.
src/Clustering/Options/ClusteringOptions.cs (1)
63-67: Consider deprecating RandomState in favor of the inherited Seed property.The delegation to
Seedmaintains backward compatibility, but having two properties for the same value could confuse users. Consider adding an[Obsolete]attribute to guide users toward the canonicalSeedproperty.💡 Optional: Mark RandomState as obsolete
+ [Obsolete("Use the inherited Seed property instead. RandomState will be removed in a future version.")] public int? RandomState { get => Seed; set => Seed = value; }src/Agents/HyperparameterResponseParser.cs (3)
21-21: Consider making this classinternalto maintain the facade pattern.Per the coding guidelines, implementation details should be hidden behind the
AiModelBuilderfacade. This parser is an internal infrastructure component that users shouldn't interact with directly.-public class HyperparameterResponseParser +internal class HyperparameterResponseParserAs per coding guidelines: "Prefer
internaloverpublicfor all classes, methods, and properties unless they are part of the facade API."
63-63: Regex patterns withCompiledflag should be static fields for efficiency.These regex patterns are created inside methods but use
RegexOptions.Compiled. This means the regex is recompiled on every method invocation, negating the performance benefit of compilation. Consider extracting to static readonly fields.♻️ Suggested refactor
+private static readonly Regex JsonBlockPattern = new(@"```(?:json)?\s*\n?([\s\S]*?)\n?\s*```", RegexOptions.Compiled); +private static readonly Regex RawJsonPattern = new(@"\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}", RegexOptions.Compiled); +private static readonly Regex MarkdownBoldPattern = new(@"\*\*(\w[\w_]*?):\*\*\s*([^\s(]+)", RegexOptions.Compiled); +private static readonly Regex ColonSeparatedPattern = new(@"^\s*[-\*\s]*(\w[\w_]*?)\s*[:=][^\S\n]*([^\s(,]+)", RegexOptions.Multiline | RegexOptions.Compiled); internal Dictionary<string, object> TryParseJson(string text) { var result = new Dictionary<string, object>(); - var jsonBlockPattern = new Regex(@"```(?:json)?\s*\n?([\s\S]*?)\n?\s*```", RegexOptions.Compiled); - var matches = jsonBlockPattern.Matches(text); + var matches = JsonBlockPattern.Matches(text);Also applies to: 75-75, 116-116, 138-138
210-218: Consider making the non-parameter set a static readonly field.The
HashSetis recreated on every call. Since the contents are constant, this can be a static readonly field to avoid repeated allocations.♻️ Suggested refactor
+private static readonly HashSet<string> NonParameterNames = new(StringComparer.OrdinalIgnoreCase) +{ + "step", "note", "example", "reason", "because", "since", + "model", "algorithm", "method", "approach", "result", + "summary", "recommendation", "suggestion", "tip" +}; private static bool IsCommonNonParameter(string name) { - var nonParams = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - "step", "note", "example", "reason", "because", "since", - "model", "algorithm", "method", "approach", "result", - "summary", "recommendation", "suggestion", "tip" - }; - return nonParams.Contains(name); + return NonParameterNames.Contains(name); }src/Models/Options/PortfolioOptimizerOptions.cs (1)
1-3: Redundant using directive for own namespace.Line 1 imports
AiDotNet.Models.Options, but the file is declared in that same namespace on line 3. This using can be safely removed.🧹 Suggested cleanup
-using AiDotNet.Models.Options; - namespace AiDotNet.Models.Options;src/Models/AgentRecommendation.cs (1)
405-429: Consider keeping detailed application results internal if not user-facing.If end users only need a summary, consider exposing a simple string/summary instead and keeping
HyperparameterApplicationResultinternal to limit surface area.💡 Suggested change (if not meant to be public)
-public HyperparameterApplicationResult? HyperparameterApplicationResult { get; set; } +internal HyperparameterApplicationResult? HyperparameterApplicationResult { get; set; }As per coding guidelines, "Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs" and "Prefer internal over public for all classes, methods, and properties unless they are part of the facade API".
src/Regression/RegressionBase.cs (1)
31-68: Consider explicit interface implementation to avoid expanding the public surface.
IfIConfigurableModel<T>stays non-public, this can be hidden from consumers and keepRegressionBase<T>’s public API stable.♻️ Suggested refactor
- public virtual ModelOptions GetOptions() => Options; + ModelOptions IConfigurableModel<T>.GetOptions() => Options;As per coding guidelines: "Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."tests/AiDotNet.Tests/UnitTests/Agents/HyperparameterRegistryTests.cs (1)
200-236: Validation behavior aligns with lenient-by-design approach.The tests confirm that out-of-range values produce warnings rather than failures, allowing the applicator to proceed with caution. This matches the opt-in, non-blocking philosophy of the feature.
Consider adding a test that verifies
ValidatereturnsIsValid = falsefor truly invalid scenarios (if any exist), to ensure theInvalid()factory method gets exercised.src/Agents/HyperparameterValidationResult.cs (1)
41-46: Minor:Invalid()reusesWarningproperty for the error reason.The
Invalid()factory setsHasWarning = trueand stores the reason inWarning, which slightly conflates warnings and errors. This works but could be clearer with anErrorMessageproperty or renamed toMessage.Not blocking — the current design is functional and the intent is clear.
src/MetaLearning/Options/MetaSGDOptions.cs (1)
712-713: Consider extracting magic number10.0as a named constant.The validation
MaxLearningRate > 10.0uses a hardcoded value. A named constant likeMaxAllowedLearningRatewould improve readability and make the limit discoverable/adjustable.private const double MaxAllowedLearningRate = 10.0;Not blocking — the current validation is functional.
tests/AiDotNet.Tests/IntegrationTests/Agents/HyperparameterAutoApplyIntegrationTests.cs (1)
22-76: Prefer exercising the facade in end‑to‑end tests.These tests directly instantiate parser/registry/applicator. For the true end‑to‑end path, consider routing at least one integration test through
AiModelBuilder(withAgentAssistanceOptions.EnableAutoApplyHyperparameters) to keep tests aligned to the facade and reduce coupling to internal components.As per coding guidelines, “Prefer testing through the public facade (
AiModelBuilder) when possible.”
|
…itance (#460) - Add GetOptions() override to all 49 RL agent classes - Make 65+ standalone options classes inherit from ModelOptions (via background agent) - Remove duplicate Seed properties from 11 options classes that conflict with ModelOptions.Seed - Make ConsensusClusteringOptions inherit from ModelOptions, rename RandomSeed to Seed - Add GetOptions to remaining clustering, meta-learning, and time series models - Fix MultiLabelClassifierBase to support IConfigurableModel<T> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/Models/Options/NASOptions.cs (1)
371-404:⚠️ Potential issue | 🟡 Minor
Withmethod does not copy the inheritedSeedproperty fromModelOptions.The copy logic manually assigns all local properties but misses the base class
Seedproperty. This means callingWith(...)on an options instance that hasSeedset will produce a copy whereSeedis reset to its default value.Proposed fix
var copy = new NASOptions<T> { + Seed = Seed, Strategy = Strategy, TargetPlatform = TargetPlatform,src/Models/Options/FinMAOptions.cs (1)
38-60:⚠️ Potential issue | 🟠 MajorCopy base
ModelOptionsstate when cloning.After inheriting
ModelOptions, the copy ctor does not propagate base properties (e.g.,Seed), so cloning can silently drop determinism settings.🔧 Suggested fix
public FinMAOptions(FinMAOptions<T> other) { if (other == null) throw new ArgumentNullException(nameof(other)); + Seed = other.Seed; MaxSequenceLength = other.MaxSequenceLength; VocabularySize = other.VocabularySize; HiddenDimension = other.HiddenDimension;src/Clustering/Ensemble/ConsensusClusteringOptions.cs (1)
34-38:⚠️ Potential issue | 🟠 MajorValidate
NumBaseClusteringsto avoid divide‑by‑zero.
BuildCoAssociationMatrixdivides byclusterings.Count; a value ≤ 0 will crash at runtime.✅ Suggested guarded setter
- public int NumBaseClusterings { get; set; } = 10; + private int _numBaseClusterings = 10; + public int NumBaseClusterings + { + get => _numBaseClusterings; + set => _numBaseClusterings = value < 1 + ? throw new ArgumentOutOfRangeException(nameof(NumBaseClusterings), "Must be >= 1.") + : value; + }src/Clustering/Density/Denclue.cs (1)
49-62:⚠️ Potential issue | 🟠 MajorEnsure
GetOptions()returns the same instance the model actually uses.When
optionsis null, the constructor creates two separateDenclueOptionsinstances (one for baseOptions, one for_options).GetOptions()exposes_options, butHillClimbreadsOptions.MaxIterations, so auto-applied changes can be ignored and the model becomes inconsistent. Use a single options instance.🔧 Proposed fix
public Denclue(DenclueOptions<T>? options = null) : base(options ?? new DenclueOptions<T>()) { - _options = options ?? new DenclueOptions<T>(); + _options = (DenclueOptions<T>)Options; }src/Clustering/Neural/SelfOrganizingMap.cs (1)
50-65:⚠️ Potential issue | 🟠 MajorKeep
GetOptions()aligned with the baseOptionsinstance.When
optionsis null,_optionsandOptionsare different objects. Training readsOptions.RandomState/MaxIterations, while auto-apply mutates_optionsviaGetOptions(), so applied values can be silently ignored. Point_optionstoOptionsto ensure a single source of truth.🔧 Suggested fix
public SelfOrganizingMap(SOMOptions<T>? options = null) : base(options ?? new SOMOptions<T>()) { - _options = options ?? new SOMOptions<T>(); + _options = (SOMOptions<T>)Options; }
🟠 Major comments (30)
src/Models/Options/REINFORCEOptions.cs-25-25 (1)
25-25:⚠️ Potential issue | 🟠 MajorConsider reducing public API surface for
REINFORCEOptions<T>.Unless this is intentionally exposed via
AiModelBuilder/AiModelResult, make itinternalto preserve the facade boundary.Suggested change
-public class REINFORCEOptions<T> : ModelOptions +internal class REINFORCEOptions<T> : ModelOptionsAs per coding guidelines, “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public for all classes, methods, and properties unless they are part of the facade API.”
src/Audio/Fingerprinting/SpectrogramFingerprintOptions.cs-8-8 (1)
8-8:⚠️ Potential issue | 🟠 MajorConsider reducing public API surface for options types.
Line 8 keeps this options class public, but the facade guidelines require users to interact only with
AiModelBuilder/AiModelResult. If this type isn’t explicitly part of the facade API, make itinternalor ensure the facade is the only exposure point.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicunless part of the facade API.”src/Regression/RobustRegression.cs-46-47 (1)
46-47:⚠️ Potential issue | 🟠 MajorPublic
GetOptions()may violate the facade-only API surface.This adds a new public method on a model type, which conflicts with the “users should only interact with AiModelBuilder/AiModelResult” guideline. If this is only for internal pipeline use, consider an explicit interface implementation (or internal visibility in the base/interface) to avoid expanding the public API. As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs… Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API.”src/Models/Options/TradingAgentOptions.cs-67-70 (1)
67-70:⚠️ Potential issue | 🟠 MajorProperty shadowing with
newundermines theModelOptionsconsolidation.Using
new int? Seedhides the inheritedSeedfromModelOptionsrather than reusing it. This creates two separateSeedstorage slots:
- Accessing
Seedvia aTradingAgentOptions<T>reference uses this property- Accessing
Seedvia aModelOptionsreference uses the base property- The reflection-based
AgentHyperparameterApplicatormay set the wrong one depending on which type it reflects overSince the PR objective is to consolidate
Seedinto the base class, this shadowing appears unintentional. Remove the redeclaration and let the class inheritSeedfromModelOptions.Proposed fix
- /// <summary> - /// Random seed for reproducibility. - /// </summary> - public new int? Seed { get; set; }src/Audio/Classification/SceneClassifierOptions.cs-9-9 (1)
9-9:⚠️ Potential issue | 🟠 MajorPublic API surface expanded via inheritance — confirm intent.
Line 9 makes a public options type inheritModelOptions, which likely exposes additional public members (e.g.,Seed) to consumers. If this type isn’t meant to be part of the public surface, consider making itinternalor otherwise hiding the inherited members. As per coding guidelines, “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API.”src/Models/Options/AiModelResultOptions.cs-83-83 (1)
83-83: 🛠️ Refactor suggestion | 🟠 MajorAvoid expanding public API outside the facade.
Inheriting
ModelOptionsadds its public members (e.g.,Seed) onto a public, non-facade type, widening the API surface beyondAiModelBuilder/AiModelResult. Consider making this typeinternalor moving/encapsulating the options behind the facade to keep the surface minimal.Possible direction
-public class AiModelResultOptions<T, TInput, TOutput> : ModelOptions +internal class AiModelResultOptions<T, TInput, TOutput> : ModelOptionsAs per coding guidelines, “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs”.src/Audio/AudioGen/AudioGenOptions.cs-73-73 (1)
73-73:⚠️ Potential issue | 🟠 MajorProperty shadowing with
newcan cause subtle bugs in reflection-based code.Using
newto shadowSeedmeans:
- When accessed via a
ModelOptionsreference, the base classSeedis read/written—not this one.- The reflection-based
AgentHyperparameterApplicatormay set the base property while this derived property remains unset (or vice versa).The PR objective mentioned "removed duplicate Seed properties" to favor base-type handling. If the base
ModelOptions.Seedisint?, consider removing this shadowed property entirely and inheriting. If the base is non-nullableint, consider making the base nullable instead to avoid shadowing across all derived options classes.#!/bin/bash # Description: Check the type of Seed in ModelOptions to understand the shadowing necessity. # Find ModelOptions base class definition and inspect Seed property ast-grep --pattern 'class ModelOptions { $$$ Seed { $$ } $$$ }' # Alternative: search for Seed property definition in ModelOptions rg -n -A2 -B2 'public.*Seed' --glob '**/ModelOptions.cs'src/Clustering/Streaming/OnlineKMeans.cs-52-53 (1)
52-53:⚠️ Potential issue | 🟠 MajorPublic
GetOptionsexpands API beyond the facadeThis adds a new public method on a model type, which appears to conflict with the facade-only public surface. If possible, keep
GetOptionsoff the public API (e.g., internal or explicit interface implementation) or otherwise confirm that this is intended to be part of the facade. As per coding guidelines: "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/Models/Options/StratifiedKFoldCrossValidationFitDetectorOptions.cs-34-34 (1)
34-34:⚠️ Potential issue | 🟠 MajorFacade pattern: avoid expanding public API with options types.
This class is
publicinsrc/**and now inheritsModelOptions, which further exposes base members (e.g.,Seed). If it’s not intended to be part of the facade, please make itinternalor route access throughAiModelBuilder/AiModelResultonly.🔧 Suggested change (if not facade-exposed)
-public class StratifiedKFoldCrossValidationFitDetectorOptions : ModelOptions +internal class StratifiedKFoldCrossValidationFitDetectorOptions : ModelOptionsAs per coding guidelines, "Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs" and "Prefer internal over public for all classes, methods, and properties unless they are part of the facade API."
src/Models/Options/DDPGOptions.cs-9-9 (1)
9-9: 🛠️ Refactor suggestion | 🟠 MajorNarrow the public API surface for options types.
Since this line changes the class declaration, please re-check whetherDDPGOptions<T>must be public. If it’s not explicitly exposed via the facade, make itinternalto avoid expanding the public API.Proposed change
-public class DDPGOptions<T> : ModelOptions +internal class DDPGOptions<T> : ModelOptionsAs per coding guidelines, “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API.”src/Models/Options/PatchTSTOptions.cs-33-33 (2)
33-33:⚠️ Potential issue | 🟠 MajorReduce public API surface for options types.
PatchTSTOptions<T>is public, which broadens the surface outside the facade. Unless this is intentionally exposed viaAiModelBuilder/AiModelResult, make itinternal.💡 Suggested change
-public class PatchTSTOptions<T> : ModelOptions +internal class PatchTSTOptions<T> : ModelOptionsAs per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes… unless they are part of the facade API.”
33-33:⚠️ Potential issue | 🟠 MajorAvoid dual seed sources after inheriting
ModelOptions.With
ModelOptionsnow in the inheritance chain, keeping a separateRandomSeedproperty risks conflicting values (e.g., agent setsSeedwhile training readsRandomSeed). Consider removingRandomSeedor shimming it to the baseSeedto keep a single source of truth.💡 Suggested shim to avoid divergence
- public int? RandomSeed { get; set; } + public int? RandomSeed + { + get => Seed; + set => Seed = value; + }src/Models/Options/FineTuningOptions.cs-18-18 (1)
18-18:⚠️ Potential issue | 🟠 MajorPublic API surface likely widened beyond the facade.
Inheriting
ModelOptionson a public options type exposes additional members (e.g.,Seed) that sit outside theAiModelBuilder/AiModelResultfacade. Please confirm this is intended or makeFineTuningOptions<T>internal (and expose via the facade) to keep the API surface tight.
As per coding guidelines: “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs … Prefer internal over public for all classes, methods, and properties unless they are part of the facade API.”src/Models/Options/ModelStatsOptions.cs-30-30 (1)
30-30: 🛠️ Refactor suggestion | 🟠 MajorConsider making this options type internal to preserve the facade-only API.
Inheriting
ModelOptionsadds public members to a public type outside the facade. If this isn’t intended for direct user consumption, mark itinternaland access viaAiModelBuilder/AiModelResult.♻️ Suggested change
-public class ModelStatsOptions : ModelOptions +internal class ModelStatsOptions : ModelOptionsAs per coding guidelines, “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public for all classes, methods, and properties unless they are part of the facade API”.
src/Models/Options/DuelingDQNOptions.cs-9-9 (1)
9-9: 🛠️ Refactor suggestion | 🟠 MajorRe-check public exposure now that
ModelOptionsis inherited.This keeps a non-facade options type public while adding base members to its surface. If it’s not meant to be user-facing, prefer
internaland expose configuration through the facade.♻️ Suggested change
-public class DuelingDQNOptions<T> : ModelOptions +internal class DuelingDQNOptions<T> : ModelOptionsAs per coding guidelines, “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public for all classes, methods, and properties unless they are part of the facade API”.
src/Models/Options/FeatureImportanceFitDetectorOptions.cs-19-19 (1)
19-19:⚠️ Potential issue | 🟠 MajorConfirm facade compliance for this newly-expanded public options type.
Line 19 now exposes
ModelOptionsmembers (e.g.,Seed) on a public options class, expanding the public API surface. If this type isn’t meant to be directly user-facing, consider making itinternal(or otherwise restricting exposure) to keep the facade boundaries intact; if it must be public, please confirm that the added base members are intentional and documented. As per coding guidelines: “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs.”src/Clustering/Density/Denclue.cs-49-50 (1)
49-50:⚠️ Potential issue | 🟠 MajorPublic
GetOptions()expands API surface outside the facade.This introduces a new public method on a non-facade class, which conflicts with the facade-only API policy. Consider making this internal (or exposing it only through
AiModelBuilder/AiModelResult) if external callers don’t need it. As per coding guidelines, “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API.”src/ReinforcementLearning/Agents/DynaQPlusAgent.cs-16-18 (1)
16-18:⚠️ Potential issue | 🟠 MajorPublic
GetOptions()expands non-facade API surface.
This adds a new public method on a model class; the facade guideline says users should only interact withAiModelBuilder/AiModelResult. If this is only for internal hyperparameter application, consider making the access internal (e.g., internal interface or builder-mediated access).As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor non-facade members.”src/ReinforcementLearning/Agents/EveryVisitMonteCarloAgent.cs-16-17 (1)
16-17: 🛠️ Refactor suggestion | 🟠 MajorFacade API leakage via public
GetOptions.This adds a new public entry point on a non‑facade class. If this is only meant for internal hyperparameter plumbing, consider making it internal or an explicit interface implementation (so it doesn’t surface on the public API), or document why it must be public.
As per coding guidelines: “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicunless part of the facade API.”src/Clustering/Hierarchical/BisectingKMeans.cs-45-47 (1)
45-47:⚠️ Potential issue | 🟠 MajorPublic
GetOptions()expands API beyond the facade.This exposes internal configuration on a non-facade type; please keep this internal/explicitly-implemented (or route through
AiModelBuilder/AiModelResult) unless there’s a strong public API requirement.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor implementation details.”src/Clustering/Density/DBSCAN.cs-51-52 (1)
51-52:⚠️ Potential issue | 🟠 MajorFacade pattern/API surface: avoid new public entry points on model classes.
Line 51-52 adds a public
GetOptions()on a non‑facade type. This expands the public API surface beyondAiModelBuilder/AiModelResult. Can this be made internal/explicit‑interface (behind an internal interface) or routed through the facade instead?As per coding guidelines, “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public for all classes, methods, and properties unless they are part of the facade API.”
src/ReinforcementLearning/Agents/DreamerAgent.cs-46-47 (1)
46-47:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This adds a public method on a non‑facade type, which conflicts with the facade-only public surface guidance. Please consider making options access internal/explicit to the facade (or otherwise justify why this must be publicly exposed).
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/MetaLearning/Algorithms/MANNAlgorithm.cs-96-97 (1)
96-97:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This exposes options via a public method on a non‑facade class. Please consider keeping option access internal/behind the facade (or otherwise justify the public exposure).
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/MetaLearning/Algorithms/NTMAlgorithm.cs-101-102 (1)
101-102:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This adds a public options accessor on a non‑facade class. Please consider keeping it internal/behind the facade or justify why it needs to be public.
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/ReinforcementLearning/Agents/A2CAgent.cs-41-42 (1)
41-42:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This adds public options exposure on a non‑facade type. Please consider making it internal/explicitly facade‑only or provide justification for the public surface.
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/ReinforcementLearning/Agents/DDPGAgent.cs-48-49 (1)
48-49:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This introduces a public options accessor on a non‑facade class. Consider keeping it internal/behind the facade or justify the public surface.
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/ReinforcementLearning/Agents/DQNAgent.cs-44-45 (1)
44-45:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This exposes options via a public method on a non‑facade type. Please keep this internal/behind the facade or justify the public exposure.
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/Classification/MultiLabel/MLkNNClassifier.cs-36-37 (1)
36-37:⚠️ Potential issue | 🟠 MajorPublic GetOptions expands non‑facade API surface
This exposes options publicly on a non‑facade class. Please consider keeping it internal/behind the facade or justify why this must be public.
As per coding guidelines, "Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/ReinforcementLearning/Agents/A3CAgent.cs-41-43 (1)
41-43:⚠️ Potential issue | 🟠 MajorFacade pattern: avoid introducing new public API on non-facade types.
GetOptions()is a new public method on a non-facade class, expanding the public surface. If this is required for hyperparameter application, consider making the interface internal or using explicit interface implementation so the concrete type’s public API stays minimal.As per coding guidelines: "Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs" and "Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API."src/Models/Options/ObjectDetectionOptions.cs-15-15 (1)
15-15:⚠️ Potential issue | 🟠 MajorConsider making
ObjectDetectionOptions<T>internal to preserve the facade.Unless this type is intentionally exposed by
AiModelBuilder/AiModelResult, keep itinternalto avoid expanding the public API surface.🔧 Suggested change
-public class ObjectDetectionOptions<T> : ModelOptions +internal class ObjectDetectionOptions<T> : ModelOptionsAs per coding guidelines: “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public for all classes, methods, and properties unless they are part of the facade API.”
🧹 Nitpick comments (17)
src/Models/Options/ActiveLearningOptions.cs (1)
119-119: Consider makingActiveLearningOptionsinternal unless it’s part of the facade surface.The facade guideline says only
AiModelBuilder/AiModelResult(and types they directly expose) should be public. If this options type isn’t intentionally exposed, preferinternalto keep the API surface minimal.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs… Preferinternaloverpublicunless part of the facade API.”src/Models/Options/PrecisionRecallCurveFitDetectorOptions.cs (1)
40-40: Consider reducing API surface: make thisinternalunless facade needs it.This options class is
publicoutside the facade types. Per the architecture guidelines, confirm it must be public; otherwise preferinternalto keep the public surface minimal.If you want a quick check, I can help generate a script to locate direct public exposure or usage from
AiModelBuilder/AiModelResult.
As per coding guidelines, “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes…unless they are part of the facade API.”src/Classification/OrdinalRegression.cs (1)
53-54: Consider reducing public API exposure ofGetOptionsif not strictly required.This adds a public method on a non-facade class; if
GetOptionsis only needed internally, consider explicit interface implementation or narrowing visibility (where possible) to keep the facade pattern intact.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs… Any new public methods or classes that users might call directly should be scrutinized.”src/Models/Options/PortfolioOptions.cs (1)
7-7: Confirm this options type should remain public.
Given the facade pattern, consider making thisinternalunless it’s part of the intended public surface viaAiModelBuilder/AiModelResult. As per coding guidelines, “Users should ONLY interact withAiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes unless they are part of the facade API.”src/Models/Options/NASOptions.cs (1)
287-290: Consider consolidatingRandomSeedwith inheritedSeedproperty.The class now inherits a
Seedproperty fromModelOptions, but also defines its ownRandomSeedproperty at line 290. Having two seed-related properties with different names could confuse users about which one to set.Based on the PR objective to "removed duplicate Seed properties," consider whether
RandomSeedshould be removed in favor of the inheritedSeed, or if there's a semantic distinction that warrants keeping both (in which case, the XML documentation should clarify the difference).Option A: Remove duplicate and use inherited property
- /// <summary> - /// Gets or sets the random seed for reproducibility. - /// </summary> - /// <remarks> - /// <para><b>For Reproducibility:</b> Set a seed to get repeatable results.</para> - /// </remarks> - public int? RandomSeed { get; set; }Then update any internal usages to reference
Seedinstead.src/Models/Options/IQLOptions.cs (1)
67-77: Consider extending validation to cover IQL-specific hyperparameters.Given the PR's focus on hyperparameter infrastructure and validation, you might enhance
Validate()to cover constraints already documented in comments (e.g.,Expectiletypically 0.7-0.9) and standard RL invariants (learning rates > 0,DiscountFactor∈ (0, 1]).💡 Example validation additions
public void Validate() { if (StateSize <= 0) throw new ArgumentException("StateSize must be greater than 0", nameof(StateSize)); if (ActionSize <= 0) throw new ArgumentException("ActionSize must be greater than 0", nameof(ActionSize)); if (BatchSize <= 0) throw new ArgumentException("BatchSize must be greater than 0", nameof(BatchSize)); if (BufferSize <= 0) throw new ArgumentException("BufferSize must be greater than 0", nameof(BufferSize)); + if (Expectile <= 0 || Expectile >= 1) + throw new ArgumentException("Expectile must be in (0, 1), typically 0.7-0.9", nameof(Expectile)); }src/Models/Options/ShapleyValueFitDetectorOptions.cs (1)
1-1: Remove redundant using if covered by global usings.If
AiDotNet.Models.Optionsis already in the project-level global usings (as per repo convention), this file-scoped using adds noise and can be dropped.♻️ Suggested cleanup
-using AiDotNet.Models.Options;Based on learnings: global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*); prefer relying on global usings to reduce boilerplate.
src/Audio/Classification/GenreClassifierOptions.cs (1)
9-9: Architectural consideration (optional): Per the facade pattern guidelines, domain-specific options classes like this could potentially beinternalif users only configure them indirectly throughAiModelBuilder. However, given this PR standardizes 60+ options classes withpublicvisibility for direct configuration access, keeping consistency here is reasonable. This is something to consider holistically in a future API surface review rather than piecemeal.src/Audio/Whisper/WhisperOptions.cs (1)
25-25: Confirm public API expansion from ModelOptions inheritance.Line 25 now exposes ModelOptions members (e.g., Seed) on a public options type. If WhisperOptions isn’t meant to be part of the facade API surface, consider making it internal (or hiding the base members) to keep users on AiModelBuilder/AiModelResult.
As per coding guidelines: “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public.”🔧 Optional change if WhisperOptions should not be public
-public class WhisperOptions : ModelOptions +internal class WhisperOptions : ModelOptionssrc/Models/Options/SECBERTOptions.cs (1)
108-128: Consider calling base validation ifModelOptionshas or gains aValidate()method.If
ModelOptionsprovides (or will provide) its ownValidate()method for properties likeSeed, this override should chain tobase.Validate(). This future-proofs the code against base class validation additions.src/Audio/Speaker/SpeakerEmbeddingOptions.cs (1)
1-1: Consider relying on global usings to avoid redundant imports.Line 1 may be redundant if AiDotNet.* is already globally imported; consider removing it to keep the file consistent with the project’s global-using setup.
♻️ Optional cleanup
-using AiDotNet.Models.Options;Based on learnings, "global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) ... Prefer relying on global usings to reduce boilerplate."
src/Clustering/Ensemble/ConsensusClustering.cs (1)
49-50: Consider limiting the new publicGetOptions()exposure.
If this accessor is only for internal auto-apply plumbing, prefer an internal/explicit interface implementation to keep end‑users on the facade API.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs.”src/Clustering/Ensemble/ConsensusClusteringOptions.cs (1)
32-32: Double‑check public exposure of options types.
If end users should only interact with the facade, consider making options internal or otherwise hiding them from the public surface.As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs.”src/Models/Options/BloombergGPTOptions.cs (1)
43-43: Consider making this typeinternalunless it’s part of the facade API.If this options class isn’t directly exposed by
AiModelBuilder/AiModelResult, keeping itpublicexpands the public surface area unnecessarily.As per coding guidelines, “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs… Preferinternaloverpublicunless part of the facade API.”src/Models/Options/SACOptions.cs (1)
30-30: Confirm public API exposure from inheritingModelOptions.This adds a new public base type (and its members, e.g., Seed) to a public options class, which can broaden the public surface and be breaking for downstream subclasses. Please confirm this aligns with the facade design; if not, consider keeping options internal or exposing them only via
AiModelBuilder.As per coding guidelines: Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs, and preferinternaloverpublicfor other types.src/MetaLearning/Algorithms/MetaSGDAlgorithm.cs (2)
687-687: Consider makingPerParameterOptimizerinternal.Per the architecture guidelines, implementation details should prefer
internaloverpublic.PerParameterOptimizerappears to be an internal helper for the Meta-SGD algorithm rather than part of the public facade API. Users interact withMetaSGDAlgorithmand shouldn't need direct access to this optimizer class.♻️ Suggested change
-public class PerParameterOptimizer<T, TInput, TOutput> +internal class PerParameterOptimizer<T, TInput, TOutput>As per coding guidelines: "Prefer
internaloverpublicfor all classes, methods, and properties unless they are part of the facade API."
1086-1086: Consider makingMetaSGDAdaptedModelinternal.Similar to
PerParameterOptimizer, this wrapper model is an implementation detail. TheAdapt()method returnsIModel<TInput, TOutput, ModelMetadata<T>>, so consumers work through the interface rather than the concrete type. Making this class internal would reduce the public API surface without breaking functionality.♻️ Suggested change
-public class MetaSGDAdaptedModel<T, TInput, TOutput> : IModel<TInput, TOutput, ModelMetadata<T>> +internal class MetaSGDAdaptedModel<T, TInput, TOutput> : IModel<TInput, TOutput, ModelMetadata<T>>As per coding guidelines: "Helper classes, utilities, and implementation details should be
internal."
…rmed models (#460) Add GetOptions() overrides to 45 model classes across: - Core Neural Networks: MeshCNN, MixtureOfExperts - Audio: AudioLDM, MusicGen, StableAudio, MusicSourceSeparator, VoxLingua107, Wav2Vec2LID, ECAPA-TDNN, SpeakerEmbedding, SpeakerDiarizer, AudioEventDetector, SceneClassifier, GenreClassifier - Finance Graph: TemporalGCN, STGNN, RelationalGCN, MTGNN, GraphWaveNet, DCRNN - Finance Risk: NeuralCVaR, NeuralStressTest, SAINT, TabNet, TabTransformer - Finance Portfolio: HierarchicalRiskParity, BlackLittermanNeural, AttentionAllocation - Finance NLP: FinBERT - Finance Trading: AlphaFactor, FactorTransformer, FactorVAE, MarketMaking - Finance Probabilistic: DiffusionTS, ScoreGrad, TimeGrad, TSDiff, CSDI - Finance StateSpace: TimeMachine, S4, Hippo - Finance Volatility: NeuralGARCH, RealizedVolatilityTransformer - PhysicsInformed: InverseProblemPINN, MultiScalePINN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Finance/NLP/FinBERT.cs (1)
193-205:⚠️ Potential issue | 🟠 MajorOptions mutations won’t update cached fields/layers.
Options = _optionsexposes mutable options after construction, but_maxSequenceLength,_numLayers,_dropoutRate, etc. are copied once and layers are initialized from those values. If auto-apply mutates options later, FinBERT continues using stale values and recommendations won’t actually take effect. Consider syncing cached fields (and reinitializing layers when safe) after recommendations, or skip auto-apply for this model.♻️ Suggested refactor to centralize option sync
+ private void ApplyOptionsSnapshot() + { + _maxSequenceLength = _options.MaxSequenceLength; + _vocabularySize = _options.VocabularySize; + _hiddenDimension = _options.HiddenDimension; + _numAttentionHeads = _options.NumAttentionHeads; + _intermediateDimension = _options.IntermediateDimension; + _numLayers = _options.NumLayers; + _numSentimentClasses = _options.NumSentimentClasses; + _dropoutRate = _options.DropoutRate; + }- _options = options ?? new FinBERTOptions<T>(); - Options = _options; + _options = options ?? new FinBERTOptions<T>(); + Options = _options; + ApplyOptionsSnapshot();- _options = options ?? new FinBERTOptions<T>(); - Options = _options; + _options = options ?? new FinBERTOptions<T>(); + Options = _options; + ApplyOptionsSnapshot();Also applies to: 236-248
🤖 Fix all issues with AI agents
In `@src/Audio/Classification/AudioEventDetector.cs`:
- Around line 62-64: The AudioEventDetector class's constructors do not assign
the backing field to the base Options property, causing GetOptions() (which
returns _options) to diverge from the inherited Options; update each constructor
(the ONNX constructor, the native constructor, and ensure the legacy constructor
which delegates to native inherits this) to set Options = _options after
_options is initialized so the base Options property and the GetOptions()
override remain synchronized (refer to AudioEventDetector, GetOptions, _options,
and Options).
In `@src/Audio/Classification/SceneClassifier.cs`:
- Around line 48-52: The class exposes _options via GetOptions but never wires
the base Options or refreshes derived state; update each constructor to assign
Options = _options and call a new RefreshDerivedState() method that rebuilds
SampleRate, ClassLabels and feature extractors from _options, and also invoke
RefreshDerivedState() whenever Options/_options is changed (e.g., via an
OnOptionsChanged or setter hook) so runtime option changes reinitialize
dependent state in SceneClassifier (referencing SceneClassifierOptions,
GetOptions, Options, SampleRate, ClassLabels, and the feature extractor fields).
In `@src/Audio/SourceSeparation/MusicSourceSeparator.cs`:
- Around line 48-51: The public override GetOptions() on MusicSourceSeparator
exposes internal configuration (SourceSeparationOptions/ModelOptions); change
its accessibility to a non-public surface—either make the override internal
(internal override ModelOptions GetOptions()) or remove the public member and
implement it as an explicit interface or internal helper (e.g., an explicit
IInternalModelOptions.GetOptions implementation or a private/internal
GetOptionsInternal) so only internal components (AiModelBuilder/AiModelResult)
can access it; update all internal callers to use the new non-public API and
ensure the method signature still returns ModelOptions.SourceSeparationOptions
mapping as before.
In `@src/Finance/Forecasting/StateSpace/S4.cs`:
- Around line 87-91: The S4 class currently snapshots S4Options<T> into private
fields at construction (private field _options and usage in layer-building) so
later changes via GetOptions or exposed options won't affect the cached fields
or the built layer stack; add a refresh hook (e.g., a public method
RefreshConfiguration or ApplyOptions) that copies current values from _options
back into the internal cached fields and rebuilds the dependent layers (call the
existing layer construction routine such as BuildLayers/RebuildLayerStack or the
constructor logic), and invoke this hook whenever options are mutated (or expose
a setter that calls it); ensure you update references to _options, S4Options<T>,
and any cached parameters used in forward/initialization to use the refreshed
cached values and add minimal synchronization (lock) around the rebuild to be
thread-safe.
In `@src/Finance/Graph/GraphWaveNet.cs`:
- Around line 99-103: The class currently snapshots GraphWaveNetOptions<T> into
the private field _options and precomputes adjacency/transpose and layer
configuration in the constructor, so later changes to options won’t propagate;
add a refresh hook such as a public method RefreshOptions(GraphWaveNetOptions<T>
newOptions) (or ApplyOptions) that updates the private _options, recomputes
adjacency/transpose and reinitializes layer configuration (the same logic
currently in the constructor), and ensure callers invoke this after
auto-applying hyperparameters; reference the existing private field _options and
the constructor logic that builds adjacency/transpose and layers to locate where
to extract and reuse the recomputation code.
In `@src/Finance/Graph/MTGNN.cs`:
- Around line 97-101: The class caches MTGNNOptions<T> in the private field
_options and builds embeddings, adjacency matrices and layers at construction,
so subsequent mutations via GetOptions() do not update those cached structures;
add a refresh path (e.g., a public RefreshOptions or ApplyOptionsChanges method)
that takes the current _options (or a new MTGNNOptions<T>), re-syncs the cached
fields, disposes/rebuilds embeddings and adjacency structures and reinitializes
layers (referencing the constructor logic that creates
embeddings/adjacency/layers) and call this method from any external setter or
option-wiring point so runtime hyperparameter changes take effect.
In `@src/Finance/Probabilistic/CSDI.cs`:
- Around line 96-99: GetOptions currently returns the live _options instance but
the CSDI class snapshots _options into private fields and builds diffusion
schedule/layers in the constructor, so post-construction mutations to _options
(via auto-apply) leave cached state stale; either change GetOptions to return an
immutable deep copy of _options to prevent external mutation, or implement a
refresh method (e.g., RefreshOptions or RebuildDependentState) that re-reads
_options and reconstructs the cached schedule/layers and any other snapshot
fields (called from the constructor and any option-apply hook) so GetOptions can
safely expose the current configuration without desyncing inference (references:
GetOptions, _options, the CSDI constructor, diffusion schedule/layers private
fields).
In `@src/Finance/Risk/NeuralCVaR.cs`:
- Around line 32-36: NeuralCVaR<T> stores a readonly _options and returns it via
GetOptions(), so post-construction changes to those options won't rebuild the
network; add a public Refresh(NeuralCVaROptions<T> newOptions) (or
ReloadOptions) on NeuralCVaR<T> that replaces the current options and calls the
existing network reinitialization routine (e.g., invoke the method that builds
layers—add a RebuildNetworkLayers/ReinitializeLayers helper if none exists) to
reconstruct layers and refresh any cached config, and change GetOptions() to
return a defensive copy (deep clone) of _options rather than the internal
reference to prevent external mutation; update any constructors/usage to call
Refresh when live option updates are applied.
In `@src/Finance/Risk/TabNet.cs`:
- Around line 28-32: The class caches TabNetOptions<T> in the private field
_options and exposes them via GetOptions(), but updating those live options does
not rebuild the cached config or layer stack the constructor creates; add an
explicit refresh/update path (e.g., a public UpdateOptions(TabNetOptions<T>
newOptions) or OnOptionsChanged method) that assigns _options = newOptions,
re-syncs any cached fields derived from _options, and calls the layer
reinitialization routine (implement or call a RebuildLayers/InitializeLayers
private method that encapsulates the constructor's layer-creation logic used in
the class constructor); ensure the update method is thread-safe (lock or
Interlocked) and used wherever options can change so new options take effect
immediately.
In `@src/Finance/Trading/Agents/MarketMakingAgent.cs`:
- Around line 23-28: The public GetOptions() method on MarketMakingAgent<T>
expands the public API surface; change its accessibility to internal (or make
the MarketMakingAgent<T> class internal) so only the facade types
(AiModelBuilder / AiModelResult) are exposed; specifically, update the signature
of GetOptions() (and any interface/base it implements, e.g., ModelOptions
retrieval) to internal and ensure MarketMakingOptions<T> usage remains
accessible internally without exposing the method publicly.
🧹 Nitpick comments (2)
src/Finance/Portfolio/BlackLittermanNeural.cs (1)
32-34: Consider reducing public API exposure ofGetOptions.Line 32-34 adds/solidifies a public override that exposes internal configuration. If this is only needed for internal auto-apply plumbing, consider making it internal or an explicit interface member to keep the public surface minimal and aligned with the facade model.
As per coding guidelines: “Users should ONLY interact with
AiModelBuilder.csandAiModelResult.cs” and “Preferinternaloverpublicfor all classes, methods, and properties unless they are part of the facade API.”src/Finance/NLP/FinBERT.cs (1)
104-108: Re-check the publicGetOptions()exposure against the facade API.
If this exists only for internal auto-apply, consider reducing visibility (e.g., explicit interface implementation orinternalin the base) to avoid expanding the public surface of model classes.
As per coding guidelines, “Users should ONLY interact with AiModelBuilder.cs and AiModelResult.cs” and “Prefer internal over public”.
) Create domain-specific options classes for models that don't yet have private _options fields. Each class inherits from the appropriate base (NeuralNetworkOptions, DocumentNeuralNetworkOptions, PhysicsInformedOptions) and can be extended with model-specific hyperparameters over time. - 79 core NeuralNetworks/Options/ files - 29 Document/Options/ files - 30 Video/Options/ files - 9 PhysicsInformed/Options/ files - 2 PointCloud/Options/ files - 1 ProgramSynthesis/Options/ file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add _options field, GetOptions() override, and constructor parameter to all core neural network model files. Also add 12 new options classes for models that were missing them (BGE, ColBERT, ConditionalGAN, DCGAN, InstructorEmbedding, MatryoshkaEmbedding, SGPT, SimCSE, SPLADE, and 3 PhysicsInformed PINNs). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… NeuralRadianceFields, and ProgramSynthesis models (#460) Wire _options fields, GetOptions() overrides, and constructor parameters into 78 remaining model classes across all domains: - 29 Document models (OCR, layout-aware, graph-based, pixel-to-sequence, vision-language) - 30 Video models (action recognition, depth, enhancement, generation, segmentation, tracking, etc.) - 12 PhysicsInformed models (PINNs, neural operators, scientific ML) - 3 PointCloud models (DGCNN, PointNet, PointNet++) - 3 NeuralRadianceFields models (GaussianSplatting, InstantNGP, NeRF) - 1 ProgramSynthesis model (NeuralProgramSynthesizer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create a fresh default options instance of the same concrete type to compare property values, instead of comparing against default(T). This correctly handles non-zero defaults (e.g., LearningRate=0.001) and reference types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/Agents/AgentHyperparameterApplicator.cs`:
- Around line 194-210: Replace the current enum handling block
(targetType.IsEnum / Enum.Parse) to validate and log failures: use Enum.TryParse
for string inputs and if it fails call _logger?.LogWarning with the string and
targetType.Name and return null; for numeric inputs convert to the enum's
underlying type using Convert.ChangeType with CultureInfo.InvariantCulture,
check membership with Enum.IsDefined before calling Enum.ToObject, log a warning
if not defined, and catch/ log any conversion exceptions via _logger?.LogWarning
then return null; remove the silent catch that returns null after Enum.Parse.
- Use Enum.IsDefined after parsing to reject undefined enum values - Handle numeric-to-enum conversion via Enum.ToObject with validation - Catch specific exceptions instead of generic catch for enum parsing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…engine ops [prototype] Rewrites the inference-only cached GQA layer to apply RoPE via Engine.ApplyRoPEInterleaved and grouped-query attention via Engine.ScaledDotProductAttentionGqa (unexpanded K/V) — a device-agnostic, GPU-graph-recordable forward that drops the two managed unrecordable hotspots (managed RoPE + ExpandKVHeads). RotaryPositionalEncodingLayer exposes its cos/sin caches (GetInterleavedCaches) for the fused op. ALiBi keeps the expand + FlashAttention path. Verified: the optimized (non-paged) model's forward matches the original managed GQA decoder within 1e-3 (InferenceOptimizer_CachedGroupedQueryAttention_RewrittenForward_MatchesOriginal), run against a local AiDotNet.Tensors probe. PROTOTYPE / RELEASE-GATED: the pin is a LOCAL probe (0.117.200-devexec) of the unreleased AiDotNet.Tensors PR #828 (device-agnostic execution). It MUST be bumped to the released AiDotNet.Tensors version before this lands / any AiDotNet PR — CI cannot restore the probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ecode/prefill perf (#1917) * feat(device): model.To(DeviceInfo) / layer.To(DeviceInfo) placement API PyTorch-style device placement, enum-only (no magic-string overloads). LayerBase.To moves registered parameters + buffers and recurses sublayers; NeuralNetworkBase.To moves every layer. Skips zero-length (deferred) params. First user-facing piece of the device-agnostic execution model; builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(device): facade ConfigureModel(source, DeviceInfo) overload (Level 1) Load a pretrained checkpoint and place the whole model on a device in one call — the common "load onto my GPU" case — via the type-safe enum DeviceInfo (no device strings). Additive overload: existing ConfigureModel(source) callers are unchanged. Delegates to model.To(device). Builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(device): regression test for model.To(OpenCL()) placement -> 7042 Proves the PyTorch-style placement API (model.To(DeviceInfo.OpenCL())) drives correct GPU execution to llama.cpp's greedy token. Uses AutoDetectAndConfigureGpu so placement (Tensor.To -> global backend) and execution (Current dispatcher) share one backend; skips when no GPU is wired in-process (validates on DevHost/CUDA). Surfaced that To() and Current use different backend acquisition — unify in Phase 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(device): phase-2 fusion acceptance test (skipped) + document the gap Runs the whole decoder forward inside a DeferredScope (BeginDeferredScope -> Predict -> Execute) as the acceptance check for transparent fusion. Today it returns token 0 (all-zero logits) instead of 7042: the graph capture/replay does not materialize the decoder's output, so transparent fusion needs the graph path (output binding + RoPE/GQA-SDPA/RMSNorm recording) debugged before it can wrap Predict. Skipped with that reason; un-skip when the graph path is fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): inferenceOptimizer recurses into PreLNTransformerBlock for GQA KV-cache Adds PreLNTransformerBlock.ReplaceAttention (mirrors TransformerEncoderBlock) and a PreLNTransformerBlock case in ApplyAttentionOptimizations that swaps the block's nested GroupedQueryAttentionLayer for a KV-cached CachedGroupedQueryAttention (shared helper BuildCachedGqaReplacement, reused by the top-level GQA case). This is the first piece of wiring the GGUF/LLaMA decoder (GQA nested in PreLNTransformerBlock) to incremental KV-cache decode. Still inert end-to-end: InitializeGQAKVCache's collection scan is also top-level (won't yet find the nested cached GQA), and ServableModelWrapper requires a paged cache that GQA lacks — so the incremental clone is still discarded and serving falls back to the eager model (no regression). Remaining: recurse InitializeGQAKVCache; accept the non-paged GQA cache as the incremental cache. Builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): enumerateAttentionHosts reaches PreLNTransformerBlock attention The shared attention-host enumerator (used by the GQA/paged KV-cache init, attention-optimizability detection, and quantization scans) recursed into TransformerEncoderBlock/TransformerDecoderBlock but not PreLNTransformerBlock, where LLaMA/GGUF decoders host their grouped-query attention. Add that case so InitializeGQAKVCache (and the other scans) find the nested CachedGroupedQueryAttention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): make PagedAttentionKernel grouped-query-attention aware Add PagedAttentionConfig.NumQueryHeads (0 => same as NumHeads = MHA). NumHeads is now the KV/cache head count; query heads may outnumber it under GQA, with each KV head shared by NumQueryHeads/NumHeads query heads (kvHead = qHead / group). Threaded through every compute path — ComputeAttention, ComputeTiledPagedAttention (decode), ComputeContiguousCausalPrefill (prefill), ComputeBatchedAttention, and the fused Forward/ForwardQuantized (asymmetric q_proj vs k_proj/v_proj, matching HF). K/V buffers size to the KV heads the cache stores; per-query-head online-softmax scratch. MHA stays the group==1 special case (all 43 PagedAttention tests unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): make PagedCachedMultiHeadAttention grouped-query-attention aware Add kvHeadCount ctor param (0 => headCount = MHA). K/V weights are now [embDim, kvHeadCount*headDim] (narrower under GQA); Q/O stay [embDim, embDim]. Threaded kvProjDim through every projection + cache write in the per-token, batched-GEMM, and contiguous-prefill paths; RoPE applies to Q over query heads and K over KV heads; param serialization and int8 quant use the real per-weight widths. Expose KVHeadCount for the optimizer to build the paged config/kernel. Head split/merge and repeat_kv now use vectorized library ops (Reshape/Transpose and Engine.TensorGather on the head axis) instead of scalar nested loops. MHA is the kvHeadCount==headCount special case; all 43 PagedAttention tests unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): wire grouped-query decoders onto the paged KV-cache path InitializePagedKVCache now sizes the PagedKVCache by the KV-head count and passes NumQueryHeads to the kernel so it repeats each KV head across its query-head group. BuildPagedGqaReplacement converts a GroupedQueryAttentionLayer (top-level or nested in PreLNTransformerBlock) to a PagedCachedMultiHeadAttention with the source KV-head count, copying its [Q][K][V][O][outBias] parameters and RoPE/ALiBi config; the GQA branches prefer it when paged KV is enabled. Guarded by GroupedQueryAttentionLayer. UsesProjectionBias: models with Q/K/V projection bias (Qwen2-style) fall back to the contiguous CachedGroupedQueryAttention, which the paged layer cannot represent. 200 paged/optimizer/KVCache/GQA tests green net10.0; both TFMs build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(serving): paged grouped-query attention matches the original decoder Numerical-parity test: a tiny GQA decoder (numHeads=4, numKVHeads=2, RoPE) nested in a PreLNTransformerBlock is optimized with the paged KV cache enabled, and the optimized forward matches the original within 1e-3 — proving the paged-GQA kernel repeat-KV, the layer's narrow K/V projections, the [Q][K][V][O][outBias] weight copy, and the interleaved-RoPE convention are all faithful. Also asserts the GQA is rewritten to PagedCachedMultiHeadAttention and the paged KV cache is live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(serialize): groupedQueryAttentionLayer round-trips RoPE, mask, bias, softcap A cloned GQA layer (serialize -> deserialize) silently lost its RoPE positional encoding, causal mask, custom head dimension, Q/K/V projection bias, and attention logit soft-cap: GetMetadata never persisted them and the deserializer defaulted the bools to false with no ConfigurePositionalEncoding call. So a cloned decoder computed bidirectional, RoPE-less attention and diverged — which is exactly what breaks the incremental-serving clone of a GGUF/LLaMA decoder. Persist all of them in GetMetadata and restore them (incl. RoPE via the layer's ConfigurePositionalEncoding) on deserialize. New test clones a tiny RoPE+causal GQA model and asserts forward parity within 1e-4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serialize): make PreLNTransformerBlock cloneable so paged serving engages The block hosts a polymorphic (T5/MHA/GQA) attention sublayer, so it had no deserialization constructor and cloning threw — which silently disabled the paged incremental-generation clone (ServableModelWrapper.BuildIncrementalModel) for every GGUF/LLaMA decoder. GetMetadata now persists the block dims + FFN activation + the nested attention as an 'Attn.'-prefixed self-contained sub-blob (type + its metadata + shapes); a new DeserializationHelper branch rebuilds the attention recursively via CreateLayerFromType (E1 keeps its RoPE/mask) and constructs the block. OnFirstForward resolves the lazy norms + FFN in forward order (deser/resolve path only, so no RNG perturbation on a normal forward) so ParameterCount is correct before SetParameters. The paged-GQA parity test now runs with cloneModel: true (the real serving path) and still matches the original within 1e-3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(serving): grouped-query decoder builds the paged incremental path End-to-end proof that paged-GQA engages in serving: a numHeads>numKVHeads PreLN decoder (RoPE) wrapped in ServableModelWrapper now reports SupportsIncrementalGeneration true and generates in-range tokens. Exercises the full chain — GQA-aware paged kernel, narrow-K/V paged attention layer, optimizer GQA->paged rewrite, and PreLNTransformerBlock + GQA serialization (the clone BuildIncrementalModel performs). Before this the clone threw and every LLaMA/GGUF-style decoder silently fell back to the stateless path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(inference): cachedGroupedQueryAttention forward onto recordable engine ops [prototype] Rewrites the inference-only cached GQA layer to apply RoPE via Engine.ApplyRoPEInterleaved and grouped-query attention via Engine.ScaledDotProductAttentionGqa (unexpanded K/V) — a device-agnostic, GPU-graph-recordable forward that drops the two managed unrecordable hotspots (managed RoPE + ExpandKVHeads). RotaryPositionalEncodingLayer exposes its cos/sin caches (GetInterleavedCaches) for the fused op. ALiBi keeps the expand + FlashAttention path. Verified: the optimized (non-paged) model's forward matches the original managed GQA decoder within 1e-3 (InferenceOptimizer_CachedGroupedQueryAttention_RewrittenForward_MatchesOriginal), run against a local AiDotNet.Tensors probe. PROTOTYPE / RELEASE-GATED: the pin is a LOCAL probe (0.117.200-devexec) of the unreleased AiDotNet.Tensors PR #828 (device-agnostic execution). It MUST be bumped to the released AiDotNet.Tensors version before this lands / any AiDotNet PR — CI cannot restore the probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump probe pin to 0.117.201-devexec (GQA-SDPA causal KV-cache offset fix) Local probe of the Tensors branch after the OpenCL GQA-SDPA causal offset fix. Still release-gated — bumps to the released AiDotNet.Tensors before any AiDotNet PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gguf): serve Q8_0 weights quantized via block-Q8_0 GEMM (decode) + profiling harness keeps GGUF Q8_0 linear weights in their native int8 layout instead of expanding to fp32 at load, and runs the block-Q8_0 GEMM (Tensors Q8BlockGemm, ggml q8_0 parity) directly on them for float inference. GgufFile.TryReadQ8_0Raw reads the native blocks (int8 + per-32 fp16 scale); GgufModelSource.TryReadQ8_0 maps the HF name; LlamaModelBuilder.LoadDense installs them on the FFN + lm_head DenseLayers; DenseLayer runs the quantized GEMM on the float inference path. gated to small M (decode/small-batch, where the naive int8 kernel is bandwidth-bound and beats fp32 BLAS); large-M prefill stays on fp32 until the int8 GEMM is register-tiled, so prefill is unchanged (451 tok/s) while decode runs on int8. gguf 7042 llama.cpp parity preserved. also adds the DEVHOST_PROFILE steady-state loop used to profile the forward. NOTE: release-gated on Tensors Q8BlockGemm (probe pin 0.118.10-q8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(gguf): gate Q8_0 quantized forward on VNNI hardware the block-Q8_0 GEMM only beats the tuned fp32 BLAS when the CPU has a 1-instruction VNNI int8 multiply-accumulate (AvxVnni / Avx512 vpdpbusd). on AVX2-only CPUs the 3-instruction maddubs dot is no faster, so engaging it there would regress. gate the DenseLayer quantized path on AvxVnni/Avx512BW (net471 has no intrinsics -> false), so it auto-engages on the VNNI serving hardware and stays on fp32 otherwise. weights are still read + kept as Q8_0 regardless (RAM benefit); only the forward path is gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(serving): bulk-read paged KV under one lock + decode benchmark harness decode profiling (new DEVHOST_DECODE mode: real incremental paged generation, reports tok/s) showed ComputeTiledPagedAttention read the KV history one locked ReadKey/ReadValue per position -> O(seqLen) contended Monitor.Enter per layer per token. add PagedKVCache.ReadKeyValueRange (one lock, whole range) and read the layer's KV once into a pooled buffer; the attention loop then indexes it lock-free. removes the per-position locking (helps concurrent multi-sequence load where the cache lock is genuinely contended). 14/14 paged-attention + incremental-generation tests green (identical output). single-stream decode ~130ms/token is serial per-token compute-bound (POOL_THREADS 1/2/4 all equal), not lock-bound — the forward-thread lock samples were blocked-wait, not CPU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(decode): vectorize the per-token projection matvec (scalar -> AVX2) MatVecMul (the q/k/v/o + attention decode projections, run 7x per layer per token on the incremental path) was a pure scalar double loop -- ~150M scalar FMAs/token for a 135M model, the dominant fixed per-token decode cost. vectorize the inner dot with System.Numerics.Vector<float> (8-wide FMA + horizontal sum + scalar tail), net471 keeps the scalar path. decode 6.9 -> 11.3 tok/s (145 -> 88 ms/token, 1.6x) on smollm2-135m. 14/14 paged + incremental tests green (identical output). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ecode/prefill perf (#1917) * feat(device): model.To(DeviceInfo) / layer.To(DeviceInfo) placement API PyTorch-style device placement, enum-only (no magic-string overloads). LayerBase.To moves registered parameters + buffers and recurses sublayers; NeuralNetworkBase.To moves every layer. Skips zero-length (deferred) params. First user-facing piece of the device-agnostic execution model; builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(device): facade ConfigureModel(source, DeviceInfo) overload (Level 1) Load a pretrained checkpoint and place the whole model on a device in one call — the common "load onto my GPU" case — via the type-safe enum DeviceInfo (no device strings). Additive overload: existing ConfigureModel(source) callers are unchanged. Delegates to model.To(device). Builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(device): regression test for model.To(OpenCL()) placement -> 7042 Proves the PyTorch-style placement API (model.To(DeviceInfo.OpenCL())) drives correct GPU execution to llama.cpp's greedy token. Uses AutoDetectAndConfigureGpu so placement (Tensor.To -> global backend) and execution (Current dispatcher) share one backend; skips when no GPU is wired in-process (validates on DevHost/CUDA). Surfaced that To() and Current use different backend acquisition — unify in Phase 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(device): phase-2 fusion acceptance test (skipped) + document the gap Runs the whole decoder forward inside a DeferredScope (BeginDeferredScope -> Predict -> Execute) as the acceptance check for transparent fusion. Today it returns token 0 (all-zero logits) instead of 7042: the graph capture/replay does not materialize the decoder's output, so transparent fusion needs the graph path (output binding + RoPE/GQA-SDPA/RMSNorm recording) debugged before it can wrap Predict. Skipped with that reason; un-skip when the graph path is fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): inferenceOptimizer recurses into PreLNTransformerBlock for GQA KV-cache Adds PreLNTransformerBlock.ReplaceAttention (mirrors TransformerEncoderBlock) and a PreLNTransformerBlock case in ApplyAttentionOptimizations that swaps the block's nested GroupedQueryAttentionLayer for a KV-cached CachedGroupedQueryAttention (shared helper BuildCachedGqaReplacement, reused by the top-level GQA case). This is the first piece of wiring the GGUF/LLaMA decoder (GQA nested in PreLNTransformerBlock) to incremental KV-cache decode. Still inert end-to-end: InitializeGQAKVCache's collection scan is also top-level (won't yet find the nested cached GQA), and ServableModelWrapper requires a paged cache that GQA lacks — so the incremental clone is still discarded and serving falls back to the eager model (no regression). Remaining: recurse InitializeGQAKVCache; accept the non-paged GQA cache as the incremental cache. Builds net10.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): enumerateAttentionHosts reaches PreLNTransformerBlock attention The shared attention-host enumerator (used by the GQA/paged KV-cache init, attention-optimizability detection, and quantization scans) recursed into TransformerEncoderBlock/TransformerDecoderBlock but not PreLNTransformerBlock, where LLaMA/GGUF decoders host their grouped-query attention. Add that case so InitializeGQAKVCache (and the other scans) find the nested CachedGroupedQueryAttention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): make PagedAttentionKernel grouped-query-attention aware Add PagedAttentionConfig.NumQueryHeads (0 => same as NumHeads = MHA). NumHeads is now the KV/cache head count; query heads may outnumber it under GQA, with each KV head shared by NumQueryHeads/NumHeads query heads (kvHead = qHead / group). Threaded through every compute path — ComputeAttention, ComputeTiledPagedAttention (decode), ComputeContiguousCausalPrefill (prefill), ComputeBatchedAttention, and the fused Forward/ForwardQuantized (asymmetric q_proj vs k_proj/v_proj, matching HF). K/V buffers size to the KV heads the cache stores; per-query-head online-softmax scratch. MHA stays the group==1 special case (all 43 PagedAttention tests unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): make PagedCachedMultiHeadAttention grouped-query-attention aware Add kvHeadCount ctor param (0 => headCount = MHA). K/V weights are now [embDim, kvHeadCount*headDim] (narrower under GQA); Q/O stay [embDim, embDim]. Threaded kvProjDim through every projection + cache write in the per-token, batched-GEMM, and contiguous-prefill paths; RoPE applies to Q over query heads and K over KV heads; param serialization and int8 quant use the real per-weight widths. Expose KVHeadCount for the optimizer to build the paged config/kernel. Head split/merge and repeat_kv now use vectorized library ops (Reshape/Transpose and Engine.TensorGather on the head axis) instead of scalar nested loops. MHA is the kvHeadCount==headCount special case; all 43 PagedAttention tests unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serving): wire grouped-query decoders onto the paged KV-cache path InitializePagedKVCache now sizes the PagedKVCache by the KV-head count and passes NumQueryHeads to the kernel so it repeats each KV head across its query-head group. BuildPagedGqaReplacement converts a GroupedQueryAttentionLayer (top-level or nested in PreLNTransformerBlock) to a PagedCachedMultiHeadAttention with the source KV-head count, copying its [Q][K][V][O][outBias] parameters and RoPE/ALiBi config; the GQA branches prefer it when paged KV is enabled. Guarded by GroupedQueryAttentionLayer. UsesProjectionBias: models with Q/K/V projection bias (Qwen2-style) fall back to the contiguous CachedGroupedQueryAttention, which the paged layer cannot represent. 200 paged/optimizer/KVCache/GQA tests green net10.0; both TFMs build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(serving): paged grouped-query attention matches the original decoder Numerical-parity test: a tiny GQA decoder (numHeads=4, numKVHeads=2, RoPE) nested in a PreLNTransformerBlock is optimized with the paged KV cache enabled, and the optimized forward matches the original within 1e-3 — proving the paged-GQA kernel repeat-KV, the layer's narrow K/V projections, the [Q][K][V][O][outBias] weight copy, and the interleaved-RoPE convention are all faithful. Also asserts the GQA is rewritten to PagedCachedMultiHeadAttention and the paged KV cache is live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(serialize): groupedQueryAttentionLayer round-trips RoPE, mask, bias, softcap A cloned GQA layer (serialize -> deserialize) silently lost its RoPE positional encoding, causal mask, custom head dimension, Q/K/V projection bias, and attention logit soft-cap: GetMetadata never persisted them and the deserializer defaulted the bools to false with no ConfigurePositionalEncoding call. So a cloned decoder computed bidirectional, RoPE-less attention and diverged — which is exactly what breaks the incremental-serving clone of a GGUF/LLaMA decoder. Persist all of them in GetMetadata and restore them (incl. RoPE via the layer's ConfigurePositionalEncoding) on deserialize. New test clones a tiny RoPE+causal GQA model and asserts forward parity within 1e-4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(serialize): make PreLNTransformerBlock cloneable so paged serving engages The block hosts a polymorphic (T5/MHA/GQA) attention sublayer, so it had no deserialization constructor and cloning threw — which silently disabled the paged incremental-generation clone (ServableModelWrapper.BuildIncrementalModel) for every GGUF/LLaMA decoder. GetMetadata now persists the block dims + FFN activation + the nested attention as an 'Attn.'-prefixed self-contained sub-blob (type + its metadata + shapes); a new DeserializationHelper branch rebuilds the attention recursively via CreateLayerFromType (E1 keeps its RoPE/mask) and constructs the block. OnFirstForward resolves the lazy norms + FFN in forward order (deser/resolve path only, so no RNG perturbation on a normal forward) so ParameterCount is correct before SetParameters. The paged-GQA parity test now runs with cloneModel: true (the real serving path) and still matches the original within 1e-3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(serving): grouped-query decoder builds the paged incremental path End-to-end proof that paged-GQA engages in serving: a numHeads>numKVHeads PreLN decoder (RoPE) wrapped in ServableModelWrapper now reports SupportsIncrementalGeneration true and generates in-range tokens. Exercises the full chain — GQA-aware paged kernel, narrow-K/V paged attention layer, optimizer GQA->paged rewrite, and PreLNTransformerBlock + GQA serialization (the clone BuildIncrementalModel performs). Before this the clone threw and every LLaMA/GGUF-style decoder silently fell back to the stateless path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(inference): cachedGroupedQueryAttention forward onto recordable engine ops [prototype] Rewrites the inference-only cached GQA layer to apply RoPE via Engine.ApplyRoPEInterleaved and grouped-query attention via Engine.ScaledDotProductAttentionGqa (unexpanded K/V) — a device-agnostic, GPU-graph-recordable forward that drops the two managed unrecordable hotspots (managed RoPE + ExpandKVHeads). RotaryPositionalEncodingLayer exposes its cos/sin caches (GetInterleavedCaches) for the fused op. ALiBi keeps the expand + FlashAttention path. Verified: the optimized (non-paged) model's forward matches the original managed GQA decoder within 1e-3 (InferenceOptimizer_CachedGroupedQueryAttention_RewrittenForward_MatchesOriginal), run against a local AiDotNet.Tensors probe. PROTOTYPE / RELEASE-GATED: the pin is a LOCAL probe (0.117.200-devexec) of the unreleased AiDotNet.Tensors PR #828 (device-agnostic execution). It MUST be bumped to the released AiDotNet.Tensors version before this lands / any AiDotNet PR — CI cannot restore the probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump probe pin to 0.117.201-devexec (GQA-SDPA causal KV-cache offset fix) Local probe of the Tensors branch after the OpenCL GQA-SDPA causal offset fix. Still release-gated — bumps to the released AiDotNet.Tensors before any AiDotNet PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gguf): serve Q8_0 weights quantized via block-Q8_0 GEMM (decode) + profiling harness keeps GGUF Q8_0 linear weights in their native int8 layout instead of expanding to fp32 at load, and runs the block-Q8_0 GEMM (Tensors Q8BlockGemm, ggml q8_0 parity) directly on them for float inference. GgufFile.TryReadQ8_0Raw reads the native blocks (int8 + per-32 fp16 scale); GgufModelSource.TryReadQ8_0 maps the HF name; LlamaModelBuilder.LoadDense installs them on the FFN + lm_head DenseLayers; DenseLayer runs the quantized GEMM on the float inference path. gated to small M (decode/small-batch, where the naive int8 kernel is bandwidth-bound and beats fp32 BLAS); large-M prefill stays on fp32 until the int8 GEMM is register-tiled, so prefill is unchanged (451 tok/s) while decode runs on int8. gguf 7042 llama.cpp parity preserved. also adds the DEVHOST_PROFILE steady-state loop used to profile the forward. NOTE: release-gated on Tensors Q8BlockGemm (probe pin 0.118.10-q8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(gguf): gate Q8_0 quantized forward on VNNI hardware the block-Q8_0 GEMM only beats the tuned fp32 BLAS when the CPU has a 1-instruction VNNI int8 multiply-accumulate (AvxVnni / Avx512 vpdpbusd). on AVX2-only CPUs the 3-instruction maddubs dot is no faster, so engaging it there would regress. gate the DenseLayer quantized path on AvxVnni/Avx512BW (net471 has no intrinsics -> false), so it auto-engages on the VNNI serving hardware and stays on fp32 otherwise. weights are still read + kept as Q8_0 regardless (RAM benefit); only the forward path is gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(serving): bulk-read paged KV under one lock + decode benchmark harness decode profiling (new DEVHOST_DECODE mode: real incremental paged generation, reports tok/s) showed ComputeTiledPagedAttention read the KV history one locked ReadKey/ReadValue per position -> O(seqLen) contended Monitor.Enter per layer per token. add PagedKVCache.ReadKeyValueRange (one lock, whole range) and read the layer's KV once into a pooled buffer; the attention loop then indexes it lock-free. removes the per-position locking (helps concurrent multi-sequence load where the cache lock is genuinely contended). 14/14 paged-attention + incremental-generation tests green (identical output). single-stream decode ~130ms/token is serial per-token compute-bound (POOL_THREADS 1/2/4 all equal), not lock-bound — the forward-thread lock samples were blocked-wait, not CPU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(decode): vectorize the per-token projection matvec (scalar -> AVX2) MatVecMul (the q/k/v/o + attention decode projections, run 7x per layer per token on the incremental path) was a pure scalar double loop -- ~150M scalar FMAs/token for a 135M model, the dominant fixed per-token decode cost. vectorize the inner dot with System.Numerics.Vector<float> (8-wide FMA + horizontal sum + scalar tail), net471 keeps the scalar path. decode 6.9 -> 11.3 tok/s (145 -> 88 ms/token, 1.6x) on smollm2-135m. 14/14 paged + incremental tests green (identical output). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>




Summary
Implements Issue #460: Auto-Apply Agent Hyperparameter Recommendations. When the AI agent analyzes user data and recommends hyperparameters, those recommendations are now parsed from the LLM response and applied to model options automatically (opt-in via
EnableAutoApplyHyperparameters).Core Infrastructure
n_estimators,learning_rate) to C# property names (e.g.,NumberOfTrees,LearningRate) with validation ranges for tree, NN, linear, neighbor/kernel, and time series model familiesGetOptions()for post-construction hyperparameter accessOptions Hierarchy Standardization
ModelOptions(the base withSeedproperty)NeuralNetworkOptionsbase and domain-specific bases (Audio, Document, Financial, Physics-Informed)ClusteringOptions,ReinforcementLearningOptions, 17 MetaLearning options, 3 standalone NN options, and 4 financial options to inherit fromModelOptionsIConfigurableModel<T>+GetOptions()to 11 base classes covering ~460+ modelsModel Wiring
Options = _optionsin 45 model constructors (28 financial + 17 remaining NN models) soGetOptions()returns the correct derived options typePipeline Integration
AiModelBuildernow usesHyperparameterResponseParserinstead of placeholder textApplyAgentRecommendationsCoreapplies hyperparameters whenEnableAutoApplyHyperparametersis trueEnableAutoApplyHyperparameterstoAgentAssistanceOptions(default: false, enabled in Comprehensive preset)AgentRecommendation.HyperparameterApplicationResultBug Fix
HyperparameterResponseParser.TryParseColonSeparatedregex where\s*after[:=]could span newlines, causing header lines like "Settings:" to consume the next line's first token. Fixed by using[^\S\n]*for same-line whitespace only.Test plan
dotnet build --no-restorepasses with 0 errorsCloses #460
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Bug Fixes & Maintenance