diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa14471..1b29c928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +### Bug Fixes + +- **anthropic:** support streaming for completion API ([#191](https://github.com/traceloop/openllmetry-js/issues/191)) ([0efb330](https://github.com/traceloop/openllmetry-js/commit/0efb3302f9ff767cba29519265c13ca4f02cf612)) + +### Features + +- **anthropic:** instrumentation ([#188](https://github.com/traceloop/openllmetry-js/issues/188)) ([f40df74](https://github.com/traceloop/openllmetry-js/commit/f40df747143279a9a153db58020bf4531b5c171f)) + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) ### Bug Fixes diff --git a/README.md b/README.md index d6cffd95..f9d2103f 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ OpenLLMetry-JS can instrument everything that [OpenTelemetry already instruments - ✅ OpenAI - ✅ Azure OpenAI -- ⏳ Anthropic +- ✅ Anthropic - ✅ Cohere - ⏳ Replicate - ⏳ HuggingFace diff --git a/lerna.json b/lerna.json index 4da0e867..4f9c5074 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "0.5.29", + "version": "0.6.0", "packages": ["packages/*"], "useNx": true } diff --git a/package-lock.json b/package-lock.json index 1750503d..6110f9b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7412,6 +7412,10 @@ "resolved": "packages/ai-semantic-conventions", "link": true }, + "node_modules/@traceloop/instrumentation-anthropic": { + "resolved": "packages/instrumentation-anthropic", + "link": true + }, "node_modules/@traceloop/instrumentation-azure": { "resolved": "packages/instrumentation-azure", "link": true @@ -21979,7 +21983,7 @@ }, "packages/ai-semantic-conventions": { "name": "@traceloop/ai-semantic-conventions", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.7.0" @@ -21988,15 +21992,92 @@ "node": ">=14" } }, + "packages/instrumentation-anthropic": { + "name": "@traceloop/instrumentation-anthropic", + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.22.0", + "@opentelemetry/instrumentation": "^0.49.0", + "@opentelemetry/semantic-conventions": "^1.22.0", + "@traceloop/ai-semantic-conventions": "^0.6.0" + }, + "devDependencies": { + "@anthropic-ai/sdk": "^0.20.1", + "@pollyjs/adapter-node-http": "^6.0.6", + "@pollyjs/core": "^6.0.6", + "@pollyjs/persister-fs": "^6.0.6", + "@types/mocha": "^10.0.6", + "ts-mocha": "^10.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "packages/instrumentation-anthropic/node_modules/@anthropic-ai/sdk": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.20.1.tgz", + "integrity": "sha512-isoelcNvCC0dKJ9cXPIpHaHjrW4EQlXIp4f/3zT3jMZDqSHCx5PVnfsgOiERM5NUTpdKGMDURewoKYK4a9Wu3w==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "packages/instrumentation-anthropic/node_modules/@opentelemetry/instrumentation": { + "version": "0.49.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.49.1.tgz", + "integrity": "sha512-0DLtWtaIppuNNRRllSD4bjU8ZIiLp1cDXvJEbp752/Zf+y3gaLNaoGRGIlX4UHhcsrmtL+P2qxi3Hodi8VuKiQ==", + "dependencies": { + "@opentelemetry/api-logs": "0.49.1", + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "1.7.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "packages/instrumentation-anthropic/node_modules/@types/node": { + "version": "18.19.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", + "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "packages/instrumentation-anthropic/node_modules/import-in-the-middle": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz", + "integrity": "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==", + "dependencies": { + "acorn": "^8.8.2", + "acorn-import-assertions": "^1.9.0", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "packages/instrumentation-azure": { "name": "@traceloop/instrumentation-azure", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@azure/openai": "^1.0.0-beta.10", @@ -22040,13 +22121,13 @@ }, "packages/instrumentation-bedrock": { "name": "@traceloop/instrumentation-bedrock", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@aws-sdk/client-bedrock-runtime": "^3.499.0", @@ -22088,13 +22169,13 @@ }, "packages/instrumentation-cohere": { "name": "@traceloop/instrumentation-cohere", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.44.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@pollyjs/adapter-node-http": "^6.0.6", @@ -22108,13 +22189,13 @@ }, "packages/instrumentation-langchain": { "name": "@traceloop/instrumentation-langchain", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@langchain/community": "^0.0.34", @@ -22696,13 +22777,13 @@ }, "packages/instrumentation-llamaindex": { "name": "@traceloop/instrumentation-llamaindex", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", "lodash": "^4.17.21" }, "devDependencies": { @@ -22747,13 +22828,13 @@ }, "packages/instrumentation-openai": { "name": "@traceloop/instrumentation-openai", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", "tiktoken": "^1.0.13" }, "devDependencies": { @@ -22799,13 +22880,13 @@ }, "packages/instrumentation-pinecone": { "name": "@traceloop/instrumentation-pinecone", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@pinecone-database/pinecone": "^2.0.1", @@ -22847,13 +22928,13 @@ }, "packages/instrumentation-vertexai": { "name": "@traceloop/instrumentation-vertexai", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@google-cloud/aiplatform": "^3.10.0", @@ -22895,6 +22976,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { + "@anthropic-ai/sdk": "^0.20.1", "@aws-sdk/client-bedrock-runtime": "^3.499.0", "@azure/openai": "^1.0.0-beta.11", "@google-cloud/aiplatform": "^3.10.0", @@ -22911,6 +22993,21 @@ "node": ">=14" } }, + "packages/sample-app/node_modules/@anthropic-ai/sdk": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.20.1.tgz", + "integrity": "sha512-isoelcNvCC0dKJ9cXPIpHaHjrW4EQlXIp4f/3zT3jMZDqSHCx5PVnfsgOiERM5NUTpdKGMDURewoKYK4a9Wu3w==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, "packages/sample-app/node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", @@ -23294,6 +23391,14 @@ } } }, + "packages/sample-app/node_modules/@types/node": { + "version": "18.19.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", + "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "packages/sample-app/node_modules/replicate": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/replicate/-/replicate-0.18.1.tgz", @@ -23309,20 +23414,21 @@ }, "packages/traceloop-sdk": { "name": "@traceloop/node-server-sdk", - "version": "0.5.29", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/exporter-trace-otlp-proto": "^0.49.1", "@opentelemetry/sdk-node": "^0.49.1", - "@traceloop/ai-semantic-conventions": "^0.5.29", - "@traceloop/instrumentation-azure": "^0.5.29", - "@traceloop/instrumentation-bedrock": "^0.5.29", - "@traceloop/instrumentation-cohere": "^0.5.29", - "@traceloop/instrumentation-langchain": "^0.5.29", - "@traceloop/instrumentation-llamaindex": "^0.5.29", - "@traceloop/instrumentation-openai": "^0.5.29", - "@traceloop/instrumentation-pinecone": "^0.5.29", - "@traceloop/instrumentation-vertexai": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", + "@traceloop/instrumentation-anthropic": "^0.6.0", + "@traceloop/instrumentation-azure": "^0.6.0", + "@traceloop/instrumentation-bedrock": "^0.6.0", + "@traceloop/instrumentation-cohere": "^0.6.0", + "@traceloop/instrumentation-langchain": "^0.6.0", + "@traceloop/instrumentation-llamaindex": "^0.6.0", + "@traceloop/instrumentation-openai": "^0.6.0", + "@traceloop/instrumentation-pinecone": "^0.6.0", + "@traceloop/instrumentation-vertexai": "^0.6.0", "@types/nunjucks": "^3.2.5", "cross-fetch": "^4.0.0", "fetch-retry": "^5.0.6", @@ -23332,6 +23438,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.20.1", "@aws-sdk/client-bedrock-runtime": "^3.499.0", "@azure/openai": "^1.0.0-beta.11", "@google-cloud/aiplatform": "^3.10.0", @@ -23354,6 +23461,31 @@ "node": ">=14" } }, + "packages/traceloop-sdk/node_modules/@anthropic-ai/sdk": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.20.1.tgz", + "integrity": "sha512-isoelcNvCC0dKJ9cXPIpHaHjrW4EQlXIp4f/3zT3jMZDqSHCx5PVnfsgOiERM5NUTpdKGMDURewoKYK4a9Wu3w==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "packages/traceloop-sdk/node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", + "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "packages/traceloop-sdk/node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", diff --git a/packages/ai-semantic-conventions/CHANGELOG.md b/packages/ai-semantic-conventions/CHANGELOG.md index e029fea5..e7a49fa2 100644 --- a/packages/ai-semantic-conventions/CHANGELOG.md +++ b/packages/ai-semantic-conventions/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/ai-semantic-conventions + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) ### Bug Fixes diff --git a/packages/ai-semantic-conventions/package.json b/packages/ai-semantic-conventions/package.json index 7df77f78..4b94f056 100644 --- a/packages/ai-semantic-conventions/package.json +++ b/packages/ai-semantic-conventions/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/ai-semantic-conventions", - "version": "0.5.29", + "version": "0.6.0", "description": "OpenTelemetry ai-specific semantic conventions", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/packages/instrumentation-anthropic/.eslintignore b/packages/instrumentation-anthropic/.eslintignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/packages/instrumentation-anthropic/.eslintignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/packages/instrumentation-anthropic/.eslintrc.json b/packages/instrumentation-anthropic/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/packages/instrumentation-anthropic/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/instrumentation-anthropic/.prettierignore b/packages/instrumentation-anthropic/.prettierignore new file mode 100644 index 00000000..258e27f6 --- /dev/null +++ b/packages/instrumentation-anthropic/.prettierignore @@ -0,0 +1,2 @@ +/dist +/coverage \ No newline at end of file diff --git a/packages/instrumentation-anthropic/CHANGELOG.md b/packages/instrumentation-anthropic/CHANGELOG.md new file mode 100644 index 00000000..c84a8b69 --- /dev/null +++ b/packages/instrumentation-anthropic/CHANGELOG.md @@ -0,0 +1,14 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +### Bug Fixes + +- **anthropic:** support streaming for completion API ([#191](https://github.com/traceloop/openllmetry-js/issues/191)) ([0efb330](https://github.com/traceloop/openllmetry-js/commit/0efb3302f9ff767cba29519265c13ca4f02cf612)) + +### Features + +- **anthropic:** instrumentation ([#188](https://github.com/traceloop/openllmetry-js/issues/188)) ([f40df74](https://github.com/traceloop/openllmetry-js/commit/f40df747143279a9a153db58020bf4531b5c171f)) diff --git a/packages/instrumentation-anthropic/README.md b/packages/instrumentation-anthropic/README.md new file mode 100644 index 00000000..fdf5bc1b --- /dev/null +++ b/packages/instrumentation-anthropic/README.md @@ -0,0 +1,55 @@ +# OpenTelemetry Anthropic instrumentation for Node.js + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +This module provides automatic instrumentation for [`Anthropic Typescript SDK`](https://github.com/anthropics/anthropic-sdk-typescript) module, which may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@traceloop/node-server-sdk`](https://www.npmjs.com/package/@traceloop/node-server-sdk) bundle. + +If total installation size is not constrained, it is recommended to use the [`@traceloop/node-server-sdk`](https://www.npmjs.com/package/@traceloop/node-server-sdk) bundle for the most seamless instrumentation experience. + +Compatible with OpenTelemetry JS API and SDK `1.0+`. + +## Installation + +```bash +npm install --save @traceloop/instrumentation-anthropic +``` + +## Supported Versions + +- `>=0.9.1` + +## Usage + +To load a specific plugin, specify it in the registerInstrumentations's configuration: + +```js +const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); +const { + AnthropicInstrumentation, +} = require("@traceloop/instrumentation-anthropic"); +const { registerInstrumentations } = require("@opentelemetry/instrumentation"); + +const provider = new NodeTracerProvider(); +provider.register(); + +registerInstrumentations({ + instrumentations: [new AnthropicInstrumentation()], +}); +``` + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- For help or feedback on this project, join us on [Slack][slack-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[slack-url]: https://traceloop.com/slack +[license-url]: https://github.com/traceloop/openllmetry-js/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@traceloop/instrumentation-anthropic +[npm-img]: https://badge.fury.io/js/%40traceloop%2Finstrumentation-anthropic.svg diff --git a/packages/instrumentation-anthropic/package.json b/packages/instrumentation-anthropic/package.json new file mode 100644 index 00000000..07ad0411 --- /dev/null +++ b/packages/instrumentation-anthropic/package.json @@ -0,0 +1,54 @@ +{ + "name": "@traceloop/instrumentation-anthropic", + "version": "0.6.0", + "description": "Anthropic Instrumentaion", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "repository": "traceloop/openllmetry-js", + "scripts": { + "build": "rollup -c", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "test": "ts-mocha -p tsconfig.json 'test/**/*.test.ts'" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "tracing", + "openai", + "anthropic" + ], + "author": "Traceloop", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "files": [ + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "doc", + "LICENSE", + "README.md", + "package.json" + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@opentelemetry/core": "^1.22.0", + "@opentelemetry/instrumentation": "^0.49.0", + "@opentelemetry/semantic-conventions": "^1.22.0", + "@traceloop/ai-semantic-conventions": "^0.6.0" + }, + "devDependencies": { + "@anthropic-ai/sdk": "^0.20.1", + "@pollyjs/adapter-node-http": "^6.0.6", + "@pollyjs/core": "^6.0.6", + "@pollyjs/persister-fs": "^6.0.6", + "@types/mocha": "^10.0.6", + "ts-mocha": "^10.0.0" + }, + "homepage": "https://github.com/traceloop/openllmetry-js/tree/main/packages/instrumentation-anthropic" +} diff --git a/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions-streaming_2198009633/recording.har b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions-streaming_2198009633/recording.har new file mode 100644 index 00000000..bf3d5d6c --- /dev/null +++ b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions-streaming_2198009633/recording.har @@ -0,0 +1,166 @@ +{ + "log": { + "_recordingName": "Test Anthropic instrumentation/should set attributes in span for completions (streaming)", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "411df901c53641005b599f973e8b2068", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 149, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "149" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "Anthropic/JS 0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v18.17.1" + }, + { + "_fromType": "array", + "name": "anthropic-version", + "value": "2023-06-01" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.anthropic.com" + } + ], + "headersSize": 548, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"model\": \"claude-2\",\n \"max_tokens_to_sample\": 300,\n \"prompt\": \"\\n\\nHuman: Tell me a joke about OpenTelemetry\\n\\nAssistant:\",\n \"stream\": true\n}" + }, + "queryString": [], + "url": "https://api.anthropic.com/v1/complete" + }, + "response": { + "bodySize": 6983, + "content": { + "mimeType": "text/event-stream; charset=utf-8", + "size": 6983, + "text": "event: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" Here\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"'s\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: ping\r\ndata: {\"type\": \"ping\"}\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" a\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" silly\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" joke\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" about\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" Open\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"Tele\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"metry\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\":\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"\\n\\nWhat\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" do\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" you\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" call\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" an\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" Open\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"Tele\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"metry\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" collector\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" that\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" keeps\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" getting\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" distracted\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"?\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" A\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" span\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"-\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"t\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"ention\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" deficit\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\" tracer\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"!\",\"stop_reason\":null,\"model\":\"claude-2.1\",\"stop\":null,\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\nevent: completion\r\ndata: {\"type\":\"completion\",\"id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\",\"completion\":\"\",\"stop_reason\":\"stop_sequence\",\"model\":\"claude-2.1\",\"stop\":\"\\n\\nHuman:\",\"log_id\":\"compl_01NxHrRrvyAuE77TjBS7tyTt\" }\r\n\r\n" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Fri, 05 Apr 2024 11:20:12 GMT" + }, + { + "name": "content-type", + "value": "text/event-stream; charset=utf-8" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": "request-id", + "value": "req_01QgLb4tbuUR3myMtpiyaSZX" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "86f925c39fd7a268-FCO" + } + ], + "headersSize": 299, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-04-05T11:20:12.047Z", + "time": 2070, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 2070 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions_1224394582/recording.har b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions_1224394582/recording.har new file mode 100644 index 00000000..9a9ef085 --- /dev/null +++ b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-completions_1224394582/recording.har @@ -0,0 +1,171 @@ +{ + "log": { + "_recordingName": "Test Anthropic instrumentation/should set attributes in span for completions", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "3d54b7ae72fa4dc3272d8f7c5bcddf6d", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 131, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "131" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "Anthropic/JS 0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v18.17.1" + }, + { + "_fromType": "array", + "name": "anthropic-version", + "value": "2023-06-01" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.anthropic.com" + } + ], + "headersSize": 548, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"model\": \"claude-2\",\n \"max_tokens_to_sample\": 300,\n \"prompt\": \"\\n\\nHuman: Tell me a joke about OpenTelemetry\\n\\nAssistant:\"\n}" + }, + "queryString": [], + "url": "https://api.anthropic.com/v1/complete" + }, + "response": { + "bodySize": 600, + "content": { + "encoding": "base64", + "mimeType": "application/json", + "size": 600, + "text": "[\"H4sIAAAAAAAAA4xSwWobMRD9lWddfNmY2s0h+BLS9hBDIJdAWjCYyWpqqdZqttIoW7X034s2DSQlh14EmuG9efPm/TJaRzZb08swBlYv0XTG2+fK4d366qOupw2fX1R7MX7m8/c3m3u2pnsJ2Rpcc+JlBiF41cBo3aKc4MogCfQgRXE7crzjwANrqtt93Md7VzFRhjp+3YUm6hklOhrHeokP3FPJDK9wZBEFeaSYF43kNXDiqFCZKa302qZHi0zedtibT3Opww7qfDxhB0ePDBXBQLGil6j8Q/Nib9DI7yb5R1l7fZ8xUTjBRxUQHiitcBsZmWp+ni7qOLWZX6TgFGXqMDESUwgVPmZNZWhi+ZFTbWqOoCQlWjhOvNibJ4dIYQVVCnoKARTfdEod6TLjKJHRJ/pZL3GFWFTr7NRs1A5ORp6pgj+xxVCRfVNDqjyMmkGKb3Li/NbFVrhhxcDzLvBfG9HSzlRtZcfUrK4YJDGUexclyLGeqeOB7VMSFqYzWWU8JKY8R2f+Zf5eOPZsOjOI5dACGKhYPtus1n8hZjsbcl0GilvTmSDHw/9E9fcfAAAA//8DAOxCDkfmAgAA\"]" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Fri, 05 Apr 2024 11:20:12 GMT" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "request-id", + "value": "req_01MF9Mk2Pm5K7idAdKGVuKtn" + }, + { + "name": "x-cloud-trace-context", + "value": "bfd63b2c08c48c70d3ba04406af4864c" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "86f925a33e42a268-FCO" + }, + { + "name": "content-encoding", + "value": "gzip" + } + ], + "headersSize": 339, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-04-05T11:20:06.759Z", + "time": 5267, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 5267 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages-streaming_44400664/recording.har b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages-streaming_44400664/recording.har new file mode 100644 index 00000000..90b95cab --- /dev/null +++ b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages-streaming_44400664/recording.har @@ -0,0 +1,171 @@ +{ + "log": { + "_recordingName": "Test Anthropic instrumentation/should set attributes in span for messages (streaming)", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "169f6f7c83e96867124983806f6d7be4", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 188, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "188" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "Anthropic/JS 0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v18.17.1" + }, + { + "_fromType": "array", + "name": "anthropic-version", + "value": "2023-06-01" + }, + { + "_fromType": "array", + "name": "x-stainless-helper-method", + "value": "stream" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.anthropic.com" + } + ], + "headersSize": 583, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"max_tokens\": 1024,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about OpenTelemetry\"\n }\n ],\n \"model\": \"claude-3-opus-20240229\",\n \"stream\": true\n}" + }, + "queryString": [], + "url": "https://api.anthropic.com/v1/messages" + }, + "response": { + "bodySize": 19048, + "content": { + "mimeType": "text/event-stream; charset=utf-8", + "size": 19048, + "text": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_01B1V5rsrrCx1gdMW82NDgHk\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"claude-3-opus-20240229\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":17,\"output_tokens\":1}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Sure\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"!\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Here\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"'s\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" joke\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" about\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Open\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"el\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\":\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\nWhy\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" did\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" developer\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" love\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" using\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Open\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"el\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"?\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\nBecause\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" it\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" made\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" tr\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"acing\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" their\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" code\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"span\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\"-\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"t\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"astic\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" experience\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"!\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\nExplanation\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\":\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Open\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"el\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" is\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" set\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" of\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" APIs\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" SD\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"K\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"s\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" and\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" tools\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" used\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" for\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" collecting\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" and\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" ex\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"porting\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" tel\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" data\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" (\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"like\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" traces\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" metrics\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" and\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" logs\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\")\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" to\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" help\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" observe\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" and\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" monitor\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" distributed\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" systems\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" In\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Open\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"el\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"span\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" represents\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" single\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" operation\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" or\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" unit\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" of\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" work\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" within\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" trace\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" joke\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" plays\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" on\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" similarity\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" between\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" word\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"span\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" and\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"fantastic\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\",\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" impl\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ying\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" that\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" using\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Open\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"el\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"emet\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ry\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" makes\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" tr\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"acing\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" code\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" great\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" experience\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" for\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" developers\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"output_tokens\":151}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Fri, 05 Apr 2024 11:20:24 GMT" + }, + { + "name": "content-type", + "value": "text/event-stream; charset=utf-8" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": "request-id", + "value": "req_01MSkAn89zj7EbecovHP9E65" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "86f926063927a268-FCO" + } + ], + "headersSize": 299, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-04-05T11:20:22.711Z", + "time": 9590, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 9590 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages_831758903/recording.har b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages_831758903/recording.har new file mode 100644 index 00000000..a86aea9c --- /dev/null +++ b/packages/instrumentation-anthropic/recordings/Test-Anthropic-instrumentation_3769946143/should-set-attributes-in-span-for-messages_831758903/recording.har @@ -0,0 +1,171 @@ +{ + "log": { + "_recordingName": "Test Anthropic instrumentation/should set attributes in span for messages", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "736e4bfd3852112815402bf773b26d6e", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 170, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "content-length", + "value": "170" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "Anthropic/JS 0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-lang", + "value": "js" + }, + { + "_fromType": "array", + "name": "x-stainless-package-version", + "value": "0.20.1" + }, + { + "_fromType": "array", + "name": "x-stainless-os", + "value": "MacOS" + }, + { + "_fromType": "array", + "name": "x-stainless-arch", + "value": "arm64" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime", + "value": "node" + }, + { + "_fromType": "array", + "name": "x-stainless-runtime-version", + "value": "v18.17.1" + }, + { + "_fromType": "array", + "name": "anthropic-version", + "value": "2023-06-01" + }, + { + "_fromType": "array", + "name": "accept-encoding", + "value": "gzip,deflate" + }, + { + "name": "host", + "value": "api.anthropic.com" + } + ], + "headersSize": 548, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json", + "params": [], + "text": "{\n \"max_tokens\": 1024,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about OpenTelemetry\"\n }\n ],\n \"model\": \"claude-3-opus-20240229\"\n}" + }, + "queryString": [], + "url": "https://api.anthropic.com/v1/messages" + }, + "response": { + "bodySize": 736, + "content": { + "encoding": "base64", + "mimeType": "application/json", + "size": 736, + "text": "[\"H4sIAAAAAAAAA1RSX2vbMBD/Kle95MUpbdZR5pexQseS0Q220nUsI8jWJdYs37m6U10T+t2HnJWsT4a7+/2V98Y7U5pOdpuz8+un1erx56dvl6vP9/cPVzd0dacfr01hdOwxX6GI3aEpTOSQB1bEi1pSU5iaSZHUlL/2L/eKT3kzfUrzPUUsoMGIMwELf7hFsBUnha890i0G7FDjWK5pTR/A4SMG7jHCYEMr4EkZLFQ2giUHHB3GTOOip/YUbhvMO0VyGMFKKwWszQ9OwcHICYJvEZRB4wicIhAOr2XnlRV0IKModu9hqVBbAo22xkwQD4qwjdyBNggdd0g6cfchH3md+I8rrzMBwfiI7mRtcqps8pgrYh88Tj6/ZKClbHoJfcQtRujGQzbJtBVCosmMm+InclynLIPuFJbgmGYKgyXNuIplQrXEAzQ8QJfqBpazDqRHcp52wAQ21NxwAJdingwcW2g4RfnP7bHSYNOukUlc7Di5XsIuoUjuYBYRiBUiWjfCliNwlaPbygevI3g6lDglymqNrbwKjKincGPHCkHU1+1LhRzcfGul8UzoYLAj8PbwAhn8r5fspeE+T7JixlUoerI25vl3YTp2GExp6mCTw/mbOfdJ5ouzxcXZYvHOFEaU+01EK0ymNEhuoynSy0LwISHVaEpKIRQmTb9+uTee+qQb5RZJTHl+WRhO+mr09uL5+S8AAAD//wMACb+NKFkDAAA=\"]" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Fri, 05 Apr 2024 11:20:22 GMT" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "request-id", + "value": "req_0182vaPgMhnXsMnBS73p6Yqd" + }, + { + "name": "x-cloud-trace-context", + "value": "42ba21496f6152a6803a8a29a8855cd7" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "cf-cache-status", + "value": "DYNAMIC" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "cf-ray", + "value": "86f925d0bb53a268-FCO" + }, + { + "name": "content-encoding", + "value": "gzip" + } + ], + "headersSize": 339, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-04-05T11:20:14.137Z", + "time": 8561, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 8561 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/instrumentation-anthropic/rollup.config.js b/packages/instrumentation-anthropic/rollup.config.js new file mode 100644 index 00000000..691c0ca5 --- /dev/null +++ b/packages/instrumentation-anthropic/rollup.config.js @@ -0,0 +1,37 @@ +const dts = require("rollup-plugin-dts"); +const typescript = require("@rollup/plugin-typescript"); +const json = require("@rollup/plugin-json"); + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const name = require("./package.json").main.replace(/\.js$/, ""); + +const bundle = (config) => ({ + ...config, + input: "src/index.ts", + external: (id) => !/^[./]/.test(id), +}); + +exports.default = [ + bundle({ + plugins: [typescript.default(), json.default()], + output: [ + { + file: `${name}.js`, + format: "cjs", + sourcemap: true, + }, + { + file: `${name}.mjs`, + format: "es", + sourcemap: true, + }, + ], + }), + bundle({ + plugins: [dts.default()], + output: { + file: `${name}.d.ts`, + format: "es", + }, + }), +]; diff --git a/packages/instrumentation-anthropic/src/index.ts b/packages/instrumentation-anthropic/src/index.ts new file mode 100644 index 00000000..ec28d1d9 --- /dev/null +++ b/packages/instrumentation-anthropic/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Traceloop + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from "./instrumentation"; +export * from "./types"; diff --git a/packages/instrumentation-anthropic/src/instrumentation.ts b/packages/instrumentation-anthropic/src/instrumentation.ts new file mode 100644 index 00000000..00880d2d --- /dev/null +++ b/packages/instrumentation-anthropic/src/instrumentation.ts @@ -0,0 +1,428 @@ +/* + * Copyright Traceloop + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + context, + trace, + Span, + Attributes, + SpanKind, + SpanStatusCode, +} from "@opentelemetry/api"; +import { + InstrumentationBase, + InstrumentationModuleDefinition, + InstrumentationNodeModuleDefinition, + safeExecuteInTheMiddle, +} from "@opentelemetry/instrumentation"; +import { + CONTEXT_KEY_ALLOW_TRACE_CONTENT, + SpanAttributes, +} from "@traceloop/ai-semantic-conventions"; +import { AnthropicInstrumentationConfig } from "./types"; +import { version } from "../package.json"; +import type * as anthropic from "@anthropic-ai/sdk"; +import type { + CompletionCreateParamsNonStreaming, + CompletionCreateParamsStreaming, + Completion, +} from "@anthropic-ai/sdk/resources/completions"; +import type { + MessageCreateParamsNonStreaming, + MessageCreateParamsStreaming, + Message, + MessageStreamEvent, +} from "@anthropic-ai/sdk/resources/messages"; +import type { Stream } from "@anthropic-ai/sdk/streaming"; + +export class AnthropicInstrumentation extends InstrumentationBase { + protected declare _config: AnthropicInstrumentationConfig; + + constructor(config: AnthropicInstrumentationConfig = {}) { + super("@traceloop/instrumentation-anthropic", version, config); + } + + public override setConfig(config: AnthropicInstrumentationConfig = {}) { + super.setConfig(config); + } + + public manuallyInstrument(module: typeof anthropic) { + this._diag.debug(`Patching @anthropic-ai/sdk manually`); + + this._wrap( + module.Anthropic.Completions.prototype, + "create", + this.patchAnthropic("completion"), + ); + this._wrap( + module.Anthropic.Messages.prototype, + "create", + this.patchAnthropic("chat"), + ); + } + + protected init(): InstrumentationModuleDefinition { + const module = new InstrumentationNodeModuleDefinition( + "@anthropic-ai/sdk", + [">=0.9.1"], + this.patch.bind(this), + this.unpatch.bind(this), + ); + return module; + } + + private patch(moduleExports: typeof anthropic, moduleVersion?: string) { + this._diag.debug(`Patching @anthropic-ai/sdk@${moduleVersion}`); + + this._wrap( + moduleExports.Anthropic.Completions.prototype, + "create", + this.patchAnthropic("completion"), + ); + this._wrap( + moduleExports.Anthropic.Messages.prototype, + "create", + this.patchAnthropic("chat"), + ); + return moduleExports; + } + + private unpatch( + moduleExports: typeof anthropic, + moduleVersion?: string, + ): void { + this._diag.debug(`Unpatching @azure/openai@${moduleVersion}`); + + this._unwrap(moduleExports.Anthropic.Completions.prototype, "create"); + this._unwrap(moduleExports.Anthropic.Messages.prototype, "create"); + } + + private patchAnthropic(type: "chat" | "completion") { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const plugin = this; + // eslint-disable-next-line @typescript-eslint/ban-types + return (original: Function) => { + return function method(this: any, ...args: unknown[]) { + const span = + type === "chat" + ? plugin.startSpan({ + type, + params: args[0] as MessageCreateParamsNonStreaming & { + extraAttributes?: Record; + }, + }) + : plugin.startSpan({ + type, + params: args[0] as CompletionCreateParamsNonStreaming & { + extraAttributes?: Record; + }, + }); + + const execContext = trace.setSpan(context.active(), span); + const execPromise = safeExecuteInTheMiddle( + () => { + return context.with(execContext, () => { + if ((args?.[0] as any)?.extraAttributes) { + delete (args[0] as any).extraAttributes; + } + return original.apply(this, args); + }); + }, + (e) => { + if (e) { + plugin._diag.error("Error in Anthropic instrumentation", e); + } + }, + ); + + if ( + ( + args[0] as + | MessageCreateParamsStreaming + | CompletionCreateParamsStreaming + ).stream && + type === "completion" // For some reason, this causes an exception with chat, so disabled for now + ) { + return context.bind( + execContext, + plugin._streamingWrapPromise({ + span, + type, + promise: execPromise, + }), + ); + } + + const wrappedPromise = plugin._wrapPromise(type, span, execPromise); + + return context.bind(execContext, wrappedPromise as any); + }; + }; + } + + private startSpan({ + type, + params, + }: + | { + type: "chat"; + params: MessageCreateParamsNonStreaming & { + extraAttributes?: Record; + }; + } + | { + type: "completion"; + params: CompletionCreateParamsNonStreaming & { + extraAttributes?: Record; + }; + }): Span { + const attributes: Attributes = { + [SpanAttributes.LLM_VENDOR]: "Anthropic", + [SpanAttributes.LLM_REQUEST_TYPE]: type, + }; + + attributes[SpanAttributes.LLM_REQUEST_MODEL] = params.model; + attributes[SpanAttributes.LLM_TEMPERATURE] = params.temperature; + attributes[SpanAttributes.LLM_TOP_P] = params.top_p; + attributes[SpanAttributes.LLM_TOP_K] = params.top_k; + + if (type === "completion") { + attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] = + params.max_tokens_to_sample; + } else { + attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] = params.max_tokens; + } + + if ( + params.extraAttributes !== undefined && + typeof params.extraAttributes === "object" + ) { + Object.keys(params.extraAttributes).forEach((key: string) => { + attributes[key] = params.extraAttributes![key]; + }); + } + + if (this._shouldSendPrompts()) { + if (type === "chat") { + params.messages.forEach((message, index) => { + attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.role`] = + message.role; + if (typeof message.content === "string") { + attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = + (message.content as string) || ""; + } else { + attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = + JSON.stringify(message.content); + } + }); + } else { + attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`] = "user"; + attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`] = params.prompt; + } + } + + return this.tracer.startSpan(`anthropic.${type}`, { + kind: SpanKind.CLIENT, + attributes, + }); + } + + private async *_streamingWrapPromise({ + span, + type, + promise, + }: + | { + span: Span; + type: "chat"; + promise: Promise>; + } + | { + span: Span; + type: "completion"; + promise: Promise>; + }) { + if (type === "chat") { + const result: Message = { + id: "0", + type: "message", + model: "", + role: "assistant", + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 0, output_tokens: 0 }, + content: [], + }; + for await (const chunk of await promise) { + yield chunk; + + switch (chunk.type) { + case "content_block_start": + if (result.content.length <= chunk.index) { + result.content.push(chunk.content_block); + } + break; + + case "content_block_delta": + if (chunk.index < result.content.length) { + result.content[chunk.index] = { + type: "text", + text: result.content[chunk.index].text + chunk.delta.text, + }; + } + } + } + + this._endSpan({ span, type, result }); + } else { + const result: Completion = { + id: "0", + type: "completion", + model: "", + completion: "", + stop_reason: null, + }; + for await (const chunk of await promise) { + yield chunk; + + result.id = chunk.id; + result.model = chunk.model; + + if (chunk.stop_reason) { + result.stop_reason = chunk.stop_reason; + } + if (chunk.model) { + result.model = chunk.model; + } + if (chunk.completion) { + result.completion += chunk.completion; + } + } + + this._endSpan({ span, type, result }); + } + } + + private _wrapPromise( + type: "chat" | "completion", + span: Span, + promise: Promise, + ): Promise { + return promise + .then((result) => { + return new Promise((resolve) => { + if (type === "chat") { + this._endSpan({ + type, + span, + result: result as Message, + }); + } else { + this._endSpan({ + type, + span, + result: result as Completion, + }); + } + + resolve(result); + }); + }) + .catch((error: Error) => { + return new Promise((_, reject) => { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message, + }); + span.recordException(error); + span.end(); + + reject(error); + }); + }); + } + + private _endSpan({ + span, + type, + result, + }: + | { span: Span; type: "chat"; result: Message } + | { + span: Span; + type: "completion"; + result: Completion; + }) { + span.setAttribute(SpanAttributes.LLM_RESPONSE_MODEL, result.model); + if (type === "chat" && result.usage) { + span.setAttribute( + SpanAttributes.LLM_USAGE_TOTAL_TOKENS, + result.usage?.input_tokens + result.usage?.output_tokens, + ); + span.setAttribute( + SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, + result.usage?.output_tokens, + ); + span.setAttribute( + SpanAttributes.LLM_USAGE_PROMPT_TOKENS, + result.usage?.input_tokens, + ); + } + + result.stop_reason && + span.setAttribute( + `${SpanAttributes.LLM_COMPLETIONS}.0.finish_reason`, + result.stop_reason, + ); + + if (this._shouldSendPrompts()) { + if (type === "chat") { + span.setAttribute( + `${SpanAttributes.LLM_COMPLETIONS}.0.role`, + "assistant", + ); + span.setAttribute( + `${SpanAttributes.LLM_COMPLETIONS}.0.content`, + JSON.stringify(result.content), + ); + } else { + span.setAttribute( + `${SpanAttributes.LLM_COMPLETIONS}.0.role`, + "assistant", + ); + span.setAttribute( + `${SpanAttributes.LLM_COMPLETIONS}.0.content`, + result.completion, + ); + } + } + + span.end(); + } + + private _shouldSendPrompts() { + const contextShouldSendPrompts = context + .active() + .getValue(CONTEXT_KEY_ALLOW_TRACE_CONTENT); + + if (contextShouldSendPrompts !== undefined) { + return contextShouldSendPrompts; + } + + return this._config.traceContent !== undefined + ? this._config.traceContent + : true; + } +} diff --git a/packages/instrumentation-anthropic/src/types.ts b/packages/instrumentation-anthropic/src/types.ts new file mode 100644 index 00000000..2fbf4036 --- /dev/null +++ b/packages/instrumentation-anthropic/src/types.ts @@ -0,0 +1,9 @@ +import { InstrumentationConfig } from "@opentelemetry/instrumentation"; + +export interface AnthropicInstrumentationConfig extends InstrumentationConfig { + /** + * Whether to log prompts, completions and embeddings on traces. + * @default true + */ + traceContent?: boolean; +} diff --git a/packages/instrumentation-anthropic/test/instrumentation.test.ts b/packages/instrumentation-anthropic/test/instrumentation.test.ts new file mode 100644 index 00000000..04caec91 --- /dev/null +++ b/packages/instrumentation-anthropic/test/instrumentation.test.ts @@ -0,0 +1,292 @@ +/* + * Copyright Traceloop + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from "assert"; + +import { context } from "@opentelemetry/api"; +import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"; +import { + BasicTracerProvider, + InMemorySpanExporter, + SimpleSpanProcessor, +} from "@opentelemetry/sdk-trace-base"; + +import * as AnthropicModule from "@anthropic-ai/sdk"; + +import { AnthropicInstrumentation } from "../src/instrumentation"; + +import { Polly, setupMocha as setupPolly } from "@pollyjs/core"; +import NodeHttpAdapter from "@pollyjs/adapter-node-http"; +import FSPersister from "@pollyjs/persister-fs"; +import { SpanAttributes } from "@traceloop/ai-semantic-conventions"; + +const memoryExporter = new InMemorySpanExporter(); + +Polly.register(NodeHttpAdapter); +Polly.register(FSPersister); + +describe("Test Anthropic instrumentation", async function () { + const provider = new BasicTracerProvider(); + let instrumentation: AnthropicInstrumentation; + let contextManager: AsyncHooksContextManager; + let anthropic: AnthropicModule.Anthropic; + + setupPolly({ + adapters: ["node-http"], + persister: "fs", + recordIfMissing: process.env.RECORD_MODE === "NEW", + matchRequestsBy: { + headers: false, + }, + }); + + before(async () => { + if (process.env.RECORD_MODE !== "NEW") { + process.env.ANTHROPIC_API_KEY = "test-key"; + } + provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); + instrumentation = new AnthropicInstrumentation(); + instrumentation.setTracerProvider(provider); + + const anthropicModule: typeof AnthropicModule = await import( + "@anthropic-ai/sdk" + ); + anthropic = new anthropicModule.Anthropic(); + }); + + beforeEach(function () { + contextManager = new AsyncHooksContextManager().enable(); + context.setGlobalContextManager(contextManager); + + const { server } = this.polly as Polly; + server.any().on("beforePersist", (_req, recording) => { + recording.request.headers = recording.request.headers.filter( + ({ name }: { name: string }) => name !== "x-api-key", + ); + }); + }); + + afterEach(async () => { + memoryExporter.reset(); + context.disable(); + }); + + it("should set attributes in span for completions", async () => { + const result = await anthropic.completions.create({ + model: "claude-2", + max_tokens_to_sample: 300, + prompt: `${AnthropicModule.HUMAN_PROMPT} Tell me a joke about OpenTelemetry${AnthropicModule.AI_PROMPT}`, + }); + + const spans = memoryExporter.getFinishedSpans(); + const completionSpan = spans.find( + (span) => span.name === "anthropic.completion", + ); + + assert.ok(result); + assert.ok(completionSpan); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`], + "claude-2", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_REQUEST_MAX_TOKENS}`], + 300, + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`], + "claude-2.1", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`], + "user", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`], + `${AnthropicModule.HUMAN_PROMPT} Tell me a joke about OpenTelemetry${AnthropicModule.AI_PROMPT}`, + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`], + "assistant", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`], + result.completion, + ); + }).timeout(30000); + + it("should set attributes in span for completions (streaming)", async () => { + const result = await anthropic.completions.create({ + model: "claude-2", + max_tokens_to_sample: 300, + prompt: `${AnthropicModule.HUMAN_PROMPT} Tell me a joke about OpenTelemetry${AnthropicModule.AI_PROMPT}`, + stream: true, + }); + + let completion = ""; + for await (const chunk of result) { + assert.ok(chunk); + completion += chunk.completion; + } + + const spans = memoryExporter.getFinishedSpans(); + const completionSpan = spans.find( + (span) => span.name === "anthropic.completion", + ); + + assert.ok(completionSpan); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`], + "claude-2", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_REQUEST_MAX_TOKENS}`], + 300, + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`], + "claude-2.1", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`], + "user", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`], + `${AnthropicModule.HUMAN_PROMPT} Tell me a joke about OpenTelemetry${AnthropicModule.AI_PROMPT}`, + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`], + "assistant", + ); + assert.strictEqual( + completionSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`], + completion, + ); + }).timeout(30000); + + it("should set attributes in span for messages", async () => { + const message = await anthropic.messages.create({ + max_tokens: 1024, + messages: [ + { role: "user", content: "Tell me a joke about OpenTelemetry" }, + ], + model: "claude-3-opus-20240229", + }); + + const spans = memoryExporter.getFinishedSpans(); + const chatSpan = spans.find((span) => span.name === "anthropic.chat"); + + assert.ok(message); + assert.ok(chatSpan); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`], + "claude-3-opus-20240229", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`], + "claude-3-opus-20240229", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MAX_TOKENS}`], + 1024, + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`], + "user", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`], + `Tell me a joke about OpenTelemetry`, + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`], + "assistant", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`], + JSON.stringify(message.content), + ); + assert.equal( + chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`], + 17, + ); + assert.equal( + +chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]! + + +chatSpan.attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]!, + chatSpan.attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`], + ); + }).timeout(30000); + + it.skip( + "should set attributes in span for messages (streaming)", + async () => { + const stream = anthropic.messages.stream({ + max_tokens: 1024, + messages: [ + { role: "user", content: "Tell me a joke about OpenTelemetry" }, + ], + model: "claude-3-opus-20240229", + }); + const message = await stream.finalMessage(); + + const spans = memoryExporter.getFinishedSpans(); + const chatSpan = spans.find((span) => span.name === "anthropic.chat"); + + assert.ok(message); + assert.ok(chatSpan); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`], + "claude-3-opus-20240229", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`], + "claude-3-opus-20240229", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MAX_TOKENS}`], + 1024, + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`], + "user", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`], + `Tell me a joke about OpenTelemetry`, + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`], + "assistant", + ); + assert.strictEqual( + chatSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`], + JSON.stringify(message.content), + ); + assert.equal( + chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`], + 17, + ); + assert.equal( + +chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]! + + +chatSpan.attributes[ + `${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}` + ]!, + chatSpan.attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`], + ); + }, + ).timeout(30000); +}); diff --git a/packages/instrumentation-anthropic/tsconfig.json b/packages/instrumentation-anthropic/tsconfig.json new file mode 100644 index 00000000..559aaa89 --- /dev/null +++ b/packages/instrumentation-anthropic/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "." + }, + "files": [], + "include": ["src/**/*.ts", "test/**/*.ts", "package.json"], + "references": [] +} diff --git a/packages/instrumentation-azure/CHANGELOG.md b/packages/instrumentation-azure/CHANGELOG.md index 7e311ec4..c5a3e194 100644 --- a/packages/instrumentation-azure/CHANGELOG.md +++ b/packages/instrumentation-azure/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.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +### Features + +- **anthropic:** instrumentation ([#188](https://github.com/traceloop/openllmetry-js/issues/188)) ([f40df74](https://github.com/traceloop/openllmetry-js/commit/f40df747143279a9a153db58020bf4531b5c171f)) + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) ### Bug Fixes diff --git a/packages/instrumentation-azure/README.md b/packages/instrumentation-azure/README.md index 1c0b22a6..7d26695e 100644 --- a/packages/instrumentation-azure/README.md +++ b/packages/instrumentation-azure/README.md @@ -49,5 +49,5 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [slack-url]: https://traceloop.com/slack [license-url]: https://github.com/traceloop/openllmetry-js/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat -[npm-url]: https://www.npmjs.com/package/@traceloop/instrumentation-openai -[npm-img]: https://badge.fury.io/js/%40traceloop%2Finstrumentation-openai.svg +[npm-url]: https://www.npmjs.com/package/@traceloop/instrumentation-azure +[npm-img]: https://badge.fury.io/js/%40traceloop%2Finstrumentation-azure.svg diff --git a/packages/instrumentation-azure/package.json b/packages/instrumentation-azure/package.json index 4bbcd66c..e7cdb719 100644 --- a/packages/instrumentation-azure/package.json +++ b/packages/instrumentation-azure/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-azure", - "version": "0.5.29", + "version": "0.6.0", "description": "Azure OpenAI Instrumentaion", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@azure/openai": "^1.0.0-beta.10", diff --git a/packages/instrumentation-bedrock/CHANGELOG.md b/packages/instrumentation-bedrock/CHANGELOG.md index 30a7bdf6..4e824a33 100644 --- a/packages/instrumentation-bedrock/CHANGELOG.md +++ b/packages/instrumentation-bedrock/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.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +### Features + +- **anthropic:** instrumentation ([#188](https://github.com/traceloop/openllmetry-js/issues/188)) ([f40df74](https://github.com/traceloop/openllmetry-js/commit/f40df747143279a9a153db58020bf4531b5c171f)) + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-bedrock diff --git a/packages/instrumentation-bedrock/README.md b/packages/instrumentation-bedrock/README.md index fd153d27..d3370a66 100644 --- a/packages/instrumentation-bedrock/README.md +++ b/packages/instrumentation-bedrock/README.md @@ -1,4 +1,4 @@ -# OpenTelemetry VertexAI instrumentation for Node.js +# OpenTelemetry Bedrock instrumentation for Node.js [![NPM Published Version][npm-img]][npm-url] [![Apache License][license-image]][license-image] diff --git a/packages/instrumentation-bedrock/package.json b/packages/instrumentation-bedrock/package.json index 1da127d4..dda343c2 100644 --- a/packages/instrumentation-bedrock/package.json +++ b/packages/instrumentation-bedrock/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-bedrock", - "version": "0.5.29", + "version": "0.6.0", "description": "Amazon Bedrock Instrumentation", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@aws-sdk/client-bedrock-runtime": "^3.499.0", diff --git a/packages/instrumentation-cohere/CHANGELOG.md b/packages/instrumentation-cohere/CHANGELOG.md index 5bdf9fb1..b8c5d8ff 100644 --- a/packages/instrumentation-cohere/CHANGELOG.md +++ b/packages/instrumentation-cohere/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-cohere + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-cohere diff --git a/packages/instrumentation-cohere/package.json b/packages/instrumentation-cohere/package.json index 2bcf9cb3..2ef4c2b9 100644 --- a/packages/instrumentation-cohere/package.json +++ b/packages/instrumentation-cohere/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-cohere", - "version": "0.5.29", + "version": "0.6.0", "description": "Cohere Instrumentation", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.44.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@pollyjs/adapter-node-http": "^6.0.6", diff --git a/packages/instrumentation-langchain/CHANGELOG.md b/packages/instrumentation-langchain/CHANGELOG.md index ad4f7dd5..ad087caf 100644 --- a/packages/instrumentation-langchain/CHANGELOG.md +++ b/packages/instrumentation-langchain/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-langchain + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-langchain diff --git a/packages/instrumentation-langchain/package.json b/packages/instrumentation-langchain/package.json index 3f55d6c5..7aa7568b 100644 --- a/packages/instrumentation-langchain/package.json +++ b/packages/instrumentation-langchain/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-langchain", - "version": "0.5.29", + "version": "0.6.0", "description": "OpenTelemetry instrumentation for LangchainJS", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@langchain/community": "^0.0.34", diff --git a/packages/instrumentation-llamaindex/CHANGELOG.md b/packages/instrumentation-llamaindex/CHANGELOG.md index ee42bced..54f89534 100644 --- a/packages/instrumentation-llamaindex/CHANGELOG.md +++ b/packages/instrumentation-llamaindex/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-llamaindex + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-llamaindex diff --git a/packages/instrumentation-llamaindex/package.json b/packages/instrumentation-llamaindex/package.json index 7f6989ce..876bdb89 100644 --- a/packages/instrumentation-llamaindex/package.json +++ b/packages/instrumentation-llamaindex/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-llamaindex", - "version": "0.5.29", + "version": "0.6.0", "description": "Llamaindex Instrumentation", "main": "dist/index.js", "module": "dist/index.mjs", @@ -39,7 +39,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", "lodash": "^4.17.21" }, "devDependencies": { diff --git a/packages/instrumentation-openai/CHANGELOG.md b/packages/instrumentation-openai/CHANGELOG.md index 0801fa15..cb191dde 100644 --- a/packages/instrumentation-openai/CHANGELOG.md +++ b/packages/instrumentation-openai/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-openai + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) ### Bug Fixes diff --git a/packages/instrumentation-openai/package.json b/packages/instrumentation-openai/package.json index e814e13d..9881bb66 100644 --- a/packages/instrumentation-openai/package.json +++ b/packages/instrumentation-openai/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-openai", - "version": "0.5.29", + "version": "0.6.0", "description": "OpenAI Instrumentaion", "main": "dist/index.js", "module": "dist/index.mjs", @@ -39,7 +39,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", "tiktoken": "^1.0.13" }, "devDependencies": { diff --git a/packages/instrumentation-pinecone/CHANGELOG.md b/packages/instrumentation-pinecone/CHANGELOG.md index 49e8ab2d..6740aa05 100644 --- a/packages/instrumentation-pinecone/CHANGELOG.md +++ b/packages/instrumentation-pinecone/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-pinecone + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-pinecone diff --git a/packages/instrumentation-pinecone/package.json b/packages/instrumentation-pinecone/package.json index 1557623b..692c79bc 100644 --- a/packages/instrumentation-pinecone/package.json +++ b/packages/instrumentation-pinecone/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-pinecone", - "version": "0.5.29", + "version": "0.6.0", "description": "OpenTelemetry instrumentation for pinecone vector DB", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@pinecone-database/pinecone": "^2.0.1", diff --git a/packages/instrumentation-vertexai/CHANGELOG.md b/packages/instrumentation-vertexai/CHANGELOG.md index 0af2fbd1..0ddd1be8 100644 --- a/packages/instrumentation-vertexai/CHANGELOG.md +++ b/packages/instrumentation-vertexai/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +**Note:** Version bump only for package @traceloop/instrumentation-vertexai + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) **Note:** Version bump only for package @traceloop/instrumentation-vertexai diff --git a/packages/instrumentation-vertexai/package.json b/packages/instrumentation-vertexai/package.json index 516d0875..6a5aad7a 100644 --- a/packages/instrumentation-vertexai/package.json +++ b/packages/instrumentation-vertexai/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/instrumentation-vertexai", - "version": "0.5.29", + "version": "0.6.0", "description": "Google's VertexAI Instrumentation", "main": "dist/index.js", "module": "dist/index.mjs", @@ -39,7 +39,7 @@ "@opentelemetry/core": "^1.22.0", "@opentelemetry/instrumentation": "^0.49.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@traceloop/ai-semantic-conventions": "^0.5.29" + "@traceloop/ai-semantic-conventions": "^0.6.0" }, "devDependencies": { "@google-cloud/aiplatform": "^3.10.0", diff --git a/packages/sample-app/package.json b/packages/sample-app/package.json index a9bdadcf..71e004b2 100644 --- a/packages/sample-app/package.json +++ b/packages/sample-app/package.json @@ -4,6 +4,7 @@ "description": "Sample app for using Traceloop SDK", "scripts": { "build": "tsc --build tsconfig.json", + "run:anthropic": "npm run build && node dist/src/sample_anthropic.js", "run:cohere": "npm run build && node dist/src/sample_cohere.js", "run:bedrock:ai21": "npm run build && node dist/src/bedrock/ai21.js", "run:bedrock:amazon": "npm run build && node dist/src/bedrock/amazon.js", @@ -30,6 +31,7 @@ "node": ">=14" }, "dependencies": { + "@anthropic-ai/sdk": "^0.20.1", "@aws-sdk/client-bedrock-runtime": "^3.499.0", "@azure/openai": "^1.0.0-beta.11", "@google-cloud/aiplatform": "^3.10.0", diff --git a/packages/sample-app/src/sample_anthropic.ts b/packages/sample-app/src/sample_anthropic.ts new file mode 100644 index 00000000..24581855 --- /dev/null +++ b/packages/sample-app/src/sample_anthropic.ts @@ -0,0 +1,25 @@ +import * as traceloop from "@traceloop/node-server-sdk"; +import Anthropic from "@anthropic-ai/sdk"; + +traceloop.initialize({ + appName: "sample_anthropic", + apiKey: process.env.TRACELOOP_API_KEY, + disableBatch: true, +}); +const anthropic = new Anthropic({}); + +async function main() { + const completion = await anthropic.messages.create({ + max_tokens: 1024, + model: "claude-3-opus-20240229", + messages: [ + { + role: "user", + content: "How does a court case get to the Supreme Court?", + }, + ], + }); + console.log(completion.content); +} + +main(); diff --git a/packages/traceloop-sdk/CHANGELOG.md b/packages/traceloop-sdk/CHANGELOG.md index 4d981738..e43ebb25 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.6.0](https://github.com/traceloop/openllmetry-js/compare/v0.5.29...v0.6.0) (2024-04-05) + +### Features + +- **anthropic:** instrumentation ([#188](https://github.com/traceloop/openllmetry-js/issues/188)) ([f40df74](https://github.com/traceloop/openllmetry-js/commit/f40df747143279a9a153db58020bf4531b5c171f)) + ## [0.5.29](https://github.com/traceloop/openllmetry-js/compare/v0.5.28...v0.5.29) (2024-04-03) ### Bug Fixes diff --git a/packages/traceloop-sdk/README b/packages/traceloop-sdk/README new file mode 100644 index 00000000..0cba606c --- /dev/null +++ b/packages/traceloop-sdk/README @@ -0,0 +1,43 @@ +# OpenLLMetry SDK for Node.js + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +OpenLLMetry-JS is a set of extensions built on top of [OpenTelemetry](https://opentelemetry.io/) that gives you complete observability over your LLM application. Because it uses OpenTelemetry under the hood, it can be connected to your existing observability solutions - Datadog, Honeycomb, and others. + +## 🚀 Getting Started + +The easiest way to get started is to use our SDK. +For a complete guide, go to our [docs](https://traceloop.com/docs/openllmetry/getting-started-ts). + +Install the SDK: + +```shell +npm install --save @traceloop/node-server-sdk +``` + +Then, to start instrumenting your code, just add these 2 lines to your code: + +```js +import * as traceloop from "@traceloop/node-server-sdk"; + +traceloop.initialize(); +``` + +Make sure to `import` the SDK before importing any LLM module. + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- For help or feedback on this project, join us on [Slack][slack-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[slack-url]: https://traceloop.com/slack +[license-url]: https://github.com/traceloop/openllmetry-js/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@traceloop/node-server-sdk +[npm-img]: https://badge.fury.io/js/%40traceloop%2Fnode-server-sdk.svg diff --git a/packages/traceloop-sdk/package.json b/packages/traceloop-sdk/package.json index ef3c6536..4dba8530 100644 --- a/packages/traceloop-sdk/package.json +++ b/packages/traceloop-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@traceloop/node-server-sdk", - "version": "0.5.29", + "version": "0.6.0", "description": "Traceloop Software Development Kit (SDK) for Node.js", "main": "dist/index.js", "module": "dist/index.mjs", @@ -37,15 +37,16 @@ "dependencies": { "@opentelemetry/exporter-trace-otlp-proto": "^0.49.1", "@opentelemetry/sdk-node": "^0.49.1", - "@traceloop/ai-semantic-conventions": "^0.5.29", - "@traceloop/instrumentation-azure": "^0.5.29", - "@traceloop/instrumentation-bedrock": "^0.5.29", - "@traceloop/instrumentation-cohere": "^0.5.29", - "@traceloop/instrumentation-langchain": "^0.5.29", - "@traceloop/instrumentation-llamaindex": "^0.5.29", - "@traceloop/instrumentation-openai": "^0.5.29", - "@traceloop/instrumentation-pinecone": "^0.5.29", - "@traceloop/instrumentation-vertexai": "^0.5.29", + "@traceloop/ai-semantic-conventions": "^0.6.0", + "@traceloop/instrumentation-anthropic": "^0.6.0", + "@traceloop/instrumentation-azure": "^0.6.0", + "@traceloop/instrumentation-bedrock": "^0.6.0", + "@traceloop/instrumentation-cohere": "^0.6.0", + "@traceloop/instrumentation-langchain": "^0.6.0", + "@traceloop/instrumentation-llamaindex": "^0.6.0", + "@traceloop/instrumentation-openai": "^0.6.0", + "@traceloop/instrumentation-pinecone": "^0.6.0", + "@traceloop/instrumentation-vertexai": "^0.6.0", "@types/nunjucks": "^3.2.5", "cross-fetch": "^4.0.0", "fetch-retry": "^5.0.6", @@ -57,6 +58,7 @@ "homepage": "https://github.com/traceloop/openllmetry-js/tree/main/packages/traceloop-sdk", "gitHead": "ef1e70d6037f7b5c061056ef2be16e3f55f02ed5", "devDependencies": { + "@anthropic-ai/sdk": "^0.20.1", "@aws-sdk/client-bedrock-runtime": "^3.499.0", "@azure/openai": "^1.0.0-beta.11", "@google-cloud/aiplatform": "^3.10.0", diff --git a/packages/traceloop-sdk/src/lib/interfaces/initialize-options.interface.ts b/packages/traceloop-sdk/src/lib/interfaces/initialize-options.interface.ts index f4a0cb77..f0e8ebff 100644 --- a/packages/traceloop-sdk/src/lib/interfaces/initialize-options.interface.ts +++ b/packages/traceloop-sdk/src/lib/interfaces/initialize-options.interface.ts @@ -1,6 +1,7 @@ import { SpanExporter } from "@opentelemetry/sdk-trace-base"; import type * as openai from "openai"; +import type * as anthropic from "@anthropic-ai/sdk"; import type * as azure from "@azure/openai"; import type * as cohere from "cohere-ai"; import type * as bedrock from "@aws-sdk/client-bedrock-runtime"; @@ -70,6 +71,7 @@ export interface InitializeOptions { */ instrumentModules?: { openAI?: typeof openai.OpenAI; + anthropic?: typeof anthropic; azureOpenAI?: typeof azure; cohere?: typeof cohere; bedrock?: typeof bedrock; diff --git a/packages/traceloop-sdk/src/lib/tracing/index.ts b/packages/traceloop-sdk/src/lib/tracing/index.ts index 88440f16..b9b9cef4 100644 --- a/packages/traceloop-sdk/src/lib/tracing/index.ts +++ b/packages/traceloop-sdk/src/lib/tracing/index.ts @@ -17,6 +17,7 @@ import { CONTEXT_KEY_ALLOW_TRACE_CONTENT, SpanAttributes, } from "@traceloop/ai-semantic-conventions"; +import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic"; import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; import { AzureOpenAIInstrumentation } from "@traceloop/instrumentation-azure"; import { LlamaIndexInstrumentation } from "@traceloop/instrumentation-llamaindex"; @@ -32,6 +33,7 @@ import { LangChainInstrumentation } from "@traceloop/instrumentation-langchain"; let _sdk: NodeSDK; let _spanProcessor: SimpleSpanProcessor | BatchSpanProcessor; let openAIInstrumentation: OpenAIInstrumentation | undefined; +let anthropicInstrumentation: AnthropicInstrumentation | undefined; let azureOpenAIInstrumentation: AzureOpenAIInstrumentation | undefined; let cohereInstrumentation: CohereInstrumentation | undefined; let vertexaiInstrumentation: VertexAIInstrumentation | undefined; @@ -47,6 +49,9 @@ export const initInstrumentations = () => { openAIInstrumentation = new OpenAIInstrumentation(); instrumentations.push(openAIInstrumentation); + anthropicInstrumentation = new AnthropicInstrumentation(); + instrumentations.push(anthropicInstrumentation); + azureOpenAIInstrumentation = new AzureOpenAIInstrumentation(); instrumentations.push(azureOpenAIInstrumentation); @@ -83,6 +88,12 @@ export const manuallyInitInstrumentations = ( openAIInstrumentation.manuallyInstrument(instrumentModules.openAI); } + if (instrumentModules?.anthropic) { + anthropicInstrumentation = new AnthropicInstrumentation(); + instrumentations.push(anthropicInstrumentation); + anthropicInstrumentation.manuallyInstrument(instrumentModules.anthropic); + } + if (instrumentModules?.azureOpenAI) { const instrumentation = new AzureOpenAIInstrumentation(); instrumentations.push(instrumentation as Instrumentation);