< Summary

Information
Class: Intentum.AI.SystemOne.SystemOneIntentModel
Assembly: Intentum.AI.SystemOne
File(s): /home/runner/work/Intentum/Intentum/src/Intentum.AI.SystemOne/SystemOneIntentModel.cs
Line coverage
85%
Covered lines: 68
Uncovered lines: 12
Coverable lines: 80
Total lines: 134
Line coverage: 85%
Branch coverage
42%
Covered branches: 16
Total branches: 38
Branch coverage: 42.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)50%44100%
Infer(...)83.33%66100%
BuildState(...)16.66%6670%
BuildQuestions()100%11100%
ResolveConfidence(...)25%88100%
BuildSignals(...)33.33%6652.94%
BuildReasoning(...)50%8890.9%

File(s)

/home/runner/work/Intentum/Intentum/src/Intentum.AI.SystemOne/SystemOneIntentModel.cs

#LineLine coverage
 1using System.Globalization;
 2using Intentum.Core.Behavior;
 3using Intentum.Core.Contracts;
 4using Intentum.Core.Intents;
 5
 6namespace Intentum.AI.SystemOne;
 7
 8/// <summary>
 9/// Uses a System One compatible engine as an <see cref="IIntentModel"/>: the behavior space
 10/// is sent as state, the configured intent catalog becomes one choice question, and the
 11/// engine's answer (choice + calibrated probabilities) is mapped to an <see cref="Intent"/>.
 12/// </summary>
 13public sealed class SystemOneIntentModel : IIntentModel
 14{
 15    private const string IntentQuestionId = "intent";
 16
 17    private readonly SystemOneOptions _options;
 18    private readonly HttpClient _httpClient;
 19
 20    /// <summary>Creates the model for the given engine options.</summary>
 521    public SystemOneIntentModel(SystemOneOptions options, HttpClient httpClient)
 522    {
 523        _options = options ?? throw new ArgumentNullException(nameof(options));
 524        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
 525    }
 26
 27    /// <inheritdoc />
 28    public Intent Infer(BehaviorSpace behaviorSpace, BehaviorVector? precomputedVector = null)
 429    {
 430        ArgumentNullException.ThrowIfNull(behaviorSpace);
 431        _options.Validate();
 32
 333        if (_options.IntentCatalog.Count == 0)
 134            throw new InvalidOperationException(
 135                "SystemOneOptions.IntentCatalog must contain at least one intent (name → description).");
 36
 237        var client = new SystemOneClient(_options, _httpClient);
 238        var response = client.Decide(BuildState(behaviorSpace), BuildQuestions());
 39
 240        if (!response.Answers.TryGetValue(IntentQuestionId, out var answer))
 141        {
 142            return new Intent(
 143                Name: "unknown",
 144                Signals: Array.Empty<IntentSignal>(),
 145                Confidence: IntentConfidence.FromScore(0),
 146                Reasoning: "System One engine returned no answer for the intent question.");
 47        }
 48
 149        var probabilities = answer.Probabilities;
 150        var name = answer.Choice ?? "unknown";
 151        var confidence = ResolveConfidence(answer, probabilities);
 152        var signals = BuildSignals(answer, probabilities, confidence);
 53
 154        return new Intent(
 155            Name: name,
 156            Signals: signals,
 157            Confidence: IntentConfidence.FromScore(confidence),
 158            Reasoning: BuildReasoning(answer, probabilities, name, confidence));
 259    }
 60
 61    private object BuildState(BehaviorSpace space)
 262    {
 263        var events = space.Events
 264            .Select(e => string.Create(CultureInfo.InvariantCulture, $"{e.Actor}:{e.Action}"))
 265            .ToArray();
 66
 267        if (space.Metadata.Count == 0)
 268            return new Dictionary<string, object> { ["events"] = events };
 69
 070        var state = new Dictionary<string, object> { ["events"] = events };
 071        state["metadata"] = space.Metadata.ToDictionary(m => m.Key, m => m.Value?.ToString() ?? "");
 072        return state;
 273    }
 74
 75    private IReadOnlyDictionary<string, SystemOneQuestion> BuildQuestions() =>
 276        new Dictionary<string, SystemOneQuestion>
 277        {
 278            [IntentQuestionId] = SystemOneQuestion.Choice(
 279                _options.IntentQuestionInstructions,
 680                _options.IntentCatalog.ToDictionary(c => c.Key, c => (string?)c.Value)),
 281        };
 82
 83    private static double ResolveConfidence(SystemOneAnswer answer, IReadOnlyDictionary<string, double>? probabilities)
 184    {
 185        var confidence = answer.AnswerConfidence
 186            ?? answer.Confidence
 187            ?? (probabilities is { Count: > 0 } ? probabilities.Values.Max() : 0.0);
 88
 189        return Math.Clamp(confidence, 0.0, 1.0);
 190    }
 91
 92    private IReadOnlyCollection<IntentSignal> BuildSignals(
 93        SystemOneAnswer answer,
 94        IReadOnlyDictionary<string, double>? probabilities,
 95        double confidence)
 196    {
 197        if (probabilities is null || probabilities.Count == 0)
 098        {
 099            return new[]
 0100            {
 0101                new IntentSignal(
 0102                    Source: _options.Engine,
 0103                    Description: answer.Choice ?? "no-answer",
 0104                    Weight: confidence),
 0105            };
 106        }
 107
 1108        return probabilities
 2109            .Select(p => new IntentSignal(
 2110                Source: _options.Engine,
 2111                Description: p.Key,
 2112                Weight: p.Value))
 1113            .ToArray();
 1114    }
 115
 116    private static string BuildReasoning(
 117        SystemOneAnswer answer,
 118        IReadOnlyDictionary<string, double>? probabilities,
 119        string name,
 120        double confidence)
 1121    {
 1122        if (probabilities is null || probabilities.Count == 0)
 0123            return $"choice={name}, confidence={confidence.ToString("0.00", CultureInfo.InvariantCulture)}";
 124
 1125        var parts = probabilities
 2126            .OrderByDescending(p => p.Value)
 3127            .Select(p => string.Create(
 3128                CultureInfo.InvariantCulture,
 3129                $"{p.Key}={p.Value.ToString("0.00", CultureInfo.InvariantCulture)}"));
 130
 1131        var answerType = answer.Score is not null ? "score" : answer.Noul is not null ? "noul" : "choice";
 1132        return $"{answerType}: {string.Join(", ", parts)}";
 1133    }
 134}