diff --git a/.github/workflows/server-ai.yml b/.github/workflows/server-ai.yml index 9d02e1d673..a89b97b508 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 @@ -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/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/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/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..1d7e03400d --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/package.json @@ -0,0 +1,55 @@ +{ + "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", + "zod": "^3.23.8" + }, + "devDependencies": { + "@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", + "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" + }, + "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..c4f5adff42 --- /dev/null +++ b/packages/sdk/server-ai/examples/vercel-ai/src/index.ts @@ -0,0 +1,85 @@ +/* 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); +} + +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 userMessage = { + role: 'user' as const, + content: 'What can you help me with?', + }; + + const result = await aiConfig.tracker.trackVercelAISDKGenerateTextMetrics(() => + 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, { nonInterpolatedMessages: [userMessage] })), + ); + + // eslint-disable-next-line no-restricted-syntax + 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.eslint.json b/packages/sdk/server-ai/examples/vercel-ai/tsconfig.eslint.json new file mode 100644 index 0000000000..4a68a0c7dc --- /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"] +} 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"] +}