@latitude-data/telemetry 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_
3
3
  import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
4
4
  import * as otel from '@opentelemetry/api';
5
5
  import { trace, propagation, context } from '@opentelemetry/api';
6
+ import { Translator, Provider } from 'rosetta-ai';
6
7
  import { v4 } from 'uuid';
7
8
  import { BaggageSpanProcessor, ALLOW_ALL_BAGGAGE_KEYS } from '@opentelemetry/baggage-span-processor';
8
9
  import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
@@ -118,6 +119,23 @@ function GET_GATEWAY_BASE_URL() {
118
119
  }
119
120
  const env = { GATEWAY_BASE_URL: GET_GATEWAY_BASE_URL() };
120
121
 
122
+ function toSnakeCase(str) {
123
+ return str
124
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
125
+ .replace(/[^A-Za-z0-9]+/g, '_')
126
+ .replace(/_+/g, '_')
127
+ .replace(/^_+|_+$/g, '')
128
+ .toLowerCase();
129
+ }
130
+ function toKebabCase(input) {
131
+ return input
132
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
133
+ .replace(/[^A-Za-z0-9]+/g, '-')
134
+ .replace(/-+/g, '-')
135
+ .replace(/^-+|-+$/g, '')
136
+ .toLowerCase();
137
+ }
138
+
121
139
  var StreamEventTypes;
