@latitude-data/telemetry 1.0.4 → 1.1.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.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
- import * as otel from '@opentelemetry/api';
3
- import { propagation, trace, context } from '@opentelemetry/api';
4
- import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
5
2
  import { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_TYPE, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_RESPONSE_FINISH_REASONS, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_GEN_AI_USAGE_INPUT_TOKENS } from '@opentelemetry/semantic-conventions/incubating';
3
+ import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
4
+ import * as otel from '@opentelemetry/api';
5
+ import { trace, propagation, context } from '@opentelemetry/api';
6
+ import { v4 } from 'uuid';
6
7
  import { BaggageSpanProcessor, ALLOW_ALL_BAGGAGE_KEYS } from '@opentelemetry/baggage-span-processor';
7
8
  import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
8
9
  import { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator } from '@opentelemetry/core';
@@ -74,7 +75,7 @@ const DEFAULT_REDACT_SPAN_PROCESSOR = () => new RedactSpanProcessor({
74
75
  attributes: [
75
76
  /^.*auth.*$/i,
76
77
  /^.*authorization.*$/i,
77
- /^(?!ai\.).*usage.*$/i,
78
+ /^(?!gen_ai\.).*usage.*$/i,
78
79
  /^(?!gen_ai\.).*token.*$/i,
79
80
  /^.*secret.*$/i,
80
81
  /^.*key.*$/i,
@@ -129,6 +130,15 @@ z.object({
129
130
  isError: z.boolean().optional(),
130
131
  text: z.string().optional(),
131
132
  });
133
+ z.object({
134
+ inputTokens: z.number(),
135
+ outputTokens: z.number(),
136
+ promptTokens: z.number(),
137
+ completionTokens: z.number(),
138
+ totalTokens: z.number(),
139
+ reasoningTokens: z.number(),
140
+ cachedInputTokens: z.number(),
141
+ });
132
142
 
133
143
  var ParameterType;
134
144
  (function (ParameterType) {
@@ -254,12 +264,34 @@ const CompositeEvaluationSpecification = {
254
264
  },
255
265
  };
256
266
 
267
+ const selectedContextSchema = z.object({
268
+ messageIndex: z.number().int().nonnegative(),
269
+ contentBlockIndex: z.number().int().nonnegative(),
270
+ contentType: z.enum([
271
+ 'text',
272
+ 'reasoning',
273
+ 'image',
274
+ 'file',
275
+ 'tool-call',
276
+ 'tool-result',
277
+ ]),
278
+ textRange: z
279
+ .object({
280
+ start: z.number().int().nonnegative(),
281
+ end: z.number().int().nonnegative(),
282
+ })
283
+ .optional(),
284
+ selectedText: z.string().optional(),
285
+ toolCallId: z.string().optional(),
286
+ });
257
287
  const humanEvaluationConfiguration = baseEvaluationConfiguration.extend({
258
- enableControls: z.boolean().optional(),
288
+ enableControls: z.boolean().optional(), // UI annotation controls
259
289
  criteria: z.string().optional(),
260
290
  });
261
291
  const humanEvaluationResultMetadata = baseEvaluationResultMetadata.extend({
262
292
  reason: z.string().optional(),
293
+ enrichedReason: z.string().optional(),
294
+ selectedContexts: z.array(selectedContextSchema).optional(),
263
295
  });
264
296
  const humanEvaluationResultError = baseEvaluationResultError.extend({});
265
297
  // BINARY
@@ -537,8 +569,6 @@ z.object({
537
569
  });
538
570
  z.object({
539
571
  evaluateLiveLogs: z.boolean().nullable().optional(),
540
- enableSuggestions: z.boolean().nullable().optional(),
541
- autoApplySuggestions: z.boolean().nullable().optional(),
542
572
  });
543
573
 
544
574
  var LegacyChainEventTypes;
@@ -736,6 +766,7 @@ var LogSources;
736
766
  LogSources["ShadowTest"] = "shadow_test";
737
767
  LogSources["ABTestChallenger"] = "ab_test_challenger";
738
768
  LogSources["User"] = "user";
769
+ LogSources["Optimization"] = "optimization";
739
770
  })(LogSources || (LogSources = {}));
740
771
 
741
772
  var RunSourceGroup;
@@ -746,7 +777,8 @@ var RunSourceGroup;
746
777
  ({
747
778
  [RunSourceGroup.Production]: [
748
779
  LogSources.API,
749
- LogSources.Copilot,
780
+ LogSources.ShadowTest,
781
+ LogSources.ABTestChallenger,
750
782
  LogSources.EmailTrigger,
751
783
  LogSources.IntegrationTrigger,
752
784
  LogSources.ScheduledTrigger,
@@ -775,23 +807,49 @@ var SpanKind;
775
807
  // Note: loosely based on OpenTelemetry GenAI semantic conventions
776
808
  var SpanType;
777
809
  (function (SpanType) {
778
- SpanType["Tool"] = "tool";
810
+ // Latitude wrappers
811
+ SpanType["Prompt"] = "prompt";
812
+ SpanType["Chat"] = "chat";
813
+ SpanType["External"] = "external";
814
+ SpanType["UnresolvedExternal"] = "unresolved_external";
815
+ // Added a HTTP span to capture raw HTTP requests and responses when running from Latitude
816
+ SpanType["Http"] = "http";
817
+ // Any known span from supported specifications will be grouped into one of these types
779
818
  SpanType["Completion"] = "completion";
819
+ SpanType["Tool"] = "tool";
780
820
  SpanType["Embedding"] = "embedding";
781
- SpanType["Retrieval"] = "retrieval";
782
- SpanType["Reranking"] = "reranking";
783
- SpanType["Http"] = "http";
784
821
  SpanType["Unknown"] = "unknown";
785
- SpanType["Prompt"] = "prompt";
786
- SpanType["Step"] = "step";
787
822
  })(SpanType || (SpanType = {}));
823
+ [
824
+ SpanType.Prompt,
825
+ SpanType.External,
826
+ SpanType.Chat,
827
+ ];
788
828
  const SPAN_SPECIFICATIONS = {
789
- [SpanType.Tool]: {
790
- name: 'Tool',
791
- description: 'A tool call',
792
- isGenAI: true,
829
+ [SpanType.Prompt]: {
830
+ name: 'Prompt',
831
+ description: 'A prompt span',
832
+ isGenAI: false,
793
833
  isHidden: false,
794
834
  },
835
+ [SpanType.Chat]: {
836
+ name: 'Chat',
837
+ description: 'A chat continuation span',
838
+ isGenAI: false,
839
+ isHidden: false,
840
+ },
841
+ [SpanType.External]: {
842
+ name: 'External',
843
+ description: 'An external capture span',
844
+ isGenAI: false,
845
+ isHidden: false,
846
+ },
847
+ [SpanType.UnresolvedExternal]: {
848
+ name: 'Unresolved External',
849
+ description: 'An external span that needs path resolution before storage',
850
+ isGenAI: false,
851
+ isHidden: true,
852
+ },
795
853
  [SpanType.Completion]: {
796
854
  name: 'Completion',
797
855
  description: 'A completion call',
@@ -804,15 +862,9 @@ const SPAN_SPECIFICATIONS = {
804
862
  isGenAI: true,
805
863
  isHidden: false,
806
864
  },
807
- [SpanType.Retrieval]: {
808
- name: 'Retrieval',
809
- description: 'A retrieval call',
810
- isGenAI: true,
811
- isHidden: false,
812
- },
813
- [SpanType.Reranking]: {
814
- name: 'Reranking',
815
- description: 'A reranking call',
865
+ [SpanType.Tool]: {
866
+ name: 'Tool',
867
+ description: 'A tool call',
816
868
  isGenAI: true,
817
869
  isHidden: false,
818
870
  },
@@ -828,18 +880,6 @@ const SPAN_SPECIFICATIONS = {
828
880
  isGenAI: false,
829
881
  isHidden: true,
830
882
  },
831
- [SpanType.Prompt]: {
832
- name: 'Prompt',
833
- description: 'A prompt span',
834
- isGenAI: false,
835
- isHidden: false,
836
- },
837
- [SpanType.Step]: {
838
- name: 'Step',
839
- description: 'A step span',
840
- isGenAI: false,
841
- isHidden: false,
842
- },
843
883
  };
844
884
  var SpanStatus;
845
885
  (function (SpanStatus) {
@@ -847,6 +887,11 @@ var SpanStatus;
847
887
  SpanStatus["Ok"] = "ok";
848
888
  SpanStatus["Error"] = "error";
849
889
  })(SpanStatus || (SpanStatus = {}));
890
+ new Set([
891
+ SpanType.Prompt,
892
+ SpanType.Chat,
893
+ SpanType.External,
894
+ ]);
850
895
 
851
896
  // Note: Traces are unmaterialized but this context is used to propagate the trace
852
897
  // See www.w3.org/TR/trace-context and w3c.github.io/baggage
@@ -856,6 +901,130 @@ z.object({
856
901
  baggage: z.string().optional(), // <key>=urlencoded(<value>)[,<key>=urlencoded(<value>)]*
857
902
  });
858
903
 
904
+ const ATTRIBUTES = {
905
+ // Custom attributes added and used by Latitude spans (Prompt / External / Chat)
906
+ LATITUDE: {
907
+ type: 'latitude.type',
908
+ documentUuid: 'latitude.document_uuid',
909
+ promptPath: 'latitude.prompt_path',
910
+ commitUuid: 'latitude.commit_uuid',
911
+ documentLogUuid: 'latitude.document_log_uuid',
912
+ projectId: 'latitude.project_id',
913
+ experimentUuid: 'latitude.experiment_uuid',
914
+ source: 'latitude.source',
915
+ externalId: 'latitude.external_id',
916
+ testDeploymentId: 'latitude.test_deployment_id',
917
+ previousTraceId: 'latitude.previous_trace_id',
918
+ // Custom additions to the GenAI semantic conventions (deprecated)
919
+ request: {
920
+ _root: 'gen_ai.request',
921
+ configuration: 'gen_ai.request.configuration',
922
+ template: 'gen_ai.request.template',
923
+ parameters: 'gen_ai.request.parameters',
924
+ messages: 'gen_ai.request.messages'},
925
+ response: {
926
+ _root: 'gen_ai.response',
927
+ messages: 'gen_ai.response.messages',
928
+ },
929
+ usage: {
930
+ promptTokens: 'gen_ai.usage.prompt_tokens',
931
+ cachedTokens: 'gen_ai.usage.cached_tokens',
932
+ reasoningTokens: 'gen_ai.usage.reasoning_tokens',
933
+ completionTokens: 'gen_ai.usage.completion_tokens',
934
+ },
935
+ },
936
+ // Official OpenTelemetry semantic conventions
937
+ OPENTELEMETRY: {
938
+ HTTP: {
939
+ request: {
940
+ url: 'http.request.url',
941
+ body: 'http.request.body',
942
+ header: 'http.request.header',
943
+ method: ATTR_HTTP_REQUEST_METHOD,
944
+ },
945
+ response: {
946
+ body: 'http.response.body',
947
+ header: 'http.response.header',
948
+ statusCode: ATTR_HTTP_RESPONSE_STATUS_CODE,
949
+ },
950
+ },
951
+ // GenAI semantic conventions
952
+ // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
953
+ GEN_AI: {
954
+ operation: ATTR_GEN_AI_OPERATION_NAME,
955
+ response: {
956
+ model: ATTR_GEN_AI_RESPONSE_MODEL,
957
+ finishReasons: ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
958
+ },
959
+ usage: {
960
+ inputTokens: ATTR_GEN_AI_USAGE_INPUT_TOKENS,
961
+ outputTokens: ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
962
+ },
963
+ tool: {
964
+ call: {
965
+ id: ATTR_GEN_AI_TOOL_CALL_ID,
966
+ arguments: 'gen_ai.tool.call.arguments'}},
967
+ _deprecated: {
968
+ system: ATTR_GEN_AI_SYSTEM,
969
+ tool: {
970
+ name: ATTR_GEN_AI_TOOL_NAME,
971
+ type: ATTR_GEN_AI_TOOL_TYPE,
972
+ result: {
973
+ value: 'gen_ai.tool.result.value',
974
+ isError: 'gen_ai.tool.result.is_error',
975
+ },
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
+ }},
1011
+ },
1012
+ }};
1013
+ const VALUES = {
1014
+ OPENTELEMETRY: {
1015
+ GEN_AI: {
1016
+ response: {
1017
+ finishReasons: {
1018
+ stop: 'stop',
1019
+ toolCalls: 'tool_calls'},
1020
+ },
1021
+ tool: {
1022
+ type: {
1023
+ function: 'function',
1024
+ },
1025
+ }},
1026
+ }};
1027
+
859
1028
  /* Note: Instrumentation scopes from all language SDKs */
860
1029
  const SCOPE_LATITUDE = 'so.latitude.instrumentation';
861
1030
  var InstrumentationScope;
@@ -884,43 +1053,6 @@ var InstrumentationScope;
884
1053
  InstrumentationScope["Transformers"] = "transformers";
885
1054
  InstrumentationScope["AlephAlpha"] = "alephalpha";
886
1055
  })(InstrumentationScope || (InstrumentationScope = {}));
887
- /* Note: non-standard OpenTelemetry semantic conventions used in Latitude */
888
- const ATTR_LATITUDE = 'latitude';
889
- const ATTR_LATITUDE_TYPE = `${ATTR_LATITUDE}.type`;
890
- const ATTR_LATITUDE_TEST_DEPLOYMENT_ID = `${ATTR_LATITUDE}.test_deployment_id`;
891
- const GEN_AI_TOOL_TYPE_VALUE_FUNCTION = 'function';
892
- const ATTR_GEN_AI_TOOL_CALL_ARGUMENTS = 'gen_ai.tool.call.arguments';
893
- const ATTR_GEN_AI_TOOL_RESULT_VALUE = 'gen_ai.tool.result.value';
894
- const ATTR_GEN_AI_TOOL_RESULT_IS_ERROR = 'gen_ai.tool.result.is_error';
895
- const ATTR_GEN_AI_REQUEST = 'gen_ai.request';
896
- const ATTR_GEN_AI_REQUEST_CONFIGURATION = 'gen_ai.request.configuration';
897
- const ATTR_GEN_AI_REQUEST_TEMPLATE = 'gen_ai.request.template';
898
- const ATTR_GEN_AI_REQUEST_PARAMETERS = 'gen_ai.request.parameters';
899
- const ATTR_GEN_AI_REQUEST_MESSAGES = 'gen_ai.request.messages';
900
- const ATTR_GEN_AI_RESPONSE = 'gen_ai.response';
901
- const ATTR_GEN_AI_RESPONSE_MESSAGES = 'gen_ai.response.messages';
902
- const ATTR_GEN_AI_USAGE_PROMPT_TOKENS = 'gen_ai.usage.prompt_tokens';
903
- const ATTR_GEN_AI_USAGE_CACHED_TOKENS = 'gen_ai.usage.cached_tokens';
904
- const ATTR_GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens'; // prettier-ignore
905
- const ATTR_GEN_AI_USAGE_COMPLETION_TOKENS = 'gen_ai.usage.completion_tokens'; // prettier-ignore
906
- const ATTR_GEN_AI_PROMPTS = 'gen_ai.prompt'; // gen_ai.prompt.{index}.{role/content/...}
907
- const ATTR_GEN_AI_COMPLETIONS = 'gen_ai.completion'; // gen_ai.completion.{index}.{role/content/...}
908
- const ATTR_GEN_AI_MESSAGE_ROLE = 'role';
909
- const ATTR_GEN_AI_MESSAGE_CONTENT = 'content'; // string or object
910
- const ATTR_GEN_AI_MESSAGE_TOOL_NAME = 'tool_name';
911
- const ATTR_GEN_AI_MESSAGE_TOOL_CALL_ID = 'tool_call_id';
912
- const ATTR_GEN_AI_MESSAGE_TOOL_RESULT_IS_ERROR = 'is_error';
913
- const ATTR_GEN_AI_MESSAGE_TOOL_CALLS = 'tool_calls'; // gen_ai.completion.{index}.tool_calls.{index}.{id/name/arguments}
914
- const ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ID = 'id';
915
- const ATTR_GEN_AI_MESSAGE_TOOL_CALLS_NAME = 'name';
916
- const ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ARGUMENTS = 'arguments';
917
- const GEN_AI_RESPONSE_FINISH_REASON_VALUE_STOP = 'stop';
918
- const GEN_AI_RESPONSE_FINISH_REASON_VALUE_TOOL_CALLS = 'tool_calls';
919
- const ATTR_HTTP_REQUEST_URL = 'http.request.url';
920
- const ATTR_HTTP_REQUEST_BODY = 'http.request.body';
921
- const ATTR_HTTP_REQUEST_HEADER = 'http.request.header';
922
- const ATTR_HTTP_RESPONSE_BODY = 'http.response.body';
923
- const ATTR_HTTP_RESPONSE_HEADER = 'http.response.header';
924
1056
  /* Note: Schemas for span ingestion following OpenTelemetry service request specification */
925
1057
  var Otlp;
926
1058
  (function (Otlp) {
@@ -1001,6 +1133,40 @@ var Otlp;
1001
1133
  });
1002
1134
  })(Otlp || (Otlp = {}));
1003
1135
 
1136
+ const MAX_SIMULATION_TURNS = 10;
1137
+ const SimulationSettingsSchema = z.object({
1138
+ simulateToolResponses: z.boolean().optional(),
1139
+ simulatedTools: z.array(z.string()).optional(), // Empty array means all tools are simulated (if simulateToolResponses is true).
1140
+ toolSimulationInstructions: z.string().optional(), // A prompt used to guide and generate the simulation result
1141
+ 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.
1142
+ });
1143
+
1144
+ var OptimizationEngine;
1145
+ (function (OptimizationEngine) {
1146
+ OptimizationEngine["Identity"] = "identity";
1147
+ OptimizationEngine["Gepa"] = "gepa";
1148
+ })(OptimizationEngine || (OptimizationEngine = {}));
1149
+ const OptimizationBudgetSchema = z.object({
1150
+ time: z.number().min(0).optional(),
1151
+ tokens: z.number().min(0).optional(),
1152
+ });
1153
+ z.object({
1154
+ parameters: z
1155
+ .record(z.string(), z.object({
1156
+ column: z.string().optional(), // Note: corresponding column in the user-provided trainset and testset
1157
+ isPii: z.boolean().optional(),
1158
+ }))
1159
+ .optional(),
1160
+ simulation: SimulationSettingsSchema.optional(),
1161
+ scope: z
1162
+ .object({
1163
+ configuration: z.boolean().optional(),
1164
+ instructions: z.boolean().optional(),
1165
+ })
1166
+ .optional(),
1167
+ budget: OptimizationBudgetSchema.optional(),
1168
+ });
1169
+
1004
1170
  // TODO(tracing): deprecated
1005
1171
  const HEAD_COMMIT = 'live';
1006
1172
  var Providers;
@@ -1044,6 +1210,7 @@ var DocumentTriggerParameters;
1044
1210
  DocumentTriggerParameters["Body"] = "body";
1045
1211
  DocumentTriggerParameters["Attachments"] = "attachments";
1046
1212
  })(DocumentTriggerParameters || (DocumentTriggerParameters = {}));
1213
+ const DOCUMENT_PATH_REGEXP = /^([\w-]+\/)*([\w-.])+$/;
1047
1214
 
1048
1215
  class ManualInstrumentation {
1049
1216
  enabled;
@@ -1062,7 +1229,33 @@ class ManualInstrumentation {
1062
1229
  this.enabled = false;
1063
1230
  }
1064
1231
  resume(ctx) {
1065
- return propagation.extract(otel.ROOT_CONTEXT, ctx);
1232
+ const parts = ctx.traceparent.split('-');
1233
+ if (parts.length !== 4) {
1234
+ return otel.ROOT_CONTEXT;
1235
+ }
1236
+ const [, traceId, spanId, flags] = parts;
1237
+ if (!traceId || !spanId) {
1238
+ return otel.ROOT_CONTEXT;
1239
+ }
1240
+ const spanContext = {
1241
+ traceId,
1242
+ spanId,
1243
+ traceFlags: parseInt(flags ?? '01', 16),
1244
+ isRemote: true,
1245
+ };
1246
+ let context = trace.setSpanContext(otel.ROOT_CONTEXT, spanContext);
1247
+ if (ctx.baggage) {
1248
+ const baggageEntries = {};
1249
+ for (const pair of ctx.baggage.split(',')) {
1250
+ const [key, value] = pair.split('=');
1251
+ if (key && value) {
1252
+ baggageEntries[key] = { value: decodeURIComponent(value) };
1253
+ }
1254
+ }
1255
+ const baggage = propagation.createBaggage(baggageEntries);
1256
+ context = propagation.setBaggage(context, baggage);
1257
+ }
1258
+ return context;
1066
1259
  }
1067
1260
  capitalize(str) {
1068
1261
  if (str.length === 0)
@@ -1119,9 +1312,9 @@ class ManualInstrumentation {
1119
1312
  }
1120
1313
  const span = this.tracer.startSpan(name, {
1121
1314
  attributes: {
1122
- [ATTR_LATITUDE_TYPE]: type,
1315
+ [ATTRIBUTES.LATITUDE.type]: type,
1123
1316
  ...(operation && {
1124
- [ATTR_GEN_AI_OPERATION_NAME]: operation,
1317
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.operation]: operation,
1125
1318
  }),
1126
1319
  ...(start.attributes || {}),
1127
1320
  },
@@ -1147,15 +1340,15 @@ class ManualInstrumentation {
1147
1340
  try {
1148
1341
  jsonArguments = JSON.stringify(start.call.arguments);
1149
1342
  }
1150
- catch (error) {
1343
+ catch (_error) {
1151
1344
  jsonArguments = '{}';
1152
1345
  }
1153
1346
  const span = this.span(ctx, start.name, SpanType.Tool, {
1154
1347
  attributes: {
1155
- [ATTR_GEN_AI_TOOL_NAME]: start.name,
1156
- [ATTR_GEN_AI_TOOL_TYPE]: GEN_AI_TOOL_TYPE_VALUE_FUNCTION,
1157
- [ATTR_GEN_AI_TOOL_CALL_ID]: start.call.id,
1158
- [ATTR_GEN_AI_TOOL_CALL_ARGUMENTS]: jsonArguments,
1348
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.name]: start.name,
1349
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.type]: VALUES.OPENTELEMETRY.GEN_AI.tool.type.function,
1350
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.tool.call.id]: start.call.id,
1351
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.tool.call.arguments]: jsonArguments,
1159
1352
  ...(start.attributes || {}),
1160
1353
  },
1161
1354
  });
@@ -1168,7 +1361,7 @@ class ManualInstrumentation {
1168
1361
  try {
1169
1362
  stringResult = JSON.stringify(end.result.value);
1170
1363
  }
1171
- catch (error) {
1364
+ catch (_error) {
1172
1365
  stringResult = '{}';
1173
1366
  }
1174
1367
  }
@@ -1177,8 +1370,8 @@ class ManualInstrumentation {
1177
1370
  }
1178
1371
  span.end({
1179
1372
  attributes: {
1180
- [ATTR_GEN_AI_TOOL_RESULT_VALUE]: stringResult,
1181
- [ATTR_GEN_AI_TOOL_RESULT_IS_ERROR]: end.result.isError,
1373
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.result.value]: stringResult,
1374
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.tool.result.isError]: end.result.isError,
1182
1375
  ...(end.attributes || {}),
1183
1376
  },
1184
1377
  });
@@ -1186,12 +1379,12 @@ class ManualInstrumentation {
1186
1379
  fail: span.fail,
1187
1380
  };
1188
1381
  }
1189
- attribifyMessageToolCalls(prefix, toolCalls) {
1382
+ attribifyMessageToolCalls(otelMessageField, toolCalls) {
1190
1383
  const attributes = {};
1191
1384
  for (let i = 0; i < toolCalls.length; i++) {
1192
1385
  for (const key in toolCalls[i]) {
1193
1386
  const field = this.toCamelCase(key);
1194
- let value = toolCalls[i][key];
1387
+ const value = toolCalls[i][key];
1195
1388
  if (value === null || value === undefined)
1196
1389
  continue;
1197
1390
  switch (field) {
@@ -1200,28 +1393,29 @@ class ManualInstrumentation {
1200
1393
  case 'toolUseId': {
1201
1394
  if (typeof value !== 'string')
1202
1395
  continue;
1203
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ID}`] = value;
1396
+ attributes[otelMessageField.toolCalls(i).id] = value;
1204
1397
  break;
1205
1398
  }
1206
1399
  case 'name':
1207
1400
  case 'toolName': {
1208
1401
  if (typeof value !== 'string')
1209
1402
  continue;
1210
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_NAME}`] = value;
1403
+ attributes[otelMessageField.toolCalls(i).name] = value;
1211
1404
  break;
1212
1405
  }
1213
1406
  case 'arguments':
1214
1407
  case 'toolArguments':
1215
1408
  case 'input': {
1216
1409
  if (typeof value === 'string') {
1217
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ARGUMENTS}`] = value;
1410
+ attributes[otelMessageField.toolCalls(i).arguments] = value;
1218
1411
  }
1219
1412
  else {
1220
1413
  try {
1221
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ARGUMENTS}`] = JSON.stringify(value);
1414
+ attributes[otelMessageField.toolCalls(i).arguments] =
1415
+ JSON.stringify(value);
1222
1416
  }
1223
- catch (error) {
1224
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ARGUMENTS}`] = '{}';
1417
+ catch (_error) {
1418
+ attributes[otelMessageField.toolCalls(i).arguments] = '{}';
1225
1419
  }
1226
1420
  }
1227
1421
  break;
@@ -1238,8 +1432,9 @@ class ManualInstrumentation {
1238
1432
  continue;
1239
1433
  if (typeof value.arguments !== 'string')
1240
1434
  continue;
1241
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_NAME}`] = value.name;
1242
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALLS_ARGUMENTS}`] = value.arguments;
1435
+ attributes[otelMessageField.toolCalls(i).name] = value.name;
1436
+ attributes[otelMessageField.toolCalls(i).arguments] =
1437
+ value.arguments;
1243
1438
  break;
1244
1439
  }
1245
1440
  }
@@ -1247,18 +1442,16 @@ class ManualInstrumentation {
1247
1442
  }
1248
1443
  return attributes;
1249
1444
  }
1250
- attribifyMessageContent(prefix, content) {
1445
+ attribifyMessageContent(otelMessageField, content) {
1251
1446
  let attributes = {};
1252
1447
  if (typeof content === 'string') {
1253
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_CONTENT}`] = content;
1254
1448
  return attributes;
1255
1449
  }
