Skip to content

Conversation

@alexsohn1126
Copy link
Member

@alexsohn1126 alexsohn1126 commented Oct 20, 2025

Resolves #3762

Purpose

This PR will add a new SDK - for Sentry.Extensions.AI.

It will allow users to instrument their LLM usage, including any tool calls and LLM API calls. Microsoft.Extensions.AI.Abstractions NuGet package is used in this SDK to use their interfaces and types.

Usage

In order to use this SDK, the user will have to have a compatible SDK which can instantiate an IChatClient. In the samples, I have used Microsoft.Extensions.AI.OpenAI. Another popular LLM SDK is Anthropic.SDK.

There are a few AI-exclusive options the users can change.

  • IncludeAIRequestMessages - boolean
    • Defaults to true
    • If true, we include the request message in the spans we send to Sentry.
  • IncludeAIResponseContent - boolean
    • Defaults to true
    • If true, we include the response message in the spans we send to Sentry.
  • AgentName - string
    • Defaults to Agent
    • Name of the agent which this ChatClient represents.

Once the user has set up and sends AI spans to Sentry, they will be able to see it in Insights > AI (from the sidebar).

Screen.Recording.2025-10-31.at.5.39.14.PM.mov

There are useful information about LLM usage in the webpage. Such as how many tokens the user has sent/received, what tool calls were made, how long they tool, etc.

@codecov
Copy link

codecov bot commented Oct 20, 2025

Codecov Report

❌ Patch coverage is 82.37705% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.79%. Comparing base (16e05ec) to head (c46bcb6).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/Sentry.Extensions.AI/SentryAISpanEnricher.cs 85.41% 13 Missing and 1 partial ⚠️
src/Sentry.Extensions.AI/SentryChatClient.cs 89.04% 4 Missing and 4 partials ⚠️
...try.Extensions.AI/Extensions/SentryAIExtensions.cs 22.22% 7 Missing ⚠️
...c/Sentry.Extensions.AI/SentryAIActivityListener.cs 80.00% 2 Missing and 3 partials ⚠️
...Sentry.Extensions.AI/SentryInstrumentedFunction.cs 80.76% 1 Missing and 4 partials ⚠️
src/Sentry.Extensions.AI/SentryAIUtil.cs 33.33% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4657      +/-   ##
==========================================
+ Coverage   73.66%   73.79%   +0.12%     
==========================================
  Files         476      485       +9     
  Lines       17442    17686     +244     
  Branches     3453     3497      +44     
==========================================
+ Hits        12849    13051     +202     
- Misses       3744     3774      +30     
- Partials      849      861      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexsohn1126
Copy link
Member Author

@sentry review

@alexsohn1126
Copy link
Member Author

@sentry review

@alexsohn1126
Copy link
Member Author

@BugBot review

cursor[bot]

This comment was marked as outdated.

@alexsohn1126
Copy link
Member Author

@BugBot review

@alexsohn1126
Copy link
Member Author

@sentry review

cursor[bot]

This comment was marked as outdated.

@alexsohn1126
Copy link
Member Author

@BugBot review

@alexsohn1126
Copy link
Member Author

@sentry review

@alexsohn1126
Copy link
Member Author

@BugBot review

@alexsohn1126
Copy link
Member Author

@sentry review

@alexsohn1126
Copy link
Member Author

@BugBot review

