D4S.AgentMetrics 1.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package D4S.AgentMetrics --version 1.2.0
                    
NuGet\Install-Package D4S.AgentMetrics -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="D4S.AgentMetrics" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="D4S.AgentMetrics" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="D4S.AgentMetrics" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add D4S.AgentMetrics --version 1.2.0
                    
#r "nuget: D4S.AgentMetrics, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package D4S.AgentMetrics@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=D4S.AgentMetrics&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=D4S.AgentMetrics&version=1.2.0
                    
Install as a Cake Tool

D4S.AgentMetrics

Internal NuGet package for Dev4Side AI agents built on .NET 10.

It handles, transparently:

  • Handshake — verifies at startup that the agent is registered and active in the D4S back-end.
  • Metric definition registration — sends all metric descriptors (default + custom) to the back-end.
  • Business metric tracking — exposes IMetricWriter.TrackAsync with a bounded in-memory queue.
  • Interaction tracking — measures total_interactions, avg_response_time_ms and error_rate automatically. Pick the tracker with the Transport setting: an HTTP middleware for REST/MCP agents (scoped to the endpoints you choose) or a Microsoft 365 Agents SDK middleware for Teams / custom engine agents (counts only real user messages).
  • AI token tracking — an OpenTelemetry listener harvests gen_ai.usage.* from any Microsoft.Extensions.AI chat pipeline (D4S.Agent works out of the box, zero code).
  • Periodic flush — drains the queue and POSTs the event batch to the back-end, with retry on transient failures.
  • Heartbeat — sends periodic liveness pings to the back-end.

Internal use only. This package is not intended for public distribution.


Prerequisites

The agent must already be registered in the D4S back-end (manually or via the marketplace onboarding process) — this package only verifies that the registration exists.

Everything is fail-soft: if the AgentMetrics configuration is missing, or the back-end is unreachable, or the handshake is rejected, metric tracking is silently disabled and the agent starts and runs normally. This makes it safe to keep the wiring always on (see Quick start), including in local dev where no back-end is configured.


Installation

dotnet add package D4S.AgentMetrics

Quick start

This is the wiring used by the MAF365Agent template — two lines of code, everything else is configuration:

// Program.cs
builder.Services.AddAgentMetrics(options =>
{
    builder.Configuration.GetSection("AgentMetrics").Bind(options);
    // Project-specific business metrics go here (see "Tracking custom metrics"):
});

// After UseRouting, before the endpoint mappings:
app.UseAgentMetricsTracking();
// appsettings.json
{
  "AgentMetrics": {
    "AgentId": "my-agent",
    "DisplayName": "My Agent",
    "TenantId": "customer-tenant",
    "BaseUrl": "https://my-agent.azurewebsites.net",
    "D4SApiUrl": "https://d4s-api.azurewebsites.net",
    "D4SApiKey": "", // secret: set per environment (user secrets / app settings), never commit
    // Only measure the business endpoints — keeps health probes, platform warmup pings and
    // scanner noise out of total_interactions / avg_response_time_ms:
    "TrackedPathPrefixes": [ "/api/messages", "/mcp" ],
    // Only the verb that submits a question — excludes status-polling GETs or CORS preflight
    // OPTIONS that might share the same path, so total_interactions counts exactly one event
    // per question:
    "TrackedHttpMethods": [ "POST" ]
  }
}

Leave the section empty (or omit D4SApiUrl/D4SApiKey) to run with metrics disabled — the wiring is a clean no-op in that case.


Configuration

