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 32 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
22 changes: 22 additions & 0 deletions .github/workflows/llmobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,25 @@ jobs:
uses: ./.github/actions/testagent/logs
with:
suffix: llmobs-${{ github.job }}

ai:
runs-on: ubuntu-latest
env:
PLUGINS: ai
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: yarn test:plugins:ci
- run: yarn test:llmobs:plugins:ci
shell: bash
- uses: ./.github/actions/node/active-lts
- run: yarn test:plugins:ci
- run: yarn test:llmobs:plugins:ci
shell: bash
- uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
- if: always()
uses: ./.github/actions/testagent/logs
with:
suffix: llmobs-${{ github.job }}
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@
/packages/datadog-plugin-openai/ @DataDog/ml-observability
/packages/datadog-plugin-langchain/ @DataDog/ml-observability
/packages/datadog-plugin-google-cloud-vertexai/ @DataDog/ml-observability
/packages/datadog-plugin-ai/ @DataDog/ml-observability
/packages/datadog-instrumentations/src/openai.js @DataDog/ml-observability
/packages/datadog-instrumentations/src/langchain.js @DataDog/ml-observability
/packages/datadog-instrumentations/src/google-cloud-vertexai.js @DataDog/ml-observability
/packages/datadog-instrumentations/src/ai.js @DataDog/ml-observability
/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime @DataDog/ml-observability
/packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js @DataDog/ml-observability

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ services:
- LDAP_PASSWORDS=password1,password2

testagent:
image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.24.1
image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.27.0
ports:
- "127.0.0.1:9126:9126"
environment:
Expand Down
162 changes: 162 additions & 0 deletions packages/datadog-instrumentations/src/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
'use strict'

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

const { channel, tracingChannel } = 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
}

const vercelAiTracingChannel = tracingChannel('dd-trace:vercel-ai')
const vercelAiSpanSetAttributesChannel = channel('dd-trace:vercel-ai:span:setAttributes')

const noopTracer = {
startActiveSpan () {
const fn = arguments[arguments.length - 1]

const span = {
spanContext () {
return { traceId: '', spanId: '', traceFlags: 0 }
},
setAttribute () {
return this
},
setAttributes () {
return this
},
addEvent () {
return this
},
addLink () {
return this
},
addLinks () {
return this
},
setStatus () {
return this
},
updateName () {
return this
},
end () {
return this
},
isRecording () {
return false
},
recordException () {
return this
}
}

return fn(span)
}
}

function wrapTracer (tracer) {
if (Object.hasOwn(tracer, Symbol.for('_dd.wrapped'))) return

shimmer.wrap(tracer, 'startActiveSpan', function (startActiveSpan) {
return function () {
const name = arguments[0]
const options = arguments.length > 2 ? (arguments[1] ?? {}) : {} // startActiveSpan(name, fn)
const cb = arguments[arguments.length - 1]

const ctx = {
name,
attributes: options.attributes ?? {}
}

arguments[arguments.length - 1] = shimmer.wrapFunction(cb, function (originalCb) {
return function (span) {
shimmer.wrap(span, 'end', function (spanEnd) {
return function () {
vercelAiTracingChannel.asyncEnd.publish(ctx)
return spanEnd.apply(this, arguments)
}
})

shimmer.wrap(span, 'setAttributes', function (setAttributes) {
return function (attributes) {
vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
return setAttributes.apply(this, arguments)
}
})

shimmer.wrap(span, 'recordException', function (recordException) {
return function (exception) {
ctx.error = exception
vercelAiTracingChannel.error.publish(ctx)
return recordException.apply(this, arguments)
}
})

return originalCb.apply(this, arguments)
}
})

return vercelAiTracingChannel.start.runStores(ctx, () => {
const result = startActiveSpan.apply(this, arguments)
vercelAiTracingChannel.end.publish(ctx)
return result
})
}
})

Object.defineProperty(tracer, Symbol.for('_dd.wrapped'), { value: true })
}

function wrapWithTracer (fn) {
return function () {
const options = arguments[0]

options.experimental_telemetry ??= { isEnabled: true, tracer: noopTracer }
wrapTracer(options.experimental_telemetry.tracer)

return fn.apply(this, arguments)
}
}

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

return tool.apply(this, arguments)
}
}

// CJS exports
addHook({
name: 'ai',
versions: ['>=4.0.0'],
}, exports => {
for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) {
exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true })
}

return exports
})

// ESM exports
addHook({
name: 'ai',
versions: ['>=4.0.0'],
file: 'dist/index.mjs'
}, exports => {
for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) {
exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true })
}

return exports
})
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 @@ -29,6 +29,7 @@ module.exports = {
'@smithy/smithy-client': () => require('../aws-sdk'),
'@vitest/runner': { esmFirst: true, fn: () => require('../vitest') },
aerospike: () => require('../aerospike'),
ai: () => require('../ai'),
amqp10: () => require('../amqp10'),
amqplib: () => require('../amqplib'),
avsc: () => require('../avsc'),
Expand Down
17 changes: 17 additions & 0 deletions packages/datadog-plugin-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/ai')
const VercelAITracingPlugin = require('./tracing')

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

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

const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
const { getModelProvider } = require('./utils')

class VercelAITracingPlugin extends TracingPlugin {
static get id () { return 'ai' }
static get prefix () { return 'tracing:dd-trace:vercel-ai' }

bindStart (ctx) {
const attributes = ctx.attributes

const model = attributes['ai.model.id']
const modelProvider = getModelProvider(attributes)

this.startSpan(ctx.name, {
meta: {
'resource.name': ctx.name,
'ai.request.model': model,
'ai.request.model_provider': modelProvider
}
}, ctx)

return ctx.currentStore
}

asyncEnd (ctx) {
const span = ctx.currentStore?.span
span?.finish()
}
}

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

const { parseModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')

/**
* Get the model provider from the span tags or attributes.
* This is normalized to LLM Observability model provider standards.
*
* @param {Record<string, string>} tags
* @returns {string}
*/
function getModelProvider (tags) {
const modelProviderTag = tags['ai.model.provider']
const providerParts = modelProviderTag?.split('.')
const provider = providerParts?.[0]

if (provider === 'amazon-bedrock') {
const modelId = tags['ai.model.id']
const model = modelId && parseModelId(modelId)
return model?.modelProvider ?? provider
}

return provider
}

module.exports = {
getModelProvider
}
Loading
Loading