-
Notifications
You must be signed in to change notification settings - Fork 31
feat: Add AI SDK for Server-Side JavaScript. #619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 17 commits
Commits
Show all changes
62 commits
Select commit
Hold shift + click to select a range
1e700c0
basic model
76dd721
add package that was dropped from rebase
6f393f7
change to map, switch type to Record
92ff33e
fix type in other location
d4c4fd9
change Record to string string everywhere
97365a0
switch back to unknown
488bc09
update interpolate function signature and escape within function call
679fb26
new package
5b1336e
add tracker
0a2586c
fix inadvertantly removed comment
4abed57
switch to variation ID
48b9a0f
fix package name
d34cb66
update naming
8ed5d9d
remove types file
76f25cc
exports
cf7582c
update example
c88ad2d
update how variationId is passed into tracker
9f78679
Update tracking methods
da3024f
whitespace changes
9ce1049
fix file organization
9c4e69f
openai and bedrock examples
f95775b
Merge branch 'main' into dob/modelConfig
kinyoklion 5ea8a83
feat: AI Tracking to AI SDK (#652)
InTheCloudDan fcad822
Merge branch 'dob/modelConfig' of github.com:launchdarkly/js-server-s…
kinyoklion 59d0c27
Merge branch 'dob/modelConfig' into feat/dob/REL-3130/aiExamples
kinyoklion 95fa2bc
Build updates.
kinyoklion 817d4f0
Fix bedrock example.
kinyoklion 7d91ea8
Commenting improvements.
kinyoklion f87f30d
Refine typing.
kinyoklion b6804f0
Building bedrock example.
kinyoklion 6106c82
Refactoring and cleanup.
kinyoklion c8d3c05
Renaming.
kinyoklion 96185fb
Add more comments.
kinyoklion 8eb4ad3
Convert openai example to typescript.
kinyoklion dddd5e7
CI and docs.
kinyoklion 318da7d
Linting.
kinyoklion 3c7aa41
Renames.
kinyoklion c1b1f3e
Lint
kinyoklion ef4c8ac
Example build changes.
kinyoklion f361b9b
Move AI package.
kinyoklion 7043d26
Rename AIClient file to LDAIClient.
kinyoklion 3d8697e
Merge branch 'main' into dob/modelConfig
kinyoklion e79b713
lint fix
c75ba9d
update examples to emit
fb05b7e
Better OpenAI types.
kinyoklion a4ce22e
Plurals
kinyoklion 43e9a5d
Broaden server compatibility
kinyoklion 1626a9d
Lint
kinyoklion 4186201
Discussion feedback.
kinyoklion d0e487b
Change versionId to versionKey.
kinyoklion 29024c1
Lint rules.
kinyoklion a0c9eca
Trivago lint.
kinyoklion 3cb1b89
Add release configuration.
kinyoklion e78a10d
Merge branch 'main' into dob/modelConfig
kinyoklion 3c5780c
Readme improvements. Example improvements.
kinyoklion e36bc35
Update example readme files.
kinyoklion 69086da
Add beta notice.
kinyoklion 205e600
Add markdown type annotation.
kinyoklion 6379a78
Add release configuration to update examples.
kinyoklion e20a498
Make sure examples are not published to NPM.
kinyoklion 807fa19
Node version.
kinyoklion 4ff029b
Build example deps.
kinyoklion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # LaunchDarkly AI SDK for Node.js | ||
|
|
||
| This package provides the LaunchDarkly AI SDK for Node.js. | ||
|
|
||
| ## Installation |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| { | ||
| "name": "@launchdarkly/ai", | ||
| "version": "0.1.0", | ||
| "description": "LaunchDarkly AI SDK for Node.js", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "jest", | ||
| "lint": "eslint . --ext .ts" | ||
| }, | ||
| "keywords": [ | ||
| "launchdarkly", | ||
| "ai", | ||
| "llm" | ||
| ], | ||
| "author": "LaunchDarkly", | ||
| "license": "Apache-2.0", | ||
| "dependencies": { | ||
| "@launchdarkly/node-server-sdk": "^9.5.2", | ||
| "mustache": "^4.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/jest": "^29.5.3", | ||
| "@types/mustache": "^4.2.5", | ||
| "@typescript-eslint/eslint-plugin": "^6.20.0", | ||
| "@typescript-eslint/parser": "^6.20.0", | ||
| "eslint": "^8.45.0", | ||
| "jest": "^29.6.1", | ||
| "ts-jest": "^29.1.1", | ||
| "typescript": "5.1.6" | ||
| }, | ||
| "peerDependencies": { | ||
| "@launchdarkly/node-server-sdk": ">=9.4.3" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { LDClient, LDContext } from '@launchdarkly/node-server-sdk'; | ||
|
|
||
| import { BedrockTokenUsage, FeedbackKind, TokenUsage, UnderscoreTokenUsage } from './api/metrics'; | ||
| import { usageToTokenMetrics } from './trackUtils'; | ||
|
|
||
| export class LDAIConfigTracker { | ||
| private ldClient: LDClient; | ||
| private configKey: string; | ||
| private variationId: string; | ||
| private context: LDContext; | ||
|
|
||
| constructor(ldClient: LDClient, configKey: string, variationId: string, context: LDContext) { | ||
| this.ldClient = ldClient; | ||
| this.configKey = configKey; | ||
| this.variationId = variationId; | ||
| this.context = context; | ||
| } | ||
|
|
||
| getTrackData() { | ||
| return { | ||
| configKey: this.configKey, | ||
| variationId: this.variationId, | ||
| }; | ||
| } | ||
|
|
||
| trackDuration(duration: number): void { | ||
| this.ldClient.track('$ld:ai:duration:total', this.context, this.variationId, duration); | ||
| } | ||
|
|
||
| trackTokens(tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage) { | ||
| console.log('tracking LLM tokens', tokens); | ||
| const tokenMetrics = usageToTokenMetrics(tokens); | ||
| console.log('token metrics', tokenMetrics); | ||
| if (tokenMetrics.total > 0) { | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:total', | ||
| this.context, | ||
| this.getTrackData(), | ||
| tokenMetrics.total, | ||
| ); | ||
| } | ||
| if (tokenMetrics.input > 0) { | ||
| console.log('tracking input tokens', tokenMetrics.input); | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:input', | ||
| this.context, | ||
| this.getTrackData(), | ||
| tokenMetrics.input, | ||
| ); | ||
| } | ||
| if (tokenMetrics.output > 0) { | ||
| console.log('tracking output tokens', tokenMetrics.output); | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:output', | ||
| this.context, | ||
| this.getTrackData(), | ||
| tokenMetrics.output, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| trackError(error: number) { | ||
| this.ldClient.track('$ld:ai:error', this.context, this.getTrackData(), error); | ||
| } | ||
|
|
||
| trackGeneration(generation: number) { | ||
| this.ldClient.track('$ld:ai:generation', this.context, this.getTrackData(), generation); | ||
| } | ||
|
|
||
| trackFeedback(feedback: { kind: FeedbackKind }) { | ||
| if (feedback.kind === FeedbackKind.Positive) { | ||
| this.ldClient.track('$ld:ai:feedback:user:positive', this.context, this.getTrackData(), 1); | ||
| } else if (feedback.kind === FeedbackKind.Negative) { | ||
| this.ldClient.track('$ld:ai:feedback:user:negative', this.context, this.getTrackData(), 1); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { LDAIConfigTracker } from './LDAIConfigTracker'; | ||
|
|
||
| /** | ||
| * AI Config value and tracker. | ||
| */ | ||
| export interface LDAIConfig { | ||
| /** | ||
| * The result of the AI Config evaluation. | ||
| */ | ||
| config: unknown; | ||
|
|
||
| /** | ||
| * A tracker which can be used to generate analytics for the migration. | ||
| */ | ||
| tracker: LDAIConfigTracker; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { BedrockTokenUsage, FeedbackKind, TokenUsage, UnderscoreTokenUsage } from '../metrics'; | ||
|
|
||
| export interface LDAIConfigTracker { | ||
| trackDuration: (duration: number) => void; | ||
| trackTokens: (tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage) => void; | ||
| trackError: (error: number) => void; | ||
| trackGeneration: (generation: number) => void; | ||
| trackFeedback: (feedback: { kind: FeedbackKind }) => void; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export * from './LDAIConfig'; | ||
| export * from './LDAIConfigTracker'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface BedrockTokenUsage { | ||
| inputTokens: number; | ||
| outputTokens: number; | ||
| totalTokens: number; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export enum FeedbackKind { | ||
| Positive = 'positive', | ||
| Negative = 'negative', | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface TokenMetrics { | ||
| total: number; | ||
| input: number; | ||
| output: number; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface TokenUsage { | ||
| completionTokens?: number; | ||
| promptTokens?: number; | ||
| totalTokens?: number; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface UnderscoreTokenUsage { | ||
| completion_tokens?: number; | ||
| prompt_tokens?: number; | ||
| total_tokens?: number; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export * from './BedrockTokenUsage'; | ||
| export * from './FeedbackKind'; | ||
| export * from './TokenMetrics'; | ||
| export * from './TokenUsage'; | ||
| export * from './UnderScoreTokenUsage'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import Mustache from 'mustache'; | ||
|
|
||
| import { LDClient, LDContext } from '@launchdarkly/node-server-sdk'; | ||
|
|
||
| import { LDAIConfigTracker } from './LDAIConfigTracker'; | ||
| import { LDAIConfig } from './api/config'; | ||
|
|
||
| export class AIClient { | ||
kinyoklion marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private ldClient: LDClient; | ||
|
|
||
| constructor(ldClient: LDClient) { | ||
| this.ldClient = ldClient; | ||
| } | ||
|
|
||
| /** | ||
| * Parses and interpolates a template string with the provided variables. | ||
| * | ||
| * @param template - The template string to be parsed and interpolated. | ||
| * @param variables - An object containing the variables to be used for interpolation. | ||
| * @returns The interpolated string. | ||
| */ | ||
| interpolateTemplate(template: string, variables: Record<string, unknown>): string { | ||
| return Mustache.render(template, variables, undefined, { escape: (item: any) => item }); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves and processes a prompt template based on the provided key, LaunchDarkly context, and variables. | ||
| * | ||
| * @param key - A unique identifier for the prompt template. This key is used to fetch the correct prompt from storage or configuration. | ||
| * @param context - The LaunchDarkly context object that contains relevant information about the current environment, user, or session. This context may influence how the prompt is processed or personalized. | ||
| * @param variables - A map of key-value pairs representing dynamic variables to be injected into the prompt template. The keys correspond to placeholders within the template, and the values are the corresponding replacements. | ||
| * @param defaultValue - A fallback value to be used if the prompt template associated with the key is not found or if any errors occur during processing. | ||
| * | ||
| * @returns The processed prompt after all variables have been substituted in the stored prompt template. If the prompt cannot be retrieved or processed, the `defaultValue` is returned. | ||
| * | ||
| * @example | ||
kinyoklion marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * ``` | ||
| * const key = "welcome_prompt"; | ||
| * const context = new LDContext(...); | ||
| * const variables = new Record<string, string>([["username", "John"]]); | ||
| * const defaultValue = {}}; | ||
| * | ||
| * const result = modelConfig(key, context, defaultValue, variables); | ||
| * console.log(result); | ||
| * // Output: | ||
| * { | ||
| * modelId: "gpt-4o", | ||
| * temperature: 0.2, | ||
| * maxTokens: 4096, | ||
| * userDefinedKey: "myValue", | ||
| * prompt: [ | ||
| * { | ||
| * role: "system", | ||
| * content: "You are an amazing GPT." | ||
| * }, | ||
| * { | ||
| * role: "user", | ||
| * content: "Explain how you're an amazing GPT." | ||
| * } | ||
| * ] | ||
| * } | ||
| * ``` | ||
| */ | ||
| async modelConfig( | ||
| key: string, | ||
| context: LDContext, | ||
| defaultValue: string, | ||
| variables?: Record<string, unknown>, | ||
| ): Promise<LDAIConfig> { | ||
| const detail = await this.ldClient.variationDetail(key, context, defaultValue); | ||
|
|
||
| const allVariables = { ldctx: context, ...variables }; | ||
|
|
||
| detail.value.prompt = detail.value.prompt.map((entry: any) => ({ | ||
| ...entry, | ||
| content: this.interpolateTemplate(entry.content, allVariables), | ||
| })); | ||
|
|
||
| return { | ||
| config: detail.value, | ||
| tracker: new LDAIConfigTracker( | ||
| this.ldClient, | ||
| key, | ||
| detail.value["_ldMeta"]["variationId"], | ||
| context, | ||
| ), | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export function init(ldClient: LDClient): AIClient { | ||
| return new AIClient(ldClient); | ||
| } | ||
|
|
||
| export { LDAIConfigTracker } from './LDAIConfigTracker'; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { BedrockTokenUsage, TokenMetrics, TokenUsage, UnderscoreTokenUsage } from './types'; | ||
|
|
||
| export function usageToTokenMetrics( | ||
| usage: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage, | ||
| ): TokenMetrics { | ||
| if ('inputTokens' in usage && 'outputTokens' in usage) { | ||
| // Bedrock usage | ||
| return { | ||
| total: usage.totalTokens, | ||
| input: usage.inputTokens, | ||
| output: usage.outputTokens, | ||
| }; | ||
| } | ||
|
|
||
| // OpenAI usage (both camelCase and snake_case) | ||
| return { | ||
| total: 'total_tokens' in usage ? usage.total_tokens! : (usage as TokenUsage).totalTokens ?? 0, | ||
| input: | ||
| 'prompt_tokens' in usage ? usage.prompt_tokens! : (usage as TokenUsage).promptTokens ?? 0, | ||
| output: | ||
| 'completion_tokens' in usage | ||
| ? usage.completion_tokens! | ||
| : (usage as TokenUsage).completionTokens ?? 0, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2017", | ||
| "module": "commonjs", | ||
| "declaration": true, | ||
| "outDir": "./dist", | ||
| "strict": true, | ||
| "esModuleInterop": true | ||
| }, | ||
| "include": ["src"], | ||
| "exclude": ["node_modules", "**/*.test.ts"] | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.