Option Type Default Description
AgentId string — (required) Unique agent identifier as registered in the D4S back-end.
DisplayName string — (required) Human-readable agent name shown in the dashboard.
TenantId string — (required) Tenant that owns this agent.
BaseUrl string — (required) Public URL of the agent itself.
D4SApiUrl string — (required) Base URL of the central D4S back-end.
D4SApiKey string — (required) API key sent as X-Api-Key on every back-end call.
FlushInterval TimeSpan 00:00:10 How often the metric queue is flushed.
HeartbeatInterval TimeSpan 00:02:00 How often a heartbeat ping is sent.
Transport Http | Bot Http How the agent receives questions. Selects the active interaction tracker: Http uses the HTTP middleware; Bot uses the Microsoft 365 Agents SDK middleware. The inactive one becomes a no-op even if wired.
TrackedPathPrefixes string[] [] (= all) Allow-list of request path prefixes measured by the HTTP middleware (Transport=Http). Strongly recommended on internet-facing hosts.
TrackedHttpMethods string[] [] (= all) Allow-list of HTTP methods measured by the middleware (case-insensitive). Combine with TrackedPathPrefixes so total_interactions counts exactly one event per question, even if other verbs (status-polling GET, CORS OPTIONS) share the same path.
TokenTrackingEnabled bool true Enables the OpenTelemetry token-usage listener.
TokenTelemetrySources string[] [] (= D4S.Agent sources) ActivitySource names observed for gen_ai.usage.* tags. Defaults to Dev4Side.MAF365Agent.ChatClient + Dev4Side.MAF365Agent.Guardrail.
MaxQueuedEvents int 10000 Bound of the in-memory event queue (drops newest with a warning when full).

When a required option is missing, tracking is disabled for the session (logged at startup) and every metric operation becomes a no-op.


Interaction tracking: HTTP vs Bot

total_interactions and avg_response_time_ms must count one real user question and measure from question received to complete answer produced. How to capture that depends on how the agent receives questions, so the Transport setting selects the tracker. Only the selected one is active; the other is a no-op even if wired, so a shared startup template can register both.

Transport = Http (default) — REST / MCP agents

The agent receives questions as HTTP requests. app.UseAgentMetricsTracking() (see Quick start) measures each tracked request; scope it with TrackedPathPrefixes + TrackedHttpMethods so only the question-submitting call is counted.

Transport = Bot — Teams / custom engine agents (Microsoft 365 Agents SDK)

Microsoft 365 Agents SDK agents receive every interaction as an activity on a single /api/messages endpoint, so an HTTP filter cannot tell a real message from a conversationUpdate, typing or invoke. The bot middleware (AgentMetricsBotMiddleware, a Microsoft.Agents.Builder.IMiddleware) inspects Activity.Type and counts only message activities; it times the whole turn (message in → reply sent). Just set Transport to Bot:

// appsettings.json
"AgentMetrics": { /* ...required options... */ "Transport": "Bot" }

AddAgentMetrics(...) registers the middleware in DI as IMiddleware, and the default CloudAdapter created by AddAgent<TAgent>() consumes DI-registered middleware automatically — so no code change is needed in Program.cs or the agent. Only a custom adapter that bypasses DI needs an explicit attach in its constructor:

// Only for a hand-built adapter; the default CloudAdapter picks the middleware up on its own.
adapter.UseAgentMetricsBotTracking(services); // active only when Transport = Bot

With Transport = Bot the package also registers and emits conversations — a distinct count of Activity.Conversation.Id (one event per turn), so the dashboard can show distinct sessions and derive turns-per-session as total_interactions ÷ conversations. It is not registered for Transport = Http (no conversation-id producer, so it would be a permanently empty tile).

Copilot Studio (low-code, Microsoft-hosted) agents run no code of yours, so this package cannot instrument them — interaction metrics there must come from Dataverse analytics / Power Automate.


Default profile metrics

These six metrics are always included automatically — no extra code required. UseProfile(AgentProfile.Default) is optional but recommended for clarity.

Key Label Aggregation Tracked by
total_interactions Interazioni totali Count Middleware (automatic)
avg_response_time_ms Tempo medio risposta (ms) Average Middleware (automatic)
error_rate Tasso di errore Percentage Middleware (automatic, HTTP 5xx / bot turn exception)
prompt_tokens_total Token prompt totali Sum OTel token listener (automatic)
completion_tokens_total Token completion totali Sum OTel token listener (automatic)
total_tokens_total Token totali Sum OTel token listener (automatic)

