Skip to content

Commit

Permalink
review: rename and move files and other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
lmolkova committed Aug 5, 2024
1 parent e368bb1 commit 4b25142
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 162 deletions.
6 changes: 3 additions & 3 deletions docs/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ To enable the instrumentation:

1. Set instrumentation feature-flag using one of the following options:

- set the `OPENAI_EXPERIMENTAL_ENABLE_INSTRUMENTATION` environment variable to `"true"`
- set the `OpenAI.Experimental.EnableInstrumentation` context switch to true in your application code when application
- set the `OPENAI_EXPERIMENTAL_ENABLE_OPEN_TELEMETRY` environment variable to `"true"`
- set the `OpenAI.Experimental.EnableOpenTelemetry` context switch to true in your application code when application
is starting and before initializing any OpenAI clients. For example:

```csharp
AppContext.SetSwitch("OpenAI.Experimental.EnableInstrumentation", true);
AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);
```

2. Enable OpenAI telemetry:
Expand Down
10 changes: 5 additions & 5 deletions src/Custom/Chat/ChatClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using OpenAI.Instrumentation;
using OpenAI.Telemetry;
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
Expand All @@ -15,7 +15,7 @@ namespace OpenAI.Chat;
public partial class ChatClient
{
private readonly string _model;
private readonly InstrumentationFactory _instrumentation;
private readonly OpenTelemetrySource _telemetry;

/// <summary>
/// Initializes a new instance of <see cref="ChatClient"/> that will use an API key when authenticating.
Expand Down Expand Up @@ -64,7 +64,7 @@ protected internal ChatClient(ClientPipeline pipeline, string model, Uri endpoin
_model = model;
_pipeline = pipeline;
_endpoint = endpoint;
_instrumentation = new InstrumentationFactory(model, endpoint);
_telemetry = new OpenTelemetrySource(model, endpoint);
}

/// <summary>
Expand All @@ -80,7 +80,7 @@ public virtual async Task<ClientResult<ChatCompletion>> CompleteChatAsync(IEnume

options ??= new();
CreateChatCompletionOptions(messages, ref options);
using InstrumentationScope scope = _instrumentation.StartChatScope(options);
using OpenTelemetryScope scope = _telemetry.StartChatScope(options);

try
{
Expand Down Expand Up @@ -119,7 +119,7 @@ public virtual ClientResult<ChatCompletion> CompleteChat(IEnumerable<ChatMessage

options ??= new();
CreateChatCompletionOptions(messages, ref options);
using InstrumentationScope scope = _instrumentation.StartChatScope(options);
using OpenTelemetryScope scope = _telemetry.StartChatScope(options);

try
{
Expand Down
33 changes: 33 additions & 0 deletions src/Utility/AppContextSwitchHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace OpenAI;

internal static class AppContextSwitchHelper
{
/// <summary>
/// Determines if either an AppContext switch or its corresponding Environment Variable is set
/// </summary>
/// <param name="appContexSwitchName">Name of the AppContext switch.</param>
/// <param name="environmentVariableName">Name of the Environment variable.</param>
/// <returns>If the AppContext switch has been set, returns the value of the switch.
/// If the AppContext switch has not been set, returns the value of the environment variable.
/// False if neither is set.
/// </returns>
public static bool GetConfigValue(string appContexSwitchName, string environmentVariableName)
{
// First check for the AppContext switch, giving it priority over the environment variable.
if (AppContext.TryGetSwitch(appContexSwitchName, out bool value))
{
return value;
}
// AppContext switch wasn't used. Check the environment variable.
string envVar = Environment.GetEnvironmentVariable(environmentVariableName);
if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1")))
{
return true;
}

// Default to false.
return false;
}
}
60 changes: 0 additions & 60 deletions src/Utility/Instrumentation/InstrumentationFactory.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace OpenAI.Instrumentation;
namespace OpenAI.Telemetry;

internal class Constants
internal class OpenTelemetryConstants
{
// follows OpenTelemetry GenAI semantic conventions:
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai
// follow OpenTelemetry GenAI semantic conventions:
// https://github.com/open-telemetry/semantic-conventions/tree/v1.27.0/docs/gen-ai

public const string ErrorTypeKey = "error.type";
public const string ServerAddressKey = "server.address";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
using OpenAI.Chat;
using System;
using System.Buffers;
using System.ClientModel;
using System.Diagnostics;
using System.Diagnostics.Metrics;

namespace OpenAI.Instrumentation;
using static OpenAI.Telemetry.OpenTelemetryConstants;

internal class InstrumentationScope : IDisposable
namespace OpenAI.Telemetry;

internal class OpenTelemetryScope : IDisposable
{
private static readonly ActivitySource s_chatSource = new ActivitySource("OpenAI.ChatClient");
private static readonly Meter s_chatMeter = new Meter("OpenAI.ChatClient");

// TODO: add explicit histogram buckets once System.Diagnostics.DiagnosticSource 9.0 is used
private static readonly Histogram<double> s_duration = s_chatMeter.CreateHistogram<double>(Constants.GenAiClientOperationDurationMetricName, "s", "Measures GenAI operation duration.");
private static readonly Histogram<long> s_tokens = s_chatMeter.CreateHistogram<long>(Constants.GenAiClientTokenUsageMetricName, "{token}", "Measures the number of input and output token used.");
private static readonly Histogram<double> s_duration = s_chatMeter.CreateHistogram<double>(GenAiClientOperationDurationMetricName, "s", "Measures GenAI operation duration.");
private static readonly Histogram<long> s_tokens = s_chatMeter.CreateHistogram<long>(GenAiClientTokenUsageMetricName, "{token}", "Measures the number of input and output token used.");

private readonly string _operationName;
private readonly string _serverAddress;
Expand All @@ -25,7 +26,7 @@ internal class InstrumentationScope : IDisposable
private Activity _activity;
private TagList _commonTags;

private InstrumentationScope(
private OpenTelemetryScope(
string model, string operationName,
string serverAddress, int serverPort)
{
Expand All @@ -35,12 +36,14 @@ private InstrumentationScope(
_serverPort = serverPort;
}

public static InstrumentationScope StartChat(string model, string operationName,
private static bool IsChatEnabled => s_chatSource.HasListeners() || s_tokens.Enabled || s_duration.Enabled;

public static OpenTelemetryScope StartChat(string model, string operationName,
string serverAddress, int serverPort, ChatCompletionOptions options)
{
if (s_chatSource.HasListeners() || s_tokens.Enabled || s_duration.Enabled)
if (IsChatEnabled)
{
var scope = new InstrumentationScope(model, operationName, serverAddress, serverPort);
var scope = new OpenTelemetryScope(model, operationName, serverAddress, serverPort);
scope.StartChat(options);
return scope;
}
Expand All @@ -53,20 +56,20 @@ private void StartChat(ChatCompletionOptions options)
_duration = Stopwatch.StartNew();
_commonTags = new TagList
{
{ Constants.GenAiSystemKey, Constants.GenAiSystemValue },
{ Constants.GenAiRequestModelKey, _requestModel },
{ Constants.ServerAddressKey, _serverAddress },
{ Constants.ServerPortKey, _serverPort },
{ Constants.GenAiOperationNameKey, _operationName },
{ GenAiSystemKey, GenAiSystemValue },
{ GenAiRequestModelKey, _requestModel },
{ ServerAddressKey, _serverAddress },
{ ServerPortKey, _serverPort },
{ GenAiOperationNameKey, _operationName },
};

_activity = s_chatSource.StartActivity(string.Concat(_operationName, " ", _requestModel), ActivityKind.Client);
if (_activity?.IsAllDataRequested == true)
{
RecordCommonAttributes();
SetActivityTagIfNotNull(Constants.GenAiRequestMaxTokensKey, options?.MaxTokens);
SetActivityTagIfNotNull(Constants.GenAiRequestTemperatureKey, options?.Temperature);
SetActivityTagIfNotNull(Constants.GenAiRequestTopPKey, options?.TopP);
SetActivityTagIfNotNull(GenAiRequestMaxTokensKey, options?.MaxTokens);
SetActivityTagIfNotNull(GenAiRequestTemperatureKey, options?.Temperature);
SetActivityTagIfNotNull(GenAiRequestTopPKey, options?.TopP);
}

return;
Expand All @@ -88,7 +91,7 @@ public void RecordException(Exception ex)
RecordMetrics(null, errorType, null, null);
if (_activity?.IsAllDataRequested == true)
{
_activity?.SetTag(Constants.ErrorTypeKey, errorType);
_activity?.SetTag(OpenTelemetryConstants.ErrorTypeKey, errorType);
_activity?.SetStatus(ActivityStatusCode.Error, ex?.Message ?? errorType);
}
}
Expand All @@ -100,11 +103,11 @@ public void Dispose()

private void RecordCommonAttributes()
{
_activity.SetTag(Constants.GenAiSystemKey, Constants.GenAiSystemValue);
_activity.SetTag(Constants.GenAiRequestModelKey, _requestModel);
_activity.SetTag(Constants.ServerAddressKey, _serverAddress);
_activity.SetTag(Constants.ServerPortKey, _serverPort);
_activity.SetTag(Constants.GenAiOperationNameKey, _operationName);
_activity.SetTag(GenAiSystemKey, GenAiSystemValue);
_activity.SetTag(GenAiRequestModelKey, _requestModel);
_activity.SetTag(ServerAddressKey, _serverAddress);
_activity.SetTag(ServerPortKey, _serverPort);
_activity.SetTag(GenAiOperationNameKey, _operationName);
}

private void RecordMetrics(string responseModel, string errorType, int? inputTokensUsage, int? outputTokensUsage)
Expand All @@ -114,37 +117,37 @@ private void RecordMetrics(string responseModel, string errorType, int? inputTok

if (responseModel != null)
{
tags.Add(Constants.GenAiResponseModelKey, responseModel);
tags.Add(GenAiResponseModelKey, responseModel);
}

if (inputTokensUsage != null)
{
var inputUsageTags = tags;
inputUsageTags.Add(Constants.GenAiTokenTypeKey, "input");
inputUsageTags.Add(GenAiTokenTypeKey, "input");
s_tokens.Record(inputTokensUsage.Value, inputUsageTags);
}

if (outputTokensUsage != null)
{
var outputUsageTags = tags;
outputUsageTags.Add(Constants.GenAiTokenTypeKey, "output");
outputUsageTags.Add(GenAiTokenTypeKey, "output");
s_tokens.Record(outputTokensUsage.Value, outputUsageTags);
}

if (errorType != null)
{
tags.Add(Constants.ErrorTypeKey, errorType);
tags.Add(ErrorTypeKey, errorType);
}

s_duration.Record(_duration.Elapsed.TotalSeconds, tags);
}

private void RecordResponseAttributes(string responseId, string model, ChatFinishReason? finishReason, ChatTokenUsage usage)
{
SetActivityTagIfNotNull(Constants.GenAiResponseIdKey, responseId);
SetActivityTagIfNotNull(Constants.GenAiResponseModelKey, model);
SetActivityTagIfNotNull(Constants.GenAiUsageInputTokensKey, usage?.InputTokens);
SetActivityTagIfNotNull(Constants.GenAiUsageOutputTokensKey, usage?.OutputTokens);
SetActivityTagIfNotNull(GenAiResponseIdKey, responseId);
SetActivityTagIfNotNull(GenAiResponseModelKey, model);
SetActivityTagIfNotNull(GenAiUsageInputTokensKey, usage?.InputTokens);
SetActivityTagIfNotNull(GenAiUsageOutputTokensKey, usage?.OutputTokens);
SetFinishReasonAttribute(finishReason);
}

Expand All @@ -167,7 +170,7 @@ private void SetFinishReasonAttribute(ChatFinishReason? finishReason)

// There could be multiple finish reasons, so semantic conventions use array type for the corrresponding attribute.
// It's likely to change, but for now let's report it as array.
_activity.SetTag(Constants.GenAiResponseFinishReasonKey, new[] { reasonStr });
_activity.SetTag(GenAiResponseFinishReasonKey, new[] { reasonStr });
}

private string GetChatMessageRole(ChatMessageRole? role) =>
Expand Down
30 changes: 30 additions & 0 deletions src/Utility/Telemetry/OpenTelemetrySource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using OpenAI.Chat;
using System;

namespace OpenAI.Telemetry;

internal class OpenTelemetrySource
{
private const string ChatOperationName = "chat";
private readonly bool IsOTelEnabled = AppContextSwitchHelper
.GetConfigValue("OpenAI.Experimental.EnableOpenTelemetry", "OPENAI_EXPERIMENTAL_ENABLE_OPEN_TELEMETRY");

private readonly string _serverAddress;
private readonly int _serverPort;
private readonly string _model;

public OpenTelemetrySource(string model, Uri endpoint)
{
_serverAddress = endpoint.Host;
_serverPort = endpoint.Port;
_model = model;
}

public OpenTelemetryScope StartChatScope(ChatCompletionOptions completionsOptions)
{
return IsOTelEnabled
? OpenTelemetryScope.StartChat(_model, ChatOperationName, _serverAddress, _serverPort, completionsOptions)
: null;
}

}
6 changes: 3 additions & 3 deletions tests/Chat/ChatSmokeTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using NUnit.Framework;
using OpenAI.Chat;
using OpenAI.Tests.Instrumentation;
using OpenAI.Tests.Telemetry;
using OpenAI.Tests.Utility;
using System;
using System.ClientModel;
Expand All @@ -12,7 +12,7 @@
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using static OpenAI.Tests.Instrumentation.TestMeterListener;
using static OpenAI.Tests.Telemetry.TestMeterListener;
using static OpenAI.Tests.TestHelpers;

namespace OpenAI.Tests.Chat;
Expand Down Expand Up @@ -480,7 +480,7 @@ public void SerializeChatMessageContentPartAsImageBytes(bool fromRawJson)
[NonParallelizable]
public async Task HelloWorldChatWithTracingAndMetrics()
{
using var _ = InstrumentationAppContextHelper.EnableInstrumentation();
using var _ = TestAppContextSwitchHelper.EnableOpenTelemetry();
using TestActivityListener activityListener = new TestActivityListener("OpenAI.ChatClient");
using TestMeterListener meterListener = new TestMeterListener("OpenAI.ChatClient");

Expand Down
3 changes: 2 additions & 1 deletion tests/OpenAI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</ItemGroup>

<ItemGroup>
<Compile Include="..\src\Utility\Instrumentation\*.cs" LinkBase="Instrumentation\Shared" />
<Compile Include="..\src\Utility\Telemetry\*.cs" LinkBase="Telemetry\Shared" />
<Compile Include="..\src\Utility\AppContextSwitchHelper.cs" LinkBase="Telemetry\Shared" />
</ItemGroup>
</Project>
Loading

0 comments on commit 4b25142

Please sign in to comment.