@latitude-data/telemetry 1.1.1 → 2.0.1

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.cjs CHANGED
@@ -4,6 +4,7 @@ var zod = require('zod');
4
4
  var incubating = require('@opentelemetry/semantic-conventions/incubating');
5
5
  var semanticConventions = require('@opentelemetry/semantic-conventions');
6
6
  var otel = require('@opentelemetry/api');
7
+ var rosettaAi = require('rosetta-ai');
7
8
  var uuid = require('uuid');
8
9
  var baggageSpanProcessor = require('@opentelemetry/baggage-span-processor');
9
10
  var contextAsyncHooks = require('@opentelemetry/context-async-hooks');
@@ -138,6 +139,23 @@ function GET_GATEWAY_BASE_URL() {
138
139
  }
139
140
  const env = { GATEWAY_BASE_URL: GET_GATEWAY_BASE_URL() };
140
141
 
142
+ function toSnakeCase(str) {
143
+ return str
144
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
145
+ .replace(/[^A-Za-z0-9]+/g, '_')
146
+ .replace(/_+/g, '_')
147
+ .replace(/^_+|_+$/g, '')
148
+ .toLowerCase();
149
+ }
150
+ function toKebabCase(input) {
151
+ return input
152
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
153
+ .replace(/[^A-Za-z0-9]+/g, '-')
154
+ .replace(/-+/g, '-')
155
+ .replace(/^-+|-+$/g, '')
156
+ .toLowerCase();
157
+ }
158
+
141
159
  var StreamEventTypes;