122
140
  (function (StreamEventTypes) {
123
141
  StreamEventTypes["Latitude"] = "latitude-event";
@@ -179,10 +197,24 @@ const expectedOutputConfiguration = z.object({
179
197
  parsingFormat: z.enum(['string', 'json']),
180
198
  fieldAccessor: z.string().optional(), // Field accessor to get the output from if it's a key-value format
181
199
  });
200
+ const EVALUATION_TRIGGER_TARGETS = ['first', 'every', 'last'];
201
+ const DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS = 120;
202
+ const LAST_INTERACTION_DEBOUNCE_MIN_SECONDS = 30;
203
+ const LAST_INTERACTION_DEBOUNCE_MAX_SECONDS = 60 * 60 * 24; // 1 day
204
+ const triggerConfiguration = z.object({
205
+ target: z.enum(EVALUATION_TRIGGER_TARGETS),
206
+ lastInteractionDebounce: z
207
+ .number()
208
+ .min(LAST_INTERACTION_DEBOUNCE_MIN_SECONDS)
209
+ .max(LAST_INTERACTION_DEBOUNCE_MAX_SECONDS)
210
+ .optional()
211
+ .default(DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS),
212
+ });
182
213
  const baseEvaluationConfiguration = z.object({
183
214
  reverseScale: z.boolean(), // If true, lower is better, otherwise, higher is better
184
215
  actualOutput: actualOutputConfiguration,
185
216
  expectedOutput: expectedOutputConfiguration.optional(),
217
+ trigger: triggerConfiguration.optional(),
186
218
  });
187
219
  const baseEvaluationResultMetadata = z.object({
188
220
  // configuration: Configuration snapshot is defined in every metric specification
@@ -571,14 +603,6 @@ z.object({
571
603
  evaluateLiveLogs: z.boolean().nullable().optional(),
572
604
  });
573
605
 
574
- var LegacyChainEventTypes;
575
- (function (LegacyChainEventTypes) {
576
- LegacyChainEventTypes["Error"] = "chain-error";
577
- LegacyChainEventTypes["Step"] = "chain-step";
578
- LegacyChainEventTypes["Complete"] = "chain-complete";
579
- LegacyChainEventTypes["StepComplete"] = "chain-step-complete";
580
- })(LegacyChainEventTypes || (LegacyChainEventTypes = {}));
581
-
582
606
  var ChainEventTypes;
583
607
  (function (ChainEventTypes) {
584
608
  ChainEventTypes["ChainCompleted"] = "chain-completed";
@@ -788,14 +812,6 @@ var RunSourceGroup;
788
812
  [RunSourceGroup.Playground]: [LogSources.Playground, LogSources.Experiment],
789
813
  });
790
814
 
791
- var MessageRole;
792
- (function (MessageRole) {
793
- MessageRole["system"] = "system";
794
- MessageRole["user"] = "user";
795
- MessageRole["assistant"] = "assistant";
796
- MessageRole["tool"] = "tool";
797
- })(MessageRole || (MessageRole = {}));
798
-
799
815
  var SpanKind;
800
816
  (function (SpanKind) {
801
817
  SpanKind["Internal"] = "internal";
@@ -920,12 +936,9 @@ const ATTRIBUTES = {
920
936
  _root: 'gen_ai.request',
921
937
  configuration: 'gen_ai.request.configuration',
922
938
  template: 'gen_ai.request.template',
923
- parameters: 'gen_ai.request.parameters',
924
- messages: 'gen_ai.request.messages'},
939
+ parameters: 'gen_ai.request.parameters'},
925
940
  response: {
926
- _root: 'gen_ai.response',
927
- messages: 'gen_ai.response.messages',
928
- },
941
+ _root: 'gen_ai.response'},
929
942
  usage: {
930
943
  promptTokens: 'gen_ai.usage.prompt_tokens',
931
944
  cachedTokens: 'gen_ai.usage.cached_tokens',
@@ -960,10 +973,17 @@ const ATTRIBUTES = {
960
973
  inputTokens: ATTR_GEN_AI_USAGE_INPUT_TOKENS,
961
974
  outputTokens: ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
962
975
  },
976
+ systemInstructions: 'gen_ai.system.instructions', // Contains the PARTS of the "system message"
963
977
  tool: {
964
978
  call: {
965
979
  id: ATTR_GEN_AI_TOOL_CALL_ID,
966
980
  arguments: 'gen_ai.tool.call.arguments'}},
981
+ input: {
982
+ messages: 'gen_ai.input.messages',
983
+ },
984
+ output: {
985
+ messages: 'gen_ai.output.messages',
986
+ },
967
987
  _deprecated: {
968
988
  system: ATTR_GEN_AI_SYSTEM,
969
989
  tool: {
@@ -973,40 +993,6 @@ const ATTRIBUTES = {
973
993
  value: 'gen_ai.tool.result.value',
974
994
  isError: 'gen_ai.tool.result.is_error',
975
995
  },
976
- },
977
- prompt: {
978
- _root: 'gen_ai.prompt',
979
- index: (promptIndex) => ({
980
- role: `gen_ai.prompt.${promptIndex}.role`,
981
- content: `gen_ai.prompt.${promptIndex}.content`, // string or object
982
- toolCalls: (toolCallIndex) => ({
983
- id: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.id`,
984
- name: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.name`,
985
- arguments: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.arguments`,
986
- }),
987
- tool: {
988
- callId: `gen_ai.prompt.${promptIndex}.tool_call_id`,
989
- toolName: `gen_ai.prompt.${promptIndex}.tool_name`,
990
- isError: `gen_ai.prompt.${promptIndex}.is_error`,
991
- },
992
- }),
993
- },
994
- completion: {
995
- _root: 'gen_ai.completion',
996
- index: (completionIndex) => ({
997
- role: `gen_ai.completion.${completionIndex}.role`,
998
- content: `gen_ai.completion.${completionIndex}.content`, // string or object
999
- toolCalls: (toolCallIndex) => ({
1000
- id: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.id`,
1001
- name: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.name`,
1002
- arguments: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.arguments`,
1003
- }),
1004
- tool: {
1005
- callId: `gen_ai.prompt.${completionIndex}.tool_call_id`,
1006
- toolName: `gen_ai.prompt.${completionIndex}.tool_name`,
1007
- isError: `gen_ai.prompt.${completionIndex}.is_error`,
1008
- },
1009
- }),
1010
996
  }},
1011
997
  },
1012
998
  }};
@@ -1134,11 +1120,25 @@ var Otlp;
1134
1120
  })(Otlp || (Otlp = {}));
