Skip to content

feat(ai): add vercel ai integration #5858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
94a2b68
add vercel ai integration with otel processing
sabrenner Jun 9, 2025
0ed7d4c
add some typedocs and comments
sabrenner Jun 9, 2025
233beb8
fix tagger test
sabrenner Jun 9, 2025
90f4f13
rename to 'ai'
sabrenner Jun 9, 2025
a4014cc
try doing with a custom tracer
sabrenner Jun 11, 2025
407d6dc
change up implementation slightly
sabrenner Jun 11, 2025
16c3d28
codeowners
sabrenner Jun 17, 2025
fbff2a3
undo id changes
sabrenner Jun 17, 2025
d963226
get rid of otel span start/end publishes
sabrenner Jun 17, 2025
067c596
revert llmobs tagger test change
sabrenner Jun 17, 2025
24f37a8
add better noop default tracer and esm support
sabrenner Jun 19, 2025
563d56f
delete util file
sabrenner Jun 19, 2025
7e906a2
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jun 23, 2025
47056b9
add initial test skeleton
sabrenner Jun 23, 2025
2da477f
fix duplicate wrapping
sabrenner Jun 25, 2025
a43db02
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jun 25, 2025
cc9b031
simplify patching
sabrenner Jun 25, 2025
2015808
apm tests
sabrenner Jun 25, 2025
2ce1798
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jun 26, 2025
5a5a23c
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jun 26, 2025
16c5da4
write some tests
sabrenner Jun 27, 2025
0c2df21
add rest of llmobs tests
sabrenner Jun 30, 2025
f47cc43
add ci job
sabrenner Jul 2, 2025
85ec1c8
fix node version and import issue with check and externally defined v…
sabrenner Jul 2, 2025
39f3eb9
fix metadata tagging
sabrenner Jul 2, 2025
c403681
handle tool rolls
sabrenner Jul 3, 2025
15bcb79
remove import
sabrenner Jul 3, 2025
de7c0fa
add default return and docstring for formatMessage
sabrenner Jul 7, 2025
ffb75c1
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jul 14, 2025
c60316b
fix tool message tests
sabrenner Jul 14, 2025
1824313
add model name and provider tags to apm tracing
sabrenner Jul 14, 2025
95ebae1
some self review
sabrenner Jul 14, 2025
d19e31b
address some review comments
sabrenner Jul 21, 2025
c03fc82
do not stub for tests, instead use dummy test agent
sabrenner Jul 21, 2025
20c3c63
move cassettes to local directory to fix tests
sabrenner Jul 21, 2025
0f1acf6
configurable flush interval for tests
sabrenner Jul 21, 2025
72eca83
use separate image for ai tests that have local cassettes attached
sabrenner Jul 21, 2025
ac4071c
use different port
sabrenner Jul 21, 2025
39ccf68
move test flush interval back local
sabrenner Jul 21, 2025
32d75a6
change in esm test
sabrenner Jul 21, 2025
d1fbc64
Revert "move test flush interval back local"
sabrenner Jul 23, 2025
b705691
Revert "use different port"
sabrenner Jul 23, 2025
b28bd92
Revert "use separate image for ai tests that have local cassettes att…
sabrenner Jul 23, 2025
bec9312
Revert "move cassettes to local directory to fix tests"
sabrenner Jul 23, 2025
b642965
Revert "change in esm test"
sabrenner Jul 23, 2025
bbbfffb
remove env var from llmobs workflow
sabrenner Jul 23, 2025
85abd5d
fix type hint for test util
sabrenner Jul 23, 2025
8d1662e
Merge branch 'master' of github.com:DataDog/dd-trace-js into sabrenne…
sabrenner Jul 23, 2025
2bd92ee
add test cassettes
sabrenner Jul 23, 2025
f9cfc5c
re-trigger ci
sabrenner Jul 23, 2025
9fc3d13
more review fixes
sabrenner Jul 24, 2025
5f6bc14
Merge branch 'master' into sabrenner/vercel-ai-sdk-integration
sabrenner Jul 24, 2025
25a83e5
try removing configuration
sabrenner Jul 24, 2025
8036e15
Merge branch 'sabrenner/vercel-ai-sdk-integration' of github.com:Data…
sabrenner Jul 24, 2025
3d428da
revert supported config undoing
sabrenner Jul 24, 2025
2938177
Merge branch 'master' into sabrenner/vercel-ai-sdk-integration
sabrenner Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
'@smithy/smithy-client': () => require('../aws-sdk'),
'@vitest/runner': { esmFirst: true, fn: () => require('../vitest') },
aerospike: () => require('../aerospike'),
ai: { esmFirst: true, fn: () => require('../vercel-ai') },
amqp10: () => require('../amqp10'),
amqplib: () => require('../amqplib'),
avsc: () => require('../avsc'),
Expand Down
70 changes: 70 additions & 0 deletions packages/datadog-instrumentations/src/vercel-ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict'

const { addHook } = require('./helpers/instrument')
const tracer = require('../../dd-trace')
const shimmer = require('../../datadog-shimmer')

const { TracerProvider } = tracer
const provider = new TracerProvider()
provider.register()

const { channel } = require('dc-polyfill')
const toolCreationChannel = channel('dd-trace:vercel-ai:tool')

const TRACED_FUNCTIONS = {
generateText: wrapWithTracer,
streamText: wrapWithTracer,
generateObject: wrapWithTracer,
streamObject: wrapWithTracer,
embed: wrapWithTracer,
embedMany: wrapWithTracer,
tool: wrapTool
}

