1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
using Azure.Core;
using Azure.Identity;
using Kusto.Data;
using Kusto.Data.Common;
using Kusto.Data.Net.Client;
using Microsoft.Extensions.Options;
using System.Text.Json;
using Tango.Portal.Chat.Web.Models;
namespace Tango.Portal.Chat.Web.Services
{
public sealed class ChatMessageLogger
{
private readonly ICslAdminProvider _admin;
private readonly string _database;
private readonly ILogger<ChatMessageLogger> _logger;
public ChatMessageLogger(IOptions<AdxOptions> opts, ILogger<ChatMessageLogger> logger)
{
var options = opts.Value;
_database = options.Database;
_logger = logger;
var cred = new ClientSecretCredential(
options.TenantId,
options.ClientId,
options.ClientSecret);
var kcsb = new KustoConnectionStringBuilder(options.ClusterUri)
.WithAadAzureTokenCredentialsAuthentication(cred);
_admin = KustoClientFactory.CreateCslAdminProvider(kcsb);
}
public async Task LogMessageAsync(ChatConversationMessage message, CancellationToken ct = default)
{
try
{
var json = JsonSerializer.Serialize(message);
var command = $".ingest inline into table ChatConversationsTable with (jsonMappingReference = 'ChatConversationsTableMapping') <| {json}";
var props = new ClientRequestProperties
{
ClientRequestId = $"chat_log_{Guid.NewGuid()}"
};
using var result = await _admin.ExecuteControlCommandAsync(_database, command, props);
_logger.LogDebug("Successfully logged chat message {MessageId} for session {SessionId}",
message.ID, message.SessionID);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to log chat message {MessageId} for session {SessionId}",
message.ID, message.SessionID);
}
}
public async Task LogQuestionAsync(string sessionId, string userEmail, string userName, string question, CancellationToken ct = default)
{
var message = new ChatConversationMessage
{
SessionID = sessionId,
Time = DateTime.UtcNow,
Email = userEmail,
User = userName,
Role = "user",
Classification = "question",
Message = question,
Provider = string.Empty
};
await LogMessageAsync(message, ct);
}
public async Task LogAnswerAsync(string sessionId, string userEmail, string userName, ChatResponse response, string assistantType, string provider, CancellationToken ct = default)
{
var answerMessage = BuildAnswerMessage(response);
var message = new ChatConversationMessage
{
SessionID = sessionId,
Time = DateTime.UtcNow,
Email = userEmail,
User = userName,
Role = "assistant",
Classification = assistantType,
Message = answerMessage,
Provider = provider
};
await LogMessageAsync(message, ct);
}
private static string BuildAnswerMessage(ChatResponse response)
{
var parts = new List<string>();
if (!string.IsNullOrEmpty(response.Answer))
{
parts.Add($"Answer: {response.Answer}");
}
if (!string.IsNullOrEmpty(response.UsedKql))
{
parts.Add($"Used KQL: {response.UsedKql}");
}
if (!string.IsNullOrEmpty(response.Ploty))
{
parts.Add($"Visualization: {response.Ploty}");
}
return parts.Count > 0 ? string.Join("\n\n", parts) : response.Answer ?? string.Empty;
}
}
}
|