1135
1121
 
1136
1122
  const MAX_SIMULATION_TURNS = 10;
1123
+ const globalGoalSourceSchema = z.object({
1124
+ type: z.literal('global'),
1125
+ value: z.string(),
1126
+ });
1127
+ const columnGoalSourceSchema = z.object({
1128
+ type: z.literal('column'),
1129
+ columnIndex: z.number(),
1130
+ });
1131
+ const simulatedUserGoalSourceSchema = z.discriminatedUnion('type', [
1132
+ globalGoalSourceSchema,
1133
+ columnGoalSourceSchema,
1134
+ ]);
1137
1135
  const SimulationSettingsSchema = z.object({
1138
1136
  simulateToolResponses: z.boolean().optional(),
1139
1137
  simulatedTools: z.array(z.string()).optional(), // Empty array means all tools are simulated (if simulateToolResponses is true).
1140
1138
  toolSimulationInstructions: z.string().optional(), // A prompt used to guide and generate the simulation result
1141
1139
  maxTurns: z.number().min(1).max(MAX_SIMULATION_TURNS).optional(), // The maximum number of turns to simulate. Default is 1, and any greater value will add a new user message to the simulated conversation.
1140
+ simulatedUserGoal: z.string().optional(), // Deprecated: Use simulatedUserGoalSource instead. Kept for backward compatibility.
1141
+ simulatedUserGoalSource: simulatedUserGoalSourceSchema.optional(), // The source for the simulated user goal (global text or dataset column).
1142
1142
  });
1143
1143
 
1144
1144
  var OptimizationEngine;
@@ -1212,12 +1212,18 @@ var DocumentTriggerParameters;
1212
1212
  })(DocumentTriggerParameters || (DocumentTriggerParameters = {}));
1213
1213
  const DOCUMENT_PATH_REGEXP = /^([\w-]+\/)*([\w-.])+$/;
1214
1214
 
1215
+ const translator = new Translator({
1216
+ filterEmptyMessages: true,
1217
+ providerMetadata: 'preserve',
1218
+ });
1215
1219
  class ManualInstrumentation {
1216
1220
  enabled;
1217
1221
  tracer;
1218
- constructor(tracer) {
1222
+ options;
1223
+ constructor(tracer, options) {
1219
1224
  this.enabled = false;
1220
1225
  this.tracer = tracer;
1226
+ this.options = options ?? {};
1221
1227
  }
1222
1228
  isEnabled() {
1223
1229
  return this.enabled;
@@ -1257,36 +1263,6 @@ class ManualInstrumentation {
1257
1263
  }
1258
1264
  return context;
1259
1265
  }
1260
- capitalize(str) {
1261
- if (str.length === 0)
1262
- return str;
1263
- return str.charAt(0).toUpperCase() + str.toLowerCase().slice(1);
1264
- }
1265
- toCamelCase(str) {
1266
- return str
1267
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
1268
- .replace(/[^A-Za-z0-9]+/g, ' ')
1269
- .trim()
1270
- .split(' ')
1271
- .map((w, i) => (i ? this.capitalize(w) : w.toLowerCase()))
1272
- .join('');
1273
- }
1274
- toSnakeCase(str) {
1275
- return str
1276
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
1277
- .replace(/[^A-Za-z0-9]+/g, '_')
1278
- .replace(/_+/g, '_')
1279
- .replace(/^_+|_+$/g, '')
1280
- .toLowerCase();
1281
- }
1282
- toKebabCase(input) {
1283
- return input
1284
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
1285
- .replace(/[^A-Za-z0-9]+/g, '-')
1286
- .replace(/-+/g, '-')
1287
- .replace(/^-+|-+$/g, '')
1288
- .toLowerCase();
1289
- }
1290
1266
  error(span, error, options) {
1291
1267
  options = options || {};
1292
1268
  span.recordException(error);
@@ -1334,6 +1310,9 @@ class ManualInstrumentation {
1334
1310
  },
1335
1311
  };
1336
1312
  }
1313
+ unknown(ctx, options) {
1314
+ return this.span(ctx, options?.name || SPAN_SPECIFICATIONS[SpanType.Unknown].name, SpanType.Unknown, options);
1315
+ }
1337
1316
  tool(ctx, options) {
1338
1317
  const start = options;
1339
1318
  let jsonArguments = '';
@@ -1353,7 +1332,7 @@ class ManualInstrumentation {
1353
1332
  },
1354
1333
  });