1256
1450
  try {
1257
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_CONTENT}`] =
1258
- JSON.stringify(content);
1451
+ attributes[otelMessageField.content] = JSON.stringify(content);
1259
1452
  }
1260
- catch (error) {
1261
- attributes[`${prefix}.${ATTR_GEN_AI_MESSAGE_CONTENT}`] = '[]';
1453
+ catch (_error) {
1454
+ attributes[otelMessageField.content] = '[]';
1262
1455
  }
1263
1456
  if (!Array.isArray(content))
1264
1457
  return attributes;
@@ -1278,32 +1471,34 @@ class ManualInstrumentation {
1278
1471
  if (toolCalls.length > 0) {
1279
1472
  attributes = {
1280
1473
  ...attributes,
1281
- ...this.attribifyMessageToolCalls(prefix, toolCalls),
1474
+ ...this.attribifyMessageToolCalls(otelMessageField, toolCalls),
1282
1475
  };
1283
1476
  }
1284
1477
  return attributes;
1285
1478
  }
1286
1479
  attribifyMessages(direction, messages) {
1287
- const prefix = direction === 'input' ? ATTR_GEN_AI_PROMPTS : ATTR_GEN_AI_COMPLETIONS;
1480
+ const otelField = direction === 'input'
1481
+ ? ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.prompt
1482
+ : ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.completion;
1288
1483
  let attributes = {};
1289
1484
  for (let i = 0; i < messages.length; i++) {
1290
1485
  for (const key in messages[i]) {
1291
1486
  const field = this.toCamelCase(key);
1292
- let value = messages[i][key];
1487
+ const value = messages[i][key];
1293
1488
  if (value === null || value === undefined)
1294
1489
  continue;
1295
1490
  switch (field) {
1296
1491
  case 'role': {
1297
1492
  if (typeof value !== 'string')
1298
1493
  continue;
1299
- attributes[`${prefix}.${i}.${ATTR_GEN_AI_MESSAGE_ROLE}`] = value;
1494
+ attributes[otelField.index(i).role] = value;
1300
1495
  break;
1301
1496
  }
1302
1497
  /* Tool calls for Anthropic and PromptL are in the content */
1303
1498
  case 'content': {
1304
1499
  attributes = {
1305
1500
  ...attributes,
1306
- ...this.attribifyMessageContent(`${prefix}.${i}`, value),
1501
+ ...this.attribifyMessageContent(otelField.index(i), value),
1307
1502
  };
1308
1503
  break;
1309
1504
  }
@@ -1313,7 +1508,7 @@ class ManualInstrumentation {
1313
1508
  continue;
1314
1509
  attributes = {
1315
1510
  ...attributes,
1316
- ...this.attribifyMessageToolCalls(`${prefix}.${i}`, value),
1511
+ ...this.attribifyMessageToolCalls(otelField.index(i), value),
1317
1512
  };
1318
1513
  break;
1319
1514
  }
@@ -1323,22 +1518,20 @@ class ManualInstrumentation {
1323
1518
  case 'toolUseId': {
1324
1519
  if (typeof value !== 'string')
1325
1520
  continue;
1326
- attributes[`${prefix}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_CALL_ID}`] =
1327
- value;
1521
+ attributes[otelField.index(i).tool.callId] = value;
1328
1522
  break;
1329
1523
  }
1330
1524
  case 'toolName': {
1331
1525
  if (typeof value !== 'string')
1332
1526
  continue;
1333
- attributes[`${prefix}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_NAME}`] =
1334
- value;
1527
+ attributes[otelField.index(i).tool.toolName] = value;
1335
1528
  break;
1336
1529
  }
1337
1530
  // Note: 'toolResult' is 'content' itself
1338
1531
  case 'isError': {
1339
1532
  if (typeof value !== 'boolean')
1340
1533
  continue;
1341
- attributes[`${prefix}.${i}.${ATTR_GEN_AI_MESSAGE_TOOL_RESULT_IS_ERROR}`] = value;
1534
+ attributes[otelField.index(i).tool.isError] = value;
1342
1535
  break;
1343
1536
  }
1344
1537
  }
@@ -1347,7 +1540,9 @@ class ManualInstrumentation {
1347
1540
  return attributes;
1348
1541
  }
1349
1542
  attribifyConfiguration(direction, configuration) {
1350
- const prefix = direction === 'input' ? ATTR_GEN_AI_REQUEST : ATTR_GEN_AI_RESPONSE;
1543
+ const prefix = direction === 'input'
1544
+ ? ATTRIBUTES.LATITUDE.request._root
1545
+ : ATTRIBUTES.LATITUDE.response._root;
1351
1546
  const attributes = {};
1352
1547
  for (const key in configuration) {
1353
1548
  const field = this.toSnakeCase(key);
@@ -1358,7 +1553,7 @@ class ManualInstrumentation {
1358
1553
  try {
1359
1554
  value = JSON.stringify(value);
1360
1555
  }
1361
- catch (error) {
1556
+ catch (_error) {
1362
1557
  value = '{}';
1363
1558
  }
1364
1559
  }
@@ -1369,64 +1564,75 @@ class ManualInstrumentation {
1369
1564
  completion(ctx, options) {
1370
1565
  const start = options;
1371
1566
  const configuration = {
1372
- ...start.configuration,
1567
+ ...(start.configuration ?? {}),
1373
1568
  model: start.model,
1374
1569
  };
1375
1570
  let jsonConfiguration = '';
1376
1571
  try {
1377
1572
  jsonConfiguration = JSON.stringify(configuration);
1378
1573
  }
1379
- catch (error) {
1574
+ catch (_error) {
1380
1575
  jsonConfiguration = '{}';
1381
1576
  }
1382
1577
  const attrConfiguration = this.attribifyConfiguration('input', configuration);
1578
+ const input = start.input ?? [];
1383
1579
  let jsonInput = '';
1384
1580
  try {
1385
- jsonInput = JSON.stringify(start.input);
1581
+ jsonInput = JSON.stringify(input);
1386
1582
  }
1387
- catch (error) {
1583
+ catch (_error) {
1388
1584
  jsonInput = '[]';
1389
1585
  }
1390
- const attrInput = this.attribifyMessages('input', start.input);
1586
+ const attrInput = this.attribifyMessages('input', input);
1391
1587
  const span = this.span(ctx, start.name || `${start.provider} / ${start.model}`, SpanType.Completion, {
1392
1588
  attributes: {
1393
- [ATTR_GEN_AI_SYSTEM]: start.provider,
1394
- [ATTR_GEN_AI_REQUEST_CONFIGURATION]: jsonConfiguration,
1589
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI._deprecated.system]: start.provider,
1590
+ [ATTRIBUTES.LATITUDE.request.configuration]: jsonConfiguration,
1395
1591
  ...attrConfiguration,
1396
- [ATTR_GEN_AI_REQUEST_MESSAGES]: jsonInput,
1592
+ [ATTRIBUTES.LATITUDE.request.messages]: jsonInput,
1397
1593
  ...attrInput,
1398
1594
  ...(start.attributes || {}),
1399
- ['latitude.commitUuid']: start.versionUuid,
1400
- ['latitude.documentUuid']: start.promptUuid,
1401
- ['latitude.experimentUuid']: start.experimentUuid,
1595
+ [ATTRIBUTES.LATITUDE.commitUuid]: start.versionUuid,
1596
+ [ATTRIBUTES.LATITUDE.documentUuid]: start.promptUuid,
1597
+ [ATTRIBUTES.LATITUDE.experimentUuid]: start.experimentUuid,
1402
1598
  },
1403
1599
  });
1404
1600
  return {
1405
1601
  context: span.context,
1406
1602
  end: (options) => {
1407
- const end = options;
1603
+ const end = options ?? {};
1604
+ const output = end.output ?? [];
1408
1605
  let jsonOutput = '';
1409
1606
  try {
1410
- jsonOutput = JSON.stringify(end.output);
1607
+ jsonOutput = JSON.stringify(output);
1411
1608
  }
1412
- catch (error) {
1609
+ catch (_error) {
1413
1610
  jsonOutput = '[]';
1414
1611
  }
1415
- const attrOutput = this.attribifyMessages('output', end.output);
1416
- const inputTokens = end.tokens.prompt + end.tokens.cached;
1417
- const outputTokens = end.tokens.reasoning + end.tokens.completion;
1612
+ const attrOutput = this.attribifyMessages('output', output);
1613
+ const tokens = {
1614
+ prompt: end.tokens?.prompt ?? 0,
1615
+ cached: end.tokens?.cached ?? 0,
1616
+ reasoning: end.tokens?.reasoning ?? 0,
1617
+ completion: end.tokens?.completion ?? 0,
1618
+ };
1619
+ const inputTokens = tokens.prompt + tokens.cached;
1620
+ const outputTokens = tokens.reasoning + tokens.completion;
1621
+ const finishReason = end.finishReason ?? '';
1418
1622
  span.end({
1419
1623
  attributes: {
1420
- [ATTR_GEN_AI_RESPONSE_MESSAGES]: jsonOutput,
1624
+ [ATTRIBUTES.LATITUDE.response.messages]: jsonOutput,
1421
1625
  ...attrOutput,
1422
- [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: inputTokens,
1423
- [ATTR_GEN_AI_USAGE_PROMPT_TOKENS]: end.tokens.prompt,
1424
- [ATTR_GEN_AI_USAGE_CACHED_TOKENS]: end.tokens.cached,
1425
- [ATTR_GEN_AI_USAGE_REASONING_TOKENS]: end.tokens.reasoning,
1426
- [ATTR_GEN_AI_USAGE_COMPLETION_TOKENS]: end.tokens.completion,
1427
- [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: outputTokens,
1428
- [ATTR_GEN_AI_RESPONSE_MODEL]: start.model,
1429
- [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [end.finishReason],
1626
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.inputTokens]: inputTokens,
1627
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.usage.outputTokens]: outputTokens,
1628
+ [ATTRIBUTES.LATITUDE.usage.promptTokens]: tokens.prompt,
1629
+ [ATTRIBUTES.LATITUDE.usage.cachedTokens]: tokens.cached,
1630
+ [ATTRIBUTES.LATITUDE.usage.reasoningTokens]: tokens.reasoning,
1631
+ [ATTRIBUTES.LATITUDE.usage.completionTokens]: tokens.completion,
1632
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.response.model]: start.model,
1633
+ [ATTRIBUTES.OPENTELEMETRY.GEN_AI.response.finishReasons]: [
1634
+ finishReason,
1635
+ ],
1430
1636
  ...(end.attributes || {}),
1431
1637
  },
1432
1638
  });
@@ -1437,16 +1643,10 @@ class ManualInstrumentation {
1437
1643
  embedding(ctx, options) {
1438
1644
  return this.span(ctx, options?.name || SPAN_SPECIFICATIONS[SpanType.Embedding].name, SpanType.Embedding, options);
1439
1645
  }
1440
- retrieval(ctx, options) {
1441
- return this.span(ctx, options?.name || SPAN_SPECIFICATIONS[SpanType.Retrieval].name, SpanType.Retrieval, options);
1442
- }
1443
- reranking(ctx, options) {
1444
- return this.span(ctx, options?.name || SPAN_SPECIFICATIONS[SpanType.Reranking].name, SpanType.Reranking, options);
1445
- }
1446
1646
  attribifyHeaders(direction, headers) {
1447
1647
  const prefix = direction === 'request'
1448
- ? ATTR_HTTP_REQUEST_HEADER
1449
- : ATTR_HTTP_RESPONSE_HEADER;
1648
+ ? ATTRIBUTES.OPENTELEMETRY.HTTP.request.header
1649
+ : ATTRIBUTES.OPENTELEMETRY.HTTP.response.header;
1450
1650
  const attributes = {};
1451
1651
  for (const key in headers) {
1452
1652
  const field = this.toKebabCase(key);
@@ -1470,16 +1670,16 @@ class ManualInstrumentation {
1470
1670
  try {
1471
1671
  finalBody = JSON.stringify(start.request.body);
1472
1672
  }
1473
- catch (error) {
1673
+ catch (_error) {
1474
1674
  finalBody = '{}';
1475
1675
  }
1476
1676
  }
1477
1677
  const span = this.span(ctx, start.name || `${method} ${start.request.url}`, SpanType.Http, {
1478
1678
  attributes: {
1479
- [ATTR_HTTP_REQUEST_METHOD]: method,
1480
- [ATTR_HTTP_REQUEST_URL]: start.request.url,
1679
+ [ATTRIBUTES.OPENTELEMETRY.HTTP.request.method]: method,
1680
+ [ATTRIBUTES.OPENTELEMETRY.HTTP.request.url]: start.request.url,
1481
1681
  ...attrHeaders,
1482
- [ATTR_HTTP_REQUEST_BODY]: finalBody,
1682
+ [ATTRIBUTES.OPENTELEMETRY.HTTP.request.body]: finalBody,
1483
1683
  ...(start.attributes || {}),
1484
1684
  },
1485
1685
  });
@@ -1497,15 +1697,15 @@ class ManualInstrumentation {
1497
1697
  try {
1498
1698
  finalBody = JSON.stringify(end.response.body);
1499
1699
  }
1500
- catch (error) {
1700
+ catch (_error) {
1501
1701
  finalBody = '{}';
1502
1702
  }
1503
1703
  }
1504
1704
  span.end({
1505
1705
  attributes: {
1506
- [ATTR_HTTP_RESPONSE_STATUS_CODE]: end.response.status,
1706
+ [ATTRIBUTES.OPENTELEMETRY.HTTP.response.statusCode]: end.response.status,
1507
1707
  ...attrHeaders,
1508
- [ATTR_HTTP_RESPONSE_BODY]: finalBody,
1708
+ [ATTRIBUTES.OPENTELEMETRY.HTTP.response.body]: finalBody,
1509
1709
  ...(end.attributes || {}),
1510
1710
  },
1511
1711
  });
@@ -1518,49 +1718,84 @@ class ManualInstrumentation {
1518
1718
  try {
1519
1719
  jsonParameters = JSON.stringify(parameters || {});
1520
1720
  }
1521
- catch (error) {
1721
+ catch (_error) {
1522
1722
  jsonParameters = '{}';
1523
1723
  }
1524
1724
  const attributes = {
1525
- [ATTR_GEN_AI_REQUEST_TEMPLATE]: template,
1526
- [ATTR_GEN_AI_REQUEST_PARAMETERS]: jsonParameters,
1527
- ['latitude.commitUuid']: versionUuid || HEAD_COMMIT,
1528
- ['latitude.documentUuid']: promptUuid,
1529
- ['latitude.projectId']: projectId,
1530
- ...(documentLogUuid && { ['latitude.documentLogUuid']: documentLogUuid }),
1531
- ...(experimentUuid && { ['latitude.experimentUuid']: experimentUuid }),
1725
+ [ATTRIBUTES.LATITUDE.request.template]: template,
1726
+ [ATTRIBUTES.LATITUDE.request.parameters]: jsonParameters,
1727
+ [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid || HEAD_COMMIT,
1728
+ [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid,
1729
+ [ATTRIBUTES.LATITUDE.projectId]: projectId,
1730
+ [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1731
+ ...(experimentUuid && {
1732
+ [ATTRIBUTES.LATITUDE.experimentUuid]: experimentUuid,
1733
+ }),
1532
1734
  ...(testDeploymentId && {
1533
- [ATTR_LATITUDE_TEST_DEPLOYMENT_ID]: testDeploymentId,
1735
+ [ATTRIBUTES.LATITUDE.testDeploymentId]: testDeploymentId,
1534
1736
  }),
1535
- ...(externalId && { ['latitude.externalId']: externalId }),
1536
- ...(source && { ['latitude.source']: source }),
1737
+ ...(externalId && { [ATTRIBUTES.LATITUDE.externalId]: externalId }),
1738
+ ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1537
1739
  ...(rest.attributes || {}),
1538
1740
  };
1539
1741
  return this.span(ctx, name || `prompt-${promptUuid}`, SpanType.Prompt, {
1540
1742
  attributes,
1541
1743
  });
1542
1744
  }
1543
- step(ctx, options) {
1544
- return this.span(ctx, 'step', SpanType.Step, options);
1745
+ chat(ctx, { documentLogUuid, previousTraceId, source, name, versionUuid, promptUuid, ...rest }) {
1746
+ const attributes = {
1747
+ [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1748
+ [ATTRIBUTES.LATITUDE.previousTraceId]: previousTraceId,
1749
+ ...(versionUuid && { [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid }),
1750
+ ...(promptUuid && { [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid }),
1751
+ ...(source && { [ATTRIBUTES.LATITUDE.source]: source }),
1752
+ ...(rest.attributes || {}),
1753
+ };
1754
+ return this.span(ctx, name || 'chat', SpanType.Chat, { attributes });
1755
+ }
1756
+ external(ctx, { promptUuid, documentLogUuid, source, versionUuid, externalId, name, ...rest }) {
1757
+ const attributes = {
1758
+ [ATTRIBUTES.LATITUDE.documentUuid]: promptUuid,
1759
+ [ATTRIBUTES.LATITUDE.documentLogUuid]: documentLogUuid,
1760
+ [ATTRIBUTES.LATITUDE.source]: source ?? LogSources.API,
1761
+ ...(versionUuid && { [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid }),
1762
+ ...(externalId && { [ATTRIBUTES.LATITUDE.externalId]: externalId }),
1763
+ ...(rest.attributes || {}),
1764
+ };
1765
+ return this.span(ctx, name || `external-${promptUuid}`, SpanType.External, {
1766
+ attributes,
1767
+ });
1768
+ }
1769
+ unresolvedExternal(ctx, { path, projectId, versionUuid, conversationUuid, name, ...rest }) {
1770
+ const attributes = {
1771
+ [ATTRIBUTES.LATITUDE.promptPath]: path,
1772
+ [ATTRIBUTES.LATITUDE.projectId]: projectId,
1773
+ ...(versionUuid && { [ATTRIBUTES.LATITUDE.commitUuid]: versionUuid }),
1774
+ ...(conversationUuid && {
1775
+ [ATTRIBUTES.LATITUDE.documentLogUuid]: conversationUuid,
1776
+ }),
1777
+ ...(rest.attributes || {}),
1778
+ };
1779
+ return this.span(ctx, name || `capture-${path}`, SpanType.UnresolvedExternal, { attributes });
1545
1780
  }
1546
1781
  }
1547
1782
 
1548
1783
  class LatitudeInstrumentation {
1549
1784
  options;
1550
- telemetry;
1785
+ manualTelemetry;
1551
1786
  constructor(tracer, options) {
1552
- this.telemetry = new ManualInstrumentation(tracer);
1787
+ this.manualTelemetry = new ManualInstrumentation(tracer);
1553
1788
  this.options = options;
1554
1789
  }
1555
1790
  isEnabled() {
1556
- return this.telemetry.isEnabled();
1791
+ return this.manualTelemetry.isEnabled();
1557
1792
  }
1558
1793
  enable() {
1559
1794
  this.options.module.instrument(this);
1560
- this.telemetry.enable();
1795
+ this.manualTelemetry.enable();
1561
1796
  }
1562
1797
  disable() {
1563
- this.telemetry.disable();
1798
+ this.manualTelemetry.disable();
1564
1799
  this.options.module.uninstrument();
1565
1800
  }
1566
1801
  countTokens(messages) {
@@ -1584,7 +1819,8 @@ class LatitudeInstrumentation {
1584
1819
  }
1585
1820
  async wrapRenderChain(fn, ...args) {
1586
1821
  const { prompt, parameters } = args[0];
1587
- const $prompt = this.telemetry.prompt(context.active(), {
1822
+ const $prompt = this.manualTelemetry.prompt(context.active(), {
1823
+ documentLogUuid: v4(),
1588
1824
  versionUuid: prompt.versionUuid,
1589
1825
  promptUuid: prompt.uuid,
1590
1826
  template: prompt.content,
@@ -1601,26 +1837,13 @@ class LatitudeInstrumentation {
1601
1837
  $prompt.end();
1602
1838
  return result;
1603
1839
  }
1604
- async wrapRenderStep(fn, ...args) {
1605
- const $step = this.telemetry.step(context.active());
1606
- let result;
1607
- try {
1608
- result = await context.with($step.context, async () => await fn(...args));
1609
- }
1610
- catch (error) {
1611
- $step.fail(error);
1612
- throw error;
1613
- }
1614
- $step.end();
1615
- return result;
1616
- }
1617
1840
  async wrapRenderCompletion(fn, ...args) {
1618
1841
  if (!this.options.completions) {
1619
1842
  return await fn(...args);
1620
1843
  }
1621
1844
  const { provider, config, messages } = args[0];
1622
1845
  const model = config.model || 'unknown';
1623
- const $completion = this.telemetry.completion(context.active(), {
1846
+ const $completion = this.manualTelemetry.completion(context.active(), {
1624
1847
  name: `${provider} / ${model}`,
1625
1848
  provider: provider,
1626
1849
  model: model,
@@ -1647,14 +1870,14 @@ class LatitudeInstrumentation {
1647
1870
  completion: completionTokens,
1648
1871
  },
1649
1872
  finishReason: result.toolRequests.length > 0
1650
- ? GEN_AI_RESPONSE_FINISH_REASON_VALUE_TOOL_CALLS
1651
- : GEN_AI_RESPONSE_FINISH_REASON_VALUE_STOP,
1873
+ ? VALUES.OPENTELEMETRY.GEN_AI.response.finishReasons.toolCalls
1874
+ : VALUES.OPENTELEMETRY.GEN_AI.response.finishReasons.stop,
1652
1875
  });
1653
1876
  return result;
1654
1877
  }
1655
1878
  async wrapRenderTool(fn, ...args) {
1656
1879
  const { toolRequest } = args[0];
1657
- const $tool = this.telemetry.tool(context.active(), {
1880
+ const $tool = this.manualTelemetry.tool(context.active(), {
1658
1881
  name: toolRequest.toolName,
1659
1882
  call: {
1660
1883
  id: toolRequest.toolCallId,
@@ -1679,10 +1902,157 @@ class LatitudeInstrumentation {
1679
1902
  }
1680
1903
  }
1681
1904
 
1905
+ var LatitudeErrorCodes;
1906
+ (function (LatitudeErrorCodes) {
1907
+ LatitudeErrorCodes["UnexpectedError"] = "UnexpectedError";
1908
+ LatitudeErrorCodes["OverloadedError"] = "OverloadedError";
1909
+ LatitudeErrorCodes["RateLimitError"] = "RateLimitError";
1910
+ LatitudeErrorCodes["UnauthorizedError"] = "UnauthorizedError";
1911
+ LatitudeErrorCodes["ForbiddenError"] = "ForbiddenError";
1912
+ LatitudeErrorCodes["BadRequestError"] = "BadRequestError";
1913
+ LatitudeErrorCodes["NotFoundError"] = "NotFoundError";
1914
+ LatitudeErrorCodes["ConflictError"] = "ConflictError";
1915
+ LatitudeErrorCodes["UnprocessableEntityError"] = "UnprocessableEntityError";
1916
+ LatitudeErrorCodes["NotImplementedError"] = "NotImplementedError";
1917
+ LatitudeErrorCodes["PaymentRequiredError"] = "PaymentRequiredError";
1918
+ LatitudeErrorCodes["AbortedError"] = "AbortedError";
1919
+ LatitudeErrorCodes["BillingError"] = "BillingError";
1920
+ })(LatitudeErrorCodes || (LatitudeErrorCodes = {}));
1921
+ // NOTE: If you add a new error code, please add it to the pg enum in models/runErrors.ts
1922
+ var RunErrorCodes;
1923
+ (function (RunErrorCodes) {
1924
+ RunErrorCodes["AIProviderConfigError"] = "ai_provider_config_error";
1925
+ RunErrorCodes["AIRunError"] = "ai_run_error";
1926
+ RunErrorCodes["ChainCompileError"] = "chain_compile_error";
1927
+ RunErrorCodes["DefaultProviderExceededQuota"] = "default_provider_exceeded_quota_error";
1928
+ RunErrorCodes["DefaultProviderInvalidModel"] = "default_provider_invalid_model_error";
1929
+ RunErrorCodes["DocumentConfigError"] = "document_config_error";
1930
+ RunErrorCodes["ErrorGeneratingMockToolResult"] = "error_generating_mock_tool_result";
1931
+ RunErrorCodes["FailedToWakeUpIntegrationError"] = "failed_to_wake_up_integration_error";
1932
+ RunErrorCodes["InvalidResponseFormatError"] = "invalid_response_format_error";
1933
+ RunErrorCodes["MaxStepCountExceededError"] = "max_step_count_exceeded_error";
1934
+ RunErrorCodes["MissingProvider"] = "missing_provider_error";
1935
+ RunErrorCodes["RateLimit"] = "rate_limit_error";
1936
+ RunErrorCodes["Unknown"] = "unknown_error";
1937
+ RunErrorCodes["UnsupportedProviderResponseTypeError"] = "unsupported_provider_response_type_error";
1938
+ RunErrorCodes["PaymentRequiredError"] = "payment_required_error";
1939
+ RunErrorCodes["AbortError"] = "abort_error";
1940
+ // DEPRECATED, but do not delete
1941
+ RunErrorCodes["EvaluationRunMissingProviderLogError"] = "ev_run_missing_provider_log_error";
1942
+ RunErrorCodes["EvaluationRunMissingWorkspaceError"] = "ev_run_missing_workspace_error";
1943
+ RunErrorCodes["EvaluationRunResponseJsonFormatError"] = "ev_run_response_json_format_error";
1944
+ RunErrorCodes["EvaluationRunUnsupportedResultTypeError"] = "ev_run_unsupported_result_type_error";
1945
+ })(RunErrorCodes || (RunErrorCodes = {}));
1946
+ var ApiErrorCodes;
1947
+ (function (ApiErrorCodes) {
1948
+ ApiErrorCodes["HTTPException"] = "http_exception";
1949
+ ApiErrorCodes["InternalServerError"] = "internal_server_error";
1950
+ })(ApiErrorCodes || (ApiErrorCodes = {}));
1951
+
1952
+ class LatitudeError extends Error {
1953
+ statusCode = 500;
1954
+ name = LatitudeErrorCodes.UnexpectedError;
1955
+ headers = {};
1956
+ details;
1957
+ constructor(message, details, status, name) {
1958
+ super(message);
1959
+ this.details = details ?? {};
1960
+ this.statusCode = status ?? this.statusCode;
1961
+ this.name = name ?? this.constructor.name;
1962
+ }
1963
+ serialize() {
1964
+ return {
1965
+ name: this.name,
1966
+ code: this.name,
1967
+ status: this.statusCode,
1968
+ message: this.message,
1969
+ details: this.details,
1970
+ };
1971
+ }
1972
+ static deserialize(json) {
1973
+ return new LatitudeError(json.message, json.details, json.status, json.name);
1974
+ }
1975
+ }
1976
+ class BadRequestError extends LatitudeError {
1977
+ statusCode = 400;
1978
+ name = LatitudeErrorCodes.BadRequestError;
1979
+ }
1980
+
1682
1981
  const TRACES_URL = `${env.GATEWAY_BASE_URL}/api/v3/traces`;
1683
1982
  const SERVICE_NAME = process.env.npm_package_name || 'unknown';
1684
1983
  const SCOPE_VERSION = process.env.npm_package_version || 'unknown';
1685
1984
  const BACKGROUND = () => otel.ROOT_CONTEXT;
1985
+ class SpanFactory {
1986
+ telemetry;
1987
+ constructor(telemetry) {
1988
+ this.telemetry = telemetry;
1989
+ }
1990
+ tool(options, ctx) {
1991
+ return this.telemetry.tool(ctx ?? context.active(), options);
1992
+ }
1993
+ completion(options, ctx) {
1994
+ return this.telemetry.completion(ctx ?? context.active(), options);
1995
+ }
1996
+ embedding(options, ctx) {
1997
+ return this.telemetry.embedding(ctx ?? context.active(), options);
1998
+ }
1999
+ http(options, ctx) {
2000
+ return this.telemetry.http(ctx ?? context.active(), options);
2001
+ }
2002
+ prompt(options, ctx) {
2003
+ return this.telemetry.prompt(ctx ?? context.active(), options);
2004
+ }
2005
+ chat(options, ctx) {
2006
+ return this.telemetry.chat(ctx ?? context.active(), options);
2007
+ }
2008
+ external(options, ctx) {
2009
+ return this.telemetry.external(ctx ?? context.active(), options);
2010
+ }
2011
+ }
2012
+ class ContextManager {
2013
+ telemetry;
2014
+ constructor(telemetry) {
2015
+ this.telemetry = telemetry;
2016
+ }
2017
+ resume(ctx) {
2018
+ return this.telemetry.resume(ctx);
2019
+ }
2020
+ active() {
2021
+ return context.active();
2022
+ }
2023
+ }
2024
+ class InstrumentationManager {
2025
+ instrumentations;
2026
+ constructor(instrumentations) {
2027
+ this.instrumentations = instrumentations;
2028
+ }
2029
+ enable() {
2030
+ this.instrumentations.forEach((instrumentation) => {
2031
+ if (!instrumentation.isEnabled())
2032
+ instrumentation.enable();
2033
+ });
2034
+ }
2035
+ disable() {
2036
+ this.instrumentations.forEach((instrumentation) => {
2037
+ if (instrumentation.isEnabled())
2038
+ instrumentation.disable();
2039
+ });
2040
+ }
2041
+ }
2042
+ class TracerManager {
2043
+ nodeProvider;
2044
+ scopeVersion;
2045
+ constructor(nodeProvider, scopeVersion) {
2046
+ this.nodeProvider = nodeProvider;
2047
+ this.scopeVersion = scopeVersion;
2048
+ }
2049
+ get(scope) {
2050
+ return this.provider(scope).getTracer('');
2051
+ }
2052
+ provider(scope) {
2053
+ return new ScopedTracerProvider(`${SCOPE_LATITUDE}.${scope}`, this.scopeVersion, this.nodeProvider);
2054
+ }
2055
+ }
1686
2056
  class ScopedTracerProvider {
1687
2057
  scope;
1688
2058
  version;
@@ -1720,9 +2090,13 @@ var Instrumentation;
1720
2090
  })(Instrumentation || (Instrumentation = {}));
1721
2091
  class LatitudeTelemetry {
1722
2092
  options;
1723
- provider;
1724
- telemetry;
1725
- instrumentations;
2093
+ nodeProvider;
2094
+ manualInstrumentation;
2095
+ instrumentationsList;
2096
+ span;
2097
+ context;
2098
+ instrumentation;
2099
+ tracer;
1726
2100
  constructor(apiKey, options) {
1727
2101
  this.options = options || {};
1728
2102
  if (!this.options.exporter) {
@@ -1736,63 +2110,61 @@ class LatitudeTelemetry {
1736
2110
  new W3CBaggagePropagator(),
1737
2111
  ],
1738
2112
  }));
1739
- this.provider = new NodeTracerProvider({
2113
+ this.nodeProvider = new NodeTracerProvider({
1740
2114
  resource: new Resource({ [ATTR_SERVICE_NAME]: SERVICE_NAME }),
1741
2115
  });
1742
2116
  // Note: important, must run before the exporter span processors
1743
- this.provider.addSpanProcessor(new BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS));
2117
+ this.nodeProvider.addSpanProcessor(new BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS));
1744
2118
  if (this.options.processors) {
1745
2119
  this.options.processors.forEach((processor) => {
1746
- this.provider.addSpanProcessor(processor);
2120
+ this.nodeProvider.addSpanProcessor(processor);
1747
2121
  });
1748
2122
  }
1749
2123
  else {
1750
- this.provider.addSpanProcessor(DEFAULT_REDACT_SPAN_PROCESSOR());
2124
+ this.nodeProvider.addSpanProcessor(DEFAULT_REDACT_SPAN_PROCESSOR());
1751
2125
  }
1752
2126
  if (this.options.disableBatch) {
1753
- this.provider.addSpanProcessor(new SimpleSpanProcessor(this.options.exporter));
2127
+ this.nodeProvider.addSpanProcessor(new SimpleSpanProcessor(this.options.exporter));
1754
2128
  }
1755
2129
  else {
1756
- this.provider.addSpanProcessor(new BatchSpanProcessor(this.options.exporter));
2130
+ this.nodeProvider.addSpanProcessor(new BatchSpanProcessor(this.options.exporter));
1757
2131
  }
1758
- this.provider.register();
2132
+ this.nodeProvider.register();
1759
2133
  process.on('SIGTERM', async () => this.shutdown);
1760
2134
  process.on('SIGINT', async () => this.shutdown);
1761
- this.telemetry = null;
1762
- this.instrumentations = [];
2135
+ this.manualInstrumentation = null;
2136
+ this.instrumentationsList = [];
2137
+ this.tracer = new TracerManager(this.nodeProvider, SCOPE_VERSION);
1763
2138
  this.initInstrumentations();
1764
- this.instrument();
2139
+ this.instrumentation = new InstrumentationManager(this.instrumentationsList);
2140
+ this.instrumentation.enable();
2141
+ this.span = new SpanFactory(this.manualInstrumentation);
2142
+ this.context = new ContextManager(this.manualInstrumentation);
1765
2143
  }
1766
2144
  async flush() {
1767
- await this.provider.forceFlush();
2145
+ await this.nodeProvider.forceFlush();
1768
2146
  await this.options.exporter.forceFlush?.();
1769
2147
  }
1770
2148
  async shutdown() {
1771
2149
  await this.flush();
1772
- await this.provider.shutdown();
2150
+ await this.nodeProvider.shutdown();
1773
2151
  await this.options.exporter.shutdown?.();
1774
2152
  }
1775
- tracerProvider(instrumentation) {
1776
- return new ScopedTracerProvider(`${SCOPE_LATITUDE}.${instrumentation}`, SCOPE_VERSION, this.provider);
1777
- }
1778
- tracer(instrumentation) {
1779
- return this.tracerProvider(instrumentation).getTracer('');
1780
- }
1781
2153
  // TODO(tracing): auto instrument outgoing HTTP requests
1782
2154
  initInstrumentations() {
1783
- this.instrumentations = [];
1784
- const tracer = this.tracer(InstrumentationScope.Manual);
1785
- this.telemetry = new ManualInstrumentation(tracer);
1786
- this.instrumentations.push(this.telemetry);
2155
+ this.instrumentationsList = [];
2156
+ const tracer = this.tracer.get(InstrumentationScope.Manual);
2157
+ this.manualInstrumentation = new ManualInstrumentation(tracer);
2158
+ this.instrumentationsList.push(this.manualInstrumentation);
1787
2159
  const latitude = this.options.instrumentations?.latitude;
1788
2160
  if (latitude) {
1789
- const tracer = this.tracer(Instrumentation.Latitude);
2161
+ const tracer = this.tracer.get(Instrumentation.Latitude);
1790
2162
  const instrumentation = new LatitudeInstrumentation(tracer, typeof latitude === 'object' ? latitude : { module: latitude });
1791
- this.instrumentations.push(instrumentation);
2163
+ this.instrumentationsList.push(instrumentation);
1792
2164
  }
1793
2165
  const configureInstrumentation = (instrumentationType, InstrumentationConstructor, instrumentationOptions) => {
1794
2166
  const providerPkg = this.options.instrumentations?.[instrumentationType];
1795
- const provider = this.tracerProvider(instrumentationType);
2167
+ const provider = this.tracer.provider(instrumentationType);
1796
2168
  const instrumentation = new InstrumentationConstructor(instrumentationOptions); // prettier-ignore
1797
2169
  instrumentation.setTracerProvider(provider);
1798
2170
  if (providerPkg) {
@@ -1802,7 +2174,7 @@ class LatitudeTelemetry {
1802
2174
  instrumentations: [instrumentation],
1803
2175
  tracerProvider: provider,
1804
2176
  });
1805
- this.instrumentations.push(instrumentation);
2177
+ this.instrumentationsList.push(instrumentation);
1806
2178
  };
1807
2179
  configureInstrumentation(Instrumentation.Anthropic, AnthropicInstrumentation); // prettier-ignore
1808
2180
  configureInstrumentation(Instrumentation.AIPlatform, AIPlatformInstrumentation); // prettier-ignore
@@ -1810,48 +2182,28 @@ class LatitudeTelemetry {
1810
2182
  configureInstrumentation(Instrumentation.Cohere, CohereInstrumentation); // prettier-ignore
1811
2183
  configureInstrumentation(Instrumentation.Langchain, LangChainInstrumentation); // prettier-ignore
1812
2184
  configureInstrumentation(Instrumentation.LlamaIndex, LlamaIndexInstrumentation); // prettier-ignore
1813
- configureInstrumentation(Instrumentation.OpenAI, OpenAIInstrumentation, { enrichTokens: true }); // prettier-ignore
1814
- configureInstrumentation(Instrumentation.TogetherAI, TogetherInstrumentation, { enrichTokens: true }); // prettier-ignore
2185
+ // NOTE: `stream: true` in OpenAI make enrichTokens fail, so disabling.
2186
+ configureInstrumentation(Instrumentation.OpenAI, OpenAIInstrumentation, { enrichTokens: false }); // prettier-ignore
2187
+ configureInstrumentation(Instrumentation.TogetherAI, TogetherInstrumentation, { enrichTokens: false }); // prettier-ignore
1815
2188
  configureInstrumentation(Instrumentation.VertexAI, VertexAIInstrumentation); // prettier-ignore
1816
2189
  }
1817
- instrument() {
1818
- this.instrumentations.forEach((instrumentation) => {
1819
- if (!instrumentation.isEnabled())
1820
- instrumentation.enable();
1821
- });
1822
- }
1823
- uninstrument() {
1824
- this.instrumentations.forEach((instrumentation) => {
1825
- if (instrumentation.isEnabled())
1826
- instrumentation.disable();
1827
- });
1828
- }
1829
- resume(ctx) {
1830
- return this.telemetry.resume(ctx);
1831
- }
1832
- tool(ctx, options) {
1833
- return this.telemetry.tool(ctx, options);
1834
- }
1835
- completion(ctx, options) {
1836
- return this.telemetry.completion(ctx, options);
1837
- }
1838
- embedding(ctx, options) {
1839
- return this.telemetry.embedding(ctx, options);
1840
- }
1841
- retrieval(ctx, options) {
1842
- return this.telemetry.retrieval(ctx, options);
1843
- }
1844
- reranking(ctx, options) {
1845
- return this.telemetry.reranking(ctx, options);
1846
- }
1847
- http(ctx, options) {
1848
- return this.telemetry.http(ctx, options);
1849
- }
1850
- prompt(ctx, options) {
1851
- return this.telemetry.prompt(ctx, options);
1852
- }
1853
- step(ctx, options) {
1854
- return this.telemetry.step(ctx, options);
2190
+ async capture(options, fn) {
2191
+ if (!DOCUMENT_PATH_REGEXP.test(options.path)) {
2192
+ throw new BadRequestError("Invalid path, no spaces. Only letters, numbers, '.', '-' and '_'");
2193
+ }
2194
+ const span = this.manualInstrumentation.unresolvedExternal(otel.ROOT_CONTEXT, options);
2195
+ try {
2196
+ const result = await context.with(span.context, () => fn(span.context));
2197
+ span.end();
2198
+ return result;
2199
+ }
2200
+ catch (error) {
2201
+ span.fail(error);
2202
+ throw error;
2203
+ }
2204
+ finally {
2205
+ await this.flush();
2206
+ }
1855
2207
  }
1856
2208
  }
1857
2209