dd-trace 5.61.1 → 5.63.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.
Files changed (215) hide show
  1. package/README.md +0 -5
  2. package/package.json +2 -2
  3. package/packages/datadog-instrumentations/src/ai.js +140 -0
  4. package/packages/datadog-instrumentations/src/apollo-server.js +50 -8
  5. package/packages/datadog-instrumentations/src/aws-sdk.js +49 -60
  6. package/packages/datadog-instrumentations/src/couchbase.js +102 -65
  7. package/packages/datadog-instrumentations/src/fastify.js +61 -55
  8. package/packages/datadog-instrumentations/src/graphql.js +90 -122
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/register.js +2 -22
  11. package/packages/datadog-instrumentations/src/hono.js +11 -8
  12. package/packages/datadog-instrumentations/src/http2/server.js +14 -20
  13. package/packages/datadog-instrumentations/src/knex.js +15 -17
  14. package/packages/datadog-instrumentations/src/microgateway-core.js +16 -15
  15. package/packages/datadog-instrumentations/src/mongodb-core.js +35 -32
  16. package/packages/datadog-instrumentations/src/mongodb.js +9 -13
  17. package/packages/datadog-instrumentations/src/mongoose.js +25 -29
  18. package/packages/datadog-instrumentations/src/next.js +4 -8
  19. package/packages/datadog-instrumentations/src/openai.js +0 -2
  20. package/packages/datadog-instrumentations/src/oracledb.js +39 -33
  21. package/packages/datadog-instrumentations/src/pg.js +38 -48
  22. package/packages/datadog-plugin-aerospike/src/index.js +11 -11
  23. package/packages/datadog-plugin-ai/src/index.js +17 -0
  24. package/packages/datadog-plugin-ai/src/tracing.js +33 -0
  25. package/packages/datadog-plugin-ai/src/utils.js +28 -0
  26. package/packages/datadog-plugin-amqp10/src/consumer.js +2 -2
  27. package/packages/datadog-plugin-amqp10/src/index.js +1 -1
  28. package/packages/datadog-plugin-amqp10/src/producer.js +3 -3
  29. package/packages/datadog-plugin-amqplib/src/client.js +3 -3
  30. package/packages/datadog-plugin-amqplib/src/consumer.js +2 -2
  31. package/packages/datadog-plugin-amqplib/src/index.js +1 -1
  32. package/packages/datadog-plugin-amqplib/src/producer.js +2 -2
  33. package/packages/datadog-plugin-apollo/src/gateway/execute.js +2 -4
  34. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +2 -4
  35. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  36. package/packages/datadog-plugin-apollo/src/gateway/plan.js +2 -4
  37. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +2 -4
  38. package/packages/datadog-plugin-apollo/src/gateway/request.js +2 -4
  39. package/packages/datadog-plugin-apollo/src/gateway/validate.js +2 -4
  40. package/packages/datadog-plugin-apollo/src/index.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/index.js +2 -2
  42. package/packages/datadog-plugin-aws-sdk/src/base.js +70 -46
  43. package/packages/datadog-plugin-aws-sdk/src/index.js +1 -3
  44. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +1 -3
  45. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  46. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  47. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +3 -3
  48. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  49. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +22 -20
  50. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  51. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  52. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +3 -3
  53. package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +1 -1
  54. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +3 -3
  55. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +17 -15
  56. package/packages/datadog-plugin-aws-sdk/src/services/states.js +1 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  58. package/packages/datadog-plugin-azure-functions/src/index.js +5 -5
  59. package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
  60. package/packages/datadog-plugin-azure-service-bus/src/producer.js +2 -2
  61. package/packages/datadog-plugin-bunyan/src/index.js +3 -5
  62. package/packages/datadog-plugin-cassandra-driver/src/index.js +3 -3
  63. package/packages/datadog-plugin-child_process/src/index.js +2 -2
  64. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/batch-consumer.js +1 -3
  65. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/consumer.js +1 -3
  66. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +1 -1
  67. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/producer.js +1 -3
  68. package/packages/datadog-plugin-connect/src/index.js +1 -3
  69. package/packages/datadog-plugin-couchbase/src/index.js +39 -19
  70. package/packages/datadog-plugin-cucumber/src/index.js +1 -3
  71. package/packages/datadog-plugin-cypress/src/index.js +1 -3
  72. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  73. package/packages/datadog-plugin-dns/src/index.js +1 -1
  74. package/packages/datadog-plugin-dns/src/lookup.js +2 -2
  75. package/packages/datadog-plugin-dns/src/lookup_service.js +2 -2
  76. package/packages/datadog-plugin-dns/src/resolve.js +2 -2
  77. package/packages/datadog-plugin-dns/src/reverse.js +2 -2
  78. package/packages/datadog-plugin-elasticsearch/src/index.js +1 -1
  79. package/packages/datadog-plugin-express/src/code_origin.js +1 -3
  80. package/packages/datadog-plugin-express/src/index.js +1 -1
  81. package/packages/datadog-plugin-express/src/tracing.js +1 -3
  82. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -3
  83. package/packages/datadog-plugin-fastify/src/index.js +1 -1
  84. package/packages/datadog-plugin-fastify/src/tracing.js +18 -3
  85. package/packages/datadog-plugin-fetch/src/index.js +2 -2
  86. package/packages/datadog-plugin-find-my-way/src/index.js +1 -3
  87. package/packages/datadog-plugin-fs/src/index.js +2 -2
  88. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +3 -3
  89. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  90. package/packages/datadog-plugin-google-cloud-pubsub/src/index.js +1 -1
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +2 -2
  92. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +1 -1
  93. package/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +2 -4
  94. package/packages/datadog-plugin-graphql/src/execute.js +16 -9
  95. package/packages/datadog-plugin-graphql/src/index.js +1 -1
  96. package/packages/datadog-plugin-graphql/src/parse.js +12 -7
  97. package/packages/datadog-plugin-graphql/src/resolve.js +50 -16
  98. package/packages/datadog-plugin-graphql/src/validate.js +13 -7
  99. package/packages/datadog-plugin-grpc/src/client.js +4 -4
  100. package/packages/datadog-plugin-grpc/src/index.js +1 -1
  101. package/packages/datadog-plugin-grpc/src/server.js +3 -3
  102. package/packages/datadog-plugin-hapi/src/index.js +1 -3
  103. package/packages/datadog-plugin-hono/src/index.js +1 -3
  104. package/packages/datadog-plugin-http/src/client.js +2 -2
  105. package/packages/datadog-plugin-http/src/index.js +1 -1
  106. package/packages/datadog-plugin-http/src/server.js +3 -7
  107. package/packages/datadog-plugin-http2/src/client.js +2 -2
  108. package/packages/datadog-plugin-http2/src/index.js +1 -1
  109. package/packages/datadog-plugin-http2/src/server.js +22 -11
  110. package/packages/datadog-plugin-ioredis/src/index.js +1 -3
  111. package/packages/datadog-plugin-iovalkey/src/index.js +2 -4
  112. package/packages/datadog-plugin-jest/src/index.js +1 -3
  113. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -2
  114. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -2
  115. package/packages/datadog-plugin-kafkajs/src/index.js +1 -1
  116. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -3
  117. package/packages/datadog-plugin-koa/src/index.js +1 -3
  118. package/packages/datadog-plugin-langchain/src/index.js +2 -2
  119. package/packages/datadog-plugin-langchain/src/tracing.js +30 -48
  120. package/packages/datadog-plugin-mariadb/src/index.js +2 -2
  121. package/packages/datadog-plugin-memcached/src/index.js +1 -1
  122. package/packages/datadog-plugin-microgateway-core/src/index.js +4 -4
  123. package/packages/datadog-plugin-mocha/src/index.js +1 -3
  124. package/packages/datadog-plugin-moleculer/src/client.js +2 -2
  125. package/packages/datadog-plugin-moleculer/src/index.js +1 -1
  126. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  127. package/packages/datadog-plugin-mongodb-core/src/index.js +9 -5
  128. package/packages/datadog-plugin-mongoose/src/index.js +20 -0
  129. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  130. package/packages/datadog-plugin-mysql2/src/index.js +1 -1
  131. package/packages/datadog-plugin-net/src/index.js +1 -1
  132. package/packages/datadog-plugin-net/src/ipc.js +2 -2
  133. package/packages/datadog-plugin-net/src/tcp.js +2 -2
  134. package/packages/datadog-plugin-next/src/index.js +1 -3
  135. package/packages/datadog-plugin-nyc/src/index.js +1 -3
  136. package/packages/datadog-plugin-openai/src/index.js +1 -1
  137. package/packages/datadog-plugin-openai/src/tracing.js +7 -411
  138. package/packages/datadog-plugin-opensearch/src/index.js +1 -3
  139. package/packages/datadog-plugin-oracledb/src/index.js +9 -5
  140. package/packages/datadog-plugin-pg/src/index.js +8 -5
  141. package/packages/datadog-plugin-pino/src/index.js +3 -5
  142. package/packages/datadog-plugin-playwright/src/index.js +1 -3
  143. package/packages/datadog-plugin-prisma/src/client.js +4 -6
  144. package/packages/datadog-plugin-prisma/src/engine.js +3 -3
  145. package/packages/datadog-plugin-prisma/src/index.js +1 -1
  146. package/packages/datadog-plugin-protobufjs/src/index.js +2 -6
  147. package/packages/datadog-plugin-redis/src/index.js +2 -2
  148. package/packages/datadog-plugin-restify/src/index.js +1 -3
  149. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  150. package/packages/datadog-plugin-rhea/src/index.js +1 -1
  151. package/packages/datadog-plugin-rhea/src/producer.js +2 -2
  152. package/packages/datadog-plugin-router/src/index.js +1 -3
  153. package/packages/datadog-plugin-selenium/src/index.js +1 -3
  154. package/packages/datadog-plugin-sharedb/src/index.js +1 -1
  155. package/packages/datadog-plugin-tedious/src/index.js +3 -3
  156. package/packages/datadog-plugin-undici/src/index.js +2 -4
  157. package/packages/datadog-plugin-vitest/src/index.js +1 -3
  158. package/packages/datadog-plugin-web/src/index.js +1 -3
  159. package/packages/datadog-plugin-winston/src/index.js +3 -5
  160. package/packages/dd-trace/src/appsec/channels.js +1 -0
  161. package/packages/dd-trace/src/appsec/graphql.js +14 -12
  162. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +14 -7
  163. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  164. package/packages/dd-trace/src/appsec/recommended.json +271 -2
  165. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  166. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +1 -3
  167. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +1 -3
  168. package/packages/dd-trace/src/config.js +1 -1
  169. package/packages/dd-trace/src/datastreams/checkpointer.js +23 -2
  170. package/packages/dd-trace/src/datastreams/processor.js +4 -3
  171. package/packages/dd-trace/src/guardrails/telemetry.js +18 -2
  172. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +351 -0
  173. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +179 -0
  174. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +30 -50
  175. package/packages/dd-trace/src/llmobs/plugins/openai.js +3 -5
  176. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +3 -5
  177. package/packages/dd-trace/src/llmobs/writers/base.js +3 -2
  178. package/packages/dd-trace/src/opentracing/propagation/text_map.js +25 -2
  179. package/packages/dd-trace/src/opentracing/span_context.js +4 -0
  180. package/packages/dd-trace/src/plugin_manager.js +8 -4
  181. package/packages/dd-trace/src/plugins/apollo.js +3 -3
  182. package/packages/dd-trace/src/plugins/cache.js +1 -1
  183. package/packages/dd-trace/src/plugins/client.js +3 -3
  184. package/packages/dd-trace/src/plugins/consumer.js +3 -3
  185. package/packages/dd-trace/src/plugins/database.js +2 -2
  186. package/packages/dd-trace/src/plugins/index.js +2 -0
  187. package/packages/dd-trace/src/plugins/log_plugin.js +1 -5
  188. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  189. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  190. package/packages/dd-trace/src/plugins/producer.js +3 -3
  191. package/packages/dd-trace/src/plugins/server.js +3 -3
  192. package/packages/dd-trace/src/plugins/storage.js +1 -1
  193. package/packages/dd-trace/src/plugins/tracing.js +24 -6
  194. package/packages/dd-trace/src/plugins/util/ci.js +11 -7
  195. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +15 -19
  196. package/packages/dd-trace/src/plugins/util/ip_extractor.js +44 -3
  197. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  198. package/packages/dd-trace/src/plugins/util/web.js +26 -7
  199. package/packages/dd-trace/src/profiling/config.js +2 -0
  200. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +2 -21
  201. package/packages/dd-trace/src/profiling/libuv-size.js +49 -0
  202. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +2 -6
  203. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +1 -3
  204. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +1 -3
  205. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +1 -3
  206. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +1 -3
  207. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +24 -23
  208. package/packages/dd-trace/src/profiling/profilers/event_plugins/fs.js +3 -9
  209. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +3 -9
  210. package/packages/dd-trace/src/profiling/profilers/events.js +83 -64
  211. package/packages/dd-trace/src/profiling/profilers/poisson.js +105 -0
  212. package/packages/dd-trace/src/profiling/profilers/wall.js +3 -3
  213. package/packages/dd-trace/src/remote_config/manager.js +1 -1
  214. package/packages/dd-trace/src/supported-configurations.json +2 -0
  215. package/packages/dd-trace/src/tracer_metadata.js +1 -1