1355
1334
  return {
1356
- context: span.context,
1335
+ ...span,
1357
1336
  end: (options) => {
1358
1337
  const end = options;
1359
1338
  let stringResult = '';
@@ -1376,176 +1355,15 @@ class ManualInstrumentation {
1376
1355
  },
1377
1356
  });
1378
1357
  },
1379
- fail: span.fail,
1380
1358
  };
1381
1359
  }
1382
- attribifyMessageToolCalls(otelMessageField, toolCalls) {
1383
- const attributes = {};
1384
- for (let i = 0; i < toolCalls.length; i++) {
1385
- for (const key in toolCalls[i]) {
1386
- const field = this.toCamelCase(key);
1387
- const value = toolCalls[i][key];
1388
- if (value === null || value === undefined)
1389
- continue;
1390
- switch (field) {
1391
- case 'id':
1392
- case 'toolCallId':
1393
- case 'toolUseId': {
1394
- if (typeof value !== 'string')
1395
- continue;
1396
- attributes[otelMessageField.toolCalls(i).id] = value;
1397
- break;
1398
- }
1399
- case 'name':
1400
- case 'toolName': {
1401
- if (typeof value !== 'string')
1402
- continue;
1403
- attributes[otelMessageField.toolCalls(i).name] = value;
1404
- break;
1405
- }
1406
- case 'arguments':
1407
- case 'toolArguments':
1408
- case 'input': {
1409
- if (typeof value === 'string') {
1410
- attributes[otelMessageField.toolCalls(i).arguments] = value;
1411
- }
1412
- else {
1413
- try {
1414
- attributes[otelMessageField.toolCalls(i).arguments] =
1415
- JSON.stringify(value);
1416
- }
1417
- catch (_error) {
1418
- attributes[otelMessageField.toolCalls(i).arguments] = '{}';
1419
- }
1420
- }
1421
- break;
1422
- }
1423
- /* OpenAI function calls */
1424
- case 'function': {
1425
- if (typeof value !== 'object')
1426
- continue;
1427
- if (!('name' in value))
1428
- continue;
1429
- if (typeof value.name !== 'string')
1430
- continue;
1431
- if (!('arguments' in value))
1432
- continue;
1433
- if (typeof value.arguments !== 'string')
1434
- continue;
1435
- attributes[otelMessageField.toolCalls(i).name] = value.name;
1436
- attributes[otelMessageField.toolCalls(i).arguments] =
1437
- value.arguments;
1438
- break;
1439
- }
1440
- }
1441
- }
1442
- }
1443
- return attributes;
1444
- }
1445
- attribifyMessageContent(otelMessageField, content) {
1446
- let attributes = {};
1447
- if (typeof content === 'string') {
1448
- return attributes;
1449
- }
1450
- try {
1451
- attributes[otelMessageField.content] = JSON.stringify(content);
1452
- }
1453
- catch (_error) {
1454
- attributes[otelMessageField.content] = '[]';
1455
- }
1456
- if (!Array.isArray(content))
1457
- return attributes;
1458
- /* Tool calls for Anthropic and PromptL are in the content */
1459
- const toolCalls = [];
1460
- for (const item of content) {
1461
- for (const key in item) {
1462
- if (this.toCamelCase(key) !== 'type')
1463
- continue;
1464
- if (typeof item[key] !== 'string')
1465
- continue;
1466
- if (item[key] !== 'tool-call' && item[key] !== 'tool_use')
1467
- continue;
1468
- toolCalls.push(item);
1469
- }
1470
- }
1471
- if (toolCalls.length > 0) {
1472
- attributes = {
1473
- ...attributes,
1474
- ...this.attribifyMessageToolCalls(otelMessageField, toolCalls),
1475
- };
1476
- }
1477
- return attributes;
1478
- }
1479
- attribifyMessages(direction, messages) {
1480
- const otelField = direction === 'input'
1481
- ? ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.prompt
1482
- : ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.completion;
1483
- let attributes = {};
1484
- for (let i = 0; i < messages.length; i++) {
1485
- for (const key in messages[i]) {
1486
- const field = this.toCamelCase(key);
1487
- const value = messages[i][key];
1488
- if (value === null || value === undefined)
1489
- continue;
1490
- switch (field) {
1491
- case 'role': {
1492
- if (typeof value !== 'string')
1493
- continue;
1494
- attributes[otelField.index(i).role] = value;
1495
- break;
1496
- }
1497
- /* Tool calls for Anthropic and PromptL are in the content */
1498
- case 'content': {
1499
- attributes = {
1500
- ...attributes,
1501
- ...this.attribifyMessageContent(otelField.index(i), value),
1502
- };
1503
- break;
1504
- }
1505
- /* Tool calls for OpenAI */
1506
- case 'toolCalls': {
1507
- if (!Array.isArray(value))
1508
- continue;
1509
- attributes = {
1510
- ...attributes,
1511
- ...this.attribifyMessageToolCalls(otelField.index(i), value),
1512
- };
1513
- break;
1514
- }
1515
- /* Tool result for OpenAI / Anthropic / PromptL */
1516
- case 'toolCallId':
1517
- case 'toolId':
1518
- case 'toolUseId': {
1519
- if (typeof value !== 'string')
1520
- continue;
1521
- attributes[otelField.index(i).tool.callId] = value;
1522
- break;
1523
- }
1524
- case 'toolName': {
1525
- if (typeof value !== 'string')
1526
- continue;
1527
- attributes[otelField.index(i).tool.toolName] = value;
1528
- break;
1529
- }
1530
- // Note: 'toolResult' is 'content' itself
1531
- case 'isError': {
1532
- if (typeof value !== 'boolean')
1533
- continue;
1534
- attributes[otelField.index(i).tool.isError] = value;
1535
- break;
1536
- }
1537
- }
1538
- }
1539
- }
1540
- return attributes;
1541
- }
1542
1360
  attribifyConfiguration(direction, configuration) {
1543
1361
  const prefix = direction === 'input'
1544
1362
  ? ATTRIBUTES.LATITUDE.request._root
1545
1363
  : ATTRIBUTES.LATITUDE.response._root;
1546
1364
  const attributes = {};
1547
1365
  for (const key in configuration) {
1548
- const field = this.toSnakeCase(key);
1366
+ const field = toSnakeCase(key);
1549
1367
  let value = configuration[key];
1550
1368
  if (value === null || value === undefined)
1551
1369
  continue;
@@ -1576,21 +1394,28 @@ class ManualInstrumentation {
1576
1394
  }
1577
1395
  const attrConfiguration = this.attribifyConfiguration('input', configuration);
1578
1396
  const input = start.input ?? [];
1397
+ let jsonSystem = '';
1579
1398
  let jsonInput = '';
1580
1399
  try {
1581
- jsonInput = JSON.stringify(input);
1400
+ const translated = translator.translate(input, {
1401
+ from: this.options.provider,
1402
+ to: Provider.GenAI,
1403
+ direction: 'input',
1404
+ });
1405
+ jsonSystem = JSON.stringify(translated.system ?? []);
1406
+ jsonInput = JSON.stringify(translated.messages ?? []);
1582
1407
  }
1583
1408
  catch (_error) {
1409
+ jsonSystem = '[]';
1584
1410
  jsonInput = '[]';
1585
1411
  }
1586
- const attrInput = this.attribifyMessages('input', input);
1587
1412
  const span = this.span(ctx, start.name || `${start.provider} / ${start.model}`, SpanType.Completion, {
1588
1413
  attributes: {
1589
1414
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.system]: start.provider,
1590
1415
  [ATTRIBUTES.LATITUDE.request.configuration]: jsonConfiguration,
1591
1416
  ...attrConfiguration,
1592
- [ATTRIBUTES.LATITUDE.request.messages]: jsonInput,
1593
- ...attrInput,
1417
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.systemInstructions]: jsonSystem,
1418
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.input.messages]: jsonInput,
1594
1419
  ...(start.attributes || {}),
1595
1420
  [ATTRIBUTES.LATITUDE.commitUuid]: start.versionUuid,
1596
1421
  [ATTRIBUTES.LATITUDE.documentUuid]: start.promptUuid,
@@ -1598,18 +1423,22 @@ class ManualInstrumentation {
1598
1423
  },
1599
1424
  });