142
160
  (function (StreamEventTypes) {
143
161
  StreamEventTypes["Latitude"] = "latitude-event";
@@ -199,10 +217,24 @@ const expectedOutputConfiguration = zod.z.object({
199
217
  parsingFormat: zod.z.enum(['string', 'json']),
200
218
  fieldAccessor: zod.z.string().optional(), // Field accessor to get the output from if it's a key-value format
201
219
  });
220
+ const EVALUATION_TRIGGER_TARGETS = ['first', 'every', 'last'];
221
+ const DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS = 120;
222
+ const LAST_INTERACTION_DEBOUNCE_MIN_SECONDS = 30;
223
+ const LAST_INTERACTION_DEBOUNCE_MAX_SECONDS = 60 * 60 * 24; // 1 day
224
+ const triggerConfiguration = zod.z.object({
225
+ target: zod.z.enum(EVALUATION_TRIGGER_TARGETS),
226
+ lastInteractionDebounce: zod.z
227
+ .number()
228
+ .min(LAST_INTERACTION_DEBOUNCE_MIN_SECONDS)
229
+ .max(LAST_INTERACTION_DEBOUNCE_MAX_SECONDS)
230
+ .optional()
231
+ .default(DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS),
232
+ });
202
233
  const baseEvaluationConfiguration = zod.z.object({
203
234
  reverseScale: zod.z.boolean(), // If true, lower is better, otherwise, higher is better
204
235
  actualOutput: actualOutputConfiguration,
205
236
  expectedOutput: expectedOutputConfiguration.optional(),
237
+ trigger: triggerConfiguration.optional(),
206
238
  });
207
239
  const baseEvaluationResultMetadata = zod.z.object({
208
240
  // configuration: Configuration snapshot is defined in every metric specification
@@ -591,14 +623,6 @@ zod.z.object({
591
623
  evaluateLiveLogs: zod.z.boolean().nullable().optional(),
592
624
  });
593
625
 
594
- var LegacyChainEventTypes;
595
- (function (LegacyChainEventTypes) {
596
- LegacyChainEventTypes["Error"] = "chain-error";
597
- LegacyChainEventTypes["Step"] = "chain-step";
598
- LegacyChainEventTypes["Complete"] = "chain-complete";
599
- LegacyChainEventTypes["StepComplete"] = "chain-step-complete";
600
- })(LegacyChainEventTypes || (LegacyChainEventTypes = {}));
601
-
602
626
  var ChainEventTypes;
603
627
  (function (ChainEventTypes) {
604
628
  ChainEventTypes["ChainCompleted"] = "chain-completed";
@@ -808,14 +832,6 @@ var RunSourceGroup;
808
832
  [RunSourceGroup.Playground]: [LogSources.Playground, LogSources.Experiment],
809
833
  });
810
834
 
811
- var MessageRole;
812
- (function (MessageRole) {
813
- MessageRole["system"] = "system";
814
- MessageRole["user"] = "user";
815
- MessageRole["assistant"] = "assistant";
816
- MessageRole["tool"] = "tool";
817
- })(MessageRole || (MessageRole = {}));
818
-
819
835
  var SpanKind;
820
836
  (function (SpanKind) {
821
837
  SpanKind["Internal"] = "internal";
@@ -940,12 +956,9 @@ const ATTRIBUTES = {
940
956
  _root: 'gen_ai.request',
941
957
  configuration: 'gen_ai.request.configuration',
942
958
  template: 'gen_ai.request.template',
943
- parameters: 'gen_ai.request.parameters',
944
- messages: 'gen_ai.request.messages'},
959
+ parameters: 'gen_ai.request.parameters'},
945
960
  response: {
946
- _root: 'gen_ai.response',
947
- messages: 'gen_ai.response.messages',
948
- },
961
+ _root: 'gen_ai.response'},
949
962
  usage: {
950
963
  promptTokens: 'gen_ai.usage.prompt_tokens',
951
964
  cachedTokens: 'gen_ai.usage.cached_tokens',
@@ -980,10 +993,17 @@ const ATTRIBUTES = {
980
993
  inputTokens: incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS,
981
994
  outputTokens: incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
982
995
  },
996
+ systemInstructions: 'gen_ai.system.instructions', // Contains the PARTS of the "system message"
983
997
  tool: {
984
998
  call: {
985
999
  id: incubating.ATTR_GEN_AI_TOOL_CALL_ID,
986
1000
  arguments: 'gen_ai.tool.call.arguments'}},
1001
+ input: {
1002
+ messages: 'gen_ai.input.messages',
1003
+ },
1004
+ output: {
1005
+ messages: 'gen_ai.output.messages',
1006
+ },
987
1007
  _deprecated: {
988
1008
  system: incubating.ATTR_GEN_AI_SYSTEM,
989
1009
  tool: {
@@ -993,40 +1013,6 @@ const ATTRIBUTES = {
993
1013
  value: 'gen_ai.tool.result.value',
994
1014
  isError: 'gen_ai.tool.result.is_error',
995
1015
  },
996
- },
997
- prompt: {
998
- _root: 'gen_ai.prompt',
999
- index: (promptIndex) => ({
1000
- role: `gen_ai.prompt.${promptIndex}.role`,
1001
- content: `gen_ai.prompt.${promptIndex}.content`, // string or object
1002
- toolCalls: (toolCallIndex) => ({
1003
- id: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.id`,
1004
- name: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.name`,
1005
- arguments: `gen_ai.prompt.${promptIndex}.tool_calls.${toolCallIndex}.arguments`,
1006
- }),
1007
- tool: {
1008
- callId: `gen_ai.prompt.${promptIndex}.tool_call_id`,
1009
- toolName: `gen_ai.prompt.${promptIndex}.tool_name`,
1010
- isError: `gen_ai.prompt.${promptIndex}.is_error`,
1011
- },
1012
- }),
1013
- },
1014
- completion: {
1015
- _root: 'gen_ai.completion',
1016
- index: (completionIndex) => ({
1017
- role: `gen_ai.completion.${completionIndex}.role`,
1018
- content: `gen_ai.completion.${completionIndex}.content`, // string or object
1019
- toolCalls: (toolCallIndex) => ({
1020
- id: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.id`,
1021
- name: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.name`,
1022
- arguments: `gen_ai.completion.${completionIndex}.tool_calls.${toolCallIndex}.arguments`,
1023
- }),
1024
- tool: {
1025
- callId: `gen_ai.prompt.${completionIndex}.tool_call_id`,
1026
- toolName: `gen_ai.prompt.${completionIndex}.tool_name`,
1027
- isError: `gen_ai.prompt.${completionIndex}.is_error`,
1028
- },
1029
- }),
1030
1016
  }},
1031
1017
  },
1032
1018
  }};
@@ -1154,11 +1140,25 @@ var Otlp;
1154
1140
  })(Otlp || (Otlp = {}));