function wrapWithTracer (fn) {
return function () {
const options = arguments[0]
if (options.experimental_telemetry != null) return fn.apply(this, arguments)

options.experimental_telemetry = {
isEnabled: true,
// TODO(sabrenner): need to figure out how a user can configure this tracer
// maybe we advise they do this manually?
tracer: provider.getTracer()
}

return fn.apply(this, arguments)
}
}

function wrapTool (tool) {
return function () {
const args = arguments[0]
toolCreationChannel.publish(args)

return tool.apply(this, arguments)
}
}

addHook({
name: 'ai',
versions: ['>=4.0.0'],
}, exports => {
// the exports from this package are not configurable
// to return wrapped functions with the tracer provided above,
// we need to copy the exports
const wrappedExports = {}

for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) {
const original = exports[fnName]
wrappedExports[fnName] = shimmer.wrapFunction(original, patchingFn)
}

Object.getOwnPropertyNames(exports).forEach(prop => {
if (!Object.keys(TRACED_FUNCTIONS).includes(prop)) {
wrappedExports[prop] = exports[prop]
}
})

return wrappedExports
})
17 changes: 17 additions & 0 deletions packages/datadog-plugin-vercel-ai/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

const CompositePlugin = require('../../dd-trace/src/plugins/composite')
const VercelAILLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/vercel-ai')
const VercelAITracingPlugin = require('./tracing')

class VercelAIPlugin extends CompositePlugin {
static get id () { return 'vercel-ai' }
static get plugins () {
return {
llmobs: VercelAILLMObsPlugin,
tracing: VercelAITracingPlugin
}
}
}

module.exports = VercelAIPlugin
55 changes: 55 additions & 0 deletions packages/datadog-plugin-vercel-ai/src/tracing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict'

const Plugin = require('../../dd-trace/src/plugins/plugin')

const { channel } = require('dc-polyfill')
const spanFinishCh = channel('dd-trace:otel:span:finish')

const { isVercelAISpan } = require('./util')

// filter input/output based tags for APM spans
// this is for data access controls and for sensitive data
// the LLM Observability feature is recommended in its place for better
// data access controls and sensitive data scrubbing
const TAG_PATTERNS_TO_FILTER = [
'prompt', // TODO(sabrenner): we need to refine this so it doesn't filter out prompt tokens
'messages',
'response.text',
'toolCalls',
'toolCall.args',
'toolCall.result',
'values',
'embedding'
]

/**
* Determines if an OpenTelemetry span tag should be filtered out on the final DD span
*
* @param {String} key
* @returns {Boolean}
*/
function shouldFilterTag (key) {
return TAG_PATTERNS_TO_FILTER.some(pattern => key.includes(pattern))
}

class VercelAITracingPlugin extends Plugin {
static get id () { return 'ai' }

constructor (...args) {
super(...args)

spanFinishCh.subscribe(({ ddSpan }) => {
if (!isVercelAISpan(ddSpan)) {
return
}

for (const key of Object.keys(ddSpan.context()._tags)) {
if (shouldFilterTag(key)) {
ddSpan.setTag(key, undefined)
}
}
})
}
}

module.exports = VercelAITracingPlugin
15 changes: 15 additions & 0 deletions packages/datadog-plugin-vercel-ai/src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

/**
* Determines if an OpenTelemetry span is a Vercel AI span
*
* @param {import('../../dd-trace/src/opentracing/span') | null} span
* @returns {Boolean}
*/
function isVercelAISpan (span) {
return span._name?.startsWith('ai') && span.context()._tags?.['resource.name']?.startsWith('ai')
}

module.exports = {
isVercelAISpan
}
4 changes: 2 additions & 2 deletions packages/dd-trace/src/id.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,6 @@ function writeUInt32BE (buffer, value, offset) {
buffer[0 + offset] = value & 255
}

module.exports = function createIdentifier (value, radix) {
module.exports = Object.assign(function createIdentifier (value, radix) {
return new Identifier(value, radix)
}
}, { Identifier })
21 changes: 18 additions & 3 deletions packages/dd-trace/src/llmobs/plugins/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ class LLMObsPlugin extends TracingPlugin {
throw new Error('getLLMObsSPanRegisterOptions must be implemented by the subclass')
}

start (ctx) {
start (ctx, options = {}) {
// even though llmobs span events won't be enqueued if llmobs is disabled
// we should avoid doing any computations here (these listeners aren't disabled)
const enabled = this._tracerConfig.llmobs.enabled
if (!enabled) return

const {
useLlmObsParent = true,
enterIntoLlmObsStorage = true
} = options

const parent = this.getLLMObsParent(ctx)
const apmStore = ctx.currentStore
const span = apmStore?.span
Expand All @@ -40,10 +45,20 @@ class LLMObsPlugin extends TracingPlugin {
telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.integration })

ctx.llmobs = {} // initialize context-based namespace
llmobsStorage.enterWith({ span })
ctx.llmobs.parent = parent

this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.integration, ...registerOptions })
if (enterIntoLlmObsStorage) {
llmobsStorage.enterWith({ span })
}

const coalescedRegisterOptions = {
integration: this.constructor.integration,
...registerOptions
}

coalescedRegisterOptions.parent = useLlmObsParent ? parent : span.context()._parentId

this._tagger.registerLLMObsSpan(span, coalescedRegisterOptions)
}
}

Expand Down
Loading