[Experimental(DiagnosticId.ExperimentalFeature)]
public static ChatOptions AddSentryToolInstrumentation(this ChatOptions options)
{
if (options.Tools is { Count: 0 })
Copy link
Collaborator

@jamescrosswell jamescrosswell Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (options.Tools is { Count: 0 })
if (options.Tools is not { Count: > 0 })

The above basically reads:

If options.Tools is not a non-null object with a Count greater than zero

The logic you have currently fails to match null values... it only matches a non-null object with a count of zero. That would technically be OK since the loop comparison below would compare i to null and the whole thing would evaluate as false... but:

  • We can early exit here already (and avoid that comparison)
  • It's not as obvious/explicit that will happen... anyone reading the code needs to do a bit of a double take to work that out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac9f7ce

/// </summary>
private static readonly ActivityListener FICCListener = new()
{
ShouldListenTo = source => source.Name.StartsWith(SentryAIConstants.SentryActivitySourceName),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting the instrumentation for the spans that ME AI adds to be done using this source name:
https://github.com/dotnet/extensions/blob/e7423719f55472f16e61150413fd107ea504c369/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs#L11

... but even that we might not be able to rely on, since it appears people can override the source name for some reason:
https://github.com/dotnet/extensions/blob/f8f779a6ea004bb1f26649719ca77d63a9d9417c/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClientBuilderExtensions.cs#L29

Is that why you've overriden SentryChatClient.GetService here:

public override object? GetService(Type serviceType, object? serviceKey = null) =>
serviceType == typeof(ActivitySource)
? SentryAIActivitySource.Instance
: base.GetService(serviceType, serviceKey);

It's an interesting solution... I'm wondering if there might be side effects, like if other people's code is also listening for those events and assuming they will have the original ActivitySource name. Given that it can be overriden, maybe they shouldn't be doing that anyway.

@Flash0ver thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah... it starts to make more sense now I read this:

So yeah, this looks like a nice solution.

{
/// <summary>
/// The list of strings which FunctionInvokingChatClient(FICC) uses to start the tool call <see cref="Activity"/>.
/// These can be found in FunctionInvokingChatClient.cs in GetResponseAsync or GetStreamingResponseAsync function.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more a permalink like:

Suggested change
/// These can be found in FunctionInvokingChatClient.cs in GetResponseAsync or GetStreamingResponseAsync function.
/// See:
/// https://github.com/dotnet/extensions/blob/f8f779a6ea004bb1f26649719ca77d63a9d9417c/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs#L272
///

I couldn't find anything using the names "FunctionInvokingChatClient.GetResponseAsync" or
"FunctionInvokingChatClient"... I think you mentioned those came from older versions of the code? Would be good to add permalinks to those as well in that case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I was mentioning that from the official Microsoft Extensions AI codebase. A link would definitely be better..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5745c0a

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point of passing IHub in as a dependency is to avoid statics (which are tricky to test).

We should remove the static modifier from this class... can probably ditch the Init method in favour of a regular constructor as well.

If there was some need to only have one instance (strictly), we could implement a singleton pattern here (like this).

I don't think that's necessary though...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up using a singleton pattern because we only want one Listener registered at all times. Otherwise there may be multiple spans created for a single call to the LLMs.

Fixed in 9b440a3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably easier to describe what I mean in a commit 😉

Take a look at that and see if it makes sense... it's a bit tricky because the only way to add a listener is via ActivitySource.AddActivityListener (which is static), but possible to avoid all the other static references when running tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good, using Lazy is something I haven't seen yet, that's interesting!

I changed the capitalization for AI in SentryAIActivityListener in this commit:

@jamescrosswell jamescrosswell self-requested a review November 11, 2025 01:00
Copy link
Collaborator

@jamescrosswell jamescrosswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking heaps better... I think the main outstanding issue now is this one:
#4657 (comment)

@alexsohn1126
Copy link
Member Author

Seems like this is close to being finished.. (also bugbot didn't find anything so that's a plus? :) )

Please take a look @jamescrosswell @Flash0ver and let me know if anything's off..

@jamescrosswell jamescrosswell self-requested a review November 13, 2025 01:08
Copy link
Collaborator

@jamescrosswell jamescrosswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see any remaining issues with this (assuming you're OK with the changes I made).

@Flash0ver I think it'd be good to get a second set of eyes on this one as there's quite a lot going on in this PR.

@jamescrosswell jamescrosswell self-requested a review November 13, 2025 21:36
Copy link
Collaborator

@jamescrosswell jamescrosswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @alexsohn1126 - not a trivial integration so great to get this over the line!

Base automatically changed from version6 to main November 14, 2025 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Microsoft.Extensions.AI

5 participants