diff --git a/packages/instrumentation-openai/.tav.yml b/packages/instrumentation-openai/.tav.yml index e9043d34..8072b29a 100644 --- a/packages/instrumentation-openai/.tav.yml +++ b/packages/instrumentation-openai/.tav.yml @@ -1,5 +1,11 @@ openai: - versions: - include: '>=4.19.0 <5' - mode: max-5 - commands: npm test + - versions: + include: '>=4.19.0 <6' + mode: max-5 + commands: npm test + # It is a little bit hacky to get TAV to consider prereleases. + # Once there are non-prerelease 5.x releases we can drop this block. + - versions: + include: '>=5.0.0-alpha.0 <5.0.0-rc.99' + mode: max-2 + commands: npm test diff --git a/packages/instrumentation-openai/CHANGELOG.md b/packages/instrumentation-openai/CHANGELOG.md index 8ce75ba6..c4b38271 100644 --- a/packages/instrumentation-openai/CHANGELOG.md +++ b/packages/instrumentation-openai/CHANGELOG.md @@ -1,5 +1,10 @@ # @elastic/opentelemetry-instrumentation-openai Changelog +## Unreleased + +- Support instrumenting openai@5, including preleases. Currently only 5.0.0-alpha.0 + has been released. + ## v0.4.1 - Include "LICENSE" file in the published package. diff --git a/packages/instrumentation-openai/package-lock.json b/packages/instrumentation-openai/package-lock.json index 94c12baf..93ed45ae 100644 --- a/packages/instrumentation-openai/package-lock.json +++ b/packages/instrumentation-openai/package-lock.json @@ -20,6 +20,7 @@ "@opentelemetry/exporter-metrics-otlp-proto": "^0.56.0", "@opentelemetry/exporter-trace-otlp-proto": "^0.56.0", "@opentelemetry/instrumentation-http": "^0.56.0", + "@opentelemetry/instrumentation-undici": "^0.9.0", "@opentelemetry/sdk-logs": "^0.56.0", "@opentelemetry/sdk-metrics": "^1.29.0", "@opentelemetry/sdk-node": "^0.56.0", @@ -31,7 +32,7 @@ "@typescript-eslint/parser": "5.8.1", "dotenv": "^16.4.5", "nock": "^13.5.5", - "openai": "^4.57.0", + "openai": "^4.78.0", "tape": "^5.8.1", "test-all-versions": "^6.1.0", "typescript": "4.4.4" @@ -555,6 +556,23 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.9.0.tgz", + "integrity": "sha512-lxc3cpUZ28CqbrWcUHxGW/ObDpMOYbuxF/ZOzeFZq54P9uJ2Cpa8gcrC9F716mtuiMaekwk8D6n34vg/JtkkxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.56.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, "node_modules/@opentelemetry/otlp-exporter-base": { "version": "0.56.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.56.0.tgz", @@ -3686,9 +3704,9 @@ } }, "node_modules/openai": { - "version": "4.76.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.1.tgz", - "integrity": "sha512-ci63/WFEMd6QjjEVeH0pV7hnFS6CCqhgJydSti4Aak/8uo2SpgzKjteUDaY+OkwziVj11mi6j+0mRUIiGKUzWw==", + "version": "4.78.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.78.0.tgz", + "integrity": "sha512-4rRsKkx++5m1zayxkryVH+K/z91cv1sRbaNJAhSQjZiSCQOR7eaM8KpfIssXrS9Hlpta7+VcuO/fi57pW8xGjA==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/packages/instrumentation-openai/package.json b/packages/instrumentation-openai/package.json index b351ed14..5a3fcd31 100644 --- a/packages/instrumentation-openai/package.json +++ b/packages/instrumentation-openai/package.json @@ -74,6 +74,7 @@ "@opentelemetry/exporter-metrics-otlp-proto": "^0.56.0", "@opentelemetry/exporter-trace-otlp-proto": "^0.56.0", "@opentelemetry/instrumentation-http": "^0.56.0", + "@opentelemetry/instrumentation-undici": "^0.9.0", "@opentelemetry/sdk-logs": "^0.56.0", "@opentelemetry/sdk-metrics": "^1.29.0", "@opentelemetry/sdk-node": "^0.56.0", @@ -85,7 +86,7 @@ "@typescript-eslint/parser": "5.8.1", "dotenv": "^16.4.5", "nock": "^13.5.5", - "openai": "^4.57.0", + "openai": "^4.78.0", "tape": "^5.8.1", "test-all-versions": "^6.1.0", "typescript": "4.4.4" diff --git a/packages/instrumentation-openai/src/instrumentation.ts b/packages/instrumentation-openai/src/instrumentation.ts index 4a0bd03a..67696e1a 100644 --- a/packages/instrumentation-openai/src/instrumentation.ts +++ b/packages/instrumentation-openai/src/instrumentation.ts @@ -26,6 +26,7 @@ import { InstrumentationBase, InstrumentationNodeModuleDefinition, } from '@opentelemetry/instrumentation'; +import type { InstrumentationModuleDefinition } from '@opentelemetry/instrumentation'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { @@ -98,10 +99,10 @@ export class OpenAIInstrumentation extends InstrumentationBase=4.19.0 <5'], + ['>=4.19.0 <6'], (modExports, modVer) => { debug( 'instrument openai@%s (isESM=%s), config=%o', @@ -122,8 +123,10 @@ export class OpenAIInstrumentation extends InstrumentationBase { /** @type {import('./testutils').TestFixture[]} */ let testFixtures = [ { + only: true, // XXX HERE name: 'chat-completion (captureMessageContent=true)', args: ['./fixtures/chat-completion.js'], cwd: __dirname, @@ -271,14 +272,17 @@ test('fixtures', async suite => { ); if (!usingNock) { - t.equal(spans[1].scope.name, '@opentelemetry/instrumentation-http'); + t.equal(spans[1].name, 'POST'); t.equal( spans[1].parentSpanId, spans[0].spanId, 'HTTP span is a child of the GenAI span' ); + const urlPath = + spans[1].attributes['http.target'] || // older semconv + spans[1].attributes['url.path']; t.ok( - spans[1].attributes['http.target'].includes('/chat/completions'), + urlPath.includes('/chat/completions'), 'looks like a .../chat/completions HTTP endpoint' ); } @@ -478,14 +482,17 @@ test('fixtures', async suite => { ); if (!usingNock) { - t.equal(spans[1].scope.name, '@opentelemetry/instrumentation-http'); + t.equal(spans[1].name, 'POST'); t.equal( spans[1].parentSpanId, spans[0].spanId, 'HTTP span is a child of the GenAI span' ); + const urlPath = + spans[1].attributes['http.target'] || // older semconv + spans[1].attributes['url.path']; t.ok( - spans[1].attributes['http.target'].includes('/chat/completions'), + urlPath.includes('/chat/completions'), 'looks like a .../chat/completions HTTP endpoint' ); } @@ -1244,14 +1251,17 @@ test('fixtures', async suite => { ); if (!usingNock) { - t.equal(spans[1].scope.name, '@opentelemetry/instrumentation-http'); + t.equal(spans[1].name, 'POST'); t.equal( spans[1].parentSpanId, spans[0].spanId, 'HTTP span is a child of the GenAI span' ); + const urlPath = + spans[1].attributes['http.target'] || // older semconv + spans[1].attributes['url.path']; t.ok( - spans[1].attributes['http.target'].includes('/embeddings'), + urlPath.includes('/embeddings'), 'looks like a .../embeddings HTTP endpoint' ); } diff --git a/packages/instrumentation-openai/test/fixtures/telemetry.js b/packages/instrumentation-openai/test/fixtures/telemetry.js index 67246c1a..65cd203f 100644 --- a/packages/instrumentation-openai/test/fixtures/telemetry.js +++ b/packages/instrumentation-openai/test/fixtures/telemetry.js @@ -30,6 +30,9 @@ const { const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-proto'); const { BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs'); const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); +const { + UndiciInstrumentation, +} = require('@opentelemetry/instrumentation-undici'); const { OpenAIInstrumentation } = require('../../'); // @elastic/opentelemetry-instrumentation-openai const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); @@ -50,7 +53,11 @@ const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter(), logRecordProcessor, metricReader, - instrumentations: [new HttpInstrumentation(), new OpenAIInstrumentation()], + instrumentations: [ + new HttpInstrumentation(), + new UndiciInstrumentation(), // openai@5 uses Node.js native fetch(), which is instrumented by instr-undici + new OpenAIInstrumentation(), + ], }); process.on('SIGTERM', async () => {