@latitude-data/telemetry 2.0.1 → 3.0.0-beta.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.d.ts CHANGED
@@ -18,23 +18,6 @@ type LatitudeInstrumentationOptions = {
18
18
  completions?: boolean;
19
19
  };
20
20
 
21
- declare enum LogSources {
22
- API = "api",
23
- AgentAsTool = "agent_as_tool",
24
- Copilot = "copilot",
25
- EmailTrigger = "email_trigger",
26
- Evaluation = "evaluation",
27
- Experiment = "experiment",
28
- IntegrationTrigger = "integration_trigger",
29
- Playground = "playground",
30
- ScheduledTrigger = "scheduled_trigger",
31
- SharedPrompt = "shared_prompt",
32
- ShadowTest = "shadow_test",
33
- ABTestChallenger = "ab_test_challenger",
34
- User = "user",
35
- Optimization = "optimization"
36
- }
37
-
38
21
  declare const traceContextSchema: z.ZodObject<{
39
22
  traceparent: z.ZodString;
40
23
  tracestate: z.ZodOptional<z.ZodString>;
@@ -52,12 +35,13 @@ type EndSpanOptions = {
52
35
  type ErrorOptions = {
53
36
  attributes?: otel.Attributes;
54
37
  };
55
- type StartToolSpanOptions = StartSpanOptions & {
38
+ type StartToolSpanOptions = {
56
39
  name: string;
57
40
  call: {
58
41
  id: string;
59
42
  arguments: Record<string, unknown>;
60
43
  };
44
+ attributes?: otel.Attributes;
61
45
  };
62
46
  type EndToolSpanOptions = EndSpanOptions & {
63
47
  result: {
@@ -65,14 +49,13 @@ type EndToolSpanOptions = EndSpanOptions & {
65
49
  isError: boolean;
66
50
  };
67
51
  };
68
- type StartCompletionSpanOptions = StartSpanOptions & {
52
+ type StartCompletionSpanOptions = {
53
+ name?: string;
69
54
  provider: string;
70
55
  model: string;
71
56
  configuration?: Record<string, unknown>;
72
57
  input?: string | Record<string, unknown>[];
73
- versionUuid?: string;
74
- promptUuid?: string;
75
- experimentUuid?: string;
58
+ attributes?: otel.Attributes;
76
59
  };
77
60
  type EndCompletionSpanOptions = EndSpanOptions & {
78
61
  output?: string | Record<string, unknown>[];
@@ -84,13 +67,15 @@ type EndCompletionSpanOptions = EndSpanOptions & {
84
67
  };
85
68
  finishReason?: string;
86
69
  };
87
- type StartHttpSpanOptions = StartSpanOptions & {
70
+ type StartHttpSpanOptions = {
71
+ name?: string;
88
72
  request: {
89
73
  method: string;
90
74
  url: string;
91
75
  headers: Record<string, string>;
92
76
  body: string | Record<string, unknown>;
93
77
  };
78
+ attributes?: otel.Attributes;
94
79
  };
95
80
  type EndHttpSpanOptions = EndSpanOptions & {
96
81
  response: {
@@ -99,37 +84,28 @@ type EndHttpSpanOptions = EndSpanOptions & {
99
84
  body: string | Record<string, unknown>;
100
85
  };
101
86
  };
102
- type PromptSpanOptions = StartSpanOptions & {
103
- documentLogUuid: string;
104
- versionUuid?: string;
105
- promptUuid: string;
106
- projectId?: number;
107
- experimentUuid?: string;
108
- testDeploymentId?: number;
109
- externalId?: string;
87
+ type PromptSpanOptions = {
88
+ name?: string;
110
89
  template: string;
111
90
  parameters?: Record<string, unknown>;
112
- source?: LogSources;
91
+ attributes?: otel.Attributes;
113
92
  };
114
- type ChatSpanOptions = StartSpanOptions & {
115
- documentLogUuid: string;
116
- previousTraceId: string;
117
- source?: LogSources;
118
- versionUuid?: string;
119
- promptUuid?: string;
93
+ type ChatSpanOptions = {
94
+ name?: string;
95
+ attributes?: otel.Attributes;
120
96
  };
121
- type ExternalSpanOptions = StartSpanOptions & {
122
- promptUuid: string;
123
- documentLogUuid: string;
124
- source?: LogSources;
125
- versionUuid?: string;
97
+ type ExternalSpanOptions = {
98
+ name?: string;
126
99
  externalId?: string;
100
+ attributes?: otel.Attributes;
127
101
  };
128
- type CaptureOptions = StartSpanOptions & {
102
+ type CaptureOptions = {
103
+ name?: string;
129
104
  path: string;
130
105
  projectId: number;
131
106
  versionUuid?: string;
132
107
  conversationUuid?: string;
108
+ attributes?: otel.Attributes;
133
109
  };
134
110
  type ManualInstrumentationOptions = {
135
111
  provider?: Provider;
@@ -172,22 +148,22 @@ declare class ManualInstrumentation implements BaseInstrumentation {
172
148
  context: otel.Context;
173
149
  fail: (_error: Error, _options?: ErrorOptions) => void;
174
150
  };
175
- prompt(ctx: otel.Context, { documentLogUuid, versionUuid, promptUuid, projectId, experimentUuid, testDeploymentId, externalId, template, parameters, name, source, ...rest }: PromptSpanOptions): {
151
+ prompt(ctx: otel.Context, options: PromptSpanOptions): {
176
152
  context: otel.Context;
177
153
  end: (_options?: EndSpanOptions) => void;
178
154
  fail: (_error: Error, _options?: ErrorOptions) => void;
179
155
  };
180
- chat(ctx: otel.Context, { documentLogUuid, previousTraceId, source, name, versionUuid, promptUuid, ...rest }: ChatSpanOptions): {
156
+ chat(ctx: otel.Context, options: ChatSpanOptions): {
181
157
  context: otel.Context;
182
158
  end: (_options?: EndSpanOptions) => void;
183
159
  fail: (_error: Error, _options?: ErrorOptions) => void;
184
160
  };
185
- external(ctx: otel.Context, { promptUuid, documentLogUuid, source, versionUuid, externalId, name, ...rest }: ExternalSpanOptions): {
161
+ external(ctx: otel.Context, options: ExternalSpanOptions): {
186
162
  context: otel.Context;
187
163
  end: (_options?: EndSpanOptions) => void;
188
164
  fail: (_error: Error, _options?: ErrorOptions) => void;
189
165
  };
190
- unresolvedExternal(ctx: otel.Context, { path, projectId, versionUuid, conversationUuid, name, ...rest }: CaptureOptions): {
166
+ unresolvedExternal(ctx: otel.Context, options: CaptureOptions): {
191
167
  context: otel.Context;
192
168
  end: (_options?: EndSpanOptions) => void;
193
169
  fail: (_error: Error, _options?: ErrorOptions) => void;
@@ -262,6 +238,40 @@ declare class ContextManager {
262
238
  resume(ctx: TraceContext): otel.Context;
263
239
  active(): otel.Context;
264
240
  with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(ctx: TelemetryContext, fn: F, thisArg?: ThisParameterType<F>, ...args: A): ReturnType<F>;
241
+ /**
242
+ * Sets custom attributes in the OpenTelemetry baggage that will be
243
+ * automatically propagated to all child spans in the trace.
244
+ *
245
+ * This is useful for setting trace-level metadata that should be
246
+ * inherited by all spans without manually passing them to each span.
247
+ *
248
+ * Example:
249
+ * ```typescript
250
+ * const ctx = telemetry.context.setAttributes(
251
+ * telemetry.context.active(),
252
+ * {
253
+ * 'latitude.documentLogUuid': 'uuid-123',
254
+ * 'latitude.documentUuid': 'prompt-456',
255
+ * 'latitude.commitUuid': 'commit-789',
256
+ * 'latitude.projectId': '123',
257
+ * 'custom.attribute': 'value'
258
+ * }
259
+ * )
260
+ *
261
+ * // Latitude keys are normalized to snake_case in baggage:
262
+ * // latitude.document_log_uuid, latitude.document_uuid, ...
263
+ *
264
+ * // Use context.with() to make the context active
265
+ * // All spans created within this context will automatically
266
+ * // inherit the baggage attributes without explicitly passing ctx
267
+ * telemetry.context.with(ctx, () => {
268
+ * telemetry.span.tool({ name: 'my-tool', call: { id: '1', arguments: {} } })
269
+ * // ... other spans
270
+ * })
271
+ * ```
272
+ */
273
+ setAttributes(ctx: TelemetryContext, attributes: Record<string, string>): TelemetryContext;
274
+ private normalizeBaggageKey;
265
275
  }
266
276
  declare class InstrumentationManager {
267
277
  private readonly instrumentations;
package/dist/index.js CHANGED
@@ -4,7 +4,6 @@ import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVICE_
4
4
  import * as otel from '@opentelemetry/api';
5
5
  import { trace, propagation, context } from '@opentelemetry/api';
6
6
  import { Translator, Provider } from 'rosetta-ai';
7
- import { v4 } from 'uuid';
8
7
  import { BaggageSpanProcessor, ALLOW_ALL_BAGGAGE_KEYS } from '@opentelemetry/baggage-span-processor';
9
8
  import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
10
9
  import { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator } from '@opentelemetry/core';
@@ -198,17 +197,23 @@ const expectedOutputConfiguration = z.object({
198
197
  fieldAccessor: z.string().optional(), // Field accessor to get the output from if it's a key-value format
199
198
  });
200
199
  const EVALUATION_TRIGGER_TARGETS = ['first', 'every', 'last'];
201
- const DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS = 120;
202
200
  const LAST_INTERACTION_DEBOUNCE_MIN_SECONDS = 30;
203
201
  const LAST_INTERACTION_DEBOUNCE_MAX_SECONDS = 60 * 60 * 24; // 1 day
202
+ const MIN_EVALUATION_SAMPLE_RATE = 0; // 0%
203
+ const MAX_EVALUATION_SAMPLE_RATE = 100; // 100%
204
204
  const triggerConfiguration = z.object({
205
205
  target: z.enum(EVALUATION_TRIGGER_TARGETS),
206
206
  lastInteractionDebounce: z
207
207
  .number()
208
208
  .min(LAST_INTERACTION_DEBOUNCE_MIN_SECONDS)
209
209
  .max(LAST_INTERACTION_DEBOUNCE_MAX_SECONDS)
210
- .optional()
211
- .default(DEFAULT_LAST_INTERACTION_DEBOUNCE_SECONDS),
210
+ .optional(),
211
+ sampleRate: z
212
+ .number()
213
+ .int()
214
+ .min(MIN_EVALUATION_SAMPLE_RATE)
215
+ .max(MAX_EVALUATION_SAMPLE_RATE)
216
+ .optional(),
212
217
  });
213
218
  const baseEvaluationConfiguration = z.object({
214
219
  reverseScale: z.boolean(), // If true, lower is better, otherwise, higher is better
@@ -921,16 +926,11 @@ const ATTRIBUTES = {
921
926
  // Custom attributes added and used by Latitude spans (Prompt / External / Chat)
922
927
  LATITUDE: {
923
928
  type: 'latitude.type',
924
- documentUuid: 'latitude.document_uuid',
925
929
  promptPath: 'latitude.prompt_path',
926
930
  commitUuid: 'latitude.commit_uuid',
927
931
  documentLogUuid: 'latitude.document_log_uuid',
928
932
  projectId: 'latitude.project_id',
929
- experimentUuid: 'latitude.experiment_uuid',
930
- source: 'latitude.source',
931
933
  externalId: 'latitude.external_id',
932
- testDeploymentId: 'latitude.test_deployment_id',
933
- previousTraceId: 'latitude.previous_trace_id',
934
934
  // Custom additions to the GenAI semantic conventions (deprecated)
935
935
  request: {
936
936
  _root: 'gen_ai.request',
@@ -1151,6 +1151,12 @@ const OptimizationBudgetSchema = z.object({
1151
1151
  tokens: z.number().min(0).optional(),
1152
1152
  });
1153
1153
  z.object({
1154
+ dataset: z
1155
+ .object({
1156
+ target: z.number().min(0).optional(), // Note: number of rows to curate when not provided by the user
1157
+ label: z.string().optional(), // Note: expected output column when using a labeled evaluation
1158
+ })
1159
+ .optional(),
1154
1160
  parameters: z
1155
1161
  .record(z.string(), z.object({
1156
1162
  column: z.string().optional(), // Note: corresponding column in the user-provided trainset and testset
@@ -1168,7 +1174,6 @@ z.object({
1168
1174
  });
1169
1175
 
1170
1176
  // TODO(tracing): deprecated
1171
- const HEAD_COMMIT = 'live';
1172
1177
  var Providers;
1173
1178
  (function (Providers) {
1174
1179
  Providers["OpenAI"] = "openai";
@@ -1323,6 +1328,7 @@ class ManualInstrumentation {
1323
1328
  jsonArguments = '{}';
1324
1329
  }
1325
1330
  const span = this.span(ctx, start.name, SpanType.Tool, {
1331
+ ...start,
1326
1332
  attributes: {
1327
1333
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.name]: start.name,
1328
1334
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.type]: VALUES.OPENTELEMETRY.GEN_AI.tool.type.function,
@@ -1410,6 +1416,7 @@ class ManualInstrumentation {
1410
1416
  jsonInput = '[]';
1411
1417
  }
1412
1418
  const span = this.span(ctx, start.name || `${start.provider} / ${start.model}`, SpanType.Completion, {
1419
+ ...start,
1413
1420
  attributes: {
1414
1421
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.system]: start.provider,
1415
1422
  [ATTRIBUTES.LATITUDE.request.configuration]: jsonConfiguration,
@@ -1417,9 +1424,6 @@ class ManualInstrumentation {
1417
1424
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.systemInstructions]: jsonSystem,
1418
1425
  [ATTRIBUTES.OPENTELEMETRY.GEN_AI.input.messages]: jsonInput,
1419
1426
  ...(start.attributes || {}),
1420
- [ATTRIBUTES.LATITUDE.commitUuid]: start.versionUuid,
1421
- [ATTRIBUTES.LATITUDE.documentUuid]: start.promptUuid,
1422
- [ATTRIBUTES.LATITUDE.experimentUuid]: start.experimentUuid,
1423
1427
  },
1424
1428
  });
1425
1429
  return {
@@ -1502,6 +1506,7 @@ class ManualInstrumentation {
1502
1506
  }
1503
1507
  }
1504
1508
  const span = this.span(ctx, start.name || `${method} ${start.request.url}`, SpanType.Http, {
1509
+ ...start,
1505
1510
  attributes: {
1506
1511
  [ATTRIBUTES.OPENTELEMETRY.HTTP.request.method]: method,
1507
1512
  [ATTRIBUTES.OPENTELEMETRY.HTTP.request.url]: start.request.url,
@@ -1539,7 +1544,8 @@ class ManualInstrumentation {
1539
1544
  },
1540
1545
  };
1541
1546
  }
1542
- prompt(ctx, { documentLogUuid, versionUuid, promptUuid, projectId, experimentUuid, testDeploymentId, externalId, template, parameters, name, source, ...rest }) {
1547
+ prompt(ctx, options) {
1548
+ const { template, parameters, name, attributes: userAttributes } = options;
1543
1549
  let jsonParameters = '';
1544
1550
  try {
1545
1551
  jsonParameters = JSON.stringify(parameters || {});
@@ -1550,51 +1556,27 @@ class ManualInstrumentation {
1550
1556
  const attributes = {
1551
1557
  [ATTRIBUTES.LATITUDE.request.template]: template,
1552
1558
  [ATTRIBUTES.LATITUDE.request.parameters]: jsonParameters,
1553
- [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid || HEAD_COMMIT,
1554
- [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid,
1555
- [ATTRIBUTES.LATITUDE.projectId]: projectId,
1556
- [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1557
- ...(experimentUuid && {
1558
- [ATTRIBUTES.LATITUDE.experimentUuid]: experimentUuid,
1559
- }),
1560
- ...(testDeploymentId && {
1561
- [ATTRIBUTES.LATITUDE.testDeploymentId]: testDeploymentId,
1562
- }),
1563
- ...(externalId && { [ATTRIBUTES.LATITUDE.externalId]: externalId }),
1564
- ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1565
- ...(rest.attributes || {}),
1559
+ ...(userAttributes || {}),
1566
1560
  };
1567
- return this.span(ctx, name || `prompt-${promptUuid}`, SpanType.Prompt, {
1568
- attributes,
1569
- });
1561
+ return this.span(ctx, name || 'prompt', SpanType.Prompt, { attributes });
1570
1562
  }
1571
- chat(ctx, { documentLogUuid, previousTraceId, source, name, versionUuid, promptUuid, ...rest }) {
1563
+ chat(ctx, options) {
1564
+ const { name, attributes: userAttributes } = options;
1572
1565
  const attributes = {
1573
- [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1574
- [ATTRIBUTES.LATITUDE.previousTraceId]: previousTraceId,
1575
- ...(versionUuid && { [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid }),
1576
- ...(promptUuid && { [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid }),
1577
- ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1578
- ...(rest.attributes || {}),
1566
+ ...(userAttributes || {}),
1579
1567
  };
1580
- return this.span(ctx, name || `chat-${documentLogUuid}`, SpanType.Chat, {
1581
- attributes,
1582
- });
1568
+ return this.span(ctx, name || 'chat', SpanType.Chat, { attributes });
1583
1569
  }
1584
- external(ctx, { promptUuid, documentLogUuid, source, versionUuid, externalId, name, ...rest }) {
1570
+ external(ctx, options) {
1571
+ const { externalId, name, attributes: userAttributes } = options;
1585
1572
  const attributes = {
1586
- [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid,
1587
- [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1588
- [ATTRIBUTES.LATITUDE.source]: source ?? LogSources.API,
1589
- ...(versionUuid && { [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid }),
1590
1573
  ...(externalId && { [ATTRIBUTES.LATITUDE.externalId]: externalId }),
1591
- ...(rest.attributes || {}),
1574
+ ...(userAttributes || {}),
1592
1575
  };
1593
- return this.span(ctx, name || `external-${promptUuid}`, SpanType.External, {
1594
- attributes,
1595
- });
1576
+ return this.span(ctx, name || 'external', SpanType.External, { attributes });
1596
1577
  }
1597
- unresolvedExternal(ctx, { path, projectId, versionUuid, conversationUuid, name, ...rest }) {
1578
+ unresolvedExternal(ctx, options) {
1579
+ const { path, projectId, versionUuid, conversationUuid, name, attributes: userAttributes, } = options;
1598
1580
  const attributes = {
1599
1581
  [ATTRIBUTES.LATITUDE.promptPath]: path,
1600
1582
  [ATTRIBUTES.LATITUDE.projectId]: projectId,
@@ -1602,7 +1584,7 @@ class ManualInstrumentation {
1602
1584
  ...(conversationUuid && {
1603
1585
  [ATTRIBUTES.LATITUDE.documentLogUuid]: conversationUuid,
1604
1586
  }),
1605
- ...(rest.attributes || {}),
1587
+ ...(userAttributes || {}),
1606
1588
  };
1607
1589
  return this.span(ctx, name || `capture-${path}`, SpanType.UnresolvedExternal, { attributes });
1608
1590
  }
@@ -1648,9 +1630,6 @@ class LatitudeInstrumentation {
1648
1630
  async wrapRenderChain(fn, ...args) {
1649
1631
  const { prompt, parameters } = args[0];
1650
1632
  const $prompt = this.manualTelemetry.prompt(context.active(), {
1651
- documentLogUuid: v4(),
1652
- versionUuid: prompt.versionUuid,
1653
- promptUuid: prompt.uuid,
1654
1633
  template: prompt.content,
1655
1634
  parameters: parameters,
1656
1635
  });
@@ -1854,6 +1833,51 @@ class ContextManager {
1854
1833
  with(ctx, fn, thisArg, ...args) {
1855
1834
  return context.with(ctx, fn, thisArg, ...args);
1856
1835
  }
1836
+ /**
1837
+ * Sets custom attributes in the OpenTelemetry baggage that will be
1838
+ * automatically propagated to all child spans in the trace.
1839
+ *
1840
+ * This is useful for setting trace-level metadata that should be
1841
+ * inherited by all spans without manually passing them to each span.
1842
+ *
1843
+ * Example:
1844
+ * ```typescript
1845
+ * const ctx = telemetry.context.setAttributes(
1846
+ * telemetry.context.active(),
1847
+ * {
1848
+ * 'latitude.documentLogUuid': 'uuid-123',
1849
+ * 'latitude.documentUuid': 'prompt-456',
1850
+ * 'latitude.commitUuid': 'commit-789',
1851
+ * 'latitude.projectId': '123',
1852
+ * 'custom.attribute': 'value'
1853
+ * }
1854
+ * )
1855
+ *
1856
+ * // Latitude keys are normalized to snake_case in baggage:
1857
+ * // latitude.document_log_uuid, latitude.document_uuid, ...
1858
+ *
1859
+ * // Use context.with() to make the context active
1860
+ * // All spans created within this context will automatically
1861
+ * // inherit the baggage attributes without explicitly passing ctx
1862
+ * telemetry.context.with(ctx, () => {
1863
+ * telemetry.span.tool({ name: 'my-tool', call: { id: '1', arguments: {} } })
1864
+ * // ... other spans
1865
+ * })
1866
+ * ```
1867
+ */
1868
+ setAttributes(ctx, attributes) {
1869
+ const baggage = propagation.getBaggage(ctx) || propagation.createBaggage({});
1870
+ const newBaggage = Object.entries(attributes).reduce((bag, [key, value]) => bag.setEntry(this.normalizeBaggageKey(key), { value }), baggage);
1871
+ return propagation.setBaggage(ctx, newBaggage);
1872
+ }
1873
+ normalizeBaggageKey(key) {
1874
+ if (!key.startsWith('latitude.'))
1875
+ return key;
1876
+ const [namespace, ...path] = key.split('.');
1877
+ if (!namespace || path.length === 0)
1878
+ return key;
1879
+ return [namespace, ...path.map(toSnakeCase)].join('.');
1880
+ }
1857
1881
  }
1858
1882
  class InstrumentationManager {
1859
1883
  instrumentations;