1155
1141
 
1156
1142
  const MAX_SIMULATION_TURNS = 10;
1143
+ const globalGoalSourceSchema = zod.z.object({
1144
+ type: zod.z.literal('global'),
1145
+ value: zod.z.string(),
1146
+ });
1147
+ const columnGoalSourceSchema = zod.z.object({
1148
+ type: zod.z.literal('column'),
1149
+ columnIndex: zod.z.number(),
1150
+ });
1151
+ const simulatedUserGoalSourceSchema = zod.z.discriminatedUnion('type', [
1152
+ globalGoalSourceSchema,
1153
+ columnGoalSourceSchema,
1154
+ ]);
1157
1155
  const SimulationSettingsSchema = zod.z.object({
1158
1156
  simulateToolResponses: zod.z.boolean().optional(),
1159
1157
  simulatedTools: zod.z.array(zod.z.string()).optional(), // Empty array means all tools are simulated (if simulateToolResponses is true).
1160
1158
  toolSimulationInstructions: zod.z.string().optional(), // A prompt used to guide and generate the simulation result
1161
1159
  maxTurns: zod.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.
1160
+ simulatedUserGoal: zod.z.string().optional(), // Deprecated: Use simulatedUserGoalSource instead. Kept for backward compatibility.
1161
+ simulatedUserGoalSource: simulatedUserGoalSourceSchema.optional(), // The source for the simulated user goal (global text or dataset column).
1162
1162
  });
1163
1163
 
1164
1164
  var OptimizationEngine;
@@ -1232,12 +1232,18 @@ var DocumentTriggerParameters;
1232
1232
  })(DocumentTriggerParameters || (DocumentTriggerParameters = {}));
1233
1233
  const DOCUMENT_PATH_REGEXP = /^([\w-]+\/)*([\w-.])+$/;
1234
1234
 
1235
+ const translator = new rosettaAi.Translator({
1236
+ filterEmptyMessages: true,
1237
+ providerMetadata: 'preserve',
1238
+ });
1235
1239
  class ManualInstrumentation {
1236
1240
  enabled;
1237
1241
  tracer;
1238
- constructor(tracer) {
1242
+ options;
1243
+ constructor(tracer, options) {
1239
1244
  this.enabled = false;
1240
1245
  this.tracer = tracer;
1246
+ this.options = options ?? {};
1241
1247
  }
1242
1248
  isEnabled() {
1243
1249
  return this.enabled;
@@ -1277,36 +1283,6 @@ class ManualInstrumentation {
1277
1283
  }
1278
1284
  return context;
1279
1285
  }
1280
- capitalize(str) {
1281
- if (str.length === 0)
1282
- return str;
1283
- return str.charAt(0).toUpperCase() + str.toLowerCase().slice(1);
1284
- }
1285
- toCamelCase(str) {
1286
- return str
1287
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
1288
- .replace(/[^A-Za-z0-9]+/g, ' ')
1289
- .trim()
1290
- .split(' ')
1291
- .map((w, i) => (i ? this.capitalize(w) : w.toLowerCase()))
1292
- .join('');
1293
- }
1294
- toSnakeCase(str) {
1295
- return str
1296
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
1297
- .replace(/[^A-Za-z0-9]+/g, '_')
1298
- .replace(/_+/g, '_')
1299
- .replace(/^_+|_+$/g, '')
1300
- .toLowerCase();
1301
- }
1302
- toKebabCase(input) {
1303
- return input
1304
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
1305
- .replace(/[^A-Za-z0-9]+/g, '-')
1306
- .replace(/-+/g, '-')
1307
- .replace(/^-+|-+$/g, '')
1308
- .toLowerCase();
1309
- }
1310
1286
  error(span, error, options) {
1311
1287
  options = options || {};
1312
1288
  span.recordException(error);
@@ -1354,6 +1330,9 @@ class ManualInstrumentation {
1354
1330
  },
1355
1331
  };
1356
1332
  }
1333
+ unknown(ctx, options) {
1334
+ return this.span(ctx, options?.name || SPAN_SPECIFICATIONS[SpanType.Unknown].name, SpanType.Unknown, options);
1335
+ }
1357
1336
  tool(ctx, options) {
1358
1337
  const start = options;
1359
1338
  let jsonArguments = '';
@@ -1373,7 +1352,7 @@ class ManualInstrumentation {
1373
1352
  },
1374
1353
  });
