From 646635b020ab168a40cdf170ae5b3741ae72b752 Mon Sep 17 00:00:00 2001 From: Catherine Zhang Date: Thu, 25 Jul 2024 17:05:33 -0700 Subject: [PATCH 1/5] added dashboard and otel viewing commands --- ai-cli.sln | 20 + src/ai/ai-cli.csproj | 19 +- src/ai/commands/chat_command.cs | 30 +- .../commands/parsers/chat_command_parser.cs | 10 +- .../commands/parsers/tool_command_parser.cs | 9 +- src/ai/commands/tool_command.cs | 10 +- .../AnyValueConverter.cs | 52 +++ .../otel_viewer_extension/Dashboard.cs | 76 ++++ .../IntArrayToByteStringConverter.cs | 49 +++ .../otel_viewer_extension/OtelData.cs | 91 +++++ .../otel_viewer_extension/TraceExporter.cs | 45 +++ .../otel_viewer_extension.csproj | 28 ++ .../opentelemetry/proto/collector/README.md | 10 + .../collector/trace/v1/trace_service.proto | 79 ++++ .../trace/v1/trace_service_http.yaml | 9 + .../proto/common/v1/common.proto | 81 ++++ .../proto/resource/v1/resource.proto | 37 ++ .../opentelemetry/proto/trace/v1/trace.proto | 355 ++++++++++++++++++ 18 files changed, 989 insertions(+), 21 deletions(-) create mode 100644 src/extensions/otel_viewer_extension/AnyValueConverter.cs create mode 100644 src/extensions/otel_viewer_extension/Dashboard.cs create mode 100644 src/extensions/otel_viewer_extension/IntArrayToByteStringConverter.cs create mode 100644 src/extensions/otel_viewer_extension/OtelData.cs create mode 100644 src/extensions/otel_viewer_extension/TraceExporter.cs create mode 100644 src/extensions/otel_viewer_extension/otel_viewer_extension.csproj create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/README.md create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/common/v1/common.proto create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/resource/v1/resource.proto create mode 100644 src/extensions/otel_viewer_extension/proto/opentelemetry/proto/trace/v1/trace.proto diff --git a/ai-cli.sln b/ai-cli.sln index da387f46..37868e97 100644 --- a/ai-cli.sln +++ b/ai-cli.sln @@ -39,6 +39,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{ src\telemetry\NuGet.config = src\telemetry\NuGet.config EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{13B3E651-BF79-4138-A73D-FD41F773E0F4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{A5154519-2271-45E8-8761-816440870E40}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "otel_viewer_extension", "src\extensions\otel_viewer_extension\otel_viewer_extension.csproj", "{B153ED12-2028-49A9-AD7F-9D67DA0ECA32}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -181,6 +187,18 @@ Global {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x64.Build.0 = Release|Any CPU {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x86.ActiveCfg = Release|Any CPU {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x86.Build.0 = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x64.ActiveCfg = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x64.Build.0 = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x86.ActiveCfg = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x86.Build.0 = Debug|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|Any CPU.Build.0 = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x64.ActiveCfg = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x64.Build.0 = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x86.ActiveCfg = Release|Any CPU + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +213,8 @@ Global {9499C018-FA08-4133-93B3-FC0F3863A6CC} = {C8AFF891-D6AA-4B8F-BC21-10404DF4B355} {CED7C805-0435-4BF7-A42F-9F3BBF14A18F} = {644B75F1-C768-4DB3-BAF2-C69A1F36DD28} {306A3CD6-91C2-450B-9995-79701CE63FE2} = {975EBC5A-506D-49B5-AA7F-70D3119F009D} + {A5154519-2271-45E8-8761-816440870E40} = {13B3E651-BF79-4138-A73D-FD41F773E0F4} + {B153ED12-2028-49A9-AD7F-9D67DA0ECA32} = {A5154519-2271-45E8-8761-816440870E40} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {002655B1-E1E1-4F2A-8D53-C9CD55136AE2} diff --git a/src/ai/ai-cli.csproj b/src/ai/ai-cli.csproj index cccd738b..6bf9b415 100644 --- a/src/ai/ai-cli.csproj +++ b/src/ai/ai-cli.csproj @@ -126,15 +126,16 @@ - - - - - - - - + + + + + + + + + - + diff --git a/src/ai/commands/chat_command.cs b/src/ai/commands/chat_command.cs index 78bf4436..a21f475a 100644 --- a/src/ai/commands/chat_command.cs +++ b/src/ai/commands/chat_command.cs @@ -26,6 +26,7 @@ using OpenAI.Assistants; using OpenAI.Files; using OpenAI.VectorStores; +using Azure.AI.Details.Common.CLI.Extensions.Otel; #pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -91,6 +92,8 @@ private void DoCommand(string command) case "chat.assistant.file.list": DoChatAssistantFileList().Wait(); break; case "chat.assistant.file.delete": DoChatAssistantFileDelete().Wait(); break; + case "chat.assistant.trace.get": DoChatAssistantTraceGet().Wait(); break; + default: _values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!"); break; @@ -519,13 +522,6 @@ private void CheckWriteChatAnswerOutputFile(string completeResponse) var fileName = FileHelpers.GetOutputDataFileName(outputAnswerFile, _values); FileHelpers.WriteAllText(fileName, completeResponse, Encoding.UTF8); } - - var outputAddAnswerFile = OutputAddChatAnswerFileToken.Data().GetOrDefault(_values); - if (!string.IsNullOrEmpty(outputAddAnswerFile)) - { - var fileName = FileHelpers.GetOutputDataFileName(outputAddAnswerFile, _values); - FileHelpers.AppendAllText(fileName, "\n" + completeResponse, Encoding.UTF8); - } } private async Task CheckWriteChatHistoryOutputFileAsync(AssistantClient client, AssistantThread thread) @@ -1581,6 +1577,26 @@ private async Task DoChatAssistantFileDelete() if (!_quiet) Console.WriteLine($"{message} Done!"); return true; } + private async Task DoChatAssistantTraceGet() { + var requestId = _values["chat.trace.request.id"]; + var dashboard = _values.GetOrDefault("chat.trace.dashboard", false); + var filePath = _values.GetOrDefault("chat.trace.output.file", ""); + + if (string.IsNullOrEmpty(requestId)) + { + _values.AddThrowError("ERROR:", $"Retrieving trace; requires request id."); + } + Console.WriteLine("Attempting to fetch trace data..."); + var data = await OtelData.GetTrace(requestId); + Console.WriteLine(data); + if (!string.IsNullOrEmpty(filePath)) { + await OtelData.WriteDataToFile(data, filePath); + } + if (dashboard) { + await OtelData.ExportToDashboard(data); + } + return true; + } private List ExpandFindFiles(List files) { diff --git a/src/ai/commands/parsers/chat_command_parser.cs b/src/ai/commands/parsers/chat_command_parser.cs index f250c811..c8258ffa 100644 --- a/src/ai/commands/parsers/chat_command_parser.cs +++ b/src/ai/commands/parsers/chat_command_parser.cs @@ -40,6 +40,8 @@ private static readonly (string name, bool valuesRequired)[] _commands = { ("chat.assistant.file.list", false), ("chat.assistant.file", true), + ("chat.assistant.trace.get", true), + ("chat.assistant", true), ("chat", true), }; @@ -71,6 +73,7 @@ private static IEnumerable GetCommandParsers(ICommandVal case "chat.assistant.file.upload": return _chatAssistantFileUploadCommandParsers; case "chat.assistant.file.delete": return _chatAssistantFileDeleteCommandParsers; case "chat.assistant.file.list": return _chatAssistantFileListCommandParsers; + case "chat.assistant.trace.get": return _chatAssistantTraceGetCommandParsers; } foreach (var command in _commands) @@ -152,7 +155,6 @@ public CommonChatNamedValueTokenParsers() : base( new TrueFalseNamedValueTokenParser("chat.speech.input", "010"), OutputChatAnswerFileToken.Parser(), - OutputAddChatAnswerFileToken.Parser(), OutputChatHistoryFileToken.Parser(), InputChatHistoryJsonFileToken.Parser(), InputChatParameterFileToken.Parser(), @@ -284,6 +286,12 @@ public CommonChatNamedValueTokenParsers() : base( new CommonChatNamedValueTokenParsers(), new Any1ValueNamedValueTokenParser(null, "chat.assistant.file.id", "0001"), }; + private static INamedValueTokenParser[] _chatAssistantTraceGetCommandParsers = { + new CommonChatNamedValueTokenParsers(), + new Any1ValueNamedValueTokenParser(null, "chat.trace.request.id", "0011;0101"), + new Any1ValueNamedValueTokenParser(null, "chat.trace.output.file", "0001"), + new TrueFalseNamedValueTokenParser("chat.trace.dashboard", "001"), + }; #endregion } diff --git a/src/ai/commands/parsers/tool_command_parser.cs b/src/ai/commands/parsers/tool_command_parser.cs index c0d59e84..8cd0f571 100644 --- a/src/ai/commands/parsers/tool_command_parser.cs +++ b/src/ai/commands/parsers/tool_command_parser.cs @@ -20,11 +20,14 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v { return ParseCommandValues("tool", GetCommandParsers(values), tokens, values); } - + private static readonly (string name, bool valuesRequired)[] _commands = { - ("tool", true) + ("tool.dashboard.start", false), + ("tool.dashboard.stop", false), + ("tool", true), + }; - + private static readonly string[] _partialCommands = { "tool" }; diff --git a/src/ai/commands/tool_command.cs b/src/ai/commands/tool_command.cs index c9281b30..c62d6591 100644 --- a/src/ai/commands/tool_command.cs +++ b/src/ai/commands/tool_command.cs @@ -15,7 +15,7 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; - +using Azure.AI.Details.Common.CLI.Extensions.Otel; namespace Azure.AI.Details.Common.CLI { public class ToolCommand : Command @@ -40,6 +40,12 @@ internal bool RunCommand() return _values.GetOrDefault("passed", true); } + private void StartDashboard() { + Dashboard.StartDashboard(); + } + private void StopDashboard() { + Dashboard.StopDashboard(); + } private bool RunToolCommand() { @@ -53,6 +59,8 @@ private void DoCommand(string command) switch (command) { + case "tool.dashboard.start": StartDashboard(); break; + case "tool.dashboard.stop": StopDashboard(); break; default: _values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!"); break; diff --git a/src/extensions/otel_viewer_extension/AnyValueConverter.cs b/src/extensions/otel_viewer_extension/AnyValueConverter.cs new file mode 100644 index 00000000..888f0cb5 --- /dev/null +++ b/src/extensions/otel_viewer_extension/AnyValueConverter.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTelemetry.Proto.Common.V1; +using Azure.AI.Details.Common.CLI; + +namespace Azure.AI.Details.Common.CLI.Extensions.Otel +{ + public class AnyValueConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AnyValue); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject item = JObject.Load(reader); + var anyValue = new AnyValue(); + + // Check and set the string value + if (item["hasStringValue"]?.Value() == true) + { + anyValue.StringValue = item["stringValue"]?.Value(); + } + if (item["hasBoolValue"]?.Value() == true) + { + anyValue.BoolValue = (bool)item["boolValue"]?.Value(); + } + if (item["hasIntValue"]?.Value() == true) + { + anyValue.IntValue = (int)item["intValue"]?.Value(); + } + if (item["hasDoubleValue"]?.Value() == true) + { + anyValue.DoubleValue = (double)item["doubleValue"]?.Value(); + } + return anyValue; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } + + +} diff --git a/src/extensions/otel_viewer_extension/Dashboard.cs b/src/extensions/otel_viewer_extension/Dashboard.cs new file mode 100644 index 00000000..3e18e785 --- /dev/null +++ b/src/extensions/otel_viewer_extension/Dashboard.cs @@ -0,0 +1,76 @@ +using System; +using System.Diagnostics; +using Azure.AI.Details.Common.CLI; + +namespace Azure.AI.Details.Common.CLI.Extensions.Otel +{ + public class Dashboard + { + // Execute a command in the command prompt and return the output + private static string ExecuteCommand(string command) + { + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/c {command}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using (Process process = new Process()) + { + process.StartInfo = processStartInfo; + process.Start(); + process.WaitForExit(); + return process.StandardOutput.ReadToEnd(); + } + } + public static void StartDashboard() + { + + // Commands for the terminal + string dockerStartCommand = "docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:8.0.0"; + string dockerLogsCommand = "docker logs aspire-dashboard"; + + try + { + // Start the Docker container + string startResult = ExecuteCommand(dockerStartCommand); + Console.WriteLine(startResult); + + // Optionally wait a bit before fetching logs if needed + System.Threading.Thread.Sleep(3000); + + // Get logs from the Docker container + string logsResult = ExecuteCommand(dockerLogsCommand); + Console.WriteLine(logsResult); + } + catch (Exception ex) + { + Console.WriteLine("An error occurred: " + ex.Message); + } + } + public static void StopDashboard() + { + string dockerStopCommand = "docker stop aspire-dashboard"; + try + { + // Stop the Docker container + string stopResult = ExecuteCommand(dockerStopCommand); + Console.WriteLine("Docker Stop Command Output:"); + Console.WriteLine(stopResult); + } + catch (Exception ex) + { + Console.WriteLine("An error occurred when stopping the dashboard: " + ex.Message); + } + } + public static bool IsRunning() + { + string checkCommand = "docker ps --filter \"name=aspire-dashboard\" --format \"{{.Names}}\""; + string result = ExecuteCommand(checkCommand); + return result.Contains("aspire-dashboard"); + } + } +} diff --git a/src/extensions/otel_viewer_extension/IntArrayToByteStringConverter.cs b/src/extensions/otel_viewer_extension/IntArrayToByteStringConverter.cs new file mode 100644 index 00000000..36dae2c5 --- /dev/null +++ b/src/extensions/otel_viewer_extension/IntArrayToByteStringConverter.cs @@ -0,0 +1,49 @@ +using Google.Protobuf; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Azure.AI.Details.Common.CLI; + +namespace Azure.AI.Details.Common.CLI.Extensions.Otel +{ + public class IntArrayToByteStringConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ByteString); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JArray byteValues = JArray.Load(reader); + byte[] bytes = new byte[byteValues.Count]; + + for (int i = 0; i < byteValues.Count; i++) + { + bytes[i] = (byte)byteValues[i].ToObject(); + } + + return ByteString.CopyFrom(bytes); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + ByteString byteString = (ByteString)value; + byte[] bytes = byteString.ToByteArray(); + writer.WriteStartArray(); + foreach (byte b in bytes) + { + writer.WriteValue(b); + } + writer.WriteEndArray(); + } + public bool ShouldConvert(string path) + { + return path.EndsWith(".traceId") || path.EndsWith(".spanId") || path.EndsWith(".parentSpanId"); + } + } +} diff --git a/src/extensions/otel_viewer_extension/OtelData.cs b/src/extensions/otel_viewer_extension/OtelData.cs new file mode 100644 index 00000000..1fd1cbf3 --- /dev/null +++ b/src/extensions/otel_viewer_extension/OtelData.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net.Http; +using Newtonsoft.Json; +using OpenTelemetry.Proto.Collector.Trace.V1; +using Newtonsoft.Json.Linq; +using Google.Protobuf; +using Azure.AI.Details.Common.CLI; + +namespace Azure.AI.Details.Common.CLI.Extensions.Otel +{ + public class OtelData + { + public static async Task GetTrace(string requestId) + { + // Endpoint URL with the request ID + string url = $"https://spanretriever1-webapp.whitehill-dc9ec001.westus.azurecontainerapps.io/getspan/{requestId}"; + using (HttpClient client = new HttpClient()) + { + try + { + // Send a GET request to the endpoint + HttpResponseMessage response = await client.GetAsync(url); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + + // Convert JSON to a formatted string + var parsedJson = JsonConvert.DeserializeObject(responseBody); + string formattedJson = JsonConvert.SerializeObject(parsedJson, Formatting.Indented); + + return formattedJson; + } + catch (HttpRequestException httpEx) + { + Console.WriteLine("HTTP Request failed: " + httpEx.Message); + return null; + } + } + + } + public static async Task WriteDataToFile(string data, string filePath) + { + try + { + //var data = await GetTrace(requestId); + using (StreamWriter writer = new StreamWriter(filePath)) + { + await writer.WriteAsync(data); + Console.WriteLine($"Payload successfully written to {filePath}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred while writing to the file: {ex.Message}"); + } + + } + public static async Task ExportToDashboard(string data) + { + try + { + + // Check if the dashboard (OTLP endpoint) is running + if (!Dashboard.IsRunning()) + { + Console.WriteLine("Dashboard is not running. Please start the dashboard and try again."); + return; + } + + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new AnyValueConverter()); + settings.Converters.Add(new IntArrayToByteStringConverter()); + + var request = JsonConvert.DeserializeObject(data, settings); + + var otlpEndpoint = "http://localhost:4317"; + var otlpExporter = new TraceExporter(otlpEndpoint); + var exportResponse = await otlpExporter.ExportSpansAsync(request); + + Console.WriteLine("Span data processed and sent to exporter."); + } + catch (Exception ex) + { + Console.WriteLine("An error occurred during export: " + ex.Message); + } + } + } +} diff --git a/src/extensions/otel_viewer_extension/TraceExporter.cs b/src/extensions/otel_viewer_extension/TraceExporter.cs new file mode 100644 index 00000000..cee6884f --- /dev/null +++ b/src/extensions/otel_viewer_extension/TraceExporter.cs @@ -0,0 +1,45 @@ +using Grpc.Net.Client; +using OpenTelemetry.Proto.Collector.Trace.V1; +using System.Reflection; +using Google.Protobuf.WellKnownTypes; // For Empty and other common message types +using System; +using System.Threading.Tasks; +using Google.Cloud.Trace.V1; +using OtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1; +using TraceService = OpenTelemetry.Proto.Collector.Trace.V1.TraceService; +using Azure.AI.Details.Common.CLI; + +namespace Azure.AI.Details.Common.CLI.Extensions.Otel +{ + public class TraceExporter + { + + private readonly OtlpCollector.TraceService.TraceServiceClient traceClient; + + public TraceExporter(string otlpEndpoint) + { + // Create a gRPC channel + var channel = GrpcChannel.ForAddress(otlpEndpoint); + + traceClient = new TraceService.TraceServiceClient(channel); + } + + internal async Task ExportSpansAsync(ExportTraceServiceRequest request) + { + var response = await traceClient.ExportAsync(request); + + if (response.PartialSuccess != null && response.PartialSuccess.RejectedSpans > 0) + { + Console.WriteLine($"Export partially succeeded: {response.PartialSuccess.RejectedSpans} spans rejected."); + Console.WriteLine($"Error message: {response.PartialSuccess.ErrorMessage}"); + } + else + { + Console.WriteLine("Export succeeded."); + } + return response; + } + + + } +} diff --git a/src/extensions/otel_viewer_extension/otel_viewer_extension.csproj b/src/extensions/otel_viewer_extension/otel_viewer_extension.csproj new file mode 100644 index 00000000..b95890c3 --- /dev/null +++ b/src/extensions/otel_viewer_extension/otel_viewer_extension.csproj @@ -0,0 +1,28 @@ + + + + Azure.AI.CLI.Extensions.Otel + net8.0 + Azure.AI.Details.Common.CLI.Extensions.Otel + enable + enable + true + + + + + + + + + all + + + + + + proto + + + + diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/README.md b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/README.md new file mode 100644 index 00000000..f82dbb02 --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/README.md @@ -0,0 +1,10 @@ +# OpenTelemetry Collector Proto + +This package describes the OpenTelemetry collector protocol. + +## Packages + +1. `common` package contains the common messages shared between different services. +2. `trace` package contains the Trace Service protos. +3. `metrics` package contains the Metrics Service protos. +4. `logs` package contains the Logs Service protos. diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto new file mode 100644 index 00000000..d6fe67f9 --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto @@ -0,0 +1,79 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package opentelemetry.proto.collector.trace.v1; + +import "opentelemetry/proto/trace/v1/trace.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Collector.Trace.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.collector.trace.v1"; +option java_outer_classname = "TraceServiceProto"; +option go_package = "go.opentelemetry.io/proto/otlp/collector/trace/v1"; + +// Service that can be used to push spans between one Application instrumented with +// OpenTelemetry and a collector, or between a collector and a central collector (in this +// case spans are sent/received to/from multiple Applications). +service TraceService { + // For performance reasons, it is recommended to keep this RPC + // alive for the entire life of the application. + rpc Export(ExportTraceServiceRequest) returns (ExportTraceServiceResponse) {} +} + +message ExportTraceServiceRequest { + // An array of ResourceSpans. + // For data coming from a single resource this array will typically contain one + // element. Intermediary nodes (such as OpenTelemetry Collector) that receive + // data from multiple origins typically batch the data before forwarding further and + // in that case this array will contain multiple elements. + repeated opentelemetry.proto.trace.v1.ResourceSpans resource_spans = 1; +} + +message ExportTraceServiceResponse { + // The details of a partially successful export request. + // + // If the request is only partially accepted + // (i.e. when the server accepts only parts of the data and rejects the rest) + // the server MUST initialize the `partial_success` field and MUST + // set the `rejected_` with the number of items it rejected. + // + // Servers MAY also make use of the `partial_success` field to convey + // warnings/suggestions to senders even when the request was fully accepted. + // In such cases, the `rejected_` MUST have a value of `0` and + // the `error_message` MUST be non-empty. + // + // A `partial_success` message with an empty value (rejected_ = 0 and + // `error_message` = "") is equivalent to it not being set/present. Senders + // SHOULD interpret it the same way as in the full success case. + ExportTracePartialSuccess partial_success = 1; +} + +message ExportTracePartialSuccess { + // The number of rejected spans. + // + // A `rejected_` field holding a `0` value indicates that the + // request was fully accepted. + int64 rejected_spans = 1; + + // A developer-facing human-readable message in English. It should be used + // either to explain why the server rejected parts of the data during a partial + // success or to convey warnings/suggestions during a full success. The message + // should offer guidance on how users can address such issues. + // + // error_message is an optional field. An error_message with an empty value + // is equivalent to it not being set. + string error_message = 2; +} diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml new file mode 100644 index 00000000..d091b3a8 --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml @@ -0,0 +1,9 @@ +# This is an API configuration to generate an HTTP/JSON -> gRPC gateway for the +# OpenTelemetry service using github.com/grpc-ecosystem/grpc-gateway. +type: google.api.Service +config_version: 3 +http: + rules: + - selector: opentelemetry.proto.collector.trace.v1.TraceService.Export + post: /v1/traces + body: "*" diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/common/v1/common.proto b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/common/v1/common.proto new file mode 100644 index 00000000..ff8a21a1 --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/common/v1/common.proto @@ -0,0 +1,81 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package opentelemetry.proto.common.v1; + +option csharp_namespace = "OpenTelemetry.Proto.Common.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.common.v1"; +option java_outer_classname = "CommonProto"; +option go_package = "go.opentelemetry.io/proto/otlp/common/v1"; + +// AnyValue is used to represent any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +message AnyValue { + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "empty". + oneof value { + string string_value = 1; + bool bool_value = 2; + int64 int_value = 3; + double double_value = 4; + ArrayValue array_value = 5; + KeyValueList kvlist_value = 6; + bytes bytes_value = 7; + } +} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +message ArrayValue { + // Array of values. The array may be empty (contain 0 elements). + repeated AnyValue values = 1; +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +message KeyValueList { + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + // The keys MUST be unique (it is not allowed to have more than one + // value with the same key). + repeated KeyValue values = 1; +} + +// KeyValue is a key-value pair that is used to store Span attributes, Link +// attributes, etc. +message KeyValue { + string key = 1; + AnyValue value = 2; +} + +// InstrumentationScope is a message representing the instrumentation scope information +// such as the fully qualified name and version. +message InstrumentationScope { + // An empty instrumentation scope name means the name is unknown. + string name = 1; + string version = 2; + + // Additional attributes that describe the scope. [Optional]. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + repeated KeyValue attributes = 3; + uint32 dropped_attributes_count = 4; +} diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/resource/v1/resource.proto b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/resource/v1/resource.proto new file mode 100644 index 00000000..6637560b --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/resource/v1/resource.proto @@ -0,0 +1,37 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package opentelemetry.proto.resource.v1; + +import "opentelemetry/proto/common/v1/common.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Resource.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.resource.v1"; +option java_outer_classname = "ResourceProto"; +option go_package = "go.opentelemetry.io/proto/otlp/resource/v1"; + +// Resource information. +message Resource { + // Set of attributes that describe the resource. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; + + // dropped_attributes_count is the number of dropped attributes. If the value is 0, then + // no attributes were dropped. + uint32 dropped_attributes_count = 2; +} diff --git a/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/trace/v1/trace.proto b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/trace/v1/trace.proto new file mode 100644 index 00000000..5cb2f3ce --- /dev/null +++ b/src/extensions/otel_viewer_extension/proto/opentelemetry/proto/trace/v1/trace.proto @@ -0,0 +1,355 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package opentelemetry.proto.trace.v1; + +import "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Trace.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.trace.v1"; +option java_outer_classname = "TraceProto"; +option go_package = "go.opentelemetry.io/proto/otlp/trace/v1"; + +// TracesData represents the traces data that can be stored in a persistent storage, +// OR can be embedded by other protocols that transfer OTLP traces data but do +// not implement the OTLP protocol. +// +// The main difference between this message and collector protocol is that +// in this message there will not be any "control" or "metadata" specific to +// OTLP protocol. +// +// When new fields are added into this message, the OTLP request MUST be updated +// as well. +message TracesData { + // An array of ResourceSpans. + // For data coming from a single resource this array will typically contain + // one element. Intermediary nodes that receive data from multiple origins + // typically batch the data before forwarding further and in that case this + // array will contain multiple elements. + repeated ResourceSpans resource_spans = 1; +} + +// A collection of ScopeSpans from a Resource. +message ResourceSpans { + reserved 1000; + + // The resource for the spans in this message. + // If this field is not set then no resource info is known. + opentelemetry.proto.resource.v1.Resource resource = 1; + + // A list of ScopeSpans that originate from a resource. + repeated ScopeSpans scope_spans = 2; + + // The Schema URL, if known. This is the identifier of the Schema that the resource data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to the data in the "resource" field. It does not apply + // to the data in the "scope_spans" field which have their own schema_url field. + string schema_url = 3; +} + +// A collection of Spans produced by an InstrumentationScope. +message ScopeSpans { + // The instrumentation scope information for the spans in this message. + // Semantically when InstrumentationScope isn't set, it is equivalent with + // an empty instrumentation scope name (unknown). + opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + + // A list of Spans that originate from an instrumentation scope. + repeated Span spans = 2; + + // The Schema URL, if known. This is the identifier of the Schema that the span data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to all spans and span events in the "spans" field. + string schema_url = 3; +} + +// A Span represents a single operation performed by a single component of the system. +// +// The next available field id is 17. +message Span { + // A unique identifier for a trace. All spans from the same trace share + // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR + // of length other than 16 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is required. + bytes trace_id = 1; + + // A unique identifier for a span within a trace, assigned when the span + // is created. The ID is an 8-byte array. An ID with all zeroes OR of length + // other than 8 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is required. + bytes span_id = 2; + + // trace_state conveys information about request position in multiple distributed tracing graphs. + // It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header + // See also https://github.com/w3c/distributed-tracing for more details about this field. + string trace_state = 3; + + // The `span_id` of this span's parent span. If this is a root span, then this + // field must be empty. The ID is an 8-byte array. + bytes parent_span_id = 4; + + // Flags, a bit field. + // + // Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + // Context specification. To read the 8-bit W3C trace flag, use + // `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + // + // Bits 8 and 9 represent the 3 states of whether a span's parent + // is remote. The states are (unknown, is not remote, is remote). + // To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + // To read whether the span is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + // + // When creating span messages, if the message is logically forwarded from another source + // with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD + // be copied as-is. If creating from a source that does not have an equivalent flags field + // (such as a runtime representation of an OpenTelemetry span), the high 22 bits MUST + // be set to zero. + // Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + // + // [Optional]. + fixed32 flags = 16; + + // A description of the span's operation. + // + // For example, the name can be a qualified method name or a file name + // and a line number where the operation is called. A best practice is to use + // the same display name at the same call point in an application. + // This makes it easier to correlate spans in different traces. + // + // This field is semantically required to be set to non-empty string. + // Empty value is equivalent to an unknown span name. + // + // This field is required. + string name = 5; + + // SpanKind is the type of span. Can be used to specify additional relationships between spans + // in addition to a parent/child relationship. + enum SpanKind { + // Unspecified. Do NOT use as default. + // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. + SPAN_KIND_UNSPECIFIED = 0; + + // Indicates that the span represents an internal operation within an application, + // as opposed to an operation happening at the boundaries. Default value. + SPAN_KIND_INTERNAL = 1; + + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + SPAN_KIND_SERVER = 2; + + // Indicates that the span describes a request to some remote service. + SPAN_KIND_CLIENT = 3; + + // Indicates that the span describes a producer sending a message to a broker. + // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship + // between producer and consumer spans. A PRODUCER span ends when the message was accepted + // by the broker while the logical processing of the message might span a much longer time. + SPAN_KIND_PRODUCER = 4; + + // Indicates that the span describes consumer receiving a message from a broker. + // Like the PRODUCER kind, there is often no direct critical path latency relationship + // between producer and consumer spans. + SPAN_KIND_CONSUMER = 5; + } + + // Distinguishes between spans generated in a particular context. For example, + // two spans with the same name may be distinguished using `CLIENT` (caller) + // and `SERVER` (callee) to identify queueing latency associated with the span. + SpanKind kind = 6; + + // start_time_unix_nano is the start time of the span. On the client side, this is the time + // kept by the local machine where the span execution starts. On the server side, this + // is the time when the server's application handler starts running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + fixed64 start_time_unix_nano = 7; + + // end_time_unix_nano is the end time of the span. On the client side, this is the time + // kept by the local machine where the span execution ends. On the server side, this + // is the time when the server application handler stops running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + fixed64 end_time_unix_nano = 8; + + // attributes is a collection of key/value pairs. Note, global attributes + // like server name can be set using the resource API. Examples of attributes: + // + // "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + // "/http/server_latency": 300 + // "example.com/myattribute": true + // "example.com/score": 10.239 + // + // The OpenTelemetry API specification further restricts the allowed value types: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; + + // dropped_attributes_count is the number of attributes that were discarded. Attributes + // can be discarded because their keys are too long or because there are too many + // attributes. If this value is 0, then no attributes were dropped. + uint32 dropped_attributes_count = 10; + + // Event is a time-stamped annotation of the span, consisting of user-supplied + // text description and key-value pairs. + message Event { + // time_unix_nano is the time the event occurred. + fixed64 time_unix_nano = 1; + + // name of the event. + // This field is semantically required to be set to non-empty string. + string name = 2; + + // attributes is a collection of attribute key/value pairs on the event. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; + + // dropped_attributes_count is the number of dropped attributes. If the value is 0, + // then no attributes were dropped. + uint32 dropped_attributes_count = 4; + } + + // events is a collection of Event items. + repeated Event events = 11; + + // dropped_events_count is the number of dropped events. If the value is 0, then no + // events were dropped. + uint32 dropped_events_count = 12; + + // A pointer from the current span to another span in the same trace or in a + // different trace. For example, this can be used in batching operations, + // where a single batch handler processes multiple requests from different + // traces or when the handler receives a request from a different project. + message Link { + // A unique identifier of a trace that this linked span is part of. The ID is a + // 16-byte array. + bytes trace_id = 1; + + // A unique identifier for the linked span. The ID is an 8-byte array. + bytes span_id = 2; + + // The trace_state associated with the link. + string trace_state = 3; + + // attributes is a collection of attribute key/value pairs on the link. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + repeated opentelemetry.proto.common.v1.KeyValue attributes = 4; + + // dropped_attributes_count is the number of dropped attributes. If the value is 0, + // then no attributes were dropped. + uint32 dropped_attributes_count = 5; + + // Flags, a bit field. + // + // Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + // Context specification. To read the 8-bit W3C trace flag, use + // `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + // + // Bits 8 and 9 represent the 3 states of whether the link is remote. + // The states are (unknown, is not remote, is remote). + // To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + // To read whether the link is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + // + // Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + // When creating new spans, bits 10-31 (most-significant 22-bits) MUST be zero. + // + // [Optional]. + fixed32 flags = 6; + } + + // links is a collection of Links, which are references from this span to a span + // in the same or different trace. + repeated Link links = 13; + + // dropped_links_count is the number of dropped links after the maximum size was + // enforced. If this value is 0, then no links were dropped. + uint32 dropped_links_count = 14; + + // An optional final status for this span. Semantically when Status isn't set, it means + // span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). + Status status = 15; +} + +// The Status type defines a logical error model that is suitable for different +// programming environments, including REST APIs and RPC APIs. +message Status { + reserved 1; + + // A developer-facing human readable error message. + string message = 2; + + // For the semantics of status codes see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status + enum StatusCode { + // The default status. + STATUS_CODE_UNSET = 0; + // The Span has been validated by an Application developer or Operator to + // have completed successfully. + STATUS_CODE_OK = 1; + // The Span contains an error. + STATUS_CODE_ERROR = 2; + }; + + // The status code. + StatusCode code = 3; +} + +// SpanFlags represents constants used to interpret the +// Span.flags field, which is protobuf 'fixed32' type and is to +// be used as bit-fields. Each non-zero value defined in this enum is +// a bit-mask. To extract the bit-field, for example, use an +// expression like: +// +// (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK) +// +// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. +// +// Note that Span flags were introduced in version 1.1 of the +// OpenTelemetry protocol. Older Span producers do not set this +// field, consequently consumers should not rely on the absence of a +// particular flag bit to indicate the presence of a particular feature. +enum SpanFlags { + // The zero value for the enum. Should not be used for comparisons. + // Instead use bitwise "and" with the appropriate mask as shown above. + SPAN_FLAGS_DO_NOT_USE = 0; + + // Bits 0-7 are used for trace flags. + SPAN_FLAGS_TRACE_FLAGS_MASK = 0x000000FF; + + // Bits 8 and 9 are used to indicate that the parent span or link span is remote. + // Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known. + // Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote. + SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 0x00000100; + SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK = 0x00000200; + + // Bits 10-31 are reserved for future use. +} From b132e56068e1c557723f247475f19b8c04a9d78b Mon Sep 17 00:00:00 2001 From: Catherine Zhang Date: Mon, 29 Jul 2024 10:15:34 -0700 Subject: [PATCH 2/5] added back accidentally deleted lines --- src/ai/commands/chat_command.cs | 7 +++++++ src/ai/commands/parsers/chat_command_parser.cs | 1 + 2 files changed, 8 insertions(+) diff --git a/src/ai/commands/chat_command.cs b/src/ai/commands/chat_command.cs index a21f475a..98c08623 100644 --- a/src/ai/commands/chat_command.cs +++ b/src/ai/commands/chat_command.cs @@ -522,6 +522,13 @@ private void CheckWriteChatAnswerOutputFile(string completeResponse) var fileName = FileHelpers.GetOutputDataFileName(outputAnswerFile, _values); FileHelpers.WriteAllText(fileName, completeResponse, Encoding.UTF8); } + + var outputAddAnswerFile = OutputAddChatAnswerFileToken.Data().GetOrDefault(_values); + if (!string.IsNullOrEmpty(outputAddAnswerFile)) + { + var fileName = FileHelpers.GetOutputDataFileName(outputAddAnswerFile, _values); + FileHelpers.AppendAllText(fileName, "\n" + completeResponse, Encoding.UTF8); + } } private async Task CheckWriteChatHistoryOutputFileAsync(AssistantClient client, AssistantThread thread) diff --git a/src/ai/commands/parsers/chat_command_parser.cs b/src/ai/commands/parsers/chat_command_parser.cs index c8258ffa..63c865fa 100644 --- a/src/ai/commands/parsers/chat_command_parser.cs +++ b/src/ai/commands/parsers/chat_command_parser.cs @@ -155,6 +155,7 @@ public CommonChatNamedValueTokenParsers() : base( new TrueFalseNamedValueTokenParser("chat.speech.input", "010"), OutputChatAnswerFileToken.Parser(), + OutputAddChatAnswerFileToken.Parser(), OutputChatHistoryFileToken.Parser(), InputChatHistoryJsonFileToken.Parser(), InputChatParameterFileToken.Parser(), From bfd57af275dac5069d527e9196eefaa9200fdf03 Mon Sep 17 00:00:00 2001 From: Catherine Zhang Date: Wed, 31 Jul 2024 11:47:19 -0700 Subject: [PATCH 3/5] add help support for ai tool dashboard and ai chat assistant trace get --- src/ai/.x/help/chat.assistant.trace.get | 23 +++++++++++++++++++ src/ai/.x/help/tool.dashboard | 11 +++++++++ .../commands/parsers/tool_command_parser.cs | 3 +++ .../commands/parsers/command_parser.cs | 5 +++- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/ai/.x/help/chat.assistant.trace.get create mode 100644 src/ai/.x/help/tool.dashboard diff --git a/src/ai/.x/help/chat.assistant.trace.get b/src/ai/.x/help/chat.assistant.trace.get new file mode 100644 index 00000000..10f66126 --- /dev/null +++ b/src/ai/.x/help/chat.assistant.trace.get @@ -0,0 +1,23 @@ +CHAT ASSISTANT GET + + The `ai chat assistant trace get` command retrieves the trace data from a specific request. + +USAGE: ai chat assistant trace get [...] + + GET + --request-id ID + --request-id ID --output-file FILEPATH + --request-id ID --dashboard true (Note: dashboard must be running to use this option) + +EXAMPLE + + ai chat assistant get trace --request-id 25abac2c-9949-481f-83dd-db06d2993e85 + + ai tool dashboard start + ai chat assistant get trace --request-id 25abac2c-9949-481f-83dd-db06d2993e85 --dashboard true + +SEE ALSO + + ai help chat examples + ai help chat assistant examples + ai help find topics chat assistant \ No newline at end of file diff --git a/src/ai/.x/help/tool.dashboard b/src/ai/.x/help/tool.dashboard new file mode 100644 index 00000000..202a1031 --- /dev/null +++ b/src/ai/.x/help/tool.dashboard @@ -0,0 +1,11 @@ +TOOL DASHBOARD + + The 'ai tool dashboard' command controls the .NET Aspire Dashboard docker container. + NOTE: Make sure Docker is running prior to using the command. + +USAGE: ai tool dashboard start + ai chat dashboard stop + +SEE ALSO + + ai help chat assistant get trace \ No newline at end of file diff --git a/src/ai/commands/parsers/tool_command_parser.cs b/src/ai/commands/parsers/tool_command_parser.cs index 8cd0f571..ca656cc2 100644 --- a/src/ai/commands/parsers/tool_command_parser.cs +++ b/src/ai/commands/parsers/tool_command_parser.cs @@ -24,6 +24,7 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v private static readonly (string name, bool valuesRequired)[] _commands = { ("tool.dashboard.start", false), ("tool.dashboard.stop", false), + ("tool.dashboard", false), ("tool", true), }; @@ -35,6 +36,7 @@ private static readonly (string name, bool valuesRequired)[] _commands = { private static IEnumerable GetCommandParsers(ICommandValues values) { var commandName = values.GetCommand(); + foreach (var command in _commands) { if (commandName == command.name) @@ -75,6 +77,7 @@ public CommonToolNamedValueTokenParsers() : base( }; + #endregion } } diff --git a/src/common/details/commands/parsers/command_parser.cs b/src/common/details/commands/parsers/command_parser.cs index 000d5636..7f23f25e 100644 --- a/src/common/details/commands/parsers/command_parser.cs +++ b/src/common/details/commands/parsers/command_parser.cs @@ -35,7 +35,7 @@ public static bool DispatchParseCommand(INamedValueTokens tokens, ICommandValues var parsed = Program.DispatchParseCommand(tokens, values); if (parsed || values.DisplayHelpRequested()) return parsed; - + var test = command.Split('.')[0]; switch (command.Split('.')[0]) { case "-?": @@ -83,6 +83,9 @@ protected static bool ParseCommand(string commandName, IEnumerable commands, IEnumerable partials, INamedValueTokens tokens, ICommandValues values, Func> parsers) { + var one = ParseCommandName(commands, partials, tokens, values); + var two = ParseCommandDefaults(values.GetCommand()!, parsers(values), tokens, values); + var three = ParseAllCommandValues(parsers(values), tokens, values); return ParseCommandName(commands, partials, tokens, values) && ParseCommandDefaults(values.GetCommand()!, parsers(values), tokens, values) && ParseAllCommandValues(parsers(values), tokens, values); From cf2d68e7bad1b8b4288208b5fe8f9edf1d67c921 Mon Sep 17 00:00:00 2001 From: Catherine Zhang Date: Fri, 9 Aug 2024 10:59:31 -0700 Subject: [PATCH 4/5] fixed up code from comments --- ai-cli.sln | 8 +---- src/ai/ai-cli.csproj | 20 +++++------ src/ai/commands/chat_command.cs | 4 ++- .../commands/parsers/chat_command_parser.cs | 1 + src/ai/commands/tool_command.cs | 7 ++-- .../commands/parsers/command_parser.cs | 5 +-- .../otel_viewer_extension/Dashboard.cs | 36 ++++++++++++------- .../otel_viewer_extension/OtelData.cs | 1 - 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/ai-cli.sln b/ai-cli.sln index 37868e97..37d2cde0 100644 --- a/ai-cli.sln +++ b/ai-cli.sln @@ -39,11 +39,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{ src\telemetry\NuGet.config = src\telemetry\NuGet.config EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{13B3E651-BF79-4138-A73D-FD41F773E0F4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{A5154519-2271-45E8-8761-816440870E40}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "otel_viewer_extension", "src\extensions\otel_viewer_extension\otel_viewer_extension.csproj", "{B153ED12-2028-49A9-AD7F-9D67DA0ECA32}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "otel_viewer_extension", "src\extensions\otel_viewer_extension\otel_viewer_extension.csproj", "{B153ED12-2028-49A9-AD7F-9D67DA0ECA32}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -213,8 +209,6 @@ Global {9499C018-FA08-4133-93B3-FC0F3863A6CC} = {C8AFF891-D6AA-4B8F-BC21-10404DF4B355} {CED7C805-0435-4BF7-A42F-9F3BBF14A18F} = {644B75F1-C768-4DB3-BAF2-C69A1F36DD28} {306A3CD6-91C2-450B-9995-79701CE63FE2} = {975EBC5A-506D-49B5-AA7F-70D3119F009D} - {A5154519-2271-45E8-8761-816440870E40} = {13B3E651-BF79-4138-A73D-FD41F773E0F4} - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32} = {A5154519-2271-45E8-8761-816440870E40} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {002655B1-E1E1-4F2A-8D53-C9CD55136AE2} diff --git a/src/ai/ai-cli.csproj b/src/ai/ai-cli.csproj index 6bf9b415..a0ab9f26 100644 --- a/src/ai/ai-cli.csproj +++ b/src/ai/ai-cli.csproj @@ -126,16 +126,16 @@ - - - + + + + - - - - - + + + + - + - + \ No newline at end of file diff --git a/src/ai/commands/chat_command.cs b/src/ai/commands/chat_command.cs index 98c08623..3a824daf 100644 --- a/src/ai/commands/chat_command.cs +++ b/src/ai/commands/chat_command.cs @@ -1584,7 +1584,9 @@ private async Task DoChatAssistantFileDelete() if (!_quiet) Console.WriteLine($"{message} Done!"); return true; } - private async Task DoChatAssistantTraceGet() { + + private async Task DoChatAssistantTraceGet() + { var requestId = _values["chat.trace.request.id"]; var dashboard = _values.GetOrDefault("chat.trace.dashboard", false); var filePath = _values.GetOrDefault("chat.trace.output.file", ""); diff --git a/src/ai/commands/parsers/chat_command_parser.cs b/src/ai/commands/parsers/chat_command_parser.cs index 63c865fa..54103843 100644 --- a/src/ai/commands/parsers/chat_command_parser.cs +++ b/src/ai/commands/parsers/chat_command_parser.cs @@ -40,6 +40,7 @@ private static readonly (string name, bool valuesRequired)[] _commands = { ("chat.assistant.file.list", false), ("chat.assistant.file", true), + ("chat.assistant.trace", true), ("chat.assistant.trace.get", true), ("chat.assistant", true), diff --git a/src/ai/commands/tool_command.cs b/src/ai/commands/tool_command.cs index c62d6591..187b9d68 100644 --- a/src/ai/commands/tool_command.cs +++ b/src/ai/commands/tool_command.cs @@ -40,10 +40,13 @@ internal bool RunCommand() return _values.GetOrDefault("passed", true); } - private void StartDashboard() { + private void StartDashboard() + { Dashboard.StartDashboard(); } - private void StopDashboard() { + + private void StopDashboard() + { Dashboard.StopDashboard(); } diff --git a/src/common/details/commands/parsers/command_parser.cs b/src/common/details/commands/parsers/command_parser.cs index 7f23f25e..cc6de046 100644 --- a/src/common/details/commands/parsers/command_parser.cs +++ b/src/common/details/commands/parsers/command_parser.cs @@ -35,7 +35,7 @@ public static bool DispatchParseCommand(INamedValueTokens tokens, ICommandValues var parsed = Program.DispatchParseCommand(tokens, values); if (parsed || values.DisplayHelpRequested()) return parsed; - var test = command.Split('.')[0]; + switch (command.Split('.')[0]) { case "-?": @@ -83,9 +83,6 @@ protected static bool ParseCommand(string commandName, IEnumerable commands, IEnumerable partials, INamedValueTokens tokens, ICommandValues values, Func> parsers) { - var one = ParseCommandName(commands, partials, tokens, values); - var two = ParseCommandDefaults(values.GetCommand()!, parsers(values), tokens, values); - var three = ParseAllCommandValues(parsers(values), tokens, values); return ParseCommandName(commands, partials, tokens, values) && ParseCommandDefaults(values.GetCommand()!, parsers(values), tokens, values) && ParseAllCommandValues(parsers(values), tokens, values); diff --git a/src/extensions/otel_viewer_extension/Dashboard.cs b/src/extensions/otel_viewer_extension/Dashboard.cs index 3e18e785..b0b97d35 100644 --- a/src/extensions/otel_viewer_extension/Dashboard.cs +++ b/src/extensions/otel_viewer_extension/Dashboard.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; using Azure.AI.Details.Common.CLI; namespace Azure.AI.Details.Common.CLI.Extensions.Otel @@ -9,26 +10,35 @@ public class Dashboard // Execute a command in the command prompt and return the output private static string ExecuteCommand(string command) { - ProcessStartInfo processStartInfo = new ProcessStartInfo + ProcessStartInfo processStartInfo = new ProcessStartInfo(); + + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + if (isWindows) + { + processStartInfo.FileName = "cmd.exe"; + processStartInfo.Arguments = $"/c {command}"; + } + else { - FileName = "cmd.exe", - Arguments = $"/c {command}", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - }; + processStartInfo.FileName = "/bin/bash"; + processStartInfo.Arguments = $"-c \"{command}\""; + } + + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + processStartInfo.UseShellExecute = false; + processStartInfo.CreateNoWindow = true; using (Process process = new Process()) { process.StartInfo = processStartInfo; process.Start(); - process.WaitForExit(); - return process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + return process.StandardOutput.ReadToEnd(); } } public static void StartDashboard() { - // Commands for the terminal string dockerStartCommand = "docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:8.0.0"; string dockerLogsCommand = "docker logs aspire-dashboard"; @@ -39,8 +49,8 @@ public static void StartDashboard() string startResult = ExecuteCommand(dockerStartCommand); Console.WriteLine(startResult); - // Optionally wait a bit before fetching logs if needed - System.Threading.Thread.Sleep(3000); + // Wait before fetching logs if needed + System.Threading.Thread.Sleep(3000); // Get logs from the Docker container string logsResult = ExecuteCommand(dockerLogsCommand); @@ -73,4 +83,4 @@ public static bool IsRunning() return result.Contains("aspire-dashboard"); } } -} +} \ No newline at end of file diff --git a/src/extensions/otel_viewer_extension/OtelData.cs b/src/extensions/otel_viewer_extension/OtelData.cs index 1fd1cbf3..dfb27fd6 100644 --- a/src/extensions/otel_viewer_extension/OtelData.cs +++ b/src/extensions/otel_viewer_extension/OtelData.cs @@ -45,7 +45,6 @@ public static async Task WriteDataToFile(string data, string filePath) { try { - //var data = await GetTrace(requestId); using (StreamWriter writer = new StreamWriter(filePath)) { await writer.WriteAsync(data); From da4037bb12d42d17d45460d455d902abf38027c1 Mon Sep 17 00:00:00 2001 From: czhang771 Date: Fri, 16 Aug 2024 12:05:09 -0700 Subject: [PATCH 5/5] remove unused files and lines --- ai-cli.sln | 24 ------------------------ requestid.txt | 1 - 2 files changed, 25 deletions(-) delete mode 100644 requestid.txt diff --git a/ai-cli.sln b/ai-cli.sln index 73834ffa..cd81de0d 100644 --- a/ai-cli.sln +++ b/ai-cli.sln @@ -185,30 +185,6 @@ Global {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x64.Build.0 = Release|Any CPU {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x86.ActiveCfg = Release|Any CPU {306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|x86.Build.0 = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|x64.Build.0 = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|x86.ActiveCfg = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Debug|x86.Build.0 = Debug|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|Any CPU.Build.0 = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|x64.ActiveCfg = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|x64.Build.0 = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|x86.ActiveCfg = Release|Any CPU - {7BF26AB6-8931-46CB-A330-D83DF55AB4E8}.Release|x86.Build.0 = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x64.ActiveCfg = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x64.Build.0 = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x86.ActiveCfg = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Debug|x86.Build.0 = Debug|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|Any CPU.Build.0 = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x64.ActiveCfg = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x64.Build.0 = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x86.ActiveCfg = Release|Any CPU - {B153ED12-2028-49A9-AD7F-9D67DA0ECA32}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/requestid.txt b/requestid.txt deleted file mode 100644 index 717cbc82..00000000 --- a/requestid.txt +++ /dev/null @@ -1 +0,0 @@ -5ede4876-6582-46f5-b33a-e5bf51abe2f8