Skip to content

Commit 320de0a

Browse files
Copilotzhichli
andauthored
Replace hardcoded gen_ai string constants with OTel semantic-conventions library imports
- genAiAttributes.ts: Import ATTR_GEN_AI_* from @opentelemetry/semantic-conventions/incubating and ATTR_ERROR_TYPE/ATTR_SERVER_* from @opentelemetry/semantic-conventions stable. Replace all hardcoded gen_ai.* strings in GenAiAttr, GenAiOperationName, GenAiProviderName, GenAiTokenType, and StdAttr with OTel library constants. Keep custom attributes (cache, reasoning, agent.version) that are not yet standardized. - genAiEvents.ts: Use ATTR_EVENT_NAME and EVENT_GEN_AI_CLIENT_INFERENCE_OPERATION_DETAILS from OTel library instead of hardcoded strings. - genAiMetrics.ts: Use METRIC_GEN_AI_CLIENT_OPERATION_DURATION and METRIC_GEN_AI_CLIENT_TOKEN_USAGE from OTel library. - otelChatDebugLogProvider.ts: Replace hardcoded gen_ai.* attribute strings with GenAiAttr/GenAiOperationName/CopilotChatAttr constants. Agent-Logs-Url: https://github.com/microsoft/vscode-copilot-chat/sessions/aaf7dc17-ec13-4d9a-8507-4acb95edbcbd Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com>
1 parent 4e872ea commit 320de0a

File tree

4 files changed

+116
-58
lines changed

4 files changed

+116
-58
lines changed

src/extension/trajectory/vscode-node/otelChatDebugLogProvider.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as vscode from 'vscode';
77
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
88
import { ILogService } from '../../../platform/log/common/logService';
9+
import { CopilotChatAttr, GenAiAttr, GenAiOperationName } from '../../../platform/otel/common/genAiAttributes';
910
import { IOTelService, type ICompletedSpanData, type ISpanEventData } from '../../../platform/otel/common/otelService';
1011
import { decodeSessionId } from '../../../platform/otel/common/sessionUtils';
1112
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
@@ -41,32 +42,32 @@ function coreEventToSpan(event: vscode.ChatDebugEvent, traceId: string): IComple
4142
};
4243

4344
if (event instanceof vscode.ChatDebugGenericEvent) {
44-
attributes['gen_ai.operation.name'] = 'core_event';
45-
attributes['copilot_chat.debug_name'] = event.name;
45+
attributes[GenAiAttr.OPERATION_NAME] = 'core_event';
46+
attributes[CopilotChatAttr.DEBUG_NAME] = event.name;
4647
if (event.details) { attributes['copilot_chat.event_details'] = event.details; }
4748
if (event.category) { attributes['copilot_chat.event_category'] = event.category; }
4849
attributes['copilot_chat.log_level'] = event.level;
4950
} else if (event instanceof vscode.ChatDebugToolCallEvent) {
50-
attributes['gen_ai.operation.name'] = 'execute_tool';
51-
attributes['gen_ai.tool.name'] = event.toolName;
52-
if (event.input) { attributes['gen_ai.tool.call.arguments'] = event.input; }
53-
if (event.output) { attributes['gen_ai.tool.call.result'] = event.output; }
51+
attributes[GenAiAttr.OPERATION_NAME] = GenAiOperationName.EXECUTE_TOOL;
52+
attributes[GenAiAttr.TOOL_NAME] = event.toolName;
53+
if (event.input) { attributes[GenAiAttr.TOOL_CALL_ARGUMENTS] = event.input; }
54+
if (event.output) { attributes[GenAiAttr.TOOL_CALL_RESULT] = event.output; }
5455
} else if (event instanceof vscode.ChatDebugModelTurnEvent) {
55-
attributes['gen_ai.operation.name'] = 'chat';
56-
if (event.model) { attributes['gen_ai.request.model'] = event.model; }
57-
if (event.inputTokens !== undefined) { attributes['gen_ai.usage.input_tokens'] = event.inputTokens; }
58-
if (event.outputTokens !== undefined) { attributes['gen_ai.usage.output_tokens'] = event.outputTokens; }
56+
attributes[GenAiAttr.OPERATION_NAME] = GenAiOperationName.CHAT;
57+
if (event.model) { attributes[GenAiAttr.REQUEST_MODEL] = event.model; }
58+
if (event.inputTokens !== undefined) { attributes[GenAiAttr.USAGE_INPUT_TOKENS] = event.inputTokens; }
59+
if (event.outputTokens !== undefined) { attributes[GenAiAttr.USAGE_OUTPUT_TOKENS] = event.outputTokens; }
5960
} else {
6061
// Unknown event type — store as generic
61-
attributes['gen_ai.operation.name'] = 'core_event';
62+
attributes[GenAiAttr.OPERATION_NAME] = 'core_event';
6263
}
6364

