From fa245b2add4e06b661c6615e98a4c6e895f25f99 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Mon, 15 Sep 2025 21:11:47 +0000 Subject: [PATCH 1/8] feat: Add vercel example for AI SDK --- package.json | 1 + .../server-ai/examples/vercel-ai/README.md | 49 +++++++++++ .../server-ai/examples/vercel-ai/package.json | 46 +++++++++++ .../server-ai/examples/vercel-ai/src/index.ts | 82 +++++++++++++++++++ .../examples/vercel-ai/tsconfig.json | 22 +++++ 5 files changed, 200 insertions(+) create mode 100644 packages/sdk/server-ai/examples/vercel-ai/README.md create mode 100644 packages/sdk/server-ai/examples/vercel-ai/package.json create mode 100644 packages/sdk/server-ai/examples/vercel-ai/src/index.ts create mode 100644 packages/sdk/server-ai/examples/vercel-ai/tsconfig.json diff --git a/package.json b/package.json index 693c9f0827..313d3cf351 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "packages/sdk/server-ai", "packages/sdk/server-ai/examples/bedrock", "packages/sdk/server-ai/examples/openai", + "packages/sdk/server-ai/examples/vercel-ai", "packages/telemetry/browser-telemetry", "contract-tests", "packages/sdk/combined-browser" diff --git a/packages/sdk/server-ai/examples/vercel-ai/README.md b/packages/sdk/server-ai/examples/vercel-ai/README.md new file mode 100644 index 0000000000..46616a9e52 --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/README.md @@ -0,0 +1,49 @@ +# LaunchDarkly AI SDK for OpenAI Example + +This package demonstrates the integration of LaunchDarkly's AI SDK with OpenAI via Vercel AI, allowing you to leverage LaunchDarkly's AI Config capabilities in AI-powered applications using Vercel's services. + +## Installation and Build + +When running as part of the js-core mono-repo the project will use local dependencies. +As such those dependencies need built. + +In the root of the repository run: + +```bash +yarn +``` + +And then + +```bash +yarn build +``` + +## Configuration + +Before running the example, make sure to set the following environment variables: + +- `LAUNCHDARKLY_SDK_KEY`: Your LaunchDarkly SDK key +- `LAUNCHDARKLY_AI_CONFIG_KEY`: Your LaunchDarkly AI Config key (defaults to 'sample-ai-config' if not set) +- `OPENAI_API_KEY`: Your OpenAI API key + +## Usage + +The main script (`index.js`) demonstrates how to: + +1. Initialize the LaunchDarkly SDK +2. Set up a user context +3. Initialize the LaunchDarkly AI client +4. Retrieve an AI model configuration +5. Send a prompt to a Vercel AI Model (OpenAI) +6. Track token usage + +To run the example (in the vercel-ai directory): + +```bash +yarn start +``` + +## Note + +This example uses OpenAI's chat completions API. Make sure your LaunchDarkly AI Config is set up correctly to work with OpenAI's models and API structure. diff --git a/packages/sdk/server-ai/examples/vercel-ai/package.json b/packages/sdk/server-ai/examples/vercel-ai/package.json new file mode 100644 index 0000000000..8376423d4e --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/package.json @@ -0,0 +1,46 @@ +{ + "name": "@launchdarkly/hello-vercel-ai", + "version": "0.1.0", + "description": "LaunchDarkly AI SDK for Node.js with Vercel AI", + "private": true, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "start": "yarn build && node ./dist/index.js", + "lint": "npx eslint . --ext .ts", + "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)' --ignore-path ../../../.prettierignore", + "lint:fix": "yarn run lint --fix", + "check": "yarn prettier && yarn lint && yarn build && yarn test" + }, + "keywords": [ + "launchdarkly", + "ai", + "llm" + ], + "author": "LaunchDarkly", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/openai": "2.0.30", + "@launchdarkly/node-server-sdk": "9.7.1", + "@launchdarkly/server-sdk-ai": "0.11.3", + "ai": "5.0.0", + "dotenv": "16.5.0", + "lodash": "4.17.21", + "openai": "4.96.2", + "zod": "^4.1.8" + }, + "devDependencies": { + "@tsconfig/node20": "20.1.4", + "@types/lodash": "4.17.16", + "@types/node": "22.15.3", + "typescript": "5.8.3" + }, + "directories": { + "example": "example" + }, + "repository": { + "type": "git", + "url": "github.com/launchdarkly/js-core" + } +} diff --git a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts new file mode 100644 index 0000000000..3ea08a4037 --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts @@ -0,0 +1,82 @@ +/* eslint-disable no-console */ +import { openai } from '@ai-sdk/openai'; +import { generateText, streamText } from 'ai'; +import { init, type LDClient, type LDContext } from '@launchdarkly/node-server-sdk'; +import { initAi } from '@launchdarkly/server-sdk-ai'; + +// Environment variables +const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY ?? ''; +const aiConfigKey = process.env.LAUNCHDARKLY_AI_CONFIG_KEY || 'sample-ai-config'; + +// Validate required environment variables +if (!sdkKey) { + console.error('*** Please set the LAUNCHDARKLY_SDK_KEY env first'); + process.exit(1); +} + +if (!aiConfigKey) { + console.error('*** Please set the LAUNCHDARKLY_AI_CONFIG_KEY env first'); + process.exit(1); +} + +let client: LDClient | undefined; + +async function main() { + // Initialize LaunchDarkly client + client = init(sdkKey); + + // Set up the context properties. This context should appear on your LaunchDarkly contexts dashboard + const context: LDContext = { + kind: 'user', + key: 'example-user-key', + name: 'Sandy', + }; + + try { + await client.waitForInitialization({ timeout: 10 }); + console.log('*** SDK successfully initialized'); + } catch (error) { + console.log(`*** SDK failed to initialize: ${error}`); + process.exit(1); + } + + const aiClient = initAi(client); + + // Get AI configuration from LaunchDarkly + const aiConfig = await aiClient.config(aiConfigKey, context, { model: { name: 'gpt-4' } }); + + if (!aiConfig.enabled) { + console.log('*** AI configuration is not enabled'); + process.exit(0); + } + + console.log('Using model:', aiConfig.model?.name); + + // Example of using generateText (non-streaming) + console.log('\n*** Generating text:'); + try { + const result = await aiConfig.tracker.trackVercelAISDKGenerateTextMetrics(() => + generateText(aiConfig.toVercelAISDK(openai)), + ); + console.log('Response:', result.text); + + process.stdout.write('Streaming Response: '); + const streamResult = aiConfig.tracker.trackVercelAISDKStreamTextMetrics(() => + streamText(aiConfig.toVercelAISDK(openai)), + ); + for await (const textPart of streamResult.textStream) { + process.stdout.write(textPart); + } + + console.log('\nSuccess.'); + } catch (err) { + console.error('Error:', err); + } +} + +main() + .catch((e) => console.error(e)) + .finally(async () => { + await client?.flush(); + client?.close(); + }); diff --git a/packages/sdk/server-ai/examples/vercel-ai/tsconfig.json b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.json new file mode 100644 index 0000000000..5a491900d3 --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "dist", + "baseUrl": ".", + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "sourceMap": true, + "resolveJsonModule": true, + "module": "CommonJS", + "moduleResolution": "Node" + }, + "include": ["src"], + "exclude": ["dist", "node_modules"] +} From b0f8e8d30dbad8f57503402106b95b785ca6481c Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Tue, 16 Sep 2025 22:15:32 +0000 Subject: [PATCH 2/8] fix lint error and ignore the second --- .github/workflows/server-ai.yml | 2 +- packages/sdk/server-ai/examples/vercel-ai/src/index.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/server-ai.yml b/.github/workflows/server-ai.yml index 9d02e1d673..35638b29c0 100644 --- a/.github/workflows/server-ai.yml +++ b/.github/workflows/server-ai.yml @@ -11,7 +11,7 @@ on: - '**.md' jobs: - build-test-node-server-otel: + build-test-server-sdk-ai: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts index 3ea08a4037..5e380edb25 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts +++ b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { openai } from '@ai-sdk/openai'; import { generateText, streamText } from 'ai'; + import { init, type LDClient, type LDContext } from '@launchdarkly/node-server-sdk'; import { initAi } from '@launchdarkly/server-sdk-ai'; @@ -64,6 +65,8 @@ async function main() { const streamResult = aiConfig.tracker.trackVercelAISDKStreamTextMetrics(() => streamText(aiConfig.toVercelAISDK(openai)), ); + + // eslint-disable-next-line no-restricted-syntax for await (const textPart of streamResult.textStream) { process.stdout.write(textPart); } From a3c8aca194c551d66f388583bd8c3c5cbbe3a8f6 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 13:51:12 +0000 Subject: [PATCH 3/8] resolve cursorbot comments --- .github/workflows/server-ai.yml | 5 +++++ packages/sdk/server-ai/examples/vercel-ai/src/index.ts | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/server-ai.yml b/.github/workflows/server-ai.yml index 35638b29c0..a89b97b508 100644 --- a/.github/workflows/server-ai.yml +++ b/.github/workflows/server-ai.yml @@ -35,3 +35,8 @@ jobs: yarn workspaces focus @launchdarkly/hello-openai yarn workspace @launchdarkly/hello-openai lint yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/hello-openai' run build + - name: Build Vercel AI example + run: | + yarn workspaces focus @launchdarkly/hello-vercel-ai + yarn workspace @launchdarkly/hello-vercel-ai lint + yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/hello-vercel-ai' run build diff --git a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts index 5e380edb25..a05f60ffc9 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts +++ b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts @@ -15,11 +15,6 @@ if (!sdkKey) { process.exit(1); } -if (!aiConfigKey) { - console.error('*** Please set the LAUNCHDARKLY_AI_CONFIG_KEY env first'); - process.exit(1); -} - let client: LDClient | undefined; async function main() { From c1e9bef37c2b13df80477b4f401baf83ed7cc278 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 14:09:07 +0000 Subject: [PATCH 4/8] show how a user message can be sent into the model --- packages/sdk/server-ai/examples/vercel-ai/src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts index a05f60ffc9..c4f5adff42 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/src/index.ts +++ b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts @@ -51,14 +51,19 @@ async function main() { // Example of using generateText (non-streaming) console.log('\n*** Generating text:'); try { + const userMessage = { + role: 'user' as const, + content: 'What can you help me with?', + }; + const result = await aiConfig.tracker.trackVercelAISDKGenerateTextMetrics(() => - generateText(aiConfig.toVercelAISDK(openai)), + generateText(aiConfig.toVercelAISDK(openai, { nonInterpolatedMessages: [userMessage] })), ); console.log('Response:', result.text); process.stdout.write('Streaming Response: '); const streamResult = aiConfig.tracker.trackVercelAISDKStreamTextMetrics(() => - streamText(aiConfig.toVercelAISDK(openai)), + streamText(aiConfig.toVercelAISDK(openai, { nonInterpolatedMessages: [userMessage] })), ); // eslint-disable-next-line no-restricted-syntax From 8651a44a2732326f9ce3e4c04c203b8378fc4b8e Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 14:19:05 +0000 Subject: [PATCH 5/8] add missing eslint file --- .../sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json diff --git a/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json new file mode 100644 index 0000000000..8241f86c36 --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["/**/*.ts", "/**/*.tsx"], + "exclude": ["node_modules"] +} From 505214b648c2ff8aea30e5b13c35f373949957c7 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 16:04:20 +0000 Subject: [PATCH 6/8] address cursorbot comment --- packages/sdk/server-ai/examples/bedrock/tsconfig.eslint.json | 2 +- packages/sdk/server-ai/examples/openai/tsconfig.eslint.json | 2 +- packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/server-ai/examples/bedrock/tsconfig.eslint.json b/packages/sdk/server-ai/examples/bedrock/tsconfig.eslint.json index 8241f86c36..4a68a0c7dc 100644 --- a/packages/sdk/server-ai/examples/bedrock/tsconfig.eslint.json +++ b/packages/sdk/server-ai/examples/bedrock/tsconfig.eslint.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["/**/*.ts", "/**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } diff --git a/packages/sdk/server-ai/examples/openai/tsconfig.eslint.json b/packages/sdk/server-ai/examples/openai/tsconfig.eslint.json index 8241f86c36..4a68a0c7dc 100644 --- a/packages/sdk/server-ai/examples/openai/tsconfig.eslint.json +++ b/packages/sdk/server-ai/examples/openai/tsconfig.eslint.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["/**/*.ts", "/**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } diff --git a/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json index 8241f86c36..4a68a0c7dc 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json +++ b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["/**/*.ts", "/**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } From d6b16a944ec7350af165244933c0126470fa7c82 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 17:24:49 +0000 Subject: [PATCH 7/8] clean up dependencies --- .../sdk/server-ai/examples/vercel-ai/package.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/sdk/server-ai/examples/vercel-ai/package.json b/packages/sdk/server-ai/examples/vercel-ai/package.json index 8376423d4e..cd5f99ea57 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/package.json +++ b/packages/sdk/server-ai/examples/vercel-ai/package.json @@ -25,16 +25,14 @@ "@launchdarkly/node-server-sdk": "9.7.1", "@launchdarkly/server-sdk-ai": "0.11.3", "ai": "5.0.0", - "dotenv": "16.5.0", - "lodash": "4.17.21", - "openai": "4.96.2", - "zod": "^4.1.8" + "zod": "^3.23.8" }, "devDependencies": { + "@eslint/js": "^9.35.0", "@tsconfig/node20": "20.1.4", - "@types/lodash": "4.17.16", - "@types/node": "22.15.3", - "typescript": "5.8.3" + "@typescript-eslint/parser": "^6.20.0", + "eslint": "^8.45.0", + "typescript": "^5.9.2" }, "directories": { "example": "example" From 066cb235856c918925a5ba374b451e2656e68b73 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 17 Sep 2025 17:32:08 +0000 Subject: [PATCH 8/8] match dev packages with other examples --- .../sdk/server-ai/examples/vercel-ai/package.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/sdk/server-ai/examples/vercel-ai/package.json b/packages/sdk/server-ai/examples/vercel-ai/package.json index cd5f99ea57..1d7e03400d 100644 --- a/packages/sdk/server-ai/examples/vercel-ai/package.json +++ b/packages/sdk/server-ai/examples/vercel-ai/package.json @@ -28,11 +28,22 @@ "zod": "^3.23.8" }, "devDependencies": { - "@eslint/js": "^9.35.0", + "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@tsconfig/node20": "20.1.4", + "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", "eslint": "^8.45.0", - "typescript": "^5.9.2" + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.6.3", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.7.0", + "prettier": "^3.0.0", + "rimraf": "^5.0.5", + "typedoc": "0.25.0", + "typescript": "^5.5.3" }, "directories": { "example": "example"