Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/otel/common/genAiEvents.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { GenAiAttr, GenAiOperationName, StdAttr } from './genAiAttributes';
7
import { normalizeProviderMessages, toSystemInstructions, truncateForOTel } from './messageFormatters';
8
import type { IOTelService } from './otelService';
9
import { type WorkspaceOTelMetadata, workspaceMetadataToOTelAttributes } from './workspaceOTelMetadata';
10
11
/**
12
* Emit OTel GenAI standard events via the IOTelService abstraction.
13
*/
14
export function emitInferenceDetailsEvent(
15
otel: IOTelService,
16
request: {
17
model: string;
18
temperature?: number;
19
maxTokens?: number;
20
messages?: unknown;
21
systemMessage?: unknown;
22
tools?: unknown;
23
},
24
response: {
25
id?: string;
26
model?: string;
27
finishReasons?: string[];
28
inputTokens?: number;
29
outputTokens?: number;
30
} | undefined,
31
error?: { type: string; message: string },
32
): void {
33
const attributes: Record<string, unknown> = {
34
'event.name': 'gen_ai.client.inference.operation.details',
35
[GenAiAttr.OPERATION_NAME]: GenAiOperationName.CHAT,
36
[GenAiAttr.REQUEST_MODEL]: request.model,
37
};
38
39
if (response) {
40
if (response.model) { attributes[GenAiAttr.RESPONSE_MODEL] = response.model; }
41
if (response.id) { attributes[GenAiAttr.RESPONSE_ID] = response.id; }
42
if (response.finishReasons) { attributes[GenAiAttr.RESPONSE_FINISH_REASONS] = response.finishReasons; }
43
if (response.inputTokens !== undefined) { attributes[GenAiAttr.USAGE_INPUT_TOKENS] = response.inputTokens; }
44
if (response.outputTokens !== undefined) { attributes[GenAiAttr.USAGE_OUTPUT_TOKENS] = response.outputTokens; }
45
}
46
47
if (request.temperature !== undefined) { attributes[GenAiAttr.REQUEST_TEMPERATURE] = request.temperature; }
48
if (request.maxTokens !== undefined) { attributes[GenAiAttr.REQUEST_MAX_TOKENS] = request.maxTokens; }
49
50
if (error) {
51
attributes[StdAttr.ERROR_TYPE] = error.type;
52
}
53
54
// Full content capture with truncation to prevent OTLP batch failures
55
// Normalize to OTel GenAI semantic convention format
56
if (otel.config.captureContent) {
57
if (request.messages !== undefined) {
58
const msgs = Array.isArray(request.messages) ? request.messages as ReadonlyArray<Record<string, unknown>> : undefined;
59
attributes[GenAiAttr.INPUT_MESSAGES] = truncateForOTel(JSON.stringify(
60
msgs ? normalizeProviderMessages(msgs) : request.messages
61
));
62
}
63
if (request.systemMessage !== undefined) {
64
const systemText = typeof request.systemMessage === 'string'
65
? request.systemMessage
66
: JSON.stringify(request.systemMessage);
67
const systemInstructions = toSystemInstructions(systemText);
68
if (systemInstructions !== undefined) {
69
attributes[GenAiAttr.SYSTEM_INSTRUCTIONS] = truncateForOTel(JSON.stringify(systemInstructions));
70
}
71
}
72
if (request.tools !== undefined) {
73
attributes[GenAiAttr.TOOL_DEFINITIONS] = truncateForOTel(JSON.stringify(request.tools));
74
}
75
}
76
77
otel.emitLogRecord(`GenAI inference: ${request.model}`, attributes);
78
}
79
80
/**
81
* Emit extension-specific events.
82
*/
83
export function emitSessionStartEvent(
84
otel: IOTelService,
85
sessionId: string,
86
model: string,
87
participant: string,
88
): void {
89
otel.emitLogRecord('copilot_chat.session.start', {
90
'event.name': 'copilot_chat.session.start',
91
'session.id': sessionId,
92
[GenAiAttr.REQUEST_MODEL]: model,
93
[GenAiAttr.AGENT_NAME]: participant,
94
});
95
}
96
97
export function emitToolCallEvent(
98
otel: IOTelService,
99
toolName: string,
100
durationMs: number,
101
success: boolean,
102
error?: string,
103
): void {
104
otel.emitLogRecord(`copilot_chat.tool.call: ${toolName}`, {
105
'event.name': 'copilot_chat.tool.call',
106
[GenAiAttr.TOOL_NAME]: toolName,
107
'duration_ms': durationMs,
108
'success': success,
109
...(error ? { [StdAttr.ERROR_TYPE]: error } : {}),
110
});
111
}
112
113
export function emitAgentTurnEvent(
114
otel: IOTelService,
115
turnIndex: number,
116
inputTokens: number,
117
outputTokens: number,
118
toolCallCount: number,
119
): void {
120
otel.emitLogRecord(`copilot_chat.agent.turn: ${turnIndex}`, {
121
'event.name': 'copilot_chat.agent.turn',
122
'turn.index': turnIndex,
123
[GenAiAttr.USAGE_INPUT_TOKENS]: inputTokens,
124
[GenAiAttr.USAGE_OUTPUT_TOKENS]: outputTokens,
125
'tool_call_count': toolCallCount,
126
});
127
}
128
129
// ── Agent Activity & Outcome Events ──
130
131
export function emitEditFeedbackEvent(
132
otel: IOTelService,
133
outcome: string,
134
languageId: string,
135
participant: string,
136
requestId: string,
137
editSurface: string,
138
hasRemainingEdits: boolean,
139
isNotebook: boolean,
140
workspace?: WorkspaceOTelMetadata,
141
): void {
142
otel.emitLogRecord(`copilot_chat.edit.feedback: ${outcome}`, {
143
'event.name': 'copilot_chat.edit.feedback',
144
'outcome': outcome,
145
'language_id': languageId,
146
'participant': participant,
147
'request_id': requestId,
148
'edit_surface': editSurface,
149
'has_remaining_edits': hasRemainingEdits,
150
'is_notebook': isNotebook,
151
...workspaceMetadataToOTelAttributes(workspace),
152
});
153
}
154
155
export function emitEditHunkActionEvent(
156
otel: IOTelService,
157
outcome: string,
158
languageId: string,
159
requestId: string,
160
lineCount: number,
161
linesAdded: number,
162
linesRemoved: number,
163
workspace?: WorkspaceOTelMetadata,
164
): void {
165
otel.emitLogRecord(`copilot_chat.edit.hunk.action: ${outcome}`, {
166
'event.name': 'copilot_chat.edit.hunk.action',
167
'outcome': outcome,
168
'language_id': languageId,
169
'request_id': requestId,
170
'line_count': lineCount,
171
'lines_added': linesAdded,
172
'lines_removed': linesRemoved,
173
...workspaceMetadataToOTelAttributes(workspace),
174
});
175
}
176
177
export function emitInlineDoneEvent(
178
otel: IOTelService,
179
accepted: boolean,
180
languageId: string,
181
editCount: number,
182
editLineCount: number,
183
replyType: string,
184
isNotebook: boolean,
185
workspace?: WorkspaceOTelMetadata,
186
): void {
187
otel.emitLogRecord(`copilot_chat.inline.done: ${accepted ? 'accepted' : 'rejected'}`, {
188
'event.name': 'copilot_chat.inline.done',
189
'accepted': accepted,
190
'language_id': languageId,
191
'edit_count': editCount,
192
'edit_line_count': editLineCount,
193
'reply_type': replyType,
194
'is_notebook': isNotebook,
195
...workspaceMetadataToOTelAttributes(workspace),
196
});
197
}
198
199
export function emitEditSurvivalEvent(
200
otel: IOTelService,
201
editSource: string,
202
survivalRateFourGram: number,
203
survivalRateNoRevert: number,
204
timeDelayMs: number,
205
didBranchChange: boolean,
206
requestId: string,
207
workspace?: WorkspaceOTelMetadata,
208
): void {
209
otel.emitLogRecord(`copilot_chat.edit.survival: ${editSource}`, {
210
'event.name': 'copilot_chat.edit.survival',
211
'edit_source': editSource,
212
'survival_rate_four_gram': survivalRateFourGram,
213
'survival_rate_no_revert': survivalRateNoRevert,
214
'time_delay_ms': timeDelayMs,
215
'did_branch_change': didBranchChange,
216
'request_id': requestId,
217
...workspaceMetadataToOTelAttributes(workspace),
218
});
219
}
220
221
export function emitUserFeedbackEvent(
222
otel: IOTelService,
223
rating: string,
224
participant: string,
225
conversationId: string,
226
requestId: string,
227
): void {
228
otel.emitLogRecord(`copilot_chat.user.feedback: ${rating}`, {
229
'event.name': 'copilot_chat.user.feedback',
230
'rating': rating,
231
'participant': participant,
232
'conversation_id': conversationId,
233
'request_id': requestId,
234
});
235
}
236
237
export function emitCloudSessionInvokeEvent(
238
otel: IOTelService,
239
partnerAgent: string,
240
model: string,
241
requestId: string,
242
): void {
243
otel.emitLogRecord(`copilot_chat.cloud.session.invoke: ${partnerAgent}`, {
244
'event.name': 'copilot_chat.cloud.session.invoke',
245
'partner_agent': partnerAgent,
246
'model': model,
247
'request_id': requestId,
248
});
249
}
250
251