1600
1425
  return {
1601
- context: span.context,
1426
+ ...span,
1602
1427
  end: (options) => {
1603
1428
  const end = options ?? {};
1604
1429
  const output = end.output ?? [];
1605
1430
  let jsonOutput = '';
1606
1431
  try {
1607
- jsonOutput = JSON.stringify(output);
1432
+ const translated = translator.translate(output, {
1433
+ from: this.options.provider,
1434
+ to: Provider.GenAI,
1435
+ direction: 'output',
1436
+ });
1437
+ jsonOutput = JSON.stringify(translated.messages ?? []);
1608
1438
  }
1609
1439
  catch (_error) {
1610
1440
  jsonOutput = '[]';
1611
1441
  }
1612
- const attrOutput = this.attribifyMessages('output', output);
1613
1442
  const tokens = {
1614
1443
  prompt: end.tokens?.prompt ?? 0,
1615
1444
  cached: end.tokens?.cached ?? 0,
@@ -1621,8 +1450,7 @@ class ManualInstrumentation {
1621
1450
  const finishReason = end.finishReason ?? '';
1622
1451
  span.end({
1623
1452
  attributes: {
1624
- [ATTRIBUTES.LATITUDE.response.messages]: jsonOutput,
1625
- ...attrOutput,
1453
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.output.messages]: jsonOutput,
1626
1454
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.inputTokens]: inputTokens,
1627
1455
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.outputTokens]: outputTokens,
1628
1456
  [ATTRIBUTES.LATITUDE.usage.promptTokens]: tokens.prompt,
@@ -1637,7 +1465,6 @@ class ManualInstrumentation {
1637
1465
  },
1638
1466
  });
1639
1467
  },
1640
- fail: span.fail,
1641
1468
  };
