Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
### Breaking Changes (`@microsoft/agents-a365-observability`)

- **`OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled`** — Optional `boolean` to enable content recording in OpenAI trace processor.
- **`LangChainTraceInstrumentor.instrument(module, options?)`** — New optional `{ isContentRecordingEnabled?: boolean }` parameter to enable content recording in LangChain tracer.
- **`truncateValue`** / **`MAX_ATTRIBUTE_LENGTH`** — Exported utilities for attribute value truncation (8192 char limit).
- **`SourceMetadata` renamed to `Channel`** — The exported interface representing invocation channel information is renamed from `SourceMetadata` to `Channel`.
- **`AgentRequest.sourceMetadata` renamed to `AgentRequest.channel`** — The optional property on `AgentRequest` is renamed from `sourceMetadata` to `channel` (type changed from `SourceMetadata` to `Channel`).
- **`BaggageBuilder.serviceName()` renamed to `BaggageBuilder.operationSource()`** — Fluent setter for the service name baggage value.
- **`BaggageBuilder.sourceMetadataName()` renamed to `BaggageBuilder.channelName()`** — Fluent setter for the channel name baggage value.
- **`BaggageBuilder.sourceMetadataDescription()` renamed to `BaggageBuilder.channelLink()`** — Fluent setter for the channel link baggage value.
- **`InferenceScope.start()` parameter `sourceMetadata` renamed to `channel`** — Type changed from `Pick<SourceMetadata, "name" | "description">` to `Pick<Channel, "name" | "description">`.
- **`ExecuteToolScope.start()` parameter `sourceMetadata` renamed to `channel`** — Type changed from `Pick<SourceMetadata, "name" | "description">` to `Pick<Channel, "name" | "description">`.
- **`InvokeAgentScope`** now reads `request.channel` instead of `request.sourceMetadata` for channel name/link tags.

Comment thread
fpfp100 marked this conversation as resolved.
### Breaking Changes (`@microsoft/agents-a365-observability-hosting`)

- **`ScopeUtils.deriveSourceMetadataObject()` renamed to `ScopeUtils.deriveChannelObject()`**.
- **`BaggageBuilderUtils.setSourceMetadataBaggage()` renamed to `BaggageBuilderUtils.setChannelBaggage()`**.
- **`getSourceMetadataBaggagePairs()` renamed to `getChannelBaggagePairs()`** in `TurnContextUtils`.
- **`ScopeUtils.deriveAgentDetails(turnContext, authToken)`** — New required `authToken: string` parameter.
- **`ScopeUtils.populateInferenceScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter.
- **`ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter.
- **`ScopeUtils.populateExecuteToolScopeFromTurnContext(details, turnContext, authToken, ...)`** — New required `authToken: string` parameter.
- **`ScopeUtils.buildInvokeAgentDetails(details, turnContext, authToken)`** — New required `authToken: string` parameter.

### Added
- **OutputScope**: Tracing scope for outgoing agent messages with caller details, conversation ID, source metadata, and parent span linking.

- **`OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled`** — Optional `boolean` to enable content recording in OpenAI trace processor.
- **`LangChainTraceInstrumentor.instrument(module, options?)`** — New optional `{ isContentRecordingEnabled?: boolean }` parameter to enable content recording in LangChain tracer.
- **`truncateValue`** / **`MAX_ATTRIBUTE_LENGTH`** — Exported utilities for attribute value truncation (8192 char limit).
- **OutputScope**: Tracing scope for outgoing agent messages with caller details, conversation ID, channel information, and parent span linking.
Comment thread
fpfp100 marked this conversation as resolved.
- **BaggageMiddleware**: Middleware for automatic OpenTelemetry baggage propagation from TurnContext.
- **OutputLoggingMiddleware**: Middleware that creates OutputScope spans for outgoing messages with lazy parent span linking via `A365_PARENT_SPAN_KEY`.
- **ObservabilityHostingManager**: Manager for configuring hosting-layer observability middleware with `ObservabilityHostingOptions`.