6465
// Preserve the event ID and parent for hierarchy
6566
const eventId = 'id' in event ? (event as { id?: string }).id : undefined;
6667
const parentEventId = 'parentEventId' in event ? (event as { parentEventId?: string }).parentEventId : undefined;
6768

6869
return {
69-
name: attributes['copilot_chat.debug_name'] as string ?? 'core-event',
70+
name: attributes[CopilotChatAttr.DEBUG_NAME] as string ?? 'core-event',
7071
spanId: eventId ?? id,
7172
traceId,
7273
parentSpanId: parentEventId,

src/platform/otel/common/genAiAttributes.ts

Lines changed: 98 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,60 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import {
7+
ATTR_GEN_AI_AGENT_DESCRIPTION,
8+
ATTR_GEN_AI_AGENT_ID,
9+
ATTR_GEN_AI_AGENT_NAME,
10+
ATTR_GEN_AI_CONVERSATION_ID,
11+
ATTR_GEN_AI_INPUT_MESSAGES,
12+
ATTR_GEN_AI_OPERATION_NAME,
13+
ATTR_GEN_AI_OUTPUT_MESSAGES,
14+
ATTR_GEN_AI_OUTPUT_TYPE,
15+
ATTR_GEN_AI_PROVIDER_NAME,
16+
ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY,
17+
ATTR_GEN_AI_REQUEST_MAX_TOKENS,
18+
ATTR_GEN_AI_REQUEST_MODEL,
19+
ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY,
20+
ATTR_GEN_AI_REQUEST_SEED,
21+
ATTR_GEN_AI_REQUEST_STOP_SEQUENCES,
22+
ATTR_GEN_AI_REQUEST_TEMPERATURE,
23+
ATTR_GEN_AI_REQUEST_TOP_P,
24+
ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
25+
ATTR_GEN_AI_RESPONSE_ID,
26+
ATTR_GEN_AI_RESPONSE_MODEL,
27+
ATTR_GEN_AI_SYSTEM_INSTRUCTIONS,
28+
ATTR_GEN_AI_TOKEN_TYPE,
29+
ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,
30+
ATTR_GEN_AI_TOOL_CALL_ID,
31+
ATTR_GEN_AI_TOOL_CALL_RESULT,
32+
ATTR_GEN_AI_TOOL_DEFINITIONS,
33+
ATTR_GEN_AI_TOOL_DESCRIPTION,
34+
ATTR_GEN_AI_TOOL_NAME,
35+
ATTR_GEN_AI_TOOL_TYPE,
36+
ATTR_GEN_AI_USAGE_INPUT_TOKENS,
37+
ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
38+
GEN_AI_OPERATION_NAME_VALUE_CHAT,
39+
GEN_AI_OPERATION_NAME_VALUE_EMBEDDINGS,
40+
GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL,
41+
GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT,
42+
GEN_AI_PROVIDER_NAME_VALUE_ANTHROPIC,
43+
GEN_AI_PROVIDER_NAME_VALUE_AZURE_AI_OPENAI,
44+
GEN_AI_PROVIDER_NAME_VALUE_OPENAI,
45+
GEN_AI_TOKEN_TYPE_VALUE_INPUT,
46+
GEN_AI_TOKEN_TYPE_VALUE_OUTPUT,
47+
} from '@opentelemetry/semantic-conventions/incubating';
48+
import {
49+
ATTR_ERROR_TYPE,
50+
ATTR_SERVER_ADDRESS,
51+
ATTR_SERVER_PORT,
52+
} from '@opentelemetry/semantic-conventions';
53+
654
// gen_ai.operation.name values
755
export const GenAiOperationName = {
8-
CHAT: 'chat',
9-
INVOKE_AGENT: 'invoke_agent',
10-
EXECUTE_TOOL: 'execute_tool',
11-
EMBEDDINGS: 'embeddings',
56+
CHAT: GEN_AI_OPERATION_NAME_VALUE_CHAT,
57+
INVOKE_AGENT: GEN_AI_OPERATION_NAME_VALUE_INVOKE_AGENT,
58+
EXECUTE_TOOL: GEN_AI_OPERATION_NAME_VALUE_EXECUTE_TOOL,
59+
EMBEDDINGS: GEN_AI_OPERATION_NAME_VALUE_EMBEDDINGS,
1260
/** Extension-specific: standalone markdown content event */
1361
CONTENT_EVENT: 'content_event',
1462
/** Extension-specific: hook command execution */
@@ -17,82 +65,88 @@ export const GenAiOperationName = {
1765

1866
// gen_ai.provider.name values
1967
export const GenAiProviderName = {
68+
/** Extension-specific: GitHub as a provider */
2069
GITHUB: 'github',
21-
OPENAI: 'openai',
22-
ANTHROPIC: 'anthropic',
23-
AZURE_AI_OPENAI: 'azure.ai.openai',
70+
OPENAI: GEN_AI_PROVIDER_NAME_VALUE_OPENAI,
71+
ANTHROPIC: GEN_AI_PROVIDER_NAME_VALUE_ANTHROPIC,
72+
AZURE_AI_OPENAI: GEN_AI_PROVIDER_NAME_VALUE_AZURE_AI_OPENAI,
2473
} as const;
2574

2675
// gen_ai.token.type values
2776
export const GenAiTokenType = {
28-
INPUT: 'input',
29-
OUTPUT: 'output',
77+
INPUT: GEN_AI_TOKEN_TYPE_VALUE_INPUT,
78+
OUTPUT: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT,
3079
} as const;
3180

3281
// gen_ai.tool.type values
3382
export const GenAiToolType = {
3483
FUNCTION: 'function',
84+
/** Extension-specific: VS Code extension tool */
3585
EXTENSION: 'extension',
3686
} as const;
3787

3888
/**
3989
* OTel GenAI semantic convention attribute keys.
90+
* Uses constants from `@opentelemetry/semantic-conventions/incubating` where available.
4091
* @see https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md
4192
*/
4293
export const GenAiAttr = {
4394
// Core
44-
OPERATION_NAME: 'gen_ai.operation.name',
45-
PROVIDER_NAME: 'gen_ai.provider.name',
95+
OPERATION_NAME: ATTR_GEN_AI_OPERATION_NAME,
96+
PROVIDER_NAME: ATTR_GEN_AI_PROVIDER_NAME,
4697

4798
// Request
48-
REQUEST_MODEL: 'gen_ai.request.model',
49-
REQUEST_TEMPERATURE: 'gen_ai.request.temperature',
50-
REQUEST_MAX_TOKENS: 'gen_ai.request.max_tokens',
51-
REQUEST_TOP_P: 'gen_ai.request.top_p',
52-
REQUEST_FREQUENCY_PENALTY: 'gen_ai.request.frequency_penalty',
53-
REQUEST_PRESENCE_PENALTY: 'gen_ai.request.presence_penalty',
54-
REQUEST_SEED: 'gen_ai.request.seed',
55-
REQUEST_STOP_SEQUENCES: 'gen_ai.request.stop_sequences',
99+
REQUEST_MODEL: ATTR_GEN_AI_REQUEST_MODEL,
100+
REQUEST_TEMPERATURE: ATTR_GEN_AI_REQUEST_TEMPERATURE,
101+
REQUEST_MAX_TOKENS: ATTR_GEN_AI_REQUEST_MAX_TOKENS,
102+
REQUEST_TOP_P: ATTR_GEN_AI_REQUEST_TOP_P,
103+
REQUEST_FREQUENCY_PENALTY: ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY,
104+
REQUEST_PRESENCE_PENALTY: ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY,
105+
REQUEST_SEED: ATTR_GEN_AI_REQUEST_SEED,
106+
REQUEST_STOP_SEQUENCES: ATTR_GEN_AI_REQUEST_STOP_SEQUENCES,
56107

57108
// Response
58-
RESPONSE_MODEL: 'gen_ai.response.model',
59-
RESPONSE_ID: 'gen_ai.response.id',
60-
RESPONSE_FINISH_REASONS: 'gen_ai.response.finish_reasons',
109+
RESPONSE_MODEL: ATTR_GEN_AI_RESPONSE_MODEL,
110+
RESPONSE_ID: ATTR_GEN_AI_RESPONSE_ID,
111+
RESPONSE_FINISH_REASONS: ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
61112

62113
// Usage
63-
USAGE_INPUT_TOKENS: 'gen_ai.usage.input_tokens',
64-
USAGE_OUTPUT_TOKENS: 'gen_ai.usage.output_tokens',
114+
USAGE_INPUT_TOKENS: ATTR_GEN_AI_USAGE_INPUT_TOKENS,
115+
USAGE_OUTPUT_TOKENS: ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
116+
/** Custom: not yet standardized in OTel GenAI conventions */
65117
USAGE_CACHE_READ_INPUT_TOKENS: 'gen_ai.usage.cache_read.input_tokens',
118+
/** Custom: not yet standardized in OTel GenAI conventions */
66119
USAGE_CACHE_CREATION_INPUT_TOKENS: 'gen_ai.usage.cache_creation.input_tokens',
67120
/** Custom: reasoning/thinking token count (not yet standardized in GenAI conventions) */
68121
USAGE_REASONING_TOKENS: 'gen_ai.usage.reasoning_tokens',
69122

70123
// Conversation
71-
CONVERSATION_ID: 'gen_ai.conversation.id',
72-
OUTPUT_TYPE: 'gen_ai.output.type',
124+
CONVERSATION_ID: ATTR_GEN_AI_CONVERSATION_ID,
125+
OUTPUT_TYPE: ATTR_GEN_AI_OUTPUT_TYPE,
73126

74127
// Token type (for metrics)
75-
TOKEN_TYPE: 'gen_ai.token.type',
128+
TOKEN_TYPE: ATTR_GEN_AI_TOKEN_TYPE,
76129

77130
// Agent
78-
AGENT_NAME: 'gen_ai.agent.name',
79-
AGENT_ID: 'gen_ai.agent.id',
131+
AGENT_NAME: ATTR_GEN_AI_AGENT_NAME,
132+
AGENT_ID: ATTR_GEN_AI_AGENT_ID,
133+
/** Custom: not yet standardized in OTel GenAI conventions */
80134
AGENT_VERSION: 'gen_ai.agent.version',
81-
AGENT_DESCRIPTION: 'gen_ai.agent.description',
135+
AGENT_DESCRIPTION: ATTR_GEN_AI_AGENT_DESCRIPTION,
82136

83137
// Tool
84-
TOOL_NAME: 'gen_ai.tool.name',
85-
TOOL_TYPE: 'gen_ai.tool.type',
86-
TOOL_CALL_ID: 'gen_ai.tool.call.id',
87-
TOOL_DESCRIPTION: 'gen_ai.tool.description',
88-
TOOL_CALL_ARGUMENTS: 'gen_ai.tool.call.arguments',
89-
TOOL_CALL_RESULT: 'gen_ai.tool.call.result',
138+
TOOL_NAME: ATTR_GEN_AI_TOOL_NAME,
139+
TOOL_TYPE: ATTR_GEN_AI_TOOL_TYPE,
140+
TOOL_CALL_ID: ATTR_GEN_AI_TOOL_CALL_ID,
141+
TOOL_DESCRIPTION: ATTR_GEN_AI_TOOL_DESCRIPTION,
142+
TOOL_CALL_ARGUMENTS: ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,
143+
TOOL_CALL_RESULT: ATTR_GEN_AI_TOOL_CALL_RESULT,
90144

91145
// Content (opt-in)
92-
INPUT_MESSAGES: 'gen_ai.input.messages',
93-
OUTPUT_MESSAGES: 'gen_ai.output.messages',
94-
SYSTEM_INSTRUCTIONS: 'gen_ai.system_instructions',
95-
TOOL_DEFINITIONS: 'gen_ai.tool.definitions',
146+
INPUT_MESSAGES: ATTR_GEN_AI_INPUT_MESSAGES,
147+
OUTPUT_MESSAGES: ATTR_GEN_AI_OUTPUT_MESSAGES,
148+
SYSTEM_INSTRUCTIONS: ATTR_GEN_AI_SYSTEM_INSTRUCTIONS,
149+
TOOL_DEFINITIONS: ATTR_GEN_AI_TOOL_DEFINITIONS,
96150
} as const;
97151

98152
/**
@@ -133,9 +187,10 @@ export const CopilotChatAttr = {
133187

134188
/**
135189
* Standard OTel attributes used alongside GenAI attributes.
190+
* Uses constants from `@opentelemetry/semantic-conventions`.
136191
*/
137192
export const StdAttr = {
138-
ERROR_TYPE: 'error.type',
139-
SERVER_ADDRESS: 'server.address',
140-
SERVER_PORT: 'server.port',
193+
ERROR_TYPE: ATTR_ERROR_TYPE,
194+
SERVER_ADDRESS: ATTR_SERVER_ADDRESS,
195+
SERVER_PORT: ATTR_SERVER_PORT,
141196
} as const;

src/platform/otel/common/genAiEvents.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { ATTR_EVENT_NAME, EVENT_GEN_AI_CLIENT_INFERENCE_OPERATION_DETAILS } from '@opentelemetry/semantic-conventions/incubating';
67
import { GenAiAttr, GenAiOperationName, StdAttr } from './genAiAttributes';
78
import { truncateForOTel } from './messageFormatters';
89
import type { IOTelService } from './otelService';
@@ -30,7 +31,7 @@ export function emitInferenceDetailsEvent(
3031
error?: { type: string; message: string },
3132
): void {
3233
const attributes: Record<string, unknown> = {
33-
'event.name': 'gen_ai.client.inference.operation.details',
34+
[ATTR_EVENT_NAME]: EVENT_GEN_AI_CLIENT_INFERENCE_OPERATION_DETAILS,
3435
[GenAiAttr.OPERATION_NAME]: GenAiOperationName.CHAT,
3536
[GenAiAttr.REQUEST_MODEL]: request.model,
3637
};

src/platform/otel/common/genAiMetrics.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { METRIC_GEN_AI_CLIENT_OPERATION_DURATION, METRIC_GEN_AI_CLIENT_TOKEN_USAGE } from '@opentelemetry/semantic-conventions/incubating';
67
import { GenAiAttr, StdAttr } from './genAiAttributes';
78
import type { IOTelService } from './otelService';
89

@@ -27,7 +28,7 @@ export class GenAiMetrics {
2728
errorType?: string;
2829
},
2930
): void {
30-
otel.recordMetric('gen_ai.client.operation.duration', durationSec, {
31+
otel.recordMetric(METRIC_GEN_AI_CLIENT_OPERATION_DURATION, durationSec, {
3132
[GenAiAttr.OPERATION_NAME]: attrs.operationName,
3233
[GenAiAttr.PROVIDER_NAME]: attrs.providerName,
3334
[GenAiAttr.REQUEST_MODEL]: attrs.requestModel,
@@ -50,7 +51,7 @@ export class GenAiMetrics {
5051
serverAddress?: string;
5152
},
5253
): void {
53-
otel.recordMetric('gen_ai.client.token.usage', tokenCount, {
54+
otel.recordMetric(METRIC_GEN_AI_CLIENT_TOKEN_USAGE, tokenCount, {
5455
[GenAiAttr.OPERATION_NAME]: attrs.operationName,
5556
[GenAiAttr.PROVIDER_NAME]: attrs.providerName,
5657
[GenAiAttr.TOKEN_TYPE]: tokenType,

0 commit comments

Comments
 (0)