| | | 1 | | using System.Diagnostics; |
| | | 2 | | using Intentum.Core.Behavior; |
| | | 3 | | using Intentum.Core.Contracts; |
| | | 4 | | using Intentum.Core.Intents; |
| | | 5 | | |
| | | 6 | | namespace Intentum.Observability; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Wrapper around IIntentModel that adds observability metrics and OpenTelemetry spans. |
| | | 10 | | /// </summary> |
| | | 11 | | public sealed class ObservableIntentModel : IIntentModel |
| | | 12 | | { |
| | | 13 | | private readonly IIntentModel _innerModel; |
| | | 14 | | |
| | 10 | 15 | | public ObservableIntentModel(IIntentModel innerModel) |
| | 10 | 16 | | { |
| | 10 | 17 | | _innerModel = innerModel ?? throw new ArgumentNullException(nameof(innerModel)); |
| | 9 | 18 | | } |
| | | 19 | | |
| | | 20 | | public Intent Infer(BehaviorSpace behaviorSpace, BehaviorVector? precomputedVector = null) |
| | 9 | 21 | | { |
| | 9 | 22 | | var stopwatch = Stopwatch.StartNew(); |
| | 9 | 23 | | using var activity = IntentumActivitySource.Source.StartActivity(); |
| | | 24 | | |
| | | 25 | | try |
| | 9 | 26 | | { |
| | 9 | 27 | | var intent = _innerModel.Infer(behaviorSpace, precomputedVector); |
| | | 28 | | |
| | 7 | 29 | | if (activity != null) |
| | 4 | 30 | | { |
| | 4 | 31 | | activity.DisplayName = IntentumActivitySource.InferSpanName; |
| | 4 | 32 | | activity.SetTag("intentum.intent.name", intent.Name); |
| | 4 | 33 | | activity.SetTag("intentum.intent.confidence.level", intent.Confidence.Level); |
| | 4 | 34 | | activity.SetTag("intentum.intent.confidence.score", intent.Confidence.Score); |
| | 4 | 35 | | activity.SetTag("intentum.intent.signal.count", intent.Signals.Count); |
| | 4 | 36 | | activity.SetTag("intentum.behavior.event.count", behaviorSpace.Events.Count); |
| | 4 | 37 | | var signalSummary = GetBehaviorSignalSummary(behaviorSpace); |
| | 4 | 38 | | if (signalSummary != null) |
| | 4 | 39 | | activity.SetTag("intentum.behavior.signal_summary", signalSummary); |
| | 4 | 40 | | } |
| | | 41 | | |
| | 7 | 42 | | IntentumMetrics.RecordIntentInference(intent, stopwatch.Elapsed); |
| | 7 | 43 | | IntentumMetrics.RecordBehaviorSpaceSize(behaviorSpace); |
| | | 44 | | |
| | 7 | 45 | | return intent; |
| | | 46 | | } |
| | 2 | 47 | | catch (Exception ex) |
| | 2 | 48 | | { |
| | 2 | 49 | | activity?.SetStatus(ActivityStatusCode.Error, ex.Message); |
| | 2 | 50 | | throw; |
| | | 51 | | } |
| | 7 | 52 | | } |
| | | 53 | | |
| | | 54 | | /// <summary>Builds a short summary of behavior signals for trace correlation (signal → intent).</summary> |
| | | 55 | | private static string? GetBehaviorSignalSummary(BehaviorSpace behaviorSpace) |
| | 4 | 56 | | { |
| | 4 | 57 | | if (behaviorSpace.Events.Count == 0) |
| | 0 | 58 | | return null; |
| | | 59 | | const int maxLen = 200; |
| | 4 | 60 | | var parts = behaviorSpace.Events |
| | 6 | 61 | | .Select(e => $"{e.Actor}:{e.Action}") |
| | 4 | 62 | | .ToList(); |
| | 4 | 63 | | var s = string.Join(";", parts); |
| | 4 | 64 | | return s.Length <= maxLen ? s : s[..maxLen] + "..."; |
| | 4 | 65 | | } |
| | | 66 | | } |