### Added
- **Agent365ExporterOptions**: New `httpRequestTimeoutMilliseconds` option (default 30s) controls the per-HTTP-request timeout for backend calls. This is distinct from `exporterTimeoutMilliseconds` which controls the overall BatchSpanProcessor export deadline.

### Fixed
Expand Down Expand Up @@ -86,7 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Agent-to-agent invocations
- Error tracking and performance metrics
- Request/response content (configurable)
- Source metadata and execution context
- Channel metadata and execution context

### Requirements
- Node.js 18.0 or later
Expand Down
8 changes: 4 additions & 4 deletions packages/agents-a365-observability-hosting/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ BaggageBuilderUtils.setCallerBaggage(builder, turnContext);
BaggageBuilderUtils.setExecutionTypeBaggage(builder, turnContext);
BaggageBuilderUtils.setTargetAgentBaggage(builder, turnContext);
BaggageBuilderUtils.setTenantIdBaggage(builder, turnContext);
BaggageBuilderUtils.setSourceMetadataBaggage(builder, turnContext);
BaggageBuilderUtils.setChannelBaggage(builder, turnContext);
BaggageBuilderUtils.setConversationIdBaggage(builder, turnContext);

// Build and use the scope
Expand All @@ -72,7 +72,7 @@ scope.run(() => {
| `setExecutionTypeBaggage(builder, turnContext)` | Set execution type (HumanToAgent, Agent2Agent) |
| `setTargetAgentBaggage(builder, turnContext)` | Set target agent ID, name, description |
| `setTenantIdBaggage(builder, turnContext)` | Set tenant ID from recipient or channel data |
| `setSourceMetadataBaggage(builder, turnContext)` | Set channel name and subchannel |
| `setChannelBaggage(builder, turnContext)` | Set channel name and subchannel |
| `setConversationIdBaggage(builder, turnContext)` | Set conversation ID and item link |

### TurnContextUtils ([TurnContextUtils.ts](../src/utils/TurnContextUtils.ts))
Expand All @@ -85,7 +85,7 @@ import {
getExecutionTypePair,
getTargetAgentBaggagePairs,
getTenantIdPair,
getSourceMetadataBaggagePairs,
getChannelBaggagePairs,
getConversationIdAndItemLinkPairs
} from '@microsoft/agents-a365-observability-hosting';

Expand All @@ -110,7 +110,7 @@ const agentPairs = getTargetAgentBaggagePairs(turnContext);
| `getExecutionTypePair()` | `gen_ai.execution.type` |
| `getTargetAgentBaggagePairs()` | `gen_ai.agent.id`, `gen_ai.agent.name`, `gen_ai.agent.description`, `gen_ai.agent.auid` |
| `getTenantIdPair()` | `tenant_id` |
| `getSourceMetadataBaggagePairs()` | `gen_ai.execution.source.name`, `gen_ai.execution.source.description` |
| `getChannelBaggagePairs()` | `gen_ai.execution.source.name`, `gen_ai.execution.source.description` |
| `getConversationIdAndItemLinkPairs()` | `gen_ai.conversation.id`, `gen_ai.conversation.item_link` |

### AgenticTokenCacheInstance ([AgenticTokenCache.ts](../src/caching/AgenticTokenCache.ts))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
getCallerBaggagePairs,
getTargetAgentBaggagePairs,
getTenantIdPair,
getSourceMetadataBaggagePairs,
getChannelBaggagePairs,
getConversationIdAndItemLinkPairs,
} from '../utils/TurnContextUtils';

Expand All @@ -33,7 +33,7 @@ export class BaggageMiddleware implements Middleware {
.setPairs(getCallerBaggagePairs(context))
.setPairs(getTargetAgentBaggagePairs(context))
.setPairs(getTenantIdPair(context))
.setPairs(getSourceMetadataBaggagePairs(context))
.setPairs(getChannelBaggagePairs(context))
.setPairs(getConversationIdAndItemLinkPairs(context))
.setPairs(getExecutionTypePair(context))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export class OutputLoggingMiddleware implements Middleware {

const callerDetails = ScopeUtils.deriveCallerDetails(context);
const conversationId = ScopeUtils.deriveConversationId(context);
const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(context);
const channel = ScopeUtils.deriveChannelObject(context);

context.onSendActivities(
this._createSendHandler(context, agentDetails, tenantDetails, callerDetails, conversationId, sourceMetadata)
this._createSendHandler(context, agentDetails, tenantDetails, callerDetails, conversationId, channel)
);

await next();
Expand Down Expand Up @@ -84,7 +84,7 @@ export class OutputLoggingMiddleware implements Middleware {
tenantDetails: TenantDetails,
callerDetails?: CallerDetails,
conversationId?: string,
sourceMetadata?: { name?: string; description?: string },
channel?: { name?: string; description?: string },
): SendActivitiesHandler {
return async (_ctx, activities, sendNext) => {
const messages = activities
Expand All @@ -108,7 +108,7 @@ export class OutputLoggingMiddleware implements Middleware {
tenantDetails,
callerDetails,
conversationId,
sourceMetadata,
channel,
parentSpanRef,
);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
getExecutionTypePair,
getTargetAgentBaggagePairs,
getTenantIdPair,
getSourceMetadataBaggagePairs,
getChannelBaggagePairs,
getConversationIdAndItemLinkPairs
} from './TurnContextUtils';
import { BaggageBuilder } from '@microsoft/agents-a365-observability';
Expand All @@ -34,7 +34,7 @@ export class BaggageBuilderUtils {
this.setExecutionTypeBaggage(builder, turnContext);
this.setTargetAgentBaggage(builder, turnContext);
this.setTenantIdBaggage(builder, turnContext);
this.setSourceMetadataBaggage(builder, turnContext);
this.setChannelBaggage(builder, turnContext);
this.setConversationIdBaggage(builder, turnContext);
return builder;
}
Expand Down Expand Up @@ -88,13 +88,13 @@ export class BaggageBuilderUtils {


/**
* Sets the source metadata baggage values from the TurnContext.
* Sets the channel baggage values from the TurnContext.
* @param builder The BaggageBuilder instance.
* @param turnContext The TurnContext containing activity information.
* @returns The updated BaggageBuilder instance.
*/
static setSourceMetadataBaggage(builder: BaggageBuilder, turnContext: TurnContext): BaggageBuilder {
builder.setPairs(getSourceMetadataBaggagePairs(turnContext));
static setChannelBaggage(builder: BaggageBuilder, turnContext: TurnContext): BaggageBuilder {
builder.setPairs(getChannelBaggagePairs(turnContext));
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ export class ScopeUtils {
}

/**
* Derive source metadata (channel name and description/link) from the TurnContext.
* Derive channel (name and description) from the TurnContext.
* @param turnContext Activity context
* @returns Object with optional name and description fields.
*/
public static deriveSourceMetadataObject(turnContext: TurnContext): { name?: string; description?: string } {
public static deriveChannelObject(turnContext: TurnContext): { name?: string; description?: string } {
return {
name: turnContext?.activity?.channelId,
description: turnContext?.activity?.channelIdSubChannel as string | undefined
Expand All @@ -131,7 +131,7 @@ export class ScopeUtils {

/**
* Create an `InferenceScope` using `details` and values derived from the provided `TurnContext`.
* Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (channel name/link) from context.
* Derives `agentDetails`, `tenantDetails`, `conversationId`, and `channel` (name/description) from context.
* 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.
Expand All @@ -150,7 +150,7 @@ export class ScopeUtils {
const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken);
const tenant = ScopeUtils.deriveTenantDetails(turnContext);
const conversationId = ScopeUtils.deriveConversationId(turnContext);
const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext);
const channel = ScopeUtils.deriveChannelObject(turnContext);

if (!agent) {
throw new Error('populateInferenceScopeFromTurnContext: Missing agent details on TurnContext (recipient)');
Expand All @@ -159,14 +159,14 @@ export class ScopeUtils {
throw new Error('populateInferenceScopeFromTurnContext: Missing tenant details on TurnContext (recipient)');
}

const scope = InferenceScope.start(details, agent, tenant, conversationId, sourceMetadata, undefined, startTime, endTime);
const scope = InferenceScope.start(details, agent, tenant, conversationId, channel, undefined, startTime, endTime);
this.setInputMessageTags(scope, turnContext);
return scope;
}

/**
* Create an `InvokeAgentScope` using `details` and values derived from the provided `TurnContext`.
* Populates `conversationId` and `request.sourceMetadata` (channel name/link) in `details` from the `TurnContext`, overriding any existing values.
* Populates `conversationId` and `request.channel` (name/link) in `details` from the `TurnContext`, overriding any existing values.
* Derives `tenantDetails`, `callerAgentDetails` (from caller), and `callerDetails` (from user).
Comment thread
fpfp100 marked this conversation as resolved.
* 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.
Expand Down Expand Up @@ -200,7 +200,7 @@ export class ScopeUtils {
}

/**
* Build InvokeAgentDetails by merging provided details with agent info, conversation id and source metadata from the TurnContext.
* Build InvokeAgentDetails by merging provided details with agent info, conversation id and channel 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.
Expand All @@ -212,28 +212,28 @@ export class ScopeUtils {

private static buildInvokeAgentDetailsCore(details: InvokeAgentDetails, turnContext: TurnContext, authToken: string): InvokeAgentDetails {
const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken);
const srcMetaFromContext = ScopeUtils.deriveSourceMetadataObject(turnContext);
const channelFromContext = ScopeUtils.deriveChannelObject(turnContext);
const baseRequest = details.request ?? {};
const baseSource = baseRequest.sourceMetadata ?? {};
const mergedSourceMetadata = {
...baseSource,
...(srcMetaFromContext.name !== undefined ? { name: srcMetaFromContext.name } : {}),
...(srcMetaFromContext.description !== undefined ? { description: srcMetaFromContext.description } : {}),
const baseChannel = baseRequest.channel ?? {};
const mergedChannel = {
...baseChannel,
...(channelFromContext.name !== undefined ? { name: channelFromContext.name } : {}),
...(channelFromContext.description !== undefined ? { description: channelFromContext.description } : {}),
};
return {
...details,
...agent,
conversationId: ScopeUtils.deriveConversationId(turnContext),
request: {
...baseRequest,
sourceMetadata: mergedSourceMetadata
channel: mergedChannel
}
};
}

/**
* Create an `ExecuteToolScope` using `details` and values derived from the provided `TurnContext`.
* Derives `agentDetails`, `tenantDetails`, `conversationId`, and `sourceMetadata` (channel name/link) from context.
* Derives `agentDetails`, `tenantDetails`, `conversationId`, and `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.
Expand All @@ -254,14 +254,14 @@ export class ScopeUtils {
const agent = ScopeUtils.deriveAgentDetails(turnContext, authToken);
const tenant = ScopeUtils.deriveTenantDetails(turnContext);
const conversationId = ScopeUtils.deriveConversationId(turnContext);
const sourceMetadata = ScopeUtils.deriveSourceMetadataObject(turnContext);
const channel = ScopeUtils.deriveChannelObject(turnContext);
if (!agent) {
throw new Error('populateExecuteToolScopeFromTurnContext: Missing agent details on TurnContext (recipient)');
}
if (!tenant) {
throw new Error('populateExecuteToolScopeFromTurnContext: Missing tenant details on TurnContext (recipient)');
}
const scope = ExecuteToolScope.start(details, agent, tenant, conversationId, sourceMetadata, undefined, startTime, endTime, undefined, spanKind);
const scope = ExecuteToolScope.start(details, agent, tenant, conversationId, channel, undefined, startTime, endTime, undefined, spanKind);
return scope;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ export function getTenantIdPair(turnContext: TurnContext): Array<[string, string
}

/**
* Extracts source metadata baggage pairs from the TurnContext.
* Extracts channel baggage pairs from the TurnContext.
* @param turnContext The current TurnContext (activity context)
* @returns Array of [key, value] pairs for channel name and link
* @returns Array of [key, value] pairs for channel name and subchannel description
*/
export function getSourceMetadataBaggagePairs(turnContext: TurnContext): Array<[string, string]> {
export function getChannelBaggagePairs(turnContext: TurnContext): Array<[string, string]> {
Comment thread
fpfp100 marked this conversation as resolved.
if (!turnContext) {
return [];
}
Expand Down
10 changes: 6 additions & 4 deletions packages/agents-a365-observability/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ scope.recordOutputMessages(['Hi there!']);
**Span attributes recorded:**
- Server address and port
- Session ID
- Execution type and source metadata
- Execution type and channel
- Input/output messages
- Caller details (ID, UPN, name, tenant, client IP)
- Caller agent details (if agent-to-agent)
Expand All @@ -177,7 +177,7 @@ using scope = InferenceScope.start(
agentDetails,
tenantDetails,
conversationId,
sourceMetadata
channel
);

scope.recordInputMessages(['User message']);
Expand Down Expand Up @@ -207,7 +207,7 @@ using scope = ExecuteToolScope.start(
agentDetails,
tenantDetails,
conversationId,
sourceMetadata
channel
);

// ... tool execution ...
Expand Down Expand Up @@ -258,7 +258,9 @@ const scope2 = BaggageBuilder.setRequestContext(
| `sessionId(value)` | `session_id` |
| `conversationId(value)` | `gen_ai.conversation.id` |
| `callerUpn(value)` | `gen_ai.caller.upn` |
| `sourceMetadataName(value)` | `gen_ai.execution.source.name` |
| `operationSource(value)` | `service.name` |
| `channelName(value)` | `gen_ai.execution.source.name` |
| `channelLink(value)` | `gen_ai.execution.source.description` |
Comment thread
fpfp100 marked this conversation as resolved.

## Data Interfaces

Expand Down
2 changes: 1 addition & 1 deletion packages/agents-a365-observability/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export {
export {
ExecutionType,
InvocationRole,
SourceMetadata,
Channel,
AgentRequest,
AgentDetails,
TenantDetails,
Expand Down
18 changes: 9 additions & 9 deletions packages/agents-a365-observability/src/tracing/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ export enum InferenceOperationType {


/**
* Represents metadata about the source of an invocation
* Represents channel for an invocation
*/
export interface SourceMetadata {
/** Unique identifier for the source (e.g., agent ID, user ID, system component ID) */
export interface Channel {
/** Unique identifier for the channel (e.g., Teams channel ID, Slack workspace ID) */
id?: string;

/** Human-readable name of the source */
/** Human-readable name of the channel (e.g., "Teams", "Slack", "web") */
name?: string;

/** Optional icon identifier or URL for visual representation of the source */
/** Optional icon identifier or URL for visual representation of the channel */
iconUri?: string;

/** The role of the source invoking the agent */
/** The role of the entity invoking through this channel */
role?: InvocationRole;

/** Optional description providing additional context about the source */
/** Optional description or link providing additional context about the channel */
description?: string;
Comment thread
fpfp100 marked this conversation as resolved.
}

Expand All @@ -80,8 +80,8 @@ export interface AgentRequest {
/** Optional session identifier for grouping related requests */
sessionId?: string;

/** Optional metadata about the source of the invocation */
sourceMetadata?: SourceMetadata;
/** Optional channel for the invocation */
channel?: Channel;
}

/**
Expand Down
Loading
Loading