Skip to content

Commit 584e461

Browse files
authored
chore: Add manual openLLMetry example (#1143)
1 parent 42643d9 commit 584e461

6 files changed

Lines changed: 220 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"packages/sdk/server-ai/examples/openai",
4949
"packages/sdk/server-ai/examples/tracked-chat",
5050
"packages/sdk/server-ai/examples/chat-observability",
51+
"packages/sdk/server-ai/examples/openai-observability",
5152
"packages/sdk/server-ai/examples/vercel-ai",
5253
"packages/telemetry/browser-telemetry",
5354
"packages/sdk/combined-browser",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# LaunchDarkly SDK Key (required)
2+
LAUNCHDARKLY_SDK_KEY=your-launchdarkly-sdk-key-here
3+
4+
# AI Config key (optional, defaults to 'sample-ai-config')
5+
LAUNCHDARKLY_AI_CONFIG_KEY=sample-ai-config
6+
7+
# Observability service identification (optional)
8+
SERVICE_NAME=hello-js-openai-observability
9+
SERVICE_VERSION=1.0.0
10+
11+
# OpenAI API Key (required)
12+
OPENAI_API_KEY=your-openai-api-key-here
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Provider-Specific Observability Example (OpenAI)
2+
3+
This example shows how to use the LaunchDarkly observability plugin when calling an AI provider directly — without the higher-level `createChat` abstraction. It uses OpenAI as the provider, but the same pattern applies to any provider (Bedrock, Anthropic, Vercel AI SDK, etc.).
4+
5+
## How it works
6+
7+
1. **Initialize the LaunchDarkly client** with the `Observability` plugin — this enables automatic capture of SDK operations, flag evaluations, errors, logs, and distributed traces.
8+
2. **Get the AI Config** via `completionConfig()` — this returns the model, messages, and parameters configured in LaunchDarkly, along with a `tracker` for reporting metrics.
9+
3. **Call your provider directly** and wrap it with the tracker — the tracker records latency, token usage, and success/error status.
10+
11+
The tracker provides several methods depending on your provider. This example uses `trackMetricsOf` with the LaunchDarkly OpenAI provider's `getAIMetricsFromResponse` extractor:
12+
13+
| Method | Provider |
14+
|--------|----------|
15+
| `tracker.trackMetricsOf(OpenAIProvider.getAIMetricsFromResponse, fn)` | OpenAI (recommended) |
16+
| `tracker.trackBedrockConverseMetrics(response)` | AWS Bedrock |
17+
| `tracker.trackVercelAISDKGenerateTextMetrics(fn)` | Vercel AI SDK |
18+
| `tracker.trackMetricsOf(extractor, fn)` | Any provider (custom extractor) |
19+
20+
## Prerequisites
21+
22+
1. A LaunchDarkly account and SDK key
23+
2. Node.js 16 or later
24+
3. Node server SDK v9.10 or later (required for the observability plugin)
25+
4. An OpenAI API key
26+
27+
## Setup
28+
29+
1. Install dependencies:
30+
31+
```bash
32+
yarn install
33+
```
34+
35+
2. Set up environment variables:
36+
37+
```bash
38+
cp .env.example .env
39+
```
40+
41+
Edit `.env` and add your keys.
42+
43+
3. Create an AI Config in LaunchDarkly (e.g. key `sample-ai-config`) with a completion-enabled variation and the model you want to use.
44+
45+
## Running the Example
46+
47+
```bash
48+
yarn start
49+
```
50+
51+
This will:
52+
- Initialize the LaunchDarkly client with the observability plugin
53+
- Retrieve the AI Config (model, messages, parameters) from LaunchDarkly
54+
- Call OpenAI directly using your own client
55+
- Automatically track latency, token usage, and success/error via the tracker
56+
57+
View your data in the LaunchDarkly dashboard under **Observability**.
58+
59+
## Adapting for other providers
60+
61+
To use a different provider, replace the OpenAI-specific parts:
62+
63+
1. Swap the OpenAI client for your provider's client
64+
2. Use the appropriate tracker method (see table above), or use `trackMetricsOf` with a custom metrics extractor
65+
3. Map `aiConfig.messages` and `aiConfig.model` to your provider's API format
66+
67+
See the [bedrock](../bedrock/) example for an AWS Bedrock adaptation.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "openai-observability-example",
3+
"version": "1.0.0",
4+
"description": "LaunchDarkly AI SDK example: provider-specific observability with OpenAI",
5+
"scripts": {
6+
"build": "tsc",
7+
"start": "yarn build && node ./dist/index.js"
8+
},
9+
"dependencies": {
10+
"@launchdarkly/node-server-sdk": "workspace:^",
11+
"@launchdarkly/observability-node": "^1.0.0",
12+
"@launchdarkly/server-sdk-ai": "workspace:^",
13+
"@launchdarkly/server-sdk-ai-openai": "workspace:^",
14+
"@opentelemetry/instrumentation": "^0.57.0",
15+
"@traceloop/instrumentation-openai": "^0.22.0",
16+
"dotenv": "^16.0.0",
17+
"openai": "^5.12.2"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^20.0.0",
21+
"typescript": "^5.0.0"
22+
}
23+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* eslint-disable no-console */
2+
import { registerInstrumentations } from '@opentelemetry/instrumentation';
3+
import { OpenAIInstrumentation } from '@traceloop/instrumentation-openai';
4+
import 'dotenv/config';
5+
6+
import { init, type LDContext } from '@launchdarkly/node-server-sdk';
7+
import { Observability } from '@launchdarkly/observability-node';
8+
import { initAi } from '@launchdarkly/server-sdk-ai';
9+
10+
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY;
11+
const aiConfigKey = process.env.LAUNCHDARKLY_AI_CONFIG_KEY || 'sample-ai-config';
12+
13+
if (!sdkKey) {
14+
console.error('*** Please set the LAUNCHDARKLY_SDK_KEY env first');
15+
process.exit(1);
16+
}
17+
18+
// ── 1. Initialize the LaunchDarkly client with the Observability plugin ──
19+
// The plugin automatically captures SDK operations, flag evaluations,
20+
// error monitoring, logging, and distributed tracing.
21+
const ldClient = init(sdkKey, {
22+
plugins: [
23+
new Observability({
24+
serviceName: process.env.SERVICE_NAME || 'hello-js-openai-observability',
25+
serviceVersion: process.env.SERVICE_VERSION || '1.0.0',
26+
}),
27+
],
28+
});
29+
30+
registerInstrumentations({
31+
instrumentations: [new OpenAIInstrumentation()],
32+
});
33+
34+
const context: LDContext = {
35+
kind: 'user',
36+
key: 'example-user-key',
37+
name: 'Sandy',
38+
};
39+
40+
async function main() {
41+
try {
42+
await ldClient.waitForInitialization({ timeout: 10 });
43+
console.log('*** SDK successfully initialized');
44+
} catch (error) {
45+
console.error(`*** SDK failed to initialize: ${error}`);
46+
process.exit(1);
47+
}
48+
49+
const aiClient = initAi(ldClient);
50+
51+
// ── 2. Import provider and OpenAI after instrumentation so OpenLLMetry can patch the client ──
52+
const { OpenAIProvider } = await import('@launchdarkly/server-sdk-ai-openai');
53+
const { OpenAI } = await import('openai');
54+
const openai = new OpenAI({
55+
apiKey: process.env.OPENAI_API_KEY,
56+
});
57+
58+
// ── 3. Get the AI Config (model, messages, parameters) from LaunchDarkly ──
59+
// `completionConfig` returns the resolved configuration plus a `tracker`
60+
// that you use to report metrics back to LaunchDarkly.
61+
const aiConfig = await aiClient.completionConfig(
62+
aiConfigKey,
63+
context,
64+
{
65+
model: { name: 'gpt-4' },
66+
enabled: false,
67+
},
68+
{ example_type: 'provider_observability_demo' },
69+
);
70+
71+
if (!aiConfig.enabled || !aiConfig.tracker) {
72+
console.log('*** AI configuration is not enabled');
73+
ldClient.close();
74+
process.exit(0);
75+
}
76+
77+
try {
78+
// ── 4. Call OpenAI and track metrics with the provider's extractor ──
79+
const completion = await aiConfig.tracker.trackMetricsOf(
80+
OpenAIProvider.getAIMetricsFromResponse,
81+
() =>
82+
openai.chat.completions.create({
83+
messages: aiConfig.messages || [],
84+
model: aiConfig.model?.name || 'gpt-4',
85+
temperature: (aiConfig.model?.parameters?.temperature as number) ?? 0.5,
86+
max_tokens: (aiConfig.model?.parameters?.maxTokens as number) ?? 4096,
87+
}),
88+
);
89+
90+
console.log('AI Response:', completion.choices[0]?.message.content);
91+
console.log('\nSuccess.');
92+
} catch (err) {
93+
console.error('Error:', err);
94+
} finally {
95+
ldClient.close();
96+
}
97+
}
98+
99+
main();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "CommonJS",
5+
"moduleResolution": "node",
6+
"esModuleInterop": true,
7+
"allowSyntheticDefaultImports": true,
8+
"strict": true,
9+
"skipLibCheck": true,
10+
"forceConsistentCasingInFileNames": true,
11+
"outDir": "./dist",
12+
"rootDir": "./src",
13+
"declaration": true,
14+
"sourceMap": true
15+
},
16+
"include": ["src/**/*"],
17+
"exclude": ["node_modules", "dist"]
18+
}

0 commit comments

Comments
 (0)