error_rate is a Percentage: the back-end computes it as count(error_rate) ÷ count(total_interactions). The descriptor carries DenominatorMetricKey = "total_interactions" so the back-end knows what to divide by — without it the metric renders with no value.

A seventh metric, conversations (DistinctCount), is added automatically only when Transport = Bot (see above).

avg_confidence_score was removed from the default profile in 1.1.0: it had no automatic producer and rendered as a permanently empty tile. Register it as a custom scalar in agents that actually compute a confidence score.

avg_tokens_per_interaction was removed in 1.2.0: it was averaged per LLM call, not per interaction (a single turn can span several LLM calls), so the label was misleading. Raw token counts remain for volume telemetry; cost/token analysis is handled by Azure AI Foundry.


AI token tracking

Token metrics are collected automatically by listening to the OpenTelemetry GenAI spans emitted by Microsoft.Extensions.AI pipelines wrapped with UseOpenTelemetry(...):

  • D4S.Agent / MAF365Agent hosts: zero configuration — the D4S.Agent chat and guardrail clients are already instrumented with the source names the listener observes by default.
  • Other MEAI hosts: set TokenTelemetrySources to your instrumented source names (e.g. Experimental.Microsoft.Extensions.AI for the MEAI default). Do not include agent-level sources that re-emit aggregated usage, or tokens are double-counted.
  • The listener reads both the current semantic conventions (gen_ai.usage.input_tokens / gen_ai.usage.output_tokens) and the legacy names (prompt_tokens / completion_tokens).
  • Token counts are raw volume telemetry; monetary cost is derived in Azure AI Foundry, not here.

For hosts that use the Azure AI Foundry ChatCompletionsClient directly (no MEAI pipeline), the legacy explicit wrapper is still available:

builder.Services.AddTrackedChatClient(
    new ChatCompletionsClient(endpoint, new AzureKeyCredential(apiKey)),
    model: "gpt-4o");

Tracking custom metrics

Register the project's business metrics at startup, then record events wherever they happen:

// Registration (inside AddAgentMetrics):
options.AddRanking("top_exams", "Esami più richiesti", dimensionName: "exam_name", topN: 20);
options.AddScalar("teams_messages", "Domande da Teams");                     // Count
options.AddScalar("avg_confidence_score", "Confidence medio", AggregationType.Average);

// Then inject IMetricWriter wherever you need it:
public class MyService(IMetricWriter metrics)
{
    public async Task HandleAsync(string examName, double confidence)
    {
        await metrics.TrackAsync("teams_messages");                          // pure count
        await metrics.TrackAsync("top_exams", dimension: examName);          // ranking increment
        await metrics.TrackAsync("avg_confidence_score", value: (decimal)confidence);
    }
}

Rules:

  • Metric definitions are sent to the back-end at handshake: keys must be registered at startup and stay stable across releases. Events for unregistered keys are dropped with a one-time warning.
  • Ranking metrics are count-based; the dimension string is the ranked value. Never use free user text or user identifiers as a dimension — only catalog-controlled values.

Delivery guarantees

Events are queued in memory and flushed every FlushInterval:

  • Transient back-end failures (network errors, HTTP 5xx) → the batch is re-enqueued and retried on the next cycle.
  • Permanent rejections (HTTP 4xx) → the batch is dropped with a warning.
  • The queue is bounded by MaxQueuedEvents; when full, the newest events are dropped with a warning. On process shutdown, unflushed events are lost — metrics are KPI-grade, not an audit log.

Internal use notice

This package is developed and maintained by Dev4Side for internal use only. It is not published on NuGet.org and is not intended for external consumption.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 39 8/6/2026
1.2.1 56 7/23/2026
1.2.0 55 7/23/2026
1.1.1 64 7/22/2026
1.1.0 57 7/20/2026
1.0.0 77 6/8/2026