diff --git a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs index e88a4150..132cfc54 100644 --- a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs +++ b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs @@ -17,7 +17,7 @@ public sealed class AzureAgent : ILLMAgent internal ArgumentPlaceholder ArgPlaceholder { set; get; } - private const string SettingFileName = "az.agent.json"; + private const string SettingFileName = "az.config.json"; private const string LoggingFileName = "log..txt"; private const string InstructionPrompt = """ NOTE: follow the below instructions when generating responses that include Azure CLI commands with placeholders: @@ -37,6 +37,7 @@ 7. DO NOT include the placeholder summary when the commands contains no placehol private int _turnsLeft; private CopilotResponse _copilotResponse; + private AgentSetting _setting; private readonly string _instructions; private readonly StringBuilder _buffer; @@ -84,17 +85,30 @@ public void Dispose() public void Initialize(AgentConfig config) { - _turnsLeft = int.MaxValue; SettingFile = Path.Combine(config.ConfigurationRoot, SettingFileName); - string logFile = Path.Combine(config.ConfigurationRoot, LoggingFileName); - Log.Logger = new LoggerConfiguration() - .WriteTo.Async(a => a.File( - path: logFile, - outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}", - rollingInterval: RollingInterval.Day)) - .CreateLogger(); - Log.Information("Azure agent initialized."); + _turnsLeft = int.MaxValue; + _setting = AgentSetting.LoadFromFile(SettingFile); + + if (_setting is null) + { + // Use default setting and create a setting file with the default settings. + _setting = AgentSetting.Default; + AgentSetting.NewSettingFile(SettingFile); + } + + if (_setting.Logging) + { + string logFile = Path.Combine(config.ConfigurationRoot, LoggingFileName); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Async(a => a.File( + path: logFile, + outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + rollingInterval: RollingInterval.Day)) + .CreateLogger(); + Log.Information("Azure agent initialized."); + } } public IEnumerable GetCommands() => [new ReplaceCommand(this)]; diff --git a/shell/agents/Microsoft.Azure.Agent/AzureCopilotReceiver.cs b/shell/agents/Microsoft.Azure.Agent/AzureCopilotReceiver.cs index 80f02669..6313c295 100644 --- a/shell/agents/Microsoft.Azure.Agent/AzureCopilotReceiver.cs +++ b/shell/agents/Microsoft.Azure.Agent/AzureCopilotReceiver.cs @@ -43,6 +43,8 @@ internal static async Task CreateAsync(string streamUrl) private async Task ProcessActivities() { + Log.Debug("[AzureCopilotReceiver] Receiver is up and running."); + while (_webSocket.State is WebSocketState.Open) { string closingMessage = null; @@ -55,18 +57,18 @@ private async Task ProcessActivities() { closingMessage = "Close message received"; _activityQueue.Add(new CopilotActivity { Error = new ConnectionDroppedException("The server websocket is closing. Connection dropped.") }); + Log.Information("[AzureCopilotReceiver] Web socket closed by server."); } } catch (OperationCanceledException) { // Close the web socket before the thread is going away. closingMessage = "Client closing"; - Log.Error("[AzureCopilotReceiver] Receiver thread cancelled, which means the instance was disposed."); + Log.Information("[AzureCopilotReceiver] Receiver was cancelled and disposed."); } if (closingMessage is not null) { - Log.Error("[AzureCopilotReceiver] Sending web socket closing request, message: '{0}'", closingMessage); await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, closingMessage, CancellationToken.None); _activityQueue.CompleteAdding(); break; diff --git a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs index b9d54dd0..1fa37442 100644 --- a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs +++ b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs @@ -5,6 +5,7 @@ using System.Text.Json.Nodes; using AIShell.Abstraction; +using Serilog; namespace Microsoft.Azure.Agent; @@ -145,6 +146,8 @@ private async Task StartConversationAsync(IHost host, CancellationToken cancella _expireOn = DateTime.UtcNow.AddSeconds(spl.ExpiresIn); _copilotReceiver = await AzureCopilotReceiver.CreateAsync(_streamUrl); + Log.Debug("[ChatSession] Conversation started. Id: {0}", _conversationId); + while (true) { CopilotActivity activity = _copilotReceiver.Take(cancellationToken); diff --git a/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs b/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs index e4d8092c..5674f422 100644 --- a/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs +++ b/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs @@ -433,6 +433,7 @@ private ArgumentInfo CreateArgInfo(ArgumentPair pair) // Handle non-AzCLI command. if (pair.Parameter is null) { + Log.Debug("[DataRetriever] Non-AzCLI command: '{0}'", pair.Command); return new ArgumentInfo(item.Name, item.Desc, dataType); } @@ -481,6 +482,8 @@ private List GetArgValues(ArgumentPair pair) string commandLine = $"{pair.Command} {pair.Parameter} "; string tempFile = Path.GetTempFileName(); + Log.Debug("[DataRetriever] Perform tab completion for '{0}'", commandLine); + try { using var process = new Process() diff --git a/shell/agents/Microsoft.Azure.Agent/Schema.cs b/shell/agents/Microsoft.Azure.Agent/Schema.cs index e07ee904..6e60ec67 100644 --- a/shell/agents/Microsoft.Azure.Agent/Schema.cs +++ b/shell/agents/Microsoft.Azure.Agent/Schema.cs @@ -1,3 +1,4 @@ +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -5,6 +6,51 @@ namespace Microsoft.Azure.Agent; +internal class AgentSetting +{ + public bool Logging { get; set; } + public bool Telemetry { get; set; } + + public AgentSetting() + { + // Enable logging and telemetry by default. + Logging = true; + Telemetry = true; + } + + internal static AgentSetting Default => new(); + + internal static AgentSetting LoadFromFile(string path) + { + FileInfo file = new(path); + if (file.Exists) + { + try + { + using var stream = file.OpenRead(); + return JsonSerializer.Deserialize(stream, Utils.JsonOptions); + } + catch (Exception e) + { + throw new InvalidDataException($"Parsing settings from '{path}' failed with the following error: {e.Message}", e); + } + } + + return null; + } + + internal static void NewSettingFile(string path) + { + const string content = """ + { + "logging": true, + "telemetry": true + } + """; + File.WriteAllText(path, content, Encoding.UTF8); + } +} + internal class TokenPayload { public string ConversationId { get; set; }