1642
1469
  }
1643
1470
  embedding(ctx, options) {
@@ -1649,7 +1476,7 @@ class ManualInstrumentation {
1649
1476
  : ATTRIBUTES.OPENTELEMETRY.HTTP.response.header;
1650
1477
  const attributes = {};
1651
1478
  for (const key in headers) {
1652
- const field = this.toKebabCase(key);
1479
+ const field = toKebabCase(key);
1653
1480
  const value = headers[key];
1654
1481
  if (value === null || value === undefined)
1655
1482
  continue;
@@ -1684,7 +1511,7 @@ class ManualInstrumentation {
1684
1511
  },
1685
1512
  });
1686
1513
  return {
1687
- context: span.context,
1514
+ ...span,
1688
1515
  end: (options) => {
1689
1516
  const end = options;
1690
1517
  // Note: do not serialize headers as a single attribute because fields won't be redacted
@@ -1710,7 +1537,6 @@ class ManualInstrumentation {
1710
1537
  },
1711
1538
  });
1712
1539
  },
1713
- fail: span.fail,
1714
1540
  };
1715
1541
  }
1716
1542
  prompt(ctx, { documentLogUuid, versionUuid, promptUuid, projectId, experimentUuid, testDeploymentId, externalId, template, parameters, name, source, ...rest }) {
@@ -1751,7 +1577,9 @@ class ManualInstrumentation {
1751
1577
  ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1752
1578
  ...(rest.attributes || {}),
1753
1579
  };
1754
- return this.span(ctx, name || 'chat', SpanType.Chat, { attributes });
1580
+ return this.span(ctx, name || `chat-${documentLogUuid}`, SpanType.Chat, {
1581
+ attributes,
1582
+ });
1755
1583
  }
