diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5a86a3..dd7c36a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## [Unreleased] +## [0.1.0-alpha.5] - 2026-04-24 ### Breaking Changes - Remove Azure Functions auto-instrumentation support from this package. The `instrumentationOptions.azureFunctions` option is no longer available. diff --git a/MIGRATION_A365.md b/MIGRATION_A365.md index 32440f0b..a9c3f7f0 100644 --- a/MIGRATION_A365.md +++ b/MIGRATION_A365.md @@ -126,6 +126,22 @@ useMicrosoftOpenTelemetry({ }); ``` +### A365 Configuration Coverage + +All A365 observability options are available through `a365`: + +| Option | Type | Default | Notes | +|---|---|---|---| +| `enabled` | `boolean` | `false` | Enables A365 exporter path | +| `tokenResolver` | `(agentId, tenantId, authScopes?) => string \| Promise` | — | Required when exporting to A365 | +| `clusterCategory` | `ClusterCategory` | `"prod"` | Same category values as Agent365-nodejs | +| `domainOverride` | `string` | — | Optional endpoint override (applied by exporter) | +| `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | Passed to `tokenResolver` as the third argument | +| `perRequestExport` | `boolean` | `false` | Export per trace when root span completes | +| `baggage.propagationEnabled` | `boolean` | `true` | Controls baggage middleware auto-registration when hosting is enabled | +| `baggage.enrichSpans` | `boolean` | `true` | Copy baggage values onto span attributes via `A365SpanProcessor` | +| `hosting.enabled` | `boolean` | `false` | Enables hosting middleware auto-registration when `hosting.adapter` is provided | + ## Environment Variables Environment variable names are **unchanged** from Agent365-nodejs: @@ -167,6 +183,43 @@ useMicrosoftOpenTelemetry({ > `SpanProcessor` from `@opentelemetry/sdk-trace-base` (e.g. `BatchSpanProcessor`, > `SimpleSpanProcessor`) or a custom implementation. +### Logging Level Configuration + +During migration, these environment variables control SDK diagnostics: + +| Environment Variable | Values | Behavior | +|---|---|---| +| `APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` | `ALL`, `VERBOSE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE` | Primary switch for OpenTelemetry diagnostics; also maps Azure logger levels for `VERBOSE`, `INFO`, `WARN`, `ERROR` | +| `OTEL_LOG_LEVEL` | `ALL`, `VERBOSE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE` | Used when `APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` is not set | +| `AZURE_LOG_LEVEL` | `verbose`, `info`, `warning`, `error` | Controls Azure logger level when the App Insights logging level variable is not mapped/absent | + +Example: + +```bash +set APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL=INFO +``` + +### Console Exporters During Migration + +You can keep local visibility while migrating by using console exporters. + +```typescript +useMicrosoftOpenTelemetry({ + a365: { + enabled: true, + tokenResolver: async (agentId, tenantId) => getToken(agentId, tenantId), + }, + enableConsoleExporters: true, // traces + metrics + logs to console +}); +``` + +Behavior summary: + +- `enableConsoleExporters: true`: always adds console exporters for traces, metrics, and logs. +- `enableConsoleExporters: false`: disables automatic console exporters. +- If **no** Azure Monitor/OTLP/A365 exporter is active, console exporters are auto-enabled. +- If `a365` options are provided but A365 is disabled (`a365.enabled` false/omitted), a span console exporter is added as a fallback so spans are still visible locally. + ## Scopes Scope usage is identical. Just update the import path: @@ -246,6 +299,26 @@ runWithExportToken(initialToken, async () => { }); ``` +## Hosting Middleware and Utilities + +If you previously used hosting helpers with Agent365, they are also exported from `@microsoft/opentelemetry`: + +- `BaggageMiddleware` +- `OutputLoggingMiddleware` +- `ObservabilityHostingManager` +- `BaggageBuilderUtils` +- `ScopeUtils` + +Use the same APIs with updated imports: + +```typescript +import { + BaggageMiddleware, + OutputLoggingMiddleware, + ObservabilityHostingManager, +} from "@microsoft/opentelemetry"; +``` + ## What's Not Migrated The following Agent365-nodejs components are **not** included in `@microsoft/opentelemetry` because they are runtime/hosting concerns rather than observability: @@ -267,4 +340,6 @@ The following Agent365-nodejs components are **not** included in `@microsoft/ope - [ ] Rename `SpanDetails` type references to `A365SpanDetails` - [ ] Rename `SpanProcessor` references to `A365SpanProcessor` - [ ] Verify environment variables work (names are unchanged) +- [ ] Set diagnostic logging level (`APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` or `OTEL_LOG_LEVEL`) for migration validation +- [ ] Decide whether to force console exporters (`enableConsoleExporters`) during rollout/debugging - [ ] Remove `@microsoft/agents-a365-runtime` dependency if no longer needed diff --git a/README.md b/README.md index aa7453e3..a9488659 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,17 @@ That's it — traces, metrics, and logs are collected automatically with built-i | `views` | `ViewOptions[]` | — | Metric views | | `azureMonitor` | `AzureMonitorOpenTelemetryOptions` | — | Azure Monitor backend config. When provided, Azure Monitor export is enabled | | `a365` | `A365Options` | — | A365 observability config | +| `enableConsoleExporters` | `boolean` | auto | Enable console exporters for traces, metrics, and logs | ### `InstrumentationOptions` -Most instrumentations are enabled by default. Pass `{ enabled: false }` to disable individual instrumentations, or provide an `InstrumentationConfig` object to customize them. +Most instrumentations use `InstrumentationConfig` shape (`{ enabled?: boolean, ... }`). + +- Built-in infra instrumentations (`http`, `azureSdk`, `azureFunctions`, `mongoDb`, `mySql`, `postgreSql`, `redis`, `redis4`) are enabled by default. +- Logging instrumentations (`bunyan`, `winston`) are disabled by default. +- GenAI instrumentations (`openaiAgents`, `langchain`) are enabled by default. + +Set `enabled: true` or `enabled: false` explicitly for predictable behavior. | Key | Type | Default | Description | |---|---|---|---| @@ -84,8 +91,67 @@ Most instrumentations are enabled by default. Pass `{ enabled: false }` to disab | `redis4` | `InstrumentationConfig` | enabled | Redis 4 instrumentation | | `bunyan` | `InstrumentationConfig` | disabled | Bunyan log instrumentation | | `winston` | `InstrumentationConfig` | disabled | Winston log instrumentation | -| `openaiAgents` | `boolean | OpenAIAgentsInstrumentationConfig` | disabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | -| `langchain` | `boolean | LangChainInstrumentationConfig` | disabled | LangChain instrumentation (requires `@langchain/core`) | +| `openaiAgents` | `OpenAIAgentsInstrumentationConfig` | enabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | +| `langchain` | `LangChainInstrumentationConfig` | enabled | LangChain instrumentation (requires `@langchain/core`) | + +#### Turn instrumentations on/off + +```typescript +useMicrosoftOpenTelemetry({ + instrumentationOptions: { + // Disable specific built-in instrumentations + http: { enabled: false }, + redis: { enabled: false }, + + // Enable GenAI instrumentations + openaiAgents: { + enabled: true, + isContentRecordingEnabled: true, + }, + langchain: { + enabled: true, + isContentRecordingEnabled: true, + }, + }, +}); +``` + +Disable most built-in auto-instrumentation: + +```typescript +useMicrosoftOpenTelemetry({ + instrumentationOptions: { + http: { enabled: false }, + azureSdk: { enabled: false }, + azureFunctions: { enabled: false }, + mongoDb: { enabled: false }, + mySql: { enabled: false }, + postgreSql: { enabled: false }, + redis: { enabled: false }, + redis4: { enabled: false }, + bunyan: { enabled: false }, + winston: { enabled: false }, + openaiAgents: { enabled: false }, + langchain: { enabled: false }, + }, +}); +``` + +### Console exporters + +Use console exporters when validating local telemetry or debugging setup. + +```typescript +useMicrosoftOpenTelemetry({ + enableConsoleExporters: true, +}); +``` + +Behavior: + +- `enableConsoleExporters: true`: always enable console exporters (traces, metrics, logs). +- `enableConsoleExporters: false`: do not auto-add the standard console exporters, except for the A365 span console fallback when `a365` options are provided but `a365.enabled` is `false` or omitted. +- Omitted: console exporters auto-enable only when no other exporter path is active; if `a365` options are provided but `a365.enabled` is `false` or omitted, the A365 span console fallback can still be added. ### `azureMonitor` options @@ -109,7 +175,7 @@ See the [OpenTelemetry OTLP Exporter specification](https://opentelemetry.io/doc | Option | Type | Default | Description | |---|---|---|---| | `enabled` | `boolean` | `false` | Enable A365 observability export | -| `tokenResolver` | `(agentId, tenantId) => string \| Promise` | — | Token resolver for A365 service authentication | +| `tokenResolver` | `(agentId, tenantId, authScopes?) => string \| Promise` | — | Token resolver for A365 service authentication | | `clusterCategory` | `ClusterCategory` | `"prod"` | Cluster category for endpoint resolution (`local`, `dev`, `test`, `preprod`, `firstrelease`, `prod`, `gov`, `high`, `dod`, `mooncake`, `ex`, `rx`) | | `domainOverride` | `string` | — | Override the A365 observability service domain | | `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | OAuth scopes for A365 service authentication | @@ -128,6 +194,8 @@ See the [OpenTelemetry OTLP Exporter specification](https://opentelemetry.io/doc | Option | Type | Default | Description | |---|---|---|---| | `enabled` | `boolean` | `false` | Enable hosting middleware integration (baggage middleware, output logging, etc.) | +| `adapter` | `{ use(...middlewares): void }` | — | Adapter instance where middleware is auto-registered when `enabled` is true | +| `enableOutputLogging` | `boolean` | `true` | Enable output logging middleware auto-registration | #### A365 environment variables diff --git a/samples/src/a365Export.ts b/samples/src/a365Export.ts index fa948d27..ac74728f 100644 --- a/samples/src/a365Export.ts +++ b/samples/src/a365Export.ts @@ -83,9 +83,9 @@ async function main(): Promise { // A365 observability export configuration a365: { - enabled: true, // turn on the Agent365 exporter - tokenResolver: myTokenResolver, // called per-export with (agentId, tenantId) - clusterCategory: "dev", // target cluster: dev | test | preprod | prod | … + enabled: true, // turn on the Agent365 exporter + tokenResolver: myTokenResolver, // called per-export with (agentId, tenantId) + clusterCategory: "dev", // target cluster: dev | test | preprod | prod | … }, }); diff --git a/samples/src/a365HostingMiddleware.ts b/samples/src/a365HostingMiddleware.ts index b1e5ca06..41e4497c 100644 --- a/samples/src/a365HostingMiddleware.ts +++ b/samples/src/a365HostingMiddleware.ts @@ -85,7 +85,13 @@ function createMockTurnContext(): TurnContextLike { }; const turnState = new Map(); - const sendHandlers: Array<(ctx: TurnContextLike, activities: ActivityLike[], next: () => Promise) => Promise> = []; + const sendHandlers: Array< + ( + ctx: TurnContextLike, + activities: ActivityLike[], + next: () => Promise, + ) => Promise + > = []; return { activity, @@ -104,7 +110,8 @@ function createMockTurnContext(): TurnContextLike { let chain = sendNext; for (const h of [...sendHandlers].reverse()) { const prev = chain; - chain = () => h(this as unknown as TurnContextLike, outgoing, prev as () => Promise); + chain = () => + h(this as unknown as TurnContextLike, outgoing, prev as () => Promise); } await chain(); }, @@ -178,7 +185,9 @@ async function demoOutputLoggingMiddleware(): Promise { console.log("\n=== Demo 3: OutputLoggingMiddleware ===\n"); const middleware = new OutputLoggingMiddleware(); - const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise }; + const ctx = createMockTurnContext() as TurnContextLike & { + sendActivity(text: string): Promise; + }; // Set the auth token so the middleware can derive agent details ctx.turnState.set(A365_AUTH_TOKEN_KEY, ""); @@ -242,7 +251,9 @@ async function demoScopeUtils(): Promise { ctx, authToken, ); - console.log(` InferenceScope created from TurnContext (traceId: ${inferenceScope.getSpanContext().traceId})`); + console.log( + ` InferenceScope created from TurnContext (traceId: ${inferenceScope.getSpanContext().traceId})`, + ); console.log(" Input messages from activity.text were auto-recorded."); // Simulate response @@ -262,7 +273,9 @@ async function demoScopeUtils(): Promise { async function demoFullAgentTurn(): Promise { console.log("\n=== Demo 5: Full Agent Turn with Middleware ===\n"); - const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise }; + const ctx = createMockTurnContext() as TurnContextLike & { + sendActivity(text: string): Promise; + }; const authToken = ""; // Register middleware @@ -297,7 +310,11 @@ async function demoFullAgentTurn(): Promise { // LLM inference const inference = ScopeUtils.populateInferenceScopeFromTurnContext( - { operationName: InferenceOperationType.CHAT, model: "gpt-4o", providerName: "azure-openai" }, + { + operationName: InferenceOperationType.CHAT, + model: "gpt-4o", + providerName: "azure-openai", + }, ctx, authToken, ); diff --git a/samples/src/a365ManualScopes.ts b/samples/src/a365ManualScopes.ts index f18809d2..9f84ca07 100644 --- a/samples/src/a365ManualScopes.ts +++ b/samples/src/a365ManualScopes.ts @@ -129,10 +129,7 @@ async function callLLM( const scope = InferenceScope.start(request, details, agentDetails); try { // Record what we sent to the LLM - scope.recordInputMessages([ - "You are a helpful weather assistant.", - request.content as string, - ]); + scope.recordInputMessages(["You are a helpful weather assistant.", request.content as string]); // Simulate LLM response latency await new Promise((r) => setTimeout(r, 50)); @@ -212,10 +209,7 @@ async function executeTool( * - Records the tool result as input, the natural-language answer as output * - Records token counts and a "stop" finish reason */ -async function formatResponse( - request: A365Request, - toolResult: string, -): Promise { +async function formatResponse(request: A365Request, toolResult: string): Promise { const details: InferenceDetails = { operationName: InferenceOperationType.CHAT, model: "gpt-4o", @@ -248,11 +242,7 @@ async function formatResponse( /** Record the final streamed output with `OutputScope`. */ function recordOutput(request: A365Request, answer: string): void { - const scope = OutputScope.start( - request, - { messages: [answer] }, - agentDetails, - ); + const scope = OutputScope.start(request, { messages: [answer] }, agentDetails); scope.dispose(); } @@ -284,7 +274,12 @@ function demonstrateContextPropagation(): void { const scope = InvokeAgentScope.start( { conversationId: "cross-service-conv" }, { endpoint: { host: "service-b.internal", port: 8080 } }, - { ...agentDetails, agentId: "downstream-agent", agentName: "DownstreamBot", tenantId: "contoso-tenant-id" }, + { + ...agentDetails, + agentId: "downstream-agent", + agentName: "DownstreamBot", + tenantId: "contoso-tenant-id", + }, ); console.log(" Created child span in Service B, traceId:", scope.getSpanContext().traceId); scope.recordResponse("Handled by downstream agent"); @@ -324,19 +319,14 @@ async function main(): Promise { console.log("=== A365 Manual Telemetry Scopes Demo ===\n"); // 1️⃣ InvokeAgentScope — wraps the entire agent invocation - const invokeScope = InvokeAgentScope.start( - request, - {}, - agentDetails, - { - userDetails: { - userId: "user-jane-doe", - userName: "Jane Doe", - userEmail: "jane@contoso.com", - tenantId: "contoso-tenant-id", - }, + const invokeScope = InvokeAgentScope.start(request, {}, agentDetails, { + userDetails: { + userId: "user-jane-doe", + userName: "Jane Doe", + userEmail: "jane@contoso.com", + tenantId: "contoso-tenant-id", }, - ); + }); try { console.log("1. InvokeAgentScope started"); @@ -345,7 +335,9 @@ async function main(): Promise { // 2️⃣ InferenceScope — first LLM call (decides to use a tool) console.log("2. Calling LLM (InferenceScope)..."); const toolCall = await callLLM(request, invokeScope); - console.log(` LLM wants to call tool: ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`); + console.log( + ` LLM wants to call tool: ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`, + ); // 3️⃣ ExecuteToolScope — run the tool console.log("3. Executing tool (ExecuteToolScope)..."); diff --git a/samples/src/langchainInstrumentation.ts b/samples/src/langchainInstrumentation.ts index 6234b073..e8350411 100644 --- a/samples/src/langchainInstrumentation.ts +++ b/samples/src/langchainInstrumentation.ts @@ -23,6 +23,7 @@ async function main(): Promise { }, instrumentationOptions: { langchain: { + enabled: true, isContentRecordingEnabled: true, }, }, diff --git a/samples/src/openaiInstrumentation.ts b/samples/src/openaiInstrumentation.ts index 0635974b..da9790e2 100644 --- a/samples/src/openaiInstrumentation.ts +++ b/samples/src/openaiInstrumentation.ts @@ -22,6 +22,7 @@ async function main(): Promise { }, instrumentationOptions: { openaiAgents: { + enabled: true, isContentRecordingEnabled: true, }, }, diff --git a/src/a365/configuration/A365Configuration.ts b/src/a365/configuration/A365Configuration.ts index 44c38478..3ad2afab 100644 --- a/src/a365/configuration/A365Configuration.ts +++ b/src/a365/configuration/A365Configuration.ts @@ -5,7 +5,6 @@ import type { A365Options, ClusterCategory, A365BaggageOptions, - A365HostingOptions, } from "./A365ConfigurationOptions.js"; import { Logger } from "../../shared/logging/index.js"; @@ -70,7 +69,11 @@ export class A365Configuration { public readonly enabled: boolean; /** Token resolver callback for A365 service authentication. */ - public readonly tokenResolver?: (agentId: string, tenantId: string) => string | Promise; + public readonly tokenResolver?: ( + agentId: string, + tenantId: string, + authScopes?: string[], + ) => string | Promise; /** Cluster category. */ public readonly clusterCategory: ClusterCategory; @@ -85,7 +88,11 @@ export class A365Configuration { public readonly baggage: Required; /** Hosting options. */ - public readonly hosting: Required; + public readonly hosting: { + enabled: boolean; + adapter?: { use(...middlewares: unknown[]): void }; + enableOutputLogging: boolean; + }; constructor(options?: A365Options) { // 1. Set defaults @@ -139,6 +146,8 @@ export class A365Configuration { this.hosting = { enabled: options?.hosting?.enabled ?? false, + adapter: options?.hosting?.adapter, + enableOutputLogging: options?.hosting?.enableOutputLogging ?? true, }; // Warn when A365-scoped options are set but A365 is not enabled diff --git a/src/a365/configuration/A365ConfigurationOptions.ts b/src/a365/configuration/A365ConfigurationOptions.ts index e6218acc..a9befd02 100644 --- a/src/a365/configuration/A365ConfigurationOptions.ts +++ b/src/a365/configuration/A365ConfigurationOptions.ts @@ -31,10 +31,14 @@ export interface A365Options { /** * Token resolver for authenticating with the A365 observability service. - * Called with (agentId, tenantId) extracted from span attributes. + * Called with (agentId, tenantId, authScopes) extracted from span attributes/config. * Must return a bearer token string or a promise resolving to one. */ - tokenResolver?: (agentId: string, tenantId: string) => string | Promise; + tokenResolver?: ( + agentId: string, + tenantId: string, + authScopes?: string[], + ) => string | Promise; /** Cluster category for the A365 service endpoint. */ clusterCategory?: ClusterCategory; @@ -67,4 +71,16 @@ export interface A365HostingOptions { * Requires `@microsoft/agents-hosting` as an optional peer dependency. */ enabled?: boolean; + + /** + * Adapter instance where hosting middleware will be auto-registered. + * Must expose a `use(...middlewares)` method compatible with agents-hosting adapters. + */ + adapter?: { use(...middlewares: unknown[]): void }; + + /** + * Enable output logging middleware auto-registration when hosting is enabled. + * @default true + */ + enableOutputLogging?: boolean; } diff --git a/src/a365/exporter/Agent365Exporter.ts b/src/a365/exporter/Agent365Exporter.ts index f0b65c12..ea039f31 100644 --- a/src/a365/exporter/Agent365Exporter.ts +++ b/src/a365/exporter/Agent365Exporter.ts @@ -159,7 +159,7 @@ export class Agent365Exporter implements SpanExporter { private async resolveToken(agentId: string, tenantId: string): Promise { if (!this.options.tokenResolver) return null; - const result = this.options.tokenResolver(agentId, tenantId); + const result = this.options.tokenResolver(agentId, tenantId, this.options.authScopes); return result instanceof Promise ? result : result; } diff --git a/src/a365/exporter/Agent365ExporterOptions.ts b/src/a365/exporter/Agent365ExporterOptions.ts index 419071d1..79514c42 100644 --- a/src/a365/exporter/Agent365ExporterOptions.ts +++ b/src/a365/exporter/Agent365ExporterOptions.ts @@ -10,6 +10,7 @@ import type { ClusterCategory } from "../configuration/A365ConfigurationOptions. export type TokenResolver = ( agentId: string, tenantId: string, + authScopes?: string[], ) => string | null | Promise; /** @@ -28,6 +29,9 @@ export interface Agent365ExporterOptions { /** Override the A365 observability service domain. */ domainOverride?: string; + /** OAuth scopes used during token resolution. */ + authScopes?: string[]; + /** Maximum span queue size before drops occur. @default 2048 */ maxQueueSize?: number; @@ -50,6 +54,7 @@ export class ResolvedExporterOptions { public readonly tokenResolver?: TokenResolver; public readonly useS2SEndpoint: boolean; public readonly domainOverride?: string; + public readonly authScopes: string[]; public readonly maxQueueSize: number; public readonly scheduledDelayMilliseconds: number; public readonly exporterTimeoutMilliseconds: number; @@ -61,6 +66,7 @@ export class ResolvedExporterOptions { this.tokenResolver = options?.tokenResolver; this.useS2SEndpoint = options?.useS2SEndpoint ?? false; this.domainOverride = options?.domainOverride; + this.authScopes = options?.authScopes ?? ["https://api.powerplatform.com/.default"]; this.maxQueueSize = options?.maxQueueSize ?? 2048; this.scheduledDelayMilliseconds = options?.scheduledDelayMilliseconds ?? 5000; this.exporterTimeoutMilliseconds = options?.exporterTimeoutMilliseconds ?? 90000; diff --git a/src/distro/distro.ts b/src/distro/distro.ts index c5e1e670..06e7c227 100644 --- a/src/distro/distro.ts +++ b/src/distro/distro.ts @@ -30,9 +30,15 @@ import { } from "../azureMonitor/index.js"; import { isOtlpEnabled, createOtlpComponents } from "../otlp/index.js"; import { A365Configuration, Agent365Exporter, A365SpanProcessor } from "../a365/index.js"; -import type { MicrosoftOpenTelemetryOptions } from "../types.js"; +import type { + MicrosoftOpenTelemetryOptions, + InstrumentationOptions, + OpenAIAgentsInstrumentationConfig, + LangChainInstrumentationConfig, +} from "../types.js"; import { MICROSOFT_OPENTELEMETRY_VERSION } from "../types.js"; import { createInstrumentations, createSampler, createViews } from "./instrumentations.js"; +import { Logger } from "../shared/logging/index.js"; process.env["AZURE_MONITOR_DISTRO_VERSION"] = AZURE_MONITOR_OPENTELEMETRY_VERSION; process.env["MICROSOFT_OPENTELEMETRY_VERSION"] = MICROSOFT_OPENTELEMETRY_VERSION; @@ -56,6 +62,7 @@ let disposeAzureMonitor: (() => void) | undefined; export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOptions): void { const config = new InternalConfig(options); patchOpenTelemetryInstrumentationEnable(); + initializeGenAIInstrumentations(options?.instrumentationOptions); // Azure Monitor is enabled when configured programmatically or via JSON config. // An explicit `enabled: false` always wins, even if a connection string is present. @@ -141,6 +148,7 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption const a365Exporter = new Agent365Exporter({ clusterCategory: a365Config.clusterCategory, domainOverride: a365Config.domainOverride, + authScopes: a365Config.authScopes, tokenResolver: a365Config.tokenResolver, }); // A365SpanProcessor copies baggage (tenant, agent, session, etc.) to span attributes @@ -213,6 +221,7 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption */ export function shutdownMicrosoftOpenTelemetry(): Promise { disposeAzureMonitor?.(); + void resetGenAIInstrumentations(); return sdk?.shutdown(); } @@ -224,3 +233,67 @@ export function shutdownMicrosoftOpenTelemetry(): Promise { export function _getSdkInstance(): NodeSDK | undefined { return sdk; } + +function initializeGenAIInstrumentations(options?: InstrumentationOptions): void { + const openAIOptions = options?.openaiAgents; + if (openAIOptions && openAIOptions.enabled !== false) { + void initializeOpenAIAgentsInstrumentation(openAIOptions); + } + + const langChainOptions = options?.langchain; + if (langChainOptions && langChainOptions.enabled !== false) { + void initializeLangChainInstrumentation(langChainOptions); + } +} + +async function initializeOpenAIAgentsInstrumentation( + options: OpenAIAgentsInstrumentationConfig, +): Promise { + try { + const { OpenAIAgentsTraceInstrumentor } = + await import("../genai/instrumentations/openai/openAIAgentsTraceInstrumentor.js"); + OpenAIAgentsTraceInstrumentor.instrument(options); + } catch (error) { + Logger.getInstance().warn( + "[GenAI] Failed to initialize OpenAI Agents instrumentation. " + + "Ensure @openai/agents is installed when openaiAgents config is enabled.", + error, + ); + } +} + +async function initializeLangChainInstrumentation( + options: LangChainInstrumentationConfig, +): Promise { + try { + const [{ LangChainTraceInstrumentor }, callbackManagerModule] = await Promise.all([ + import("../genai/instrumentations/langchain/langchainTraceInstrumentor.js"), + import("@langchain/core/callbacks/manager"), + ]); + LangChainTraceInstrumentor.instrument(callbackManagerModule, options); + } catch (error) { + Logger.getInstance().warn( + "[GenAI] Failed to initialize LangChain instrumentation. " + + "Ensure @langchain/core is installed when langchain config is enabled.", + error, + ); + } +} + +async function resetGenAIInstrumentations(): Promise { + try { + const { OpenAIAgentsTraceInstrumentor } = + await import("../genai/instrumentations/openai/openAIAgentsTraceInstrumentor.js"); + OpenAIAgentsTraceInstrumentor.resetInstance(); + } catch { + // Ignore when optional dependency is not installed. + } + + try { + const { LangChainTraceInstrumentor } = + await import("../genai/instrumentations/langchain/langchainTraceInstrumentor.js"); + LangChainTraceInstrumentor.resetInstance(); + } catch { + // Ignore when optional dependency is not installed. + } +} diff --git a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts index 7be586fa..8ce25fa8 100644 --- a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts +++ b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts @@ -6,29 +6,12 @@ import { diag, trace, Tracer } from "@opentelemetry/api"; import { InstrumentationBase, - InstrumentationConfig, InstrumentationModuleDefinition, } from "@opentelemetry/instrumentation"; import { setTraceProcessors, setTracingDisabled, TracingProcessor } from "@openai/agents"; +import type { OpenAIAgentsInstrumentationConfig } from "../../../types.js"; import { OpenAIAgentsTraceProcessor } from "./openAIAgentsTraceProcessor.js"; -/** - * Configuration options for the OpenAI Agents instrumentor. - */ -export interface OpenAIAgentsInstrumentationConfig extends InstrumentationConfig { - /** - * When true, the gen_ai.input.messages attribute containing LLM input - * messages will be suppressed and not attached to spans in InvokeAgent scopes. - * @default false - */ - suppressInvokeAgentInput?: boolean; - /** - * Whether to enable content recording (input/output messages, tool args, etc.). - * @default false - */ - isContentRecordingEnabled?: boolean; -} - /** * Internal singleton implementation. */ @@ -48,7 +31,10 @@ class OpenAIAgentsTraceInstrumentorImpl extends InstrumentationBase