1375
1354
  return {
1376
- context: span.context,
1355
+ ...span,
1377
1356
  end: (options) => {
1378
1357
  const end = options;
1379
1358
  let stringResult = '';
@@ -1396,176 +1375,15 @@ class ManualInstrumentation {
1396
1375
  },
1397
1376
  });
1398
1377
  },
1399
- fail: span.fail,
1400
1378
  };
1401
1379
  }
1402
- attribifyMessageToolCalls(otelMessageField, toolCalls) {
1403
- const attributes = {};
1404
- for (let i = 0; i < toolCalls.length; i++) {
1405
- for (const key in toolCalls[i]) {
1406
- const field = this.toCamelCase(key);
1407
- const value = toolCalls[i][key];
1408
- if (value === null || value === undefined)
1409
- continue;
1410
- switch (field) {
1411
- case 'id':
1412
- case 'toolCallId':
1413
- case 'toolUseId': {
1414
- if (typeof value !== 'string')
1415
- continue;
1416
- attributes[otelMessageField.toolCalls(i).id] = value;
1417
- break;
1418
- }
1419
- case 'name':
1420
- case 'toolName': {
1421
- if (typeof value !== 'string')
1422
- continue;
1423
- attributes[otelMessageField.toolCalls(i).name] = value;
1424
- break;
1425
- }
1426
- case 'arguments':
1427
- case 'toolArguments':
1428
- case 'input': {
1429
- if (typeof value === 'string') {
1430
- attributes[otelMessageField.toolCalls(i).arguments] = value;
1431
- }
1432
- else {
1433
- try {
1434
- attributes[otelMessageField.toolCalls(i).arguments] =
1435
- JSON.stringify(value);
1436
- }
1437
- catch (_error) {
1438
- attributes[otelMessageField.toolCalls(i).arguments] = '{}';
1439
- }
1440
- }
1441
- break;
1442
- }
1443
- /* OpenAI function calls */
1444
- case 'function': {
1445
- if (typeof value !== 'object')
1446
- continue;
1447
- if (!('name' in value))
1448
- continue;
1449
- if (typeof value.name !== 'string')
1450
- continue;
1451
- if (!('arguments' in value))
1452
- continue;
1453
- if (typeof value.arguments !== 'string')
1454
- continue;
1455
- attributes[otelMessageField.toolCalls(i).name] = value.name;
1456
- attributes[otelMessageField.toolCalls(i).arguments] =
1457
- value.arguments;
1458
- break;
1459
- }
1460
- }
1461
- }
1462
- }
1463
- return attributes;
1464
- }
1465
- attribifyMessageContent(otelMessageField, content) {
1466
- let attributes = {};
1467
- if (typeof content === 'string') {
1468
- return attributes;
1469
- }
1470
- try {
1471
- attributes[otelMessageField.content] = JSON.stringify(content);
1472
- }
1473
- catch (_error) {
1474
- attributes[otelMessageField.content] = '[]';
1475
- }
1476
- if (!Array.isArray(content))
1477
- return attributes;
1478
- /* Tool calls for Anthropic and PromptL are in the content */
1479
- const toolCalls = [];
1480
- for (const item of content) {
1481
- for (const key in item) {
1482
- if (this.toCamelCase(key) !== 'type')
1483
- continue;
1484
- if (typeof item[key] !== 'string')
1485
- continue;
1486
- if (item[key] !== 'tool-call' && item[key] !== 'tool_use')
1487
- continue;
1488
- toolCalls.push(item);
1489
- }
1490
- }
1491
- if (toolCalls.length > 0) {
1492
- attributes = {
1493
- ...attributes,
1494
- ...this.attribifyMessageToolCalls(otelMessageField, toolCalls),
1495
- };
1496
- }
1497
- return attributes;
1498
- }
1499
- attribifyMessages(direction, messages) {
1500
- const otelField = direction === 'input'
1501
- ? ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.prompt
1502
- : ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.completion;
1503
- let attributes = {};
1504
- for (let i = 0; i < messages.length; i++) {
1505
- for (const key in messages[i]) {
1506
- const field = this.toCamelCase(key);
1507
- const value = messages[i][key];
1508
- if (value === null || value === undefined)
1509
- continue;
1510
- switch (field) {
1511
- case 'role': {
1512
- if (typeof value !== 'string')
1513
- continue;
1514
- attributes[otelField.index(i).role] = value;
1515
- break;
1516
- }
1517
- /* Tool calls for Anthropic and PromptL are in the content */
1518
- case 'content': {
1519
- attributes = {
1520
- ...attributes,
1521
- ...this.attribifyMessageContent(otelField.index(i), value),
1522
- };
1523
- break;
1524
- }
1525
- /* Tool calls for OpenAI */
1526
- case 'toolCalls': {
1527
- if (!Array.isArray(value))
1528
- continue;
1529
- attributes = {
1530
- ...attributes,
1531
- ...this.attribifyMessageToolCalls(otelField.index(i), value),
1532
- };
1533
- break;
1534
- }
1535
- /* Tool result for OpenAI / Anthropic / PromptL */
1536
- case 'toolCallId':
1537
- case 'toolId':
1538
- case 'toolUseId': {
1539
- if (typeof value !== 'string')
1540
- continue;
1541
- attributes[otelField.index(i).tool.callId] = value;
1542
- break;
1543
- }
1544
- case 'toolName': {
1545
- if (typeof value !== 'string')
1546
- continue;
1547
- attributes[otelField.index(i).tool.toolName] = value;
1548
- break;
1549
- }
1550
- // Note: 'toolResult' is 'content' itself
1551
- case 'isError': {
1552
- if (typeof value !== 'boolean')
1553
- continue;
1554
- attributes[otelField.index(i).tool.isError] = value;
1555
- break;
1556
- }
1557
- }
1558
- }
1559
- }
1560
- return attributes;
1561
- }
1562
1380
  attribifyConfiguration(direction, configuration) {
1563
1381
  const prefix = direction === 'input'
1564
1382
  ? ATTRIBUTES.LATITUDE.request._root
1565
1383
  : ATTRIBUTES.LATITUDE.response._root;
1566
1384
  const attributes = {};
1567
1385
  for (const key in configuration) {
1568
- const field = this.toSnakeCase(key);
1386
+ const field = toSnakeCase(key);
1569
1387
  let value = configuration[key];
1570
1388
  if (value === null || value === undefined)
1571
1389
  continue;
@@ -1596,21 +1414,28 @@ class ManualInstrumentation {
1596
1414
  }
1597
1415
  const attrConfiguration = this.attribifyConfiguration('input', configuration);
1598
1416
  const input = start.input ?? [];
1417
+ let jsonSystem = '';
1599
1418
  let jsonInput = '';
1600
1419
  try {
1601
- jsonInput = JSON.stringify(input);
1420
+ const translated = translator.translate(input, {
1421
+ from: this.options.provider,
1422
+ to: rosettaAi.Provider.GenAI,
1423
+ direction: 'input',
1424
+ });
1425
+ jsonSystem = JSON.stringify(translated.system ?? []);
1426
+ jsonInput = JSON.stringify(translated.messages ?? []);
1602
1427
  }
1603
1428
  catch (_error) {
1429
+ jsonSystem = '[]';
1604
1430
  jsonInput = '[]';
1605
1431
  }
1606
- const attrInput = this.attribifyMessages('input', input);
1607
1432
  const span = this.span(ctx, start.name || `${start.provider} / ${start.model}`, SpanType.Completion, {
1608
1433
  attributes: {
1609
1434
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.system]: start.provider,
1610
1435
  [ATTRIBUTES.LATITUDE.request.configuration]: jsonConfiguration,
1611
1436
  ...attrConfiguration,
1612
- [ATTRIBUTES.LATITUDE.request.messages]: jsonInput,
1613
- ...attrInput,
1437
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.systemInstructions]: jsonSystem,
1438
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.input.messages]: jsonInput,
1614
1439
  ...(start.attributes || {}),
1615
1440
  [ATTRIBUTES.LATITUDE.commitUuid]: start.versionUuid,
1616
1441
  [ATTRIBUTES.LATITUDE.documentUuid]: start.promptUuid,
@@ -1618,18 +1443,22 @@ class ManualInstrumentation {
1618
1443
  },
1619
1444
  });