1756
1584
  external(ctx, { promptUuid, documentLogUuid, source, versionUuid, externalId, name, ...rest }) {
1757
1585
  const attributes = {
@@ -1791,8 +1619,8 @@ class LatitudeInstrumentation {
1791
1619
  return this.manualTelemetry.isEnabled();
1792
1620
  }
1793
1621
  enable() {
1794
- this.options.module.instrument(this);
1795
1622
  this.manualTelemetry.enable();
1623
+ this.options.module.instrument(this);
1796
1624
  }
1797
1625
  disable() {
1798
1626
  this.manualTelemetry.disable();
@@ -1987,6 +1815,9 @@ class SpanFactory {
1987
1815
  constructor(telemetry) {
1988
1816
  this.telemetry = telemetry;
1989
1817
  }
1818
+ span(options, ctx) {
1819
+ return this.telemetry.unknown(ctx ?? context.active(), options);
1820
+ }
1990
1821
  tool(options, ctx) {
1991
1822
  return this.telemetry.tool(ctx ?? context.active(), options);
1992
1823
  }
@@ -2020,6 +1851,9 @@ class ContextManager {
2020
1851
  active() {
2021
1852
  return context.active();
2022
1853
  }
1854
+ with(ctx, fn, thisArg, ...args) {
1855
+ return context.with(ctx, fn, thisArg, ...args);
1856
+ }
2023
1857
  }
2024
1858
  class InstrumentationManager {
2025
1859
  instrumentations;
@@ -2066,6 +1900,23 @@ class ScopedTracerProvider {
2066
1900
  return this.provider.getTracer(this.scope, this.version, options);
2067
1901
  }
2068
1902
  }
1903
+ class LifecycleManager {
1904
+ nodeProvider;
1905
+ exporter;
1906
+ constructor(nodeProvider, exporter) {
1907
+ this.nodeProvider = nodeProvider;
1908
+ this.exporter = exporter;
1909
+ }
1910
+ async flush() {
1911
+ await this.nodeProvider.forceFlush();
1912
+ await this.exporter.forceFlush?.();
1913
+ }
1914
+ async shutdown() {
1915
+ await this.flush();
1916
+ await this.nodeProvider.shutdown();
1917
+ await this.exporter.shutdown?.();
1918
+ }
1919
+ }
2069
1920
  const DEFAULT_SPAN_EXPORTER = (apiKey) => new OTLPTraceExporter({
2070
1921
  url: TRACES_URL,
2071
1922
  headers: {
@@ -2084,6 +1935,7 @@ var Instrumentation;
2084
1935
  Instrumentation["Langchain"] = "langchain";
2085
1936
  Instrumentation["Latitude"] = "latitude";
2086
1937
  Instrumentation["LlamaIndex"] = "llamaindex";
1938
+ Instrumentation["Manual"] = "manual";
2087
1939
  Instrumentation["OpenAI"] = "openai";
2088
1940
  Instrumentation["TogetherAI"] = "togetherai";
2089
1941
  Instrumentation["VertexAI"] = "vertexai";
@@ -2097,6 +1949,7 @@ class LatitudeTelemetry {
2097
1949
  context;
2098
1950
  instrumentation;
2099
1951
  tracer;
1952
+ lifecycle;
2100
1953
  constructor(apiKey, options) {
2101
1954
  this.options = options || {};
2102
1955
  if (!this.options.exporter) {
@@ -2113,6 +1966,7 @@ class LatitudeTelemetry {
2113
1966
  this.nodeProvider = new NodeTracerProvider({
2114
1967
  resource: new Resource({ [ATTR_SERVICE_NAME]: SERVICE_NAME }),
2115
1968
  });
1969
+ this.lifecycle = new LifecycleManager(this.nodeProvider, this.options.exporter);
2116
1970
  // Note: important, must run before the exporter span processors
2117
1971
  this.nodeProvider.addSpanProcessor(new BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS));
2118
1972
  if (this.options.processors) {
@@ -2142,19 +1996,16 @@ class LatitudeTelemetry {
2142
1996
  this.context = new ContextManager(this.manualInstrumentation);
2143
1997
  }
2144
1998
  async flush() {
2145
- await this.nodeProvider.forceFlush();
2146
- await this.options.exporter.forceFlush?.();
1999
+ await this.lifecycle.flush();
2147
2000
  }
2148
2001
  async shutdown() {
2149
- await this.flush();
2150
- await this.nodeProvider.shutdown();
2151
- await this.options.exporter.shutdown?.();
2002
+ await this.lifecycle.shutdown();
2152
2003
  }
2153
2004
  // TODO(tracing): auto instrument outgoing HTTP requests
2154
2005
  initInstrumentations() {
2155
2006
  this.instrumentationsList = [];
2156
- const tracer = this.tracer.get(InstrumentationScope.Manual);
2157
- this.manualInstrumentation = new ManualInstrumentation(tracer);
2007
+ const tracer = this.tracer.get(Instrumentation.Manual);
2008
+ this.manualInstrumentation = new ManualInstrumentation(tracer, this.options.instrumentations?.manual);
2158
2009
  this.instrumentationsList.push(this.manualInstrumentation);
2159
2010
  const latitude = this.options.instrumentations?.latitude;
2160
2011
  if (latitude) {
@@ -2164,12 +2015,12 @@ class LatitudeTelemetry {
2164
2015
  }
2165
2016
  const configureInstrumentation = (instrumentationType, InstrumentationConstructor, instrumentationOptions) => {
2166
2017
  const providerPkg = this.options.instrumentations?.[instrumentationType];
2018
+ if (!providerPkg)
2019
+ return;
2167
2020
  const provider = this.tracer.provider(instrumentationType);
2168
2021
  const instrumentation = new InstrumentationConstructor(instrumentationOptions); // prettier-ignore
2169
2022
  instrumentation.setTracerProvider(provider);
2170
- if (providerPkg) {
2171
- instrumentation.manuallyInstrument(providerPkg);
2172
- }
2023
+ instrumentation.manuallyInstrument(providerPkg);
2173
2024
  registerInstrumentations({
2174
2025
  instrumentations: [instrumentation],
2175
2026
  tracerProvider: provider,
@@ -2191,19 +2042,17 @@ class LatitudeTelemetry {
2191
2042
  if (!DOCUMENT_PATH_REGEXP.test(options.path)) {
2192
2043
  throw new BadRequestError("Invalid path, no spaces. Only letters, numbers, '.', '-' and '_'");
2193
2044
  }
2194
- const span = this.manualInstrumentation.unresolvedExternal(otel.ROOT_CONTEXT, options);
2045
+ const span = this.manualInstrumentation.unresolvedExternal(BACKGROUND(), options);
2046
+ let result;
2195
2047
  try {
2196
- const result = await context.with(span.context, () => fn(span.context));
2197
- span.end();
2198
- return result;
2048
+ result = await context.with(span.context, async () => await fn(span.context));
2199
2049
  }
2200
2050
  catch (error) {
2201
2051
  span.fail(error);
2202
2052
  throw error;
2203
2053
  }
2204
- finally {
2205
- await this.flush();
2206
- }
2054
+ span.end();
2055
+ return result;
2207
2056
  }
2208
2057
  }
2209
2058