@@ -0,0 +1,351 @@
1
+ 'use strict'
2
+
3
+ const BaseLLMObsPlugin = require('../base')
4
+ const { getModelProvider } = require('../../../../../datadog-plugin-ai/src/utils')
5
+
6
+ const { channel } = require('dc-polyfill')
7
+
8
+ const toolCreationCh = channel('dd-trace:vercel-ai:tool')
9
+ const setAttributesCh = channel('dd-trace:vercel-ai:span:setAttributes')
10
+
11
+ const { MODEL_NAME, MODEL_PROVIDER, NAME } = require('../../constants/tags')
12
+ const {
13
+ getSpanTags,
14
+ getOperation,
15
+ getUsage,
16
+ getJsonStringValue,
17
+ getModelMetadata,
18
+ getGenerationMetadata,
19
+ getToolNameFromTags,
20
+ getToolCallResultContent
21
+ } = require('./util')
22
+
23
+ const SPAN_NAME_TO_KIND_MAPPING = {
24
+ // embeddings
25
+ embed: 'workflow',
26
+ embedMany: 'workflow',
27
+ doEmbed: 'embedding',
28
+ // object generation
29
+ generateObject: 'workflow',
30
+ streamObject: 'workflow',
31
+ // text generation
32
+ generateText: 'workflow',
33
+ streamText: 'workflow',
34
+ // llm operations
35
+ doGenerate: 'llm',
36
+ doStream: 'llm',
37
+ // tools
38
+ toolCall: 'tool'
39
+ }
40
+
41
+ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
42
+ static id = 'ai'
43
+ static integration = 'ai'
44
+ static prefix = 'tracing:dd-trace:vercel-ai'
45
+
46
+ /**
47
+ * The available tools within the runtime scope of this integration.
48
+ * This essentially acts as a global registry for all tools made through the Vercel AI SDK.
49
+ * @type {Set<Record<string, any>>}
50
+ */
51
+ #availableTools
52
+
53
+ /**
54
+ * A mapping of tool call IDs to tool names.
55
+ * This is used to map the tool call ID to the tool name for the output message.
56
+ * @type {Record<string, string>}
57
+ */
58
+ #toolCallIdsToName
59
+
60
+ constructor (...args) {
61
+ super(...args)
62
+
63
+ this.#toolCallIdsToName = {}
64
+ this.#availableTools = new Set()
65
+ toolCreationCh.subscribe(toolArgs => {
66
+ this.#availableTools.add(toolArgs)
67
+ })
68
+
69
+ setAttributesCh.subscribe(({ ctx, attributes }) => {
70
+ Object.assign(ctx.attributes, attributes)
71
+ })
72
+ }
73
+
74
+ /**
75
+ * Does a best-effort attempt to find the right tool name for the given tool description.
76
+ * This is because the Vercel AI SDK does not tag tools by name properly, but
77
+ * rather by the index they were passed in. Tool names appear nowhere in the span tags.
78
+ *
79
+ * We use the tool description as the next best identifier for a tool.
80
+ *
81
+ * @param {string} toolDescription
82
+ * @returns {string}
83
+ */
84
+ findToolName (toolDescription) {
85
+ for (const availableTool of this.#availableTools) {
86
+ const description = availableTool.description
87
+ if (description === toolDescription) {
88
+ return availableTool.id
89
+ }
90
+ }
91
+ }
92
+
93
+ getLLMObsSpanRegisterOptions (ctx) {
94
+ const span = ctx.currentStore?.span
95
+ const operation = getOperation(span)
96
+ const kind = SPAN_NAME_TO_KIND_MAPPING[operation]
97
+ if (!kind) return
98
+
99
+ return { kind, name: operation }
100
+ }
101
+
102
+ setLLMObsTags (ctx) {
103
+ const span = ctx.currentStore?.span
104
+ if (!span) return
105
+
106
+ const operation = getOperation(span)
107
+ const kind = SPAN_NAME_TO_KIND_MAPPING[operation]
108
+ if (!kind) return
109
+
110
+ const tags = getSpanTags(ctx)
111
+
112
+ if (['embedding', 'llm'].includes(kind)) {
113
+ this._tagger._setTag(span, MODEL_NAME, tags['ai.model.id'])
114
+ this._tagger._setTag(span, MODEL_PROVIDER, getModelProvider(tags))
115
+ }
116
+
117
+ switch (operation) {
118
+ case 'embed':
119
+ case 'embedMany':
120
+ this.setEmbeddingWorkflowTags(span, tags)
121
+ break
122
+ case 'doEmbed':
123
+ this.setEmbeddingTags(span, tags)
124
+ break
125
+ case 'generateObject':
126
+ case 'streamObject':
127
+ this.setObjectGenerationTags(span, tags)
128
+ break
129
+ case 'generateText':
130
+ case 'streamText':
131
+ this.setTextGenerationTags(span, tags)
132
+ break
133
+ case 'doGenerate':
134
+ case 'doStream':
135
+ this.setLLMOperationTags(span, tags)
136
+ break
137
+ case 'toolCall':
138
+ this.setToolTags(span, tags)
139
+ break
140
+ default:
141
+ break
142
+ }
143
+ }
144
+
145
+ setEmbeddingWorkflowTags (span, tags) {
146
+ const inputs = tags['ai.value'] ?? tags['ai.values']
147
+ const parsedInputs = Array.isArray(inputs)
148
+ ? inputs.map(input => getJsonStringValue(input, ''))
149
+ : getJsonStringValue(inputs, '')
150
+
151
+ const embeddingsOutput = tags['ai.embedding'] ?? tags['ai.embeddings']
152
+ const isSingleEmbedding = !Array.isArray(embeddingsOutput)
153
+ const numberOfEmbeddings = isSingleEmbedding ? 1 : embeddingsOutput.length
154
+ const embeddingsLength = getJsonStringValue(isSingleEmbedding ? embeddingsOutput : embeddingsOutput?.[0], []).length
155
+ const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]`
156
+
157
+ this._tagger.tagTextIO(span, parsedInputs, output)
158
+
159
+ const metadata = getGenerationMetadata(tags)
160
+ this._tagger.tagMetadata(span, metadata)
161
+ }
162
+
163
+ setEmbeddingTags (span, tags) {
164
+ const inputs = tags['ai.values']
165
+ if (!Array.isArray(inputs)) return
166
+
167
+ const parsedInputs = inputs.map(input => getJsonStringValue(input, ''))
168
+
169
+ const embeddingsOutput = tags['ai.embeddings']
170
+ const numberOfEmbeddings = embeddingsOutput?.length
171
+ const embeddingsLength = getJsonStringValue(embeddingsOutput?.[0], []).length
172
+ const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]`
173
+
174
+ this._tagger.tagEmbeddingIO(span, parsedInputs, output)
175
+
176
+ const usage = tags['ai.usage.tokens']
177
+ this._tagger.tagMetrics(span, {
178
+ inputTokens: usage,
179
+ totalTokens: usage
180
+ })
181
+ }
182
+
183
+ setObjectGenerationTags (span, tags) {
184
+ const promptInfo = getJsonStringValue(tags['ai.prompt'], {})
185
+ const lastUserPrompt =
186
+ promptInfo.prompt ??
187
+ promptInfo.messages.reverse().find(message => message.role === 'user')?.content
188
+ const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt
189
+
190
+ const output = tags['ai.response.object']
191
+
192
+ this._tagger.tagTextIO(span, prompt, output)
193
+
194
+ const metadata = getGenerationMetadata(tags) ?? {}
195
+ metadata.schema = getJsonStringValue(tags['ai.schema'], {})
196
+ this._tagger.tagMetadata(span, metadata)
197
+ }
198
+
199
+ setTextGenerationTags (span, tags) {
200
+ const promptInfo = getJsonStringValue(tags['ai.prompt'], {})
201
+ const lastUserPrompt =
202
+ promptInfo.prompt ??
203
+ promptInfo.messages.reverse().find(message => message.role === 'user')?.content
204
+ const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt
205
+
206
+ const output = tags['ai.response.text']
207
+
208
+ this._tagger.tagTextIO(span, prompt, output)
209
+
210
+ const metadata = getGenerationMetadata(tags)
211
+ this._tagger.tagMetadata(span, metadata)
212
+ }
213
+
214
+ setLLMOperationTags (span, tags) {
215
+ const toolsForModel = tags['ai.prompt.tools']?.map(getJsonStringValue)
216
+
217
+ const inputMessages = getJsonStringValue(tags['ai.prompt.messages'], [])
218
+ const parsedInputMessages = []
219
+ for (const message of inputMessages) {
220
+ const formattedMessages = this.formatMessage(message, toolsForModel)
221
+ parsedInputMessages.push(...formattedMessages)
222
+ }
223
+
224
+ const outputMessage = this.formatOutputMessage(tags, toolsForModel)
225
+
226
+ this._tagger.tagLLMIO(span, parsedInputMessages, outputMessage)
227
+
228
+ const metadata = getModelMetadata(tags)
229
+ this._tagger.tagMetadata(span, metadata)
230
+
231
+ const usage = getUsage(tags)
232
+ this._tagger.tagMetrics(span, usage)
233
+ }
234
+
235
+ setToolTags (span, tags) {
236
+ const toolCallId = tags['ai.toolCall.id']
237
+ const name = getToolNameFromTags(tags) ?? this.#toolCallIdsToName[toolCallId]
238
+ if (name) this._tagger._setTag(span, NAME, name)
239
+
240
+ const input = tags['ai.toolCall.args']
241
+ const output = tags['ai.toolCall.result']
242
+
243
+ this._tagger.tagTextIO(span, input, output)
244
+ }
245
+
246
+ formatOutputMessage (tags, toolsForModel) {
247
+ const outputMessageText = tags['ai.response.text'] ?? tags['ai.response.object']
248
+ const outputMessageToolCalls = getJsonStringValue(tags['ai.response.toolCalls'], [])
249
+
250
+ const formattedToolCalls = []
251
+ for (const toolCall of outputMessageToolCalls) {
252
+ const toolCallArgs = getJsonStringValue(toolCall.args, {})
253
+ const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description
254
+ const name = this.findToolName(toolDescription)
255
+ this.#toolCallIdsToName[toolCall.toolCallId] = name
256
+
257
+ formattedToolCalls.push({
258
+ arguments: toolCallArgs,
259
+ name,
260
+ toolId: toolCall.toolCallId,
261
+ type: 'function'
262
+ })
263
+ }
264
+
265
+ return {
266
+ role: 'assistant',
267
+ content: outputMessageText,
268
+ toolCalls: formattedToolCalls
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Returns a list of formatted messages from a message object.
274
+ * Most of these will just be one entry, but in the case of a "tool" role,
275
+ * it is possible to have multiple tool call results in a single message that we
276
+ * need to split into multiple messages.
277
+ *
278
+ * @param {*} message
279
+ * @param {*} toolsForModel
280
+ * @returns {Array<{role: string, content: string, toolId?: string,
281
+ * toolCalls?: Array<{arguments: string, name: string, toolId: string, type: string}>}>}
282
+ */
283
+ formatMessage (message, toolsForModel) {
284
+ const { role, content } = message
285
+
286
+ if (role === 'system') {
287
+ return [{ role, content }]
288
+ } else if (role === 'user') {
289
+ let finalContent = ''
290
+ for (const part of content) {
291
+ const { type } = part
292
+ if (type === 'text') {
293
+ finalContent += part.text
294
+ }
295
+ }
296
+
297
+ return [{ role, content: finalContent }]
298
+ } else if (role === 'assistant') {
299
+ const toolCalls = []
300
+ let finalContent = ''
301
+
302
+ for (const part of content) {
303
+ const { type } = part
304
+ // TODO(sabrenner): do we want to include reasoning?
305
+ if (['text', 'reasoning', 'redacted-reasoning'].includes(type)) {
306
+ finalContent += part.text ?? part.data
307
+ } else if (type === 'tool-call') {
308
+ const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description
309
+ const name = this.findToolName(toolDescription)
310
+
311
+ toolCalls.push({
312
+ arguments: part.args,
313
+ name,
314
+ toolId: part.toolCallId,
315
+ type: 'function'
316
+ })
317
+ }
318
+ }
319
+
320
+ const finalMessage = {
321
+ role,
322
+ content: finalContent
323
+ }
324
+
325
+ if (toolCalls.length) {
326
+ finalMessage.toolCalls = toolCalls.length ? toolCalls : undefined
327
+ }
328
+
329
+ return [finalMessage]
330
+ } else if (role === 'tool') {
331
+ const finalMessages = []
332
+ for (const part of content) {
333
+ if (part.type === 'tool-result') {
334
+ const safeResult = getToolCallResultContent(part)
335
+
336
+ finalMessages.push({
337
+ role,
338
+ content: safeResult,
339
+ toolId: part.toolCallId
340
+ })
341
+ }
342
+ }
343
+
344
+ return finalMessages
345
+ }
346
+
347
+ return []
348
+ }
349
+ }
350
+
351
+ module.exports = VercelAILLMObsPlugin
@@ -0,0 +1,179 @@
1
+ 'use strict'
2
+
3
+ const MODEL_METADATA_KEYS = new Set([
4
+ 'frequency_penalty',
5
+ 'max_tokens',
6
+ 'presence_penalty',
7
+ 'temperature',
8
+ 'top_p',
9
+ 'top_k',
10
+ 'stop_sequences'
11
+ ])
12
+
13
+ /**
14
+ * Get the span tags from the context (either the attributes or the span tags).
15
+ *
16
+ * @param {Record<string, any>} ctx
17
+ * @returns {Record<string, any>}
18
+ */
19
+ function getSpanTags (ctx) {
20
+ const span = ctx.currentStore?.span
21
+ const carrier = ctx.attributes ?? span?.context()._tags ?? {}
22
+ return carrier
23
+ }
24
+
25
+ /**
26
+ * Get the operation name from the span name
27
+ *
28
+ * @example
29
+ * span._name = 'ai.generateText'
30
+ * getOperation(span) // 'generateText'
31
+ *
32
+ * @example
33
+ * span._name = 'ai.generateText.doGenerate'
34
+ * getOperation(span) // 'doGenerate'
35
+ *
36
+ * @param {import('../../../opentracing/span')} span
37
+ * @returns {string}
38
+ */
39
+ function getOperation (span) {
40
+ const name = span._name
41
+ if (!name) return
42
+
43
+ return name.split('.').pop()
44
+ }
45
+
46
+ /**
47
+ * Get the LLM token usage from the span tags
48
+ * @param {Record<string, string>} tags
49
+ * @returns {{inputTokens: number, outputTokens: number, totalTokens: number}}
50
+ */
51
+ function getUsage (tags) {
52
+ const usage = {}
53
+ const inputTokens = tags['ai.usage.promptTokens']
54
+ const outputTokens = tags['ai.usage.completionTokens']
55
+
56
+ if (inputTokens != null) usage.inputTokens = inputTokens
57
+ if (outputTokens != null) usage.outputTokens = outputTokens
58
+
59
+ const totalTokens = inputTokens + outputTokens
60
+ if (!Number.isNaN(totalTokens)) usage.totalTokens = totalTokens
61
+
62
+ return usage
63
+ }
64
+
65
+ /**
66
+ * Safely JSON parses a string value with a default fallback
67
+ * @param {string} str
68
+ * @param {any} defaultValue
69
+ * @returns {Record<string, any> | string | Array<any>}
70
+ */
71
+ function getJsonStringValue (str, defaultValue) {
72
+ let maybeValue = defaultValue
73
+ try {
74
+ maybeValue = JSON.parse(str)
75
+ } catch {
76
+ // do nothing
77
+ }
78
+
79
+ return maybeValue
80
+ }
81
+
82
+ /**
83
+ * Get the model metadata from the span tags (top_p, top_k, temperature, etc.)
84
+ * @param {import('../../../opentracing/span')} span
85
+ * @returns {Record<string, string> | null}
86
+ */
87
+ function getModelMetadata (tags) {
88
+ const modelMetadata = {}
89
+ for (const metadata of MODEL_METADATA_KEYS) {
90
+ const metadataTagKey = `gen_ai.request.${metadata}`
91
+ const metadataValue = tags[metadataTagKey]
92
+ if (metadataValue) {
93
+ modelMetadata[metadata] = metadataValue
94
+ }
95
+ }
96
+
97
+ return Object.keys(modelMetadata).length ? modelMetadata : null
98
+ }
99
+
100
+ /**
101
+ * Get the generation metadata from the span tags (maxSteps, maxRetries, etc.)
102
+ * @param {Record<string, string>} tags
103
+ * @returns {Record<string, string> | null}
104
+ */
105
+ function getGenerationMetadata (tags) {
106
+ const metadata = {}
107
+
108
+ for (const tag of Object.keys(tags)) {
109
+ if (!tag.startsWith('ai.settings')) continue
110
+
111
+ const settingKey = tag.split('.').pop()
112
+ const transformedKey = settingKey.replaceAll(/[A-Z]/g, letter => '_' + letter.toLowerCase())
113
+ if (MODEL_METADATA_KEYS.has(transformedKey)) continue
114
+
115
+ const settingValue = tags[tag]
116
+ metadata[settingKey] = settingValue
117
+ }
118
+
119
+ return Object.keys(metadata).length ? metadata : null
120
+ }
121
+
122
+ /**
123
+ * Get the tool name from the span tags.
124
+ * If the tool name is a parsable number, or is not found, null is returned.
125
+ * Older versions of the ai sdk would tag the tool name as its index in the tools array.
126
+ *
127
+ * @param {Record<string, string>} tags
128
+ * @returns {string | null}
129
+ */
130
+ function getToolNameFromTags (tags) {
131
+ const toolName = tags['ai.toolCall.name']
132
+ if (!toolName) return null
133
+
134
+ const parsedToolName = Number.parseInt(toolName)
135
+ if (!Number.isNaN(parsedToolName)) return null
136
+
137
+ return toolName
138
+ }
139
+
140
+ /**
141
+ * Get the content of a tool call result.
142
+ * Version 5 of the ai sdk sets this tag as `content.output`, with a `
143
+ * @param {Record<string, any>} content
144
+ * @returns {string}
145
+ */
146
+ function getToolCallResultContent (content) {
147
+ const { output, result } = content
148
+ if (output) {
149
+ if (output.type === 'text') {
150
+ return output.value
151
+ } else if (output.type === 'json') {
152
+ return JSON.stringify(output.value)
153
+ }
154
+ return '[Unparsable Tool Result]'
155
+ } else if (result) {
156
+ if (typeof result === 'string') {
157
+ return result
158
+ }
159
+
160
+ try {
161
+ return JSON.stringify(result)
162
+ } catch {
163
+ return '[Unparsable Tool Result]'
164
+ }
165
+ } else {
166
+ return '[Unsupported Tool Result]'
167
+ }
168
+ }
169
+
170
+ module.exports = {
171
+ getSpanTags,
172
+ getOperation,
173
+ getUsage,
174
+ getJsonStringValue,
175
+ getModelMetadata,
176
+ getGenerationMetadata,
177
+ getToolNameFromTags,
178
+ getToolCallResultContent
179
+ }
@@ -25,11 +25,9 @@ const ToolHandler = require('./handlers/tool')
25
25
  const VectorStoreHandler = require('./handlers/vectorstore')
26
26
 
27
27
  class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
28
- static get integration () { return 'langchain' }
29
- static get id () { return 'langchain' }
30
- static get prefix () {
31
- return 'tracing:apm:langchain:invoke'
32
- }
28
+ static integration = 'langchain'
29
+ static id = 'langchain'
30
+ static prefix = 'tracing:apm:langchain:invoke'
33
31
 
34
32
  constructor () {
35
33
  super(...arguments)
@@ -150,75 +148,57 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
150
148
  }
151
149
 
152
150
  class RunnableSequenceInvokePlugin extends BaseLangChainLLMObsPlugin {
153
- static get id () { return 'llmobs_langchain_rs_invoke' }
154
- static get lcType () { return 'chain' }
155
- static get prefix () {
156
- return 'tracing:orchestrion:@langchain/core:RunnableSequence_invoke'
157
- }
151
+ static id = 'llmobs_langchain_rs_invoke'
152
+ static lcType = 'chain'
153
+ static prefix = 'tracing:orchestrion:@langchain/core:RunnableSequence_invoke'
158
154
  }
159
155
 
160
156
  class RunnableSequenceBatchPlugin extends BaseLangChainLLMObsPlugin {
161
- static get id () { return 'llmobs_langchain_rs_batch' }
162
- static get lcType () { return 'chain' }
163
- static get prefix () {
164
- return 'tracing:orchestrion:@langchain/core:RunnableSequence_batch'
165
- }
157
+ static id = 'llmobs_langchain_rs_batch'
158
+ static lcType = 'chain'
159
+ static prefix = 'tracing:orchestrion:@langchain/core:RunnableSequence_batch'
166
160
  }
167
161
 
168
162
  class BaseChatModelGeneratePlugin extends BaseLangChainLLMObsPlugin {
169
- static get id () { return 'llmobs_langchain_chat_model_generate' }
170
- static get lcType () { return 'chat_model' }
171
- static get prefix () {
172
- return 'tracing:orchestrion:@langchain/core:BaseChatModel_generate'
173
- }
163
+ static id = 'llmobs_langchain_chat_model_generate'
164
+ static lcType = 'chat_model'
165
+ static prefix = 'tracing:orchestrion:@langchain/core:BaseChatModel_generate'
174
166
  }
175
167
 
176
168
  class BaseLLMGeneratePlugin extends BaseLangChainLLMObsPlugin {
177
- static get id () { return 'llmobs_langchain_llm_generate' }
178
- static get lcType () { return 'llm' }
179
- static get prefix () {
180
- return 'tracing:orchestrion:@langchain/core:BaseLLM_generate'
181
- }
169
+ static id = 'llmobs_langchain_llm_generate'
170
+ static lcType = 'llm'
171
+ static prefix = 'tracing:orchestrion:@langchain/core:BaseLLM_generate'
182
172
  }
183
173
 
184
174
  class EmbeddingsEmbedQueryPlugin extends BaseLangChainLLMObsPlugin {
185
- static get id () { return 'llmobs_langchain_embeddings_embed_query' }
186
- static get lcType () { return 'embedding' }
187
- static get prefix () {
188
- return 'tracing:apm:@langchain/core:Embeddings_embedQuery'
189
- }
175
+ static id = 'llmobs_langchain_embeddings_embed_query'
176
+ static lcType = 'embedding'
177
+ static prefix = 'tracing:apm:@langchain/core:Embeddings_embedQuery'
190
178
  }
191
179
 
192
180
  class EmbeddingsEmbedDocumentsPlugin extends BaseLangChainLLMObsPlugin {
193
- static get id () { return 'llmobs_langchain_embeddings_embed_documents' }
194
- static get lcType () { return 'embedding' }
195
- static get prefix () {
196
- return 'tracing:apm:@langchain/core:Embeddings_embedDocuments'
197
- }
181
+ static id = 'llmobs_langchain_embeddings_embed_documents'
182
+ static lcType = 'embedding'
183
+ static prefix = 'tracing:apm:@langchain/core:Embeddings_embedDocuments'
198
184
  }
199
185
 
200
186
  class ToolInvokePlugin extends BaseLangChainLLMObsPlugin {
201
- static get id () { return 'llmobs_langchain_tool_invoke' }
202
- static get lcType () { return 'tool' }
203
- static get prefix () {
204
- return 'tracing:orchestrion:@langchain/core:Tool_invoke'
205
- }
187
+ static id = 'llmobs_langchain_tool_invoke'
188
+ static lcType = 'tool'
189
+ static prefix = 'tracing:orchestrion:@langchain/core:Tool_invoke'
206
190
  }
207
191
 
208
192
  class VectorStoreSimilaritySearchPlugin extends BaseLangChainLLMObsPlugin {
209
- static get id () { return 'llmobs_langchain_vectorstore_similarity_search' }
210
- static get lcType () { return 'similarity_search' }
211
- static get prefix () {
212
- return 'tracing:orchestrion:@langchain/core:VectorStore_similaritySearch'
213
- }
193
+ static id = 'llmobs_langchain_vectorstore_similarity_search'
194
+ static lcType = 'similarity_search'
195
+ static prefix = 'tracing:orchestrion:@langchain/core:VectorStore_similaritySearch'
214
196
  }
215
197
 
216
198
  class VectorStoreSimilaritySearchWithScorePlugin extends BaseLangChainLLMObsPlugin {
217
- static get id () { return 'llmobs_langchain_vectorstore_similarity_search_with_score' }
218
- static get lcType () { return 'similarity_search' }
219
- static get prefix () {
220
- return 'tracing:orchestrion:@langchain/core:VectorStore_similaritySearchWithScore'
221
- }
199
+ static id = 'llmobs_langchain_vectorstore_similarity_search_with_score'
200
+ static lcType = 'similarity_search'
201
+ static prefix = 'tracing:orchestrion:@langchain/core:VectorStore_similaritySearchWithScore'
222
202
  }
223
203
 
224
204
  module.exports = [
@@ -10,11 +10,9 @@ function isIterable (obj) {
10
10
  }
11
11
 
12
12
  class OpenAiLLMObsPlugin extends LLMObsPlugin {
13
- static get id () { return 'openai' }
14
- static get integration () { return 'openai' }
15
- static get prefix () {
16
- return 'tracing:apm:openai:request'
17
- }
13
+ static id = 'openai'
14
+ static integration = 'openai'
15
+ static prefix = 'tracing:apm:openai:request'
18
16
 
19
17
  getLLMObsSpanRegisterOptions (ctx) {
20
18
  const resource = ctx.methodName
@@ -7,11 +7,9 @@ const {
7
7
  } = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils')
8
8
 
9
9
  class VertexAILLMObsPlugin extends LLMObsPlugin {
10
- static get integration () { return 'vertexai' } // used for llmobs telemetry
11
- static get id () { return 'vertexai' }
12
- static get prefix () {
13
- return 'tracing:apm:vertexai:request'
14
- }
10
+ static integration = 'vertexai' // used for llmobs telemetry
11
+ static id = 'vertexai'
12
+ static prefix = 'tracing:apm:vertexai:request'
15
13
 
16
14
  getLLMObsSpanRegisterOptions (ctx) {
17
15
  const history = ctx.instance?.historyInternal || []
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const request = require('../../exporters/common/request')
4
+ const { getEnvironmentVariable } = require('../../config-helper')
4
5
  const { URL, format } = require('node:url')
5
6
  const path = require('node:path')
6
7
 
@@ -17,8 +18,8 @@ const { parseResponseAndLog } = require('./util')
17
18
 
18
19
  class BaseLLMObsWriter {
19
20
  constructor ({ interval, timeout, eventType, config, endpoint, intake }) {
20
- this._interval = interval || 1000 // 1s
21
- this._timeout = timeout || 5000 // 5s
21
+ this._interval = interval ?? getEnvironmentVariable('_DD_LLMOBS_FLUSH_INTERVAL') ?? 1000 // 1s
22
+ this._timeout = timeout ?? getEnvironmentVariable('_DD_LLMOBS_TIMEOUT') ?? 5000 // 5s
22
23
  this._eventType = eventType
23
24
 
24
25
  this._buffer = []