Skip to content

Commit 3935de2

Browse files
authored
adding tracing for agents (Azure#50119)
* adding tracing for agents * initial streaming tracing * updates * updates and review comment related changes * adding tests * updating changelog * adding test recording, samples and updating readme * updating tests * updating tests * review changes * fixing readme * updating readme based on review comments
1 parent ec44703 commit 3935de2

24 files changed

+4581
-520
lines changed

sdk/ai/Azure.AI.Agents.Persistent/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Tracing for Agents
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/ai/Azure.AI.Agents.Persistent/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ managing search indexes, evaluating generative AI performance, and enabling Open
3535
- [Function call](#function-call)
3636
- [Azure function Call](#azure-function-call)
3737
- [OpenAPI](#create-agent-with-openapi)
38+
- [Tracing](#tracing)
39+
- [Azure Monitor Tracing](#tracing-to-azure-monitor)
40+
- [Console Tracing](#tracing-to-console)
3841
- [Troubleshooting](#troubleshooting)
3942
- [Next steps](#next-steps)
4043
- [Contributing](#contributing)
@@ -866,6 +869,63 @@ Assert.AreEqual(
866869
run.LastError?.Message);
867870
```
868871
`
872+
873+
#### Tracing
874+
875+
You can add an Application Insights Azure resource to your Azure AI Foundry project. See the Tracing tab in your AI Foundry project. If one was enabled, you use the Application Insights connection string, configure your Agents, and observe the full execution path through Azure Monitor. Typically, you might want to start tracing before you create an Agent.
876+
877+
Tracing requires enabling OpenTelemetry support. One way to do this is to set the `AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE` environment variable value to `true`. You can also enable the feature with the following code:
878+
```C# Snippet:EnableActivitySourceToGetTraces
879+
AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
880+
```
881+
882+
To enabled content recording, set the `AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED` environment variable to `true`. Content in this context refers to chat message content, function call tool related function names, function parameter names and values. Alternatively, you can control content recording with the following code:
883+
```C# Snippet:DisableContentRecordingForTraces
884+
AppContext.SetSwitch("Azure.Experimental.TraceGenAIMessageContent", false);
885+
```
886+
Set the value to `true` to enable content recording.
887+
888+
##### Tracing to Azure Montior
889+
First, set the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable to point to your Azure Monitor resource.
890+
891+
For tracing to Azure Monitor from your application, the preferred option is to use Azure.Monitor.OpenTelemetry.AspNetCore. Install the package with [NuGet](https://www.nuget.org/ ):
892+
```dotnetcli
893+
dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
894+
```
895+
896+
More information about using the Azure.Monitor.OpenTelemetry.AspNetCore package can be found [here](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/README.md ).
897+
898+
Another option is to use Azure.Monitor.OpenTelemetry.Exporter package. Install the pacakge with [NuGet](https://www.nuget.org/ )
899+
```dotnetcli
900+
dotnet add package Azure.Monitor.OpenTelemetry.Exporter
901+
```
902+
903+
Here is an example how to set up tracing to Azure monitor using Azure.Monitor.OpenTelemetry.Exporter:
904+
```C# Snippet:AgentsTelemetrySetupTracingToAzureMonitor
905+
var tracerProvider = Sdk.CreateTracerProviderBuilder()
906+
.AddSource("Azure.AI.Agents.Persistent.*")
907+
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("AgentTracingSample"))
908+
.AddAzureMonitorTraceExporter().Build();
909+
```
910+
911+
##### Tracing to Console
912+
913+
For tracing to console from your application, install the OpenTelemetry.Exporter.Console with [NuGet](https://www.nuget.org/ ):
914+
915+
```dotnetcli
916+
dotnet add package OpenTelemetry.Exporter.Console
917+
```
918+
919+
920+
Here is an example how to set up tracing to console:
921+
```C# Snippet:AgentsTelemetrySetupTracingToConsole
922+
var tracerProvider = Sdk.CreateTracerProviderBuilder()
923+
.AddSource("Azure.AI.Agents.Persistent.*") // Add the required sources name
924+
.SetResourceBuilder(OpenTelemetry.Resources.ResourceBuilder.CreateDefault().AddService("AgentTracingSample"))
925+
.AddConsoleExporter() // Export traces to the console
926+
.Build();
927+
```
928+
869929
## Troubleshooting
870930

871931
Any operation that fails will throw a [RequestFailedException][RequestFailedException]. The exception's `code` will hold the HTTP response status code. The exception's `message` contains a detailed message that may be helpful in diagnosing the issue:

sdk/ai/Azure.AI.Agents.Persistent/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "net",
44
"TagPrefix": "net/ai/Azure.AI.Agents.Persistent",
5-
"Tag": "net/ai/Azure.AI.Agents.Persistent_8e1aef00a7"
5+
"Tag": "net/ai/Azure.AI.Agents.Persistent_2b964340aa"
66
}

sdk/ai/Azure.AI.Agents.Persistent/src/Custom/Internal/ContinuationTokenPageable.cs

Lines changed: 122 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,23 @@
1111
using System.Threading.Tasks;
1212
using System.Text.Json;
1313
using System.Runtime.CompilerServices;
14+
using Azure.AI.Agents.Persistent.Telemetry;
1415

1516
namespace Azure.AI.Agents.Persistent
1617
{
18+
// Enum to specify the type of items in the pageable
19+
internal enum ContinuationItemType
20+
{
21+
Undefined,
22+
ThreadMessage,
23+
RunStep
24+
}
25+
1726
internal class ContinuationTokenPageable<T>: Pageable<T>
1827
{
1928
private readonly ContinuationTokenPageableImpl<T> _impl;
29+
private readonly ContinuationItemType _itemType;
30+
2031
public ContinuationTokenPageable(
2132
Func<int?, string, HttpMessage> createPageRequest,
2233
Func<JsonElement, T> valueFactory,
@@ -26,9 +37,14 @@ public ContinuationTokenPageable(
2637
RequestContext requestContext,
2738
string itemPropertyName = "data",
2839
string hasMoreField = "has_more",
29-
string continuationTokenName = "last_id"
40+
string continuationTokenName = "last_id",
41+
ContinuationItemType itemType = ContinuationItemType.Undefined,
42+
string threadId = null,
43+
string runId = null,
44+
Uri endpoint = null
3045
)
3146
{
47+
_itemType = itemType;
3248
_impl = new(
3349
createPageRequest: createPageRequest,
3450
valueFactory: valueFactory,
@@ -38,7 +54,11 @@ public ContinuationTokenPageable(
3854
requestContext: requestContext,
3955
itemPropertyName: itemPropertyName,
4056
hasMoreField: hasMoreField,
41-
continuationTokenName: continuationTokenName
57+
continuationTokenName: continuationTokenName,
58+
itemType: itemType,
59+
threadId: threadId,
60+
runId: runId,
61+
endpoint: endpoint
4262
);
4363
}
4464

@@ -48,6 +68,8 @@ public ContinuationTokenPageable(
4868
internal class ContinuationTokenPageableAsync<T> : AsyncPageable<T>
4969
{
5070
private readonly ContinuationTokenPageableImpl<T> _impl;
71+
private readonly ContinuationItemType _itemType;
72+
5173
public ContinuationTokenPageableAsync(
5274
Func<int?, string, HttpMessage> createPageRequest,
5375
Func<JsonElement, T> valueFactory,
@@ -57,9 +79,14 @@ public ContinuationTokenPageableAsync(
5779
RequestContext requestContext,
5880
string itemPropertyName = "data",
5981
string hasMoreField = "has_more",
60-
string continuationTokenName = "last_id"
82+
string continuationTokenName = "last_id",
83+
ContinuationItemType itemType = ContinuationItemType.Undefined,
84+
string threadId = null,
85+
string runId = null,
86+
Uri endpoint = null
6187
)
6288
{
89+
_itemType = itemType;
6390
_impl = new(
6491
createPageRequest: createPageRequest,
6592
valueFactory: valueFactory,
@@ -69,7 +96,11 @@ public ContinuationTokenPageableAsync(
6996
requestContext: requestContext,
7097
itemPropertyName: itemPropertyName,
7198
hasMoreField: hasMoreField,
72-
continuationTokenName: continuationTokenName
99+
continuationTokenName: continuationTokenName,
100+
itemType: itemType,
101+
threadId: threadId,
102+
runId: runId,
103+
endpoint: endpoint
73104
);
74105
}
75106

@@ -91,6 +122,10 @@ internal class ContinuationTokenPageableImpl<T>
91122
private readonly string _itemPropertyName;
92123
private readonly string _hasMoreField;
93124
private readonly string _continuationTokenName;
125+
private readonly ContinuationItemType _itemType;
126+
private readonly string _threadId;
127+
private readonly string _runId;
128+
private readonly Uri _endpoint;
94129

95130
public ContinuationTokenPageableImpl(
96131
Func<int?, string, HttpMessage> createPageRequest,
@@ -101,7 +136,11 @@ public ContinuationTokenPageableImpl(
101136
RequestContext requestContext,
102137
string itemPropertyName,
103138
string hasMoreField,
104-
string continuationTokenName
139+
string continuationTokenName,
140+
ContinuationItemType itemType = ContinuationItemType.Undefined,
141+
string threadId = null,
142+
string runId = null,
143+
Uri endpoint = null
105144
)
106145
{
107146
_createPageRequest = createPageRequest;
@@ -114,6 +153,10 @@ string continuationTokenName
114153
_itemPropertyName = itemPropertyName;
115154
_hasMoreField = hasMoreField;
116155
_continuationTokenName = continuationTokenName;
156+
_itemType = itemType;
157+
_threadId = threadId;
158+
_runId = runId;
159+
_endpoint = endpoint;
117160
}
118161

119162
public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -186,45 +229,93 @@ private Response GetNextResponse(int? pageSizeHint, string continuationToken)
186229
return null;
187230
}
188231

189-
using DiagnosticScope scope = _clientDiagnostics.CreateScope(_scopeName);
190-
scope.Start();
191-
try
232+
// Use GenAI scope for paging if item type is specified
233+
OpenTelemetryScope genAIScope = null;
234+
DiagnosticScope? diagnosticsScope = null;
235+
if (_itemType == ContinuationItemType.ThreadMessage)
236+
{
237+
genAIScope = OpenTelemetryScope.StartListMessages(_threadId, _runId, _endpoint);
238+
}
239+
else if (_itemType == ContinuationItemType.RunStep)
192240
{
193-
_pipeline.Send(message, _cancellationToken);
194-
return GetResponse(message);
241+
genAIScope = OpenTelemetryScope.StartListRunSteps(_threadId, _runId, _endpoint);
195242
}
196-
catch (Exception e)
243+
else
197244
{
198-
scope.Failed(e);
199-
throw;
245+
diagnosticsScope = _clientDiagnostics.CreateScope(_scopeName);
246+
}
247+
248+
using (genAIScope)
249+
{
250+
using (diagnosticsScope)
251+
{
252+
diagnosticsScope?.Start();
253+
try
254+
{
255+
_pipeline.Send(message, _cancellationToken);
256+
var response = GetResponse(message);
257+
genAIScope?.RecordPagedResponse(response);
258+
return response;
259+
}
260+
catch (Exception e)
261+
{
262+
diagnosticsScope?.Failed(e);
263+
genAIScope?.RecordError(e);
264+
throw;
265+
}
266+
}
200267
}
201268
}
202269

203270
private async ValueTask<Response> GetNextResponseAsync(int? pageSizeHint, string continuationToken, CancellationToken cancellationToken)
204271
{
205272
var message = CreateMessage(pageSizeHint, continuationToken);
206273

207-
using DiagnosticScope scope = _clientDiagnostics.CreateScope(_scopeName);
208-
scope.Start();
209-
try
274+
// Use GenAI scope for paging if item type is specified
275+
OpenTelemetryScope genAIScope = null;
276+
DiagnosticScope? diagnosticsScope = null;
277+
if (_itemType == ContinuationItemType.ThreadMessage)
210278
{
211-
if (cancellationToken.CanBeCanceled && _cancellationToken.CanBeCanceled)
212-
{
213-
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
214-
await _pipeline.SendAsync(message, cts.Token).ConfigureAwait(false);
215-
}
216-
else
217-
{
218-
var ct = cancellationToken.CanBeCanceled ? cancellationToken : _cancellationToken;
219-
await _pipeline.SendAsync(message, ct).ConfigureAwait(false);
220-
}
221-
222-
return GetResponse(message);
279+
genAIScope = OpenTelemetryScope.StartListMessages(_threadId, _runId, _endpoint);
280+
}
281+
else if (_itemType == ContinuationItemType.RunStep)
282+
{
283+
genAIScope = OpenTelemetryScope.StartListRunSteps(_threadId, _runId, _endpoint);
223284
}
224-
catch (Exception e)
285+
else
225286
{
226-
scope.Failed(e);
227-
throw;
287+
diagnosticsScope = _clientDiagnostics.CreateScope(_scopeName);
288+
}
289+
290+
using (genAIScope)
291+
{
292+
using (diagnosticsScope)
293+
{
294+
diagnosticsScope?.Start();
295+
try
296+
{
297+
if (cancellationToken.CanBeCanceled && _cancellationToken.CanBeCanceled)
298+
{
299+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
300+
await _pipeline.SendAsync(message, cts.Token).ConfigureAwait(false);
301+
}
302+
else
303+
{
304+
var ct = cancellationToken.CanBeCanceled ? cancellationToken : _cancellationToken;
305+
await _pipeline.SendAsync(message, ct).ConfigureAwait(false);
306+
}
307+
308+
var response = GetResponse(message);
309+
genAIScope?.RecordPagedResponse(response);
310+
return response;
311+
}
312+
catch (Exception e)
313+
{
314+
diagnosticsScope?.Failed(e);
315+
genAIScope?.RecordError(e);
316+
throw;
317+
}
318+
}
228319
}
229320
}
230321

0 commit comments

Comments
 (0)