From 33eca033db8bf2ab65718d29b0105b71cd569c36 Mon Sep 17 00:00:00 2001 From: Nir Gazit Date: Fri, 20 Dec 2024 14:19:07 +0200 Subject: [PATCH 1/2] fix(sdk): patch span attributes for vercel AI users (#478) --- package-lock.json | 316 ++++++++++++++++-- packages/sample-app/package.json | 5 +- packages/sample-app/src/sample_vercel_ai.ts | 26 ++ packages/traceloop-sdk/package.json | 2 + .../recording.har | 168 ++++++++++ .../traceloop-sdk/src/lib/tracing/index.ts | 67 ++++ .../traceloop-sdk/test/decorators.test.ts | 64 +++- 7 files changed, 616 insertions(+), 32 deletions(-) create mode 100644 packages/sample-app/src/sample_vercel_ai.ts create mode 100644 packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-fix-Vercel-AI-spans-to-match-OpenLLMetry-format_2061519753/recording.har diff --git a/package-lock.json b/package-lock.json index 2a15c633..f7a847de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,101 @@ "typescript": "^5.7.2" } }, + "node_modules/@ai-sdk/openai": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.0.10.tgz", + "integrity": "sha512-ltZ1B/qSHvNiXngJBVY1GJD41/kvvi9QCQeuiEdf5utJnjRlR0MKNHzb3YRhJaLKFuGFrq1vAnxlSHGANY8R7A==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "@ai-sdk/provider-utils": "2.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.2.tgz", + "integrity": "sha512-YYtP6xWQyaAf5LiWLJ+ycGTOeBLWrED7LUrvc+SQIWhGaneylqbaGsyQL7VouQUeQ4JZ1qKYZuhmi3W56HADPA==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.0.4.tgz", + "integrity": "sha512-GMhcQCZbwM6RoZCri0MWeEWXRt/T+uCxsmHEsTwNvEH3GDjNzchfX25C8ftry2MeEOOn6KfqCLSKomcgK6RoOg==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "eventsource-parser": "^3.0.0", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.0.6.tgz", + "integrity": "sha512-8Hkserq0Ge6AEi7N4hlv2FkfglAGbkoAXEZ8YSp255c3PbnZz6+/5fppw+aROmZMOfNwallSRuy1i/iPa2rBpQ==", + "dependencies": { + "@ai-sdk/provider-utils": "2.0.4", + "@ai-sdk/ui-utils": "1.0.5", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.0.5.tgz", + "integrity": "sha512-DGJSbDf+vJyWmFNexSPUsS1AAy7gtsmFmoSyNbNbJjwl9hRIf2dknfA1V0ahx6pg3NNklNYFm53L8Nphjovfvg==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "@ai-sdk/provider-utils": "2.0.4", + "zod-to-json-schema": "^3.23.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -6180,16 +6275,6 @@ "node": ">=6" } }, - "node_modules/@mistralai/mistralai": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.5.tgz", - "integrity": "sha512-yC91oJ5ScEPqbXmv3mJTwTFgu/ZtsYoOPOhaVXSsy6x4zXTqTI57yEC1flC9uiA8GpG/yhpn2BBUXF95+U9Blw==", - "peerDependencies": { - "react": "^18 || ^19", - "react-dom": "^18 || ^19", - "zod": ">= 3" - } - }, "node_modules/@mixedbread-ai/sdk": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/@mixedbread-ai/sdk/-/sdk-2.2.11.tgz", @@ -9256,7 +9341,6 @@ "resolved": "https://registry.npmjs.org/@pollyjs/adapter-fetch/-/adapter-fetch-6.0.6.tgz", "integrity": "sha512-euWzM5TnA2jptdJgjIkEd/Q2hNFB652XivPKerWpLMFB13IKlrERX9wn2Dw3pquDAP0LvXH1qVBZt5DK0Xhzyg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@pollyjs/adapter": "^6.0.6", "@pollyjs/utils": "^6.0.6", @@ -10735,6 +10819,11 @@ "@types/ms": "*" } }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -11426,6 +11515,35 @@ "node": ">=8" } }, + "node_modules/ai": { + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.0.20.tgz", + "integrity": "sha512-dYevYKtREcjSVopBDFWVNca7WJEI1p9Vr9eo7V7fZHzi2vXGDyEa2WYatjFbpR6z6gpVAxKHsof8EoN+B1IAsA==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "@ai-sdk/provider-utils": "2.0.4", + "@ai-sdk/react": "1.0.6", + "@ai-sdk/ui-utils": "1.0.5", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0", + "zod-to-json-schema": "^3.23.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -12798,6 +12916,11 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -14430,6 +14553,11 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -15333,6 +15461,14 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -17804,7 +17940,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -17878,6 +18013,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -17947,6 +18087,33 @@ "dev": true, "license": "MIT" }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.0.tgz", + "integrity": "sha512-ZkD35Mx92acjB2yNJgziGqT9oKHEOxjTBTDRpOsRWtdecL/0jM3z5kM/CTzHWvHIen1GvkM85p6TuFfDGfc8/Q==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -19087,6 +19254,16 @@ "node": ">=18.0.0" } }, + "node_modules/llamaindex/node_modules/@mistralai/mistralai": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.5.tgz", + "integrity": "sha512-yC91oJ5ScEPqbXmv3mJTwTFgu/ZtsYoOPOhaVXSsy6x4zXTqTI57yEC1flC9uiA8GpG/yhpn2BBUXF95+U9Blw==", + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "zod": ">= 3" + } + }, "node_modules/llamaindex/node_modules/@pinecone-database/pinecone": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-4.0.0.tgz", @@ -19287,6 +19464,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/llamaindex/node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/llamaindex/node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -19530,6 +19728,18 @@ "node": ">=0.10.0" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lop": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", @@ -20569,6 +20779,23 @@ "license": "MIT", "optional": true }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -22817,26 +23044,15 @@ } }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", - "license": "MIT", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "peer": true, "dependencies": { - "scheduler": "^0.25.0" + "loose-envify": "^1.1.0" }, - "peerDependencies": { - "react": "^19.0.0" + "engines": { + "node": ">=0.10.0" } }, "node_modules/react-is": { @@ -23669,9 +23885,13 @@ "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT", "peer": true }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/selderee": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", @@ -24507,6 +24727,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/synckit": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", @@ -24754,6 +24986,17 @@ "node": ">=0.8" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -25496,6 +25739,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -27270,6 +27521,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { + "@ai-sdk/openai": "^1.0.10", "@anthropic-ai/sdk": "^0.32.1", "@aws-sdk/client-bedrock-runtime": "^3.709.0", "@azure/openai": "^1.0.0-beta.13", @@ -27279,6 +27531,7 @@ "@llamaindex/openai": "^0.1.44", "@pinecone-database/pinecone": "^2.2.2", "@traceloop/node-server-sdk": "*", + "ai": "^4.0.20", "cheerio": "^1.0.0", "chromadb": "^1.9.4", "cohere-ai": "^7.15.0", @@ -28104,12 +28357,14 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@ai-sdk/openai": "^1.0.10", "@anthropic-ai/sdk": "^0.32.1", "@aws-sdk/client-bedrock-runtime": "^3.709.0", "@azure/openai": "^1.0.0-beta.13", "@google-cloud/aiplatform": "^3.34.0", "@google-cloud/vertexai": "^1.9.2", "@pinecone-database/pinecone": "^2.2.2", + "@pollyjs/adapter-fetch": "^6.0.6", "@pollyjs/adapter-node-http": "^6.0.6", "@pollyjs/core": "^6.0.6", "@pollyjs/persister-fs": "^6.0.6", @@ -28117,6 +28372,7 @@ "@types/mocha": "^10.0.10", "@types/node": "^20.17.10", "@types/uuid": "^9.0.8", + "ai": "^4.0.20", "chromadb": "^1.9.4", "cohere-ai": "^7.15.0", "esbuild": "^0.24.0", diff --git a/packages/sample-app/package.json b/packages/sample-app/package.json index f5bcd802..9ae6d17e 100644 --- a/packages/sample-app/package.json +++ b/packages/sample-app/package.json @@ -17,6 +17,7 @@ "run:decorators": "npm run build && node dist/src/sample_decorators.js", "run:with": "npm run build && node dist/src/sample_with.js", "run:prompt_mgmt": "npm run build && node dist/src/sample_prompt_mgmt.js", + "run:vercel": "npm run build && node dist/src/sample_vercel_ai.js", "run:sample_vision": "npm run build && node dist/src/sample_vision_prompt.js", "run:sample_azure": "npm run build && node dist/src/sample_azure.js", "run:openai_streaming": "npm run build && node dist/src/sample_openai_streaming.js", @@ -35,20 +36,22 @@ "node": ">=14" }, "dependencies": { + "@ai-sdk/openai": "^1.0.10", "@anthropic-ai/sdk": "^0.32.1", "@aws-sdk/client-bedrock-runtime": "^3.709.0", "@azure/openai": "^1.0.0-beta.13", "@google-cloud/aiplatform": "^3.34.0", "@google-cloud/vertexai": "^1.9.2", "@langchain/community": "^0.3.18", + "@llamaindex/openai": "^0.1.44", "@pinecone-database/pinecone": "^2.2.2", "@traceloop/node-server-sdk": "*", + "ai": "^4.0.20", "cheerio": "^1.0.0", "chromadb": "^1.9.4", "cohere-ai": "^7.15.0", "langchain": "^0.3.7", "llamaindex": "^0.8.28", - "@llamaindex/openai": "^0.1.44", "openai": "^4.76.2", "zod": "^3.24.1" }, diff --git a/packages/sample-app/src/sample_vercel_ai.ts b/packages/sample-app/src/sample_vercel_ai.ts new file mode 100644 index 00000000..98e1e517 --- /dev/null +++ b/packages/sample-app/src/sample_vercel_ai.ts @@ -0,0 +1,26 @@ +import * as traceloop from "@traceloop/node-server-sdk"; +import { openai } from "@ai-sdk/openai"; +import { generateText } from "ai"; + +traceloop.initialize({ + appName: "sample_vercel_ai", + disableBatch: true, +}); + +async function chat(question: string) { + return await traceloop.withWorkflow( + { name: "chat" }, + async () => { + const chatCompletion = await generateText({ + messages: [{ role: "user", content: question }], + model: openai("gpt-3.5-turbo"), + experimental_telemetry: { isEnabled: true }, + }); + + return chatCompletion.text; + }, + { question }, + ); +} + +chat("What is the capital of France?"); diff --git a/packages/traceloop-sdk/package.json b/packages/traceloop-sdk/package.json index 6b0af681..e50fa172 100644 --- a/packages/traceloop-sdk/package.json +++ b/packages/traceloop-sdk/package.json @@ -60,6 +60,8 @@ "homepage": "https://github.com/traceloop/openllmetry-js/tree/main/packages/traceloop-sdk", "gitHead": "ef1e70d6037f7b5c061056ef2be16e3f55f02ed5", "devDependencies": { + "@ai-sdk/openai": "^1.0.10", + "ai": "^4.0.20", "@anthropic-ai/sdk": "^0.32.1", "@aws-sdk/client-bedrock-runtime": "^3.709.0", "@azure/openai": "^1.0.0-beta.13", diff --git a/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-fix-Vercel-AI-spans-to-match-OpenLLMetry-format_2061519753/recording.har b/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-fix-Vercel-AI-spans-to-match-OpenLLMetry-format_2061519753/recording.har new file mode 100644 index 00000000..3a0aef01 --- /dev/null +++ b/packages/traceloop-sdk/recordings/Test-SDK-Decorators_847855269/should-fix-Vercel-AI-spans-to-match-OpenLLMetry-format_2061519753/recording.har @@ -0,0 +1,168 @@ +{ + "log": { + "_recordingName": "Test SDK Decorators/should fix Vercel AI spans to match OpenLLMetry format", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "3852b8fa745a6ea702f405c37cf5ffd1", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 113, + "cookies": [], + "headers": [ + { + "name": "content-type", + "value": "application/json" + } + ], + "headersSize": 167, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\"model\":\"gpt-3.5-turbo\",\"temperature\":0,\"messages\":[{\"role\":\"user\",\"content\":\"What is the capital of France?\"}]}" + }, + "queryString": [], + "url": "https://api.openai.com/v1/chat/completions" + }, + "response": { + "bodySize": 766, + "content": { + "mimeType": "application/json", + "size": 766, + "text": "{\n \"id\": \"chatcmpl-AgWBu65owDIXxVxvXgksqBfXCJiMk\",\n \"object\": \"chat.completion\",\n \"created\": 1734696882,\n \"model\": \"gpt-3.5-turbo-0125\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"The capital of France is Paris.\",\n \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 14,\n \"completion_tokens\": 8,\n \"total_tokens\": 22,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": null\n}\n" + }, + "cookies": [ + { + "domain": ".api.openai.com", + "httpOnly": true, + "name": "_cfuvid", + "path": "/", + "sameSite": "None", + "secure": true, + "value": "wuL7YK1S9W1aOvEw8WtoEYqRDcVuI6pSl9xLSztFLLs-1734696882922-0.0.1.1-604800000" + } + ], + "headers": [ + { + "name": "access-control-expose-headers", + "value": "X-Request-ID" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=86400" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "name": "cf-ray", + "value": "8f4f8dbcefc5bd44-ATL" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "content-encoding", + "value": "br" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "date", + "value": "Fri, 20 Dec 2024 12:14:42 GMT" + }, + { + "name": "openai-organization", + "value": "traceloop" + }, + { + "name": "openai-processing-ms", + "value": "174" + }, + { + "name": "openai-version", + "value": "2020-10-01" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "set-cookie", + "value": "_cfuvid=wuL7YK1S9W1aOvEw8WtoEYqRDcVuI6pSl9xLSztFLLs-1734696882922-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubDomains; preload" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "x-ratelimit-limit-requests", + "value": "5000" + }, + { + "name": "x-ratelimit-limit-tokens", + "value": "4000000" + }, + { + "name": "x-ratelimit-remaining-requests", + "value": "4999" + }, + { + "name": "x-ratelimit-remaining-tokens", + "value": "3999975" + }, + { + "name": "x-ratelimit-reset-requests", + "value": "12ms" + }, + { + "name": "x-ratelimit-reset-tokens", + "value": "0s" + }, + { + "name": "x-request-id", + "value": "req_410acc120ddc7e1a1d4d2507d30f7fb3" + } + ], + "headersSize": 913, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-12-20T12:14:41.962Z", + "time": 814, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 814 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/traceloop-sdk/src/lib/tracing/index.ts b/packages/traceloop-sdk/src/lib/tracing/index.ts index f27edab7..84b0de13 100644 --- a/packages/traceloop-sdk/src/lib/tracing/index.ts +++ b/packages/traceloop-sdk/src/lib/tracing/index.ts @@ -3,6 +3,7 @@ import { SimpleSpanProcessor, BatchSpanProcessor, SpanProcessor, + ReadableSpan, } from "@opentelemetry/sdk-trace-node"; import { baggageUtils } from "@opentelemetry/core"; import { Span, context, diag } from "@opentelemetry/api"; @@ -290,6 +291,72 @@ export const startTracing = (options: InitializeOptions) => { } }; + const originalOnEnd = _spanProcessor.onEnd?.bind(_spanProcessor); + _spanProcessor.onEnd = (span: ReadableSpan) => { + // Vercel AI Adapters + const attributes = span.attributes; + + // Adapt span names + const nameMap: Record = { + "ai.generateText.doGenerate": "ai.generateText.generate", + "ai.streamText.doStream": "ai.streamText.stream", + }; + if (span.name in nameMap) { + // Unfortuantely, the span name is not writable as this is not the intended behavior + // but it is a workaround to set the correct span name + (span as any).name = nameMap[span.name]; + } + + if ("ai.response.text" in attributes) { + attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`] = + attributes["ai.response.text"]; + attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`] = "assistant"; + delete attributes["ai.response.text"]; + } + + if ("ai.prompt.messages" in attributes) { + try { + const messages = JSON.parse(attributes["ai.prompt.messages"] as string); + messages.forEach( + (msg: { role: string; content: any }, index: number) => { + attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = + typeof msg.content === "string" + ? msg.content + : JSON.stringify(msg.content); + attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.role`] = + msg.role; + }, + ); + delete attributes["ai.prompt.messages"]; + } catch (e) { + //Skip if JSON parsing fails + } + } + + if ("ai.usage.promptTokens" in attributes) { + attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`] = + attributes["ai.usage.promptTokens"]; + delete attributes["ai.usage.promptTokens"]; + } + + if ("ai.usage.completionTokens" in attributes) { + attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`] = + attributes["ai.usage.completionTokens"]; + delete attributes["ai.usage.completionTokens"]; + } + + if ( + attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`] && + attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`] + ) { + attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`] = + Number(attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]) + + Number(attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]); + } + + originalOnEnd?.(span); + }; + if (options.exporter) { Telemetry.getInstance().capture("tracer:init", { exporter: "custom", diff --git a/packages/traceloop-sdk/test/decorators.test.ts b/packages/traceloop-sdk/test/decorators.test.ts index 0488e6da..a81ea197 100644 --- a/packages/traceloop-sdk/test/decorators.test.ts +++ b/packages/traceloop-sdk/test/decorators.test.ts @@ -20,10 +20,14 @@ import { InMemorySpanExporter } from "@opentelemetry/sdk-trace-base"; import type * as OpenAIModule from "openai"; +import { openai as vercel_openai } from "@ai-sdk/openai"; +import { generateText } from "ai"; + import * as traceloop from "../src"; import { Polly, setupMocha as setupPolly } from "@pollyjs/core"; import NodeHttpAdapter from "@pollyjs/adapter-node-http"; +import FetchAdapter from "@pollyjs/adapter-fetch"; import FSPersister from "@pollyjs/persister-fs"; import { SpanAttributes } from "@traceloop/ai-semantic-conventions"; import { ChatCompletionMessageParam } from "openai/resources/index.mjs"; @@ -31,13 +35,14 @@ import { ChatCompletionMessageParam } from "openai/resources/index.mjs"; const memoryExporter = new InMemorySpanExporter(); Polly.register(NodeHttpAdapter); +Polly.register(FetchAdapter); Polly.register(FSPersister); describe("Test SDK Decorators", () => { let openai: OpenAIModule.OpenAI; setupPolly({ - adapters: ["node-http"], + adapters: ["node-http", "fetch"], persister: "fs", recordIfMissing: process.env.RECORD_MODE === "NEW", matchRequestsBy: { @@ -622,4 +627,61 @@ describe("Test SDK Decorators", () => { JSON.stringify(result), ); }); + + it("should fix Vercel AI spans to match OpenLLMetry format", async () => { + const result = await generateText({ + messages: [{ role: "user", content: "What is the capital of France?" }], + model: vercel_openai("gpt-3.5-turbo"), + experimental_telemetry: { isEnabled: true }, + }); + + const spans = memoryExporter.getFinishedSpans(); + + const generateTextSpan = spans.find( + (span) => span.name === "ai.generateText.generate", + ); + + assert.ok(result); + assert.ok(generateTextSpan); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`], + "user", + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`], + `[{"type":"text","text":"What is the capital of France?"}]`, + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`], + "gpt-3.5-turbo", + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`], + "gpt-3.5-turbo-0125", + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`], + "assistant", + ); + assert.strictEqual( + generateTextSpan.attributes[ + `${SpanAttributes.LLM_COMPLETIONS}.0.content` + ], + result.text, + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`], + 14, + ); + assert.strictEqual( + generateTextSpan.attributes[ + `${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}` + ], + 8, + ); + assert.strictEqual( + generateTextSpan.attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`], + 22, + ); + }); }); From 9d0a3948a7db06488e979164407050dd90469a39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Dec 2024 12:21:23 +0000 Subject: [PATCH 2/2] v0.11.7 --- CHANGELOG.md | 6 ++++++ lerna.json | 2 +- package-lock.json | 3 +-- packages/traceloop-sdk/CHANGELOG.md | 6 ++++++ packages/traceloop-sdk/package.json | 4 ++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50d44f5..c474a974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.11.7](https://github.com/traceloop/openllmetry-js/compare/v0.11.6...v0.11.7) (2024-12-20) + +### Bug Fixes + +- **sdk:** patch span attributes for vercel AI users ([#478](https://github.com/traceloop/openllmetry-js/issues/478)) ([33eca03](https://github.com/traceloop/openllmetry-js/commit/33eca033db8bf2ab65718d29b0105b71cd569c36)) + ## [0.11.6](https://github.com/traceloop/openllmetry-js/compare/v0.11.5...v0.11.6) (2024-12-16) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 75bd098b..d31153b4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "0.11.6", + "version": "0.11.7", "packages": ["packages/*"], "useNx": true } diff --git a/package-lock.json b/package-lock.json index f7a847de..09f1ac17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28331,7 +28331,7 @@ }, "packages/traceloop-sdk": { "name": "@traceloop/node-server-sdk", - "version": "0.11.6", + "version": "0.11.7", "license": "Apache-2.0", "dependencies": { "@opentelemetry/exporter-trace-otlp-proto": "^0.56.0", @@ -28364,7 +28364,6 @@ "@google-cloud/aiplatform": "^3.34.0", "@google-cloud/vertexai": "^1.9.2", "@pinecone-database/pinecone": "^2.2.2", - "@pollyjs/adapter-fetch": "^6.0.6", "@pollyjs/adapter-node-http": "^6.0.6", "@pollyjs/core": "^6.0.6", "@pollyjs/persister-fs": "^6.0.6", diff --git a/packages/traceloop-sdk/CHANGELOG.md b/packages/traceloop-sdk/CHANGELOG.md index eaf5ad95..0ae7b7ff 100644 --- a/packages/traceloop-sdk/CHANGELOG.md +++ b/packages/traceloop-sdk/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.11.7](https://github.com/traceloop/openllmetry-js/compare/v0.11.6...v0.11.7) (2024-12-20) + +### Bug Fixes + +- **sdk:** patch span attributes for vercel AI users ([#478](https://github.com/traceloop/openllmetry-js/issues/478)) ([33eca03](https://github.com/traceloop/openllmetry-js/commit/33eca033db8bf2ab65718d29b0105b71cd569c36)) + ## [0.11.6](https://github.com/traceloop/openllmetry-js/compare/v0.11.5...v0.11.6) (2024-12-16) ### Bug Fixes diff --git a/packages/traceloop-sdk/package.json b/packages/traceloop-sdk/package.json index e50fa172..3bc8816e 100644 --- a/packages/traceloop-sdk/package.json +++ b/packages/traceloop-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/node-server-sdk", - "version": "0.11.6", + "version": "0.11.7", "description": "Traceloop Software Development Kit (SDK) for Node.js", "main": "dist/index.js", "module": "dist/index.mjs", @@ -61,7 +61,6 @@ "gitHead": "ef1e70d6037f7b5c061056ef2be16e3f55f02ed5", "devDependencies": { "@ai-sdk/openai": "^1.0.10", - "ai": "^4.0.20", "@anthropic-ai/sdk": "^0.32.1", "@aws-sdk/client-bedrock-runtime": "^3.709.0", "@azure/openai": "^1.0.0-beta.13", @@ -75,6 +74,7 @@ "@types/mocha": "^10.0.10", "@types/node": "^20.17.10", "@types/uuid": "^9.0.8", + "ai": "^4.0.20", "chromadb": "^1.9.4", "cohere-ai": "^7.15.0", "esbuild": "^0.24.0",