< Summary

Information
Class: Intentum.AI.Classification.PromptIntentClassifier
Assembly: Intentum.AI
File(s): /home/runner/work/Intentum/Intentum/src/Intentum.AI/Classification/PromptIntentClassifier.cs
Line coverage
94%
Covered lines: 63
Uncovered lines: 4
Coverable lines: 67
Total lines: 127
Line coverage: 94%
Branch coverage
70%
Covered branches: 21
Total branches: 30
Branch coverage: 70%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%44100%
ClassifyAsync()66.66%2424100%
.cctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%

File(s)

/home/runner/work/Intentum/Intentum/src/Intentum.AI/Classification/PromptIntentClassifier.cs

#LineLine coverage
 1#pragma warning disable IDE0052 // Positional properties used by JSON serialization
 2using System.Net.Http.Json;
 3using System.Text.Json;
 4using System.Text.Json.Serialization;
 5using Intentum.AI.Http;
 6using Intentum.Core.Behavior;
 7
 8namespace Intentum.AI.Classification;
 9
 10/// <summary>
 11/// LLM prompt-based intent classifier. Sends behavior events to an LLM API
 12/// and receives structured JSON with intent name, confidence, and reasoning.
 13/// Supports OpenAI-compatible chat completion APIs.
 14/// </summary>
 15public sealed class PromptIntentClassifier : IIntentClassifier
 16{
 17    private readonly HttpClient _httpClient;
 18    private readonly PromptClassifierOptions _options;
 19
 620    public PromptIntentClassifier(HttpClient httpClient, PromptClassifierOptions options)
 621    {
 622        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
 523        _options = options ?? throw new ArgumentNullException(nameof(options));
 424    }
 25
 26    public async Task<IntentClassificationResult> ClassifyAsync(
 27        BehaviorSpace behaviorSpace,
 28        IReadOnlyList<string>? candidateIntents = null,
 29        CancellationToken cancellationToken = default)
 430    {
 431        var events = behaviorSpace.Events
 432            .Select(e => $"  - {e.Actor}:{e.Action}")
 433            .ToList();
 34
 435        var candidateList = candidateIntents is { Count: > 0 }
 436            ? $"\nCandidate intents (pick one): {string.Join(", ", candidateIntents)}"
 437            : "";
 38
 439        var systemPrompt = _options.SystemPrompt ?? DefaultSystemPrompt;
 440        var jsonExample = """{"intent":"...","confidence":0.0-1.0,"reasoning":"..."}""";
 441        var userPrompt = $"""
 442            Behavior events observed:
 443            {string.Join("\n", events)}
 444            {candidateList}
 445
 446            Classify the intent. Respond with JSON only: {jsonExample}
 447            """;
 48
 449        var request = new ChatCompletionRequest(
 450            _options.Model,
 451            [
 452                new ChatMessage("system", systemPrompt),
 453                new ChatMessage("user", userPrompt)
 454            ],
 455            new ResponseFormat("json_object"));
 56
 457        var response = await EmbeddingHttpRetryHandler.SendWithRetryAsync(
 458            ct => _httpClient.PostAsJsonAsync(_options.Endpoint ?? "chat/completions", request, ct),
 059            (_, body) => throw new HttpRequestException($"LLM rate limited: {body}"),
 460            cancellationToken: cancellationToken);
 61
 462        var completion = await response.Content
 463            .ReadFromJsonAsync<ChatCompletionResponse>(cancellationToken);
 64
 465        var content = completion?.Choices?.FirstOrDefault()?.Message?.Content;
 466        if (string.IsNullOrWhiteSpace(content))
 167            return new IntentClassificationResult("Unknown", 0, "Empty LLM response");
 68
 69        try
 370        {
 371            var parsed = JsonSerializer.Deserialize<LlmClassificationOutput>(content, LlmJsonOptions);
 72
 273            return new IntentClassificationResult(
 274                parsed?.Intent ?? "Unknown",
 275                Math.Clamp(parsed?.Confidence ?? 0, 0, 1),
 276                parsed?.Reasoning);
 77        }
 178        catch (JsonException)
 179        {
 180            return new IntentClassificationResult("Unknown", 0, $"Failed to parse LLM response: {content}");
 81        }
 482    }
 83
 184    private static readonly JsonSerializerOptions LlmJsonOptions = new() { PropertyNameCaseInsensitive = true };
 85
 86    private const string DefaultSystemPrompt =
 87        "You are an intent classification system. Given a sequence of behavior events (actor:action pairs), " +
 88        "determine the most likely user intent. Return ONLY valid JSON with fields: intent (string), " +
 89        "confidence (number 0-1), reasoning (string).";
 90
 491    private sealed record ChatCompletionRequest(
 092        [property: JsonPropertyName("model")] string Model,
 093        [property: JsonPropertyName("messages")] IReadOnlyList<ChatMessage> Messages,
 494        [property: JsonPropertyName("response_format")] ResponseFormat? ResponseFormat = null);
 95
 896    private sealed record ChatMessage(
 097        [property: JsonPropertyName("role")] string Role,
 898        [property: JsonPropertyName("content")] string Content);
 99
 4100    private sealed record ResponseFormat(
 4101        [property: JsonPropertyName("type")] string Type);
 102
 4103    private sealed record ChatCompletionResponse(
 8104        [property: JsonPropertyName("choices")] List<ChatChoice>? Choices);
 105
 4106    private sealed record ChatChoice(
 8107        [property: JsonPropertyName("message")] ChatChoiceMessage? Message);
 108
 4109    private sealed record ChatChoiceMessage(
 8110        [property: JsonPropertyName("content")] string? Content);
 111
 2112    private sealed record LlmClassificationOutput(
 2113        [property: JsonPropertyName("intent")] string? Intent,
 2114        [property: JsonPropertyName("confidence")] double? Confidence,
 4115        [property: JsonPropertyName("reasoning")] string? Reasoning);
 116}
 117#pragma warning restore IDE0052
 118
 119/// <summary>
 120/// Options for the prompt-based intent classifier.
 121/// </summary>
 122public sealed class PromptClassifierOptions
 123{
 124    public required string Model { get; init; }
 125    public string? Endpoint { get; init; }
 126    public string? SystemPrompt { get; init; }
 127}