-
Notifications
You must be signed in to change notification settings - Fork 5
Use Activity helpers and ResolveAgentIdentity for agent telemetry #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
269ea45
2b13c9e
0f93ad0
cdde6f4
17760da
406dbcf
9e8b3ff
c6643c0
42333fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,15 +9,24 @@ import { | |
| CallerDetails, | ||
| ParentSpanRef, | ||
| logger, | ||
| isPerRequestExportEnabled, | ||
| } from '@microsoft/agents-a365-observability'; | ||
| import { ScopeUtils } from '../utils/ScopeUtils'; | ||
| import { AgenticTokenCacheInstance } from '../caching/AgenticTokenCache'; | ||
|
|
||
| /** | ||
| * TurnState key for the parent span reference. | ||
| * Set this in `turnState` to link OutputScope spans as children of an InvokeAgentScope. | ||
| */ | ||
| export const A365_PARENT_SPAN_KEY = 'A365ParentSpanId'; | ||
|
|
||
| /** | ||
| * TurnState key for the auth token. | ||
| * Set this in `turnState` so middleware can resolve the agent blueprint ID | ||
| * from token claims (used for embodied/agentic requests). | ||
| */ | ||
| export const A365_AUTH_TOKEN_KEY = 'A365AuthToken'; | ||
|
Comment on lines
+23
to
+28
|
||
|
|
||
| /** | ||
| * Middleware that creates {@link OutputScope} spans for outgoing messages. | ||
| * Links to a parent span when {@link A365_PARENT_SPAN_KEY} is set in turnState. | ||
|
|
@@ -28,7 +37,8 @@ export const A365_PARENT_SPAN_KEY = 'A365ParentSpanId'; | |
| export class OutputLoggingMiddleware implements Middleware { | ||
|
|
||
| async onTurn(context: TurnContext, next: () => Promise<void>): Promise<void> { | ||
| const agentDetails = ScopeUtils.deriveAgentDetails(context); | ||
| const authToken = this.resolveAuthToken(context); | ||
| const agentDetails = ScopeUtils.deriveAgentDetails(context, authToken); | ||
| const tenantDetails = ScopeUtils.deriveTenantDetails(context); | ||
|
|
||
| if (!agentDetails || !tenantDetails) { | ||
|
|
@@ -47,6 +57,23 @@ export class OutputLoggingMiddleware implements Middleware { | |
| await next(); | ||
| } | ||
|
|
||
| /** | ||
| * Resolve the auth token for agent identity resolution. | ||
| * When per-request export is enabled, reads from turnState. | ||
| * Otherwise, reads from the cached observability token. | ||
| */ | ||
| private resolveAuthToken(context: TurnContext): string { | ||
| if (isPerRequestExportEnabled()) { | ||
| return context.turnState.get(A365_AUTH_TOKEN_KEY) as string ?? ''; | ||
| } | ||
| const agentId = context.activity?.getAgenticInstanceId?.() ?? ''; | ||
| const tenantId = context.activity?.getAgenticTenantId?.() ?? ''; | ||
| if (agentId && tenantId) { | ||
| return AgenticTokenCacheInstance.getObservabilityToken(agentId, tenantId) ?? ''; | ||
| } | ||
| return ''; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a send handler that wraps outgoing messages in OutputScope spans. | ||
| * Reads parent span ref lazily so the agent handler can set it during `next()`. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import { | |
| InvokeAgentDetails, | ||
| ToolCallDetails, | ||
| } from '@microsoft/agents-a365-observability'; | ||
| import { resolveEmbodiedAgentIds } from './TurnContextUtils'; | ||
|
|
||
| /** | ||
| * Unified utilities to populate scope tags from a TurnContext. | ||
|
|
@@ -40,26 +41,30 @@ export class ScopeUtils { | |
| * @returns Tenant details if a recipient tenant id is present; otherwise undefined. | ||
| */ | ||
| public static deriveTenantDetails(turnContext: TurnContext): TenantDetails | undefined { | ||
| const tenantId = turnContext?.activity?.recipient?.tenantId; | ||
| const tenantId = turnContext?.activity?.getAgenticTenantId?.(); | ||
| return tenantId ? { tenantId } : undefined; | ||
|
Comment on lines
43
to
45
|
||
| } | ||
|
|
||
| /** | ||
| * Derive target agent details from the activity recipient. | ||
| * Uses {@link resolveEmbodiedAgentIds} to resolve the agent ID and blueprint ID, which are only | ||
| * set for embodied (agentic) agents — see that function for the rationale. | ||
| * @param turnContext Activity context | ||
| * @param authToken Auth token for resolving agent identity from token claims. | ||
| * @returns Agent details built from recipient properties; otherwise undefined. | ||
| */ | ||
| public static deriveAgentDetails(turnContext: TurnContext): AgentDetails | undefined { | ||
| public static deriveAgentDetails(turnContext: TurnContext, authToken: string): AgentDetails | undefined { | ||
| const recipient = turnContext?.activity?.recipient; | ||
| if (!recipient) return undefined; | ||
| const { agentId, agentBlueprintId } = resolveEmbodiedAgentIds(turnContext, authToken); | ||
| return { | ||
| agentId: recipient.agenticAppId, | ||
| agentId, | ||
| agentName: recipient.name, | ||
| agentAUID: recipient.aadObjectId, | ||
| agentBlueprintId: recipient.agenticAppBlueprintId, | ||
| agentUPN: recipient.agenticUserId, | ||
| agentBlueprintId, | ||
| agentUPN: turnContext?.activity?.getAgenticUser?.(), | ||
| agentDescription: recipient.role, | ||
| tenantId: recipient.tenantId | ||
| tenantId: turnContext?.activity?.getAgenticTenantId?.() | ||
| } as AgentDetails; | ||
| } | ||
|
|
||
|
|
@@ -127,17 +132,19 @@ export class ScopeUtils { | |
| * Also records input messages from the context if present. | ||
| * @param details The inference call details (model, provider, tokens, etc.). | ||
| * @param turnContext The current activity context to derive scope parameters from. | ||
| * @param authToken Auth token for resolving agent identity from token claims. | ||
| * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). | ||
| * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). | ||
| * @returns A started `InferenceScope` enriched with context-derived parameters. | ||
| */ | ||
| static populateInferenceScopeFromTurnContext( | ||
| details: InferenceDetails, | ||
| turnContext: TurnContext, | ||
| authToken: string, | ||
| startTime?: TimeInput, | ||
| endTime?: TimeInput | ||
| ): InferenceScope { | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext); | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); | ||
| const tenant = ScopeUtils.deriveTenantDetails(turnContext); | ||
| const conversationId = ScopeUtils.deriveConversationId(turnContext); | ||
| const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); | ||
|
|
@@ -161,20 +168,22 @@ export class ScopeUtils { | |
| * Also sets execution type and input messages from the context if present. | ||
| * @param details The invoke-agent call details to be augmented and used for the scope. | ||
| * @param turnContext The current activity context to derive scope parameters from. | ||
| * @param authToken Auth token for resolving agent identity from token claims. | ||
| * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). | ||
| * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). | ||
| * @returns A started `InvokeAgentScope` enriched with context-derived parameters. | ||
| */ | ||
| static populateInvokeAgentScopeFromTurnContext( | ||
| details: InvokeAgentDetails, | ||
| turnContext: TurnContext, | ||
| authToken: string, | ||
| startTime?: TimeInput, | ||
| endTime?: TimeInput | ||
| ): InvokeAgentScope { | ||
| const tenant = ScopeUtils.deriveTenantDetails(turnContext); | ||
| const callerAgent = ScopeUtils.deriveCallerAgent(turnContext); | ||
| const caller = ScopeUtils.deriveCallerDetails(turnContext); | ||
| const invokeAgentDetails = ScopeUtils.buildInvokeAgentDetails(details, turnContext); | ||
| const invokeAgentDetails = ScopeUtils.buildInvokeAgentDetailsCore(details, turnContext, authToken); | ||
|
|
||
| if (!tenant) { | ||
| throw new Error('populateInvokeAgentScopeFromTurnContext: Missing tenant details on TurnContext (recipient)'); | ||
|
|
@@ -189,10 +198,15 @@ export class ScopeUtils { | |
| * Build InvokeAgentDetails by merging provided details with agent info, conversation id and source metadata from the TurnContext. | ||
| * @param details Base invoke-agent details to augment | ||
| * @param turnContext Activity context | ||
| * @param authToken Auth token for resolving agent identity from token claims. | ||
| * @returns New InvokeAgentDetails suitable for starting an InvokeAgentScope. | ||
| */ | ||
| public static buildInvokeAgentDetails(details: InvokeAgentDetails, turnContext: TurnContext): InvokeAgentDetails { | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext); | ||
| public static buildInvokeAgentDetails(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails { | ||
| return ScopeUtils.buildInvokeAgentDetailsCore(details, turnContext, authToken); | ||
| } | ||
|
|
||
| private static buildInvokeAgentDetailsCore(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails { | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); | ||
| const srcMetaFromContext = ScopeUtils.deriveSourceMetadataObject(turnContext); | ||
| const baseRequest = details.request ?? {}; | ||
| const baseSource = baseRequest.sourceMetadata ?? {}; | ||
|
|
@@ -217,6 +231,7 @@ export class ScopeUtils { | |
| * Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (channel name/link) from context. | ||
| * @param details The tool call details (name, type, args, call id, etc.). | ||
| * @param turnContext The current activity context to derive scope parameters from. | ||
| * @param authToken Auth token for resolving agent identity from token claims. | ||
| * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). Useful when recording a | ||
| * tool call after execution has already completed. | ||
| * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). | ||
|
|
@@ -225,10 +240,11 @@ export class ScopeUtils { | |
| static populateExecuteToolScopeFromTurnContext( | ||
| details: ToolCallDetails, | ||
| turnContext: TurnContext, | ||
| authToken: string, | ||
| startTime?: TimeInput, | ||
| endTime?: TimeInput | ||
| ): ExecuteToolScope { | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext); | ||
| const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken); | ||
| const tenant = ScopeUtils.deriveTenantDetails(turnContext); | ||
| const conversationId = ScopeUtils.deriveConversationId(turnContext); | ||
| const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to reuse the TokenCache?
If possible id dont want to introduce another way to access the token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, we might not know if the token for non‑embodied agent case has app id or not or the appid in the token claim is the right id for the agent or not. For implication and before that question is answered, I will not make any change to use the cached token.