1620
1445
  return {
1621
- context: span.context,
1446
+ ...span,
1622
1447
  end: (options) => {
1623
1448
  const end = options ?? {};
1624
1449
  const output = end.output ?? [];
1625
1450
  let jsonOutput = '';
1626
1451
  try {
1627
- jsonOutput = JSON.stringify(output);
1452
+ const translated = translator.translate(output, {
1453
+ from: this.options.provider,
1454
+ to: rosettaAi.Provider.GenAI,
1455
+ direction: 'output',
1456
+ });
1457
+ jsonOutput = JSON.stringify(translated.messages ?? []);
1628
1458
  }
1629
1459
  catch (_error) {
1630
1460
  jsonOutput = '[]';
1631
1461
  }
1632
- const attrOutput = this.attribifyMessages('output', output);
1633
1462
  const tokens = {
1634
1463
  prompt: end.tokens?.prompt ?? 0,
1635
1464
  cached: end.tokens?.cached ?? 0,
@@ -1641,8 +1470,7 @@ class ManualInstrumentation {
1641
1470
  const finishReason = end.finishReason ?? '';
1642
1471
  span.end({
1643
1472
  attributes: {
1644
- [ATTRIBUTES.LATITUDE.response.messages]: jsonOutput,
1645
- ...attrOutput,
1473
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.output.messages]: jsonOutput,
1646
1474
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.inputTokens]: inputTokens,
1647
1475
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.outputTokens]: outputTokens,
1648
1476
  [ATTRIBUTES.LATITUDE.usage.promptTokens]: tokens.prompt,
@@ -1657,7 +1485,6 @@ class ManualInstrumentation {
1657
1485
  },
1658
1486
  });
1659
1487
  },
1660
- fail: span.fail,
1661
1488
  };
1662
1489
  }
1663
1490
  embedding(ctx, options) {
@@ -1669,7 +1496,7 @@ class ManualInstrumentation {
1669
1496
  : ATTRIBUTES.OPENTELEMETRY.HTTP.response.header;
1670
1497
  const attributes = {};
1671
1498
  for (const key in headers) {
1672
- const field = this.toKebabCase(key);
1499
+ const field = toKebabCase(key);
1673
1500
  const value = headers[key];
1674
1501
  if (value === null || value === undefined)
1675
1502
  continue;
@@ -1704,7 +1531,7 @@ class ManualInstrumentation {
1704
1531
  },
1705
1532
  });
1706
1533
  return {
1707
- context: span.context,
1534
+ ...span,
1708
1535
  end: (options) => {
1709
1536
  const end = options;
1710
1537
  // Note: do not serialize headers as a single attribute because fields won't be redacted
@@ -1730,7 +1557,6 @@ class ManualInstrumentation {
1730
1557
  },
1731
1558
  });
1732
1559
  },
1733
- fail: span.fail,
1734
1560
  };
1735
1561
  }
1736
1562
  prompt(ctx, { documentLogUuid, versionUuid, promptUuid, projectId, experimentUuid, testDeploymentId, externalId, template, parameters, name, source, ...rest }) {
@@ -1771,7 +1597,9 @@ class ManualInstrumentation {
1771
1597
  ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1772
1598
  ...(rest.attributes || {}),
1773
1599
  };
1774
- return this.span(ctx, name || 'chat', SpanType.Chat, { attributes });
1600
+ return this.span(ctx, name || `chat-${documentLogUuid}`, SpanType.Chat, {
1601
+ attributes,
1602
+ });
1775
1603
  }
1776
1604
  external(ctx, { promptUuid, documentLogUuid, source, versionUuid, externalId, name, ...rest }) {
1777
1605
  const attributes = {
@@ -1811,8 +1639,8 @@ class LatitudeInstrumentation {
1811
1639
  return this.manualTelemetry.isEnabled();
1812
1640
  }
1813
1641
  enable() {
1814
- this.options.module.instrument(this);
1815
1642
  this.manualTelemetry.enable();
1643
+ this.options.module.instrument(this);
1816
1644
  }
1817
1645
  disable() {
1818
1646
  this.manualTelemetry.disable();
@@ -2007,6 +1835,9 @@ class SpanFactory {
2007
1835
  constructor(telemetry) {
2008
1836
  this.telemetry = telemetry;
2009
1837
  }
1838
+ span(options, ctx) {
1839
+ return this.telemetry.unknown(ctx ?? otel.context.active(), options);
1840
+ }
2010
1841
  tool(options, ctx) {
2011
1842
  return this.telemetry.tool(ctx ?? otel.context.active(), options);
2012
1843
  }
@@ -2040,6 +1871,9 @@ class ContextManager {
2040
1871
  active() {
2041
1872
  return otel.context.active();
2042
1873
  }
1874
+ with(ctx, fn, thisArg, ...args) {
1875
+ return otel.context.with(ctx, fn, thisArg, ...args);
1876
+ }
2043
1877
  }
2044
1878
  class InstrumentationManager {
2045
1879
  instrumentations;
@@ -2086,6 +1920,23 @@ class ScopedTracerProvider {
2086
1920
  return this.provider.getTracer(this.scope, this.version, options);
2087
1921
  }
2088
1922
  }
1923
+ class LifecycleManager {
1924
+ nodeProvider;
1925
+ exporter;
1926
+ constructor(nodeProvider, exporter) {
1927
+ this.nodeProvider = nodeProvider;
1928
+ this.exporter = exporter;
1929
+ }
1930
+ async flush() {
1931
+ await this.nodeProvider.forceFlush();
1932
+ await this.exporter.forceFlush?.();
1933
+ }
1934
+ async shutdown() {
1935
+ await this.flush();
1936
+ await this.nodeProvider.shutdown();
1937
+ await this.exporter.shutdown?.();
1938
+ }
1939
+ }
2089
1940
  const DEFAULT_SPAN_EXPORTER = (apiKey) => new exporterTraceOtlpHttp.OTLPTraceExporter({
2090
1941
  url: TRACES_URL,
2091
1942
  headers: {
@@ -2104,6 +1955,7 @@ exports.Instrumentation = void 0;
2104
1955
  Instrumentation["Langchain"] = "langchain";
2105
1956
  Instrumentation["Latitude"] = "latitude";
2106
1957
  Instrumentation["LlamaIndex"] = "llamaindex";
1958
+ Instrumentation["Manual"] = "manual";
2107
1959
  Instrumentation["OpenAI"] = "openai";
2108
1960
  Instrumentation["TogetherAI"] = "togetherai";
2109
1961
  Instrumentation["VertexAI"] = "vertexai";
@@ -2117,6 +1969,7 @@ class LatitudeTelemetry {
2117
1969
  context;
2118
1970
  instrumentation;
2119
1971
  tracer;
1972
+ lifecycle;
2120
1973
  constructor(apiKey, options) {
2121
1974
  this.options = options || {};
2122
1975
  if (!this.options.exporter) {
@@ -2133,6 +1986,7 @@ class LatitudeTelemetry {
2133
1986
  this.nodeProvider = new sdkTraceNode.NodeTracerProvider({
2134
1987
  resource: new resources.Resource({ [semanticConventions.ATTR_SERVICE_NAME]: SERVICE_NAME }),
2135
1988
  });
1989
+ this.lifecycle = new LifecycleManager(this.nodeProvider, this.options.exporter);
2136
1990
  // Note: important, must run before the exporter span processors
2137
1991
  this.nodeProvider.addSpanProcessor(new baggageSpanProcessor.BaggageSpanProcessor(baggageSpanProcessor.ALLOW_ALL_BAGGAGE_KEYS));
2138
1992
  if (this.options.processors) {
@@ -2162,19 +2016,16 @@ class LatitudeTelemetry {
2162
2016
  this.context = new ContextManager(this.manualInstrumentation);
2163
2017
  }
2164
2018
  async flush() {
2165
- await this.nodeProvider.forceFlush();
2166
- await this.options.exporter.forceFlush?.();
2019
+ await this.lifecycle.flush();
2167
2020
  }
2168
2021
  async shutdown() {
2169
- await this.flush();
2170
- await this.nodeProvider.shutdown();
2171
- await this.options.exporter.shutdown?.();
2022
+ await this.lifecycle.shutdown();
2172
2023
  }
2173
2024
  // TODO(tracing): auto instrument outgoing HTTP requests
2174
2025
  initInstrumentations() {
2175
2026
  this.instrumentationsList = [];
2176
- const tracer = this.tracer.get(InstrumentationScope.Manual);
2177
- this.manualInstrumentation = new ManualInstrumentation(tracer);
2027
+ const tracer = this.tracer.get(exports.Instrumentation.Manual);
2028
+ this.manualInstrumentation = new ManualInstrumentation(tracer, this.options.instrumentations?.manual);
2178
2029
  this.instrumentationsList.push(this.manualInstrumentation);
2179
2030
  const latitude = this.options.instrumentations?.latitude;
2180
2031
  if (latitude) {
@@ -2184,12 +2035,12 @@ class LatitudeTelemetry {
2184
2035
  }
2185
2036
  const configureInstrumentation = (instrumentationType, InstrumentationConstructor, instrumentationOptions) => {
2186
2037
  const providerPkg = this.options.instrumentations?.[instrumentationType];
2038
+ if (!providerPkg)
2039
+ return;
2187
2040
  const provider = this.tracer.provider(instrumentationType);
2188
2041
  const instrumentation$1 = new InstrumentationConstructor(instrumentationOptions); // prettier-ignore
2189
2042
  instrumentation$1.setTracerProvider(provider);
2190
- if (providerPkg) {
2191
- instrumentation$1.manuallyInstrument(providerPkg);
2192
- }
2043
+ instrumentation$1.manuallyInstrument(providerPkg);
2193
2044
  instrumentation.registerInstrumentations({
2194
2045
  instrumentations: [instrumentation$1],
2195
2046
  tracerProvider: provider,
@@ -2211,19 +2062,17 @@ class LatitudeTelemetry {
2211
2062
  if (!DOCUMENT_PATH_REGEXP.test(options.path)) {
2212
2063
  throw new BadRequestError("Invalid path, no spaces. Only letters, numbers, '.', '-' and '_'");
2213
2064
  }
2214
- const span = this.manualInstrumentation.unresolvedExternal(otel__namespace.ROOT_CONTEXT, options);
2065
+ const span = this.manualInstrumentation.unresolvedExternal(BACKGROUND(), options);
2066
+ let result;
2215
2067
  try {
2216
- const result = await otel.context.with(span.context, () => fn(span.context));
2217
- span.end();
2218
- return result;
2068
+ result = await otel.context.with(span.context, async () => await fn(span.context));
2219
2069
  }
2220
2070
  catch (error) {
2221
2071
  span.fail(error);
2222
2072
  throw error;
2223
2073
  }
2224
- finally {
2225
- await this.flush();
2226
- }
2074
+ span.end();
2075
+ return result;
2227
2076
  }
2228
2077
  }
2229
2078