Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
6895509
chore: Build releases in dependency order. (#563)
kinyoklion Sep 3, 2024
7064b07
chore: release main (#560)
github-actions[bot] Sep 4, 2024
fcb821e
chore: Build RN example with detox. (#558)
kinyoklion Sep 4, 2024
3130c1c
chore: Standardize test organization. (#565)
kinyoklion Sep 5, 2024
4792391
feat: Add support for localStorage for the browser platform. (#566)
kinyoklion Sep 5, 2024
907d08b
fix: Correctly handle null values in JSON variations. (#569)
kinyoklion Sep 5, 2024
4932525
chore: Remove unused skip field. (#571)
kinyoklion Sep 5, 2024
ed7b27a
chore: release main (#570)
github-actions[bot] Sep 5, 2024
fca4d92
fix: Flag store should not access values from prototype. (#567)
kinyoklion Sep 6, 2024
9248035
feat: Add platform support for async hashing. (#573)
kinyoklion Sep 9, 2024
e763e5d
feat: Implement browser crypto and encoding. (#574)
kinyoklion Sep 9, 2024
a2f4398
feat: Add browser info. (#576)
kinyoklion Sep 10, 2024
fe82500
feat: Add support for conditional event source capabilities. (#577)
kinyoklion Sep 11, 2024
887548a
feat: Implement support for browser requests. (#578)
kinyoklion Sep 12, 2024
ed5a206
feat: Allow using custom user-agent name. (#580)
kinyoklion Sep 13, 2024
d11224c
fix: Use flag value whenever provided even if variaiton is null or un…
kinyoklion Sep 13, 2024
0848ab7
feat: Scaffold browser client. (#579)
kinyoklion Sep 13, 2024
38f081e
feat: Add support for browser contract tests. (#582)
kinyoklion Sep 16, 2024
a55be06
chore(deps): bump next from 14.1.2 to 14.2.10 in /packages/sdk/vercel…
dependabot[bot] Sep 18, 2024
916b724
feat: Adds support for REPORT. (#575)
tanderson-ld Sep 18, 2024
fd38a8f
feat: Implement goals for client-side SDKs. (#585)
kinyoklion Sep 19, 2024
9d93d2d
chore(deps): bump next from 14.1.2 to 14.2.10 in /packages/sdk/vercel…
dependabot[bot] Sep 19, 2024
7131e69
feat: Add URLs for custom events and URL filtering. (#587)
kinyoklion Sep 20, 2024
7dfb14d
fix: Ensure browser contract tests run during top-level build. (#589)
kinyoklion Sep 23, 2024
53f5bb8
feat: Refactor data source connection handling. (#591)
kinyoklion Sep 25, 2024
b6ff2a6
fix: Multi-kind context containing only 1 kind conveted incorrectly. …
kinyoklion Sep 26, 2024
dc4eb30
chore: release main (#572)
github-actions[bot] Sep 26, 2024
79d0b3f
chore: Update common version for browser SDK. (#595)
kinyoklion Sep 26, 2024
f2e5cbf
feat: Browser-SDK Automatically start streaming based on event handle…
kinyoklion Sep 27, 2024
9e00eb6
chore: Remove unused function in common client-side LDClientImpl. (#597)
kinyoklion Sep 27, 2024
980e4da
fix: Ensure client logger is always wrapped in a safe logger. (#599)
kinyoklion Sep 27, 2024
6f26204
feat: adds datasource status to sdk-client (#590)
tanderson-ld Sep 30, 2024
9ad25bd
chore: Remove shared mocks package. (#601)
kinyoklion Oct 1, 2024
3389983
feat: Add basic secure mode support for browser SDK. (#598)
kinyoklion Oct 2, 2024
8cd0cdc
feat: Add ESM support for common and common-client (rollup) (#604)
kinyoklion Oct 2, 2024
4e5dbee
feat: Add bootstrap support. (#600)
kinyoklion Oct 3, 2024
da31436
feat: adds support for individual flag change listeners (#608)
tanderson-ld Oct 4, 2024
04d347b
feat: Add support for hooks. (#605)
kinyoklion Oct 7, 2024
819a311
feat: Add visibility handling to allow proactive event flushing. (#607)
kinyoklion Oct 8, 2024
35fa033
fix: Use flagVersion in analytics events. (#611)
kinyoklion Oct 8, 2024
35ec8d1
fix: Fix base64 encoding of unicode characters. (#613)
kinyoklion Oct 9, 2024
9ef20a5
chore: release main (#596)
github-actions[bot] Oct 9, 2024
7b68c71
chore: Update common client version for browser package. (#614)
kinyoklion Oct 9, 2024
8b967a5
chore: Prepare for browser client releases. (#612)
kinyoklion Oct 9, 2024
fdce875
chore: Update pre-release browser config. (#616)
kinyoklion Oct 10, 2024
c31ac36
chore: release main (#615)
github-actions[bot] Oct 10, 2024
e97f478
chore: Remove release-as version for browser SDK. (#617)
kinyoklion Oct 10, 2024
794dbfc
chore: Float browser contract test dependency. (#618)
kinyoklion Oct 10, 2024
3e6d404
feat: Apply private property naming standard. Mangle browser private …
kinyoklion Oct 10, 2024
f6fc40b
fix: Do not mangle _meta. (#622)
kinyoklion Oct 11, 2024
a986478
feat: Add support for inspectors. (#625)
kinyoklion Oct 14, 2024
8c84e01
feat: Add support for client-side prerequisite events. (#606)
kinyoklion Oct 15, 2024
dee53af
feat: adds ping stream support (#624)
tanderson-ld Oct 15, 2024
2d2accd
fix: Fix common package to work with node16 module resolution. (#627)
kinyoklion Oct 17, 2024
70cf3c3
fix: Prerequisites should not trigger hooks. (#628)
kinyoklion Oct 17, 2024
e061811
fix: Update sdk-client rollup configuration to match common (#630)
kinyoklion Oct 17, 2024
ae2b5bb
chore: release main (#621)
github-actions[bot] Oct 17, 2024
9f78679
Update tracking methods
Oct 25, 2024
da3024f
whitespace changes
Oct 25, 2024
9ce1049
fix file organization
Oct 25, 2024
48cac54
chore: Change testing to use cjs. (#636)
kinyoklion Oct 28, 2024
8e3120e
chore: Enable polling report contract tests. (#639)
kinyoklion Oct 29, 2024
44a2237
feat: Add a module for increased backward compatibility. (#637)
kinyoklion Oct 29, 2024
ec4377c
feat: Refine CJS/ESM build configuration for browser SDK. (#640)
kinyoklion Oct 29, 2024
6cbf4f9
chore: release main (#638)
github-actions[bot] Oct 29, 2024
67b8cb8
chore: Package size reports. (#642)
kinyoklion Oct 30, 2024
2a9a0de
fix: Fix incompatibility with @types/node release. (#646)
kinyoklion Oct 31, 2024
473e0cb
chore: Revert - Fix incompatibility with @types/node release. (#649)
kinyoklion Oct 31, 2024
21670c4
feat: Enhance basic logger destination support. (#650)
kinyoklion Nov 1, 2024
7c5c585
fix accessor style based on feedback
Nov 1, 2024
9c6fdd3
fix Bedrock usage
Nov 1, 2024
7f58b2a
fix: Export LDInspection interface. (#653)
kinyoklion Nov 1, 2024
a543d7e
chore(deps): update reactivecircus/android-emulator-runner digest to …
renovate[bot] Nov 1, 2024
773d02c
chore: Revert "chore(deps): update reactivecircus/android-emulator-ru…
kinyoklion Nov 1, 2024
b8a2331
chore: No package-size comments on forks. (#654)
kinyoklion Nov 4, 2024
008dcf0
fix: Export required types from compat. (#645)
kinyoklion Nov 4, 2024
aee09c8
feat: Implement jest mocks for react-native. (#535)
professorice Nov 4, 2024
5899e9d
PR feedback
Nov 4, 2024
788612c
WIP: Refactoring
kinyoklion Nov 4, 2024
898e61d
WIP: More change.
kinyoklion Nov 4, 2024
f95775b
Merge branch 'main' into dob/modelConfig
kinyoklion Nov 4, 2024
9d22b8f
Merge branch 'dob/modelConfig' into feat/dob/aiTracking
kinyoklion Nov 4, 2024
7d489b3
WIP: Comments and organization.
kinyoklion Nov 4, 2024
6c30975
Basic functionality implemented.
kinyoklion Nov 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/sdk/ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest",
"lint": "eslint . --ext .ts"
"build": "npx tsc",
"lint": "npx eslint . --ext .ts",
"lint:fix": "yarn run lint --fix"
},
"keywords": [
"launchdarkly",
Expand Down
119 changes: 93 additions & 26 deletions packages/sdk/ai/src/LDAIConfigTracker.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,97 @@
import { LDClient, LDContext } from '@launchdarkly/node-server-sdk';

import { BedrockTokenUsage, FeedbackKind, TokenUsage, UnderscoreTokenUsage } from './api/metrics';
import { usageToTokenMetrics } from './trackUtils';
import {
createBedrockTokenUsage,
FeedbackKind,
OpenAITokenUsage,
TokenMetrics,
TokenUsage,
UnderscoreTokenUsage,
} from './api/metrics';

export class LDAIConfigTracker {
private ldClient: LDClient;
private configKey: string;
private variationId: string;
private configKey: string;
private context: LDContext;

constructor(ldClient: LDClient, configKey: string, variationId: string, context: LDContext) {
this.ldClient = ldClient;
this.configKey = configKey;
this.variationId = variationId;
this.configKey = configKey;
this.context = context;
}

getTrackData() {
private getTrackData() {
return {
configKey: this.configKey,
variationId: this.variationId,
configKey: this.configKey,
};
}

trackDuration(duration: number): void {
this.ldClient.track('$ld:ai:duration:total', this.context, this.variationId, duration);
this.ldClient.track('$ld:ai:duration:total', this.context, this.getTrackData(), duration);
}

async trackDurationOf(func: (...args: any[]) => Promise<any>, ...args: any[]): Promise<any> {
const startTime = Date.now();
const result = await func(...args);
const endTime = Date.now();
const duration = endTime - startTime; // duration in milliseconds
this.trackDuration(duration);
return result;
}

trackError(error: number): void {
this.ldClient.track('$ld:ai:error', this.context, this.getTrackData(), error);
}

trackTokens(tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage) {
console.log('tracking LLM tokens', tokens);
const tokenMetrics = usageToTokenMetrics(tokens);
console.log('token metrics', tokenMetrics);
trackFeedback(feedback: { kind: FeedbackKind }): void {
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);
}
}

trackGeneration(generation: number): void {
this.ldClient.track('$ld:ai:generation', this.context, this.getTrackData(), generation);
}

async trackOpenAI(func: (...args: any[]) => Promise<any>, ...args: any[]): Promise<any> {
const result = await this.trackDurationOf(func, ...args);
this.trackGeneration(1);
if (result.usage) {
this.trackTokens(new OpenAITokenUsage(result.usage));
}
return result;
}

async trackBedrockConverse(res: {
$metadata?: { httpStatusCode: number };
metrics?: { latencyMs: number };
usage?: {
inputTokens: number;
outputTokens: number;
totalTokens: number;
};
}): Promise<any> {
if (res.$metadata?.httpStatusCode === 200) {
this.trackGeneration(1);
} else if (res.$metadata?.httpStatusCode && res.$metadata.httpStatusCode >= 400) {
this.trackError(res.$metadata.httpStatusCode);
}
if (res.metrics) {
this.trackDuration(res.metrics.latencyMs);
}
if (res.usage) {
this.trackTokens(createBedrockTokenUsage(res.usage));
}
return res;
}

trackTokens(tokens: TokenUsage | UnderscoreTokenUsage | { totalTokens: number; inputTokens: number; outputTokens: number }): void {
const tokenMetrics = toMetrics(tokens);
if (tokenMetrics.total > 0) {
this.ldClient.track(
'$ld:ai:tokens:total',
Expand All @@ -40,7 +101,6 @@ export class LDAIConfigTracker {
);
}
if (tokenMetrics.input > 0) {
console.log('tracking input tokens', tokenMetrics.input);
this.ldClient.track(
'$ld:ai:tokens:input',
this.context,
Expand All @@ -49,7 +109,6 @@ export class LDAIConfigTracker {
);
}
if (tokenMetrics.output > 0) {
console.log('tracking output tokens', tokenMetrics.output);
this.ldClient.track(
'$ld:ai:tokens:output',
this.context,
Expand All @@ -58,20 +117,28 @@ export class LDAIConfigTracker {
);
}
}
}

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);
function toMetrics(
usage: TokenUsage | UnderscoreTokenUsage | { totalTokens: number; inputTokens: number; outputTokens: number },
): TokenMetrics {
if ('inputTokens' in usage && 'outputTokens' in usage) {
// Bedrock usage
return {
total: usage.totalTokens,
input: usage.inputTokens,
output: usage.outputTokens,
};
}

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);
}
}
// 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,
};
}
9 changes: 7 additions & 2 deletions packages/sdk/ai/src/api/config/LDAIConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import { LDAIConfigTracker } from './LDAIConfigTracker';
*/
export interface LDAIConfig {
/**
* The result of the AI Config evaluation.
* The result of the AI Config customization.
*/
config: unknown;

/**
* A tracker which can be used to generate analytics for the migration.
* A tracker which can be used to generate analytics.
*/
tracker: LDAIConfigTracker;

/**
* Whether the configuration is not found.
*/
noConfiguration: boolean;
}
12 changes: 10 additions & 2 deletions packages/sdk/ai/src/api/config/LDAIConfigTracker.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { BedrockTokenUsage, FeedbackKind, TokenUsage, UnderscoreTokenUsage } from '../metrics';
import { FeedbackKind, TokenUsage, UnderscoreTokenUsage } from '../metrics';

export interface LDAIConfigTracker {
trackDuration: (duration: number) => void;
trackTokens: (tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage) => void;
trackTokens: (
tokens:
| TokenUsage
| UnderscoreTokenUsage
| { totalTokens: number; inputTokens: number; outputTokens: number },
) => void;
trackError: (error: number) => void;
trackGeneration: (generation: number) => void;
trackFeedback: (feedback: { kind: FeedbackKind }) => void;
trackDurationOf: (func: (...args: any[]) => Promise<any>, ...args: any[]) => Promise<any>;
trackOpenAI: (func: Function, ...args: any[]) => any;
trackBedrockConverse: (res: any) => any;
}
2 changes: 1 addition & 1 deletion packages/sdk/ai/src/api/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './LDAIConfig';
export * from './LDAIConfigTracker';
export { LDAIConfigTracker } from './LDAIConfigTracker';
10 changes: 8 additions & 2 deletions packages/sdk/ai/src/api/metrics/BedrockTokenUsage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
export interface BedrockTokenUsage {
export function createBedrockTokenUsage(data: {
totalTokens: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
}) {
return {
total: data.totalTokens || 0,
input: data.inputTokens || 0,
output: data.outputTokens || 0,
};
}
19 changes: 19 additions & 0 deletions packages/sdk/ai/src/api/metrics/OpenAITokenUsage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export class OpenAITokenUsage {
total_tokens: number;
prompt_tokens: number;
completion_tokens: number;

constructor(data: any) {
this.total_tokens = data.total_tokens;
this.prompt_tokens = data.prompt_tokens;
this.completion_tokens = data.completion_tokens;
}

toMetrics() {
return {
total: this.total_tokens,
input: this.prompt_tokens,
output: this.completion_tokens,
};
}
}
22 changes: 18 additions & 4 deletions packages/sdk/ai/src/api/metrics/TokenUsage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
export interface TokenUsage {
completionTokens?: number;
promptTokens?: number;
totalTokens?: number;
export class TokenUsage {
totalTokens: number;
promptTokens: number;
completionTokens: number;

constructor(data: any) {
this.totalTokens = data.total_tokens || 0;
this.promptTokens = data.prompt_tokens || 0;
this.completionTokens = data.completion_tokens || 0;
}

toMetrics() {
return {
total: this.totalTokens,
input: this.promptTokens,
output: this.completionTokens,
};
}
}
22 changes: 18 additions & 4 deletions packages/sdk/ai/src/api/metrics/UnderscoreTokenUsage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
export interface UnderscoreTokenUsage {
completion_tokens?: number;
prompt_tokens?: number;
total_tokens?: number;
export class UnderscoreTokenUsage {
total_tokens: number;
prompt_tokens: number;
completion_tokens: number;

constructor(data: any) {
this.total_tokens = data.total_tokens || 0;
this.prompt_tokens = data.prompt_tokens || 0;
this.completion_tokens = data.completion_tokens || 0;
}

toMetrics() {
return {
total: this.total_tokens,
input: this.prompt_tokens,
output: this.completion_tokens,
};
}
}
1 change: 1 addition & 0 deletions packages/sdk/ai/src/api/metrics/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './BedrockTokenUsage';
export * from './FeedbackKind';
export * from './OpenAITokenUsage';
export * from './TokenMetrics';
export * from './TokenUsage';
export * from './UnderScoreTokenUsage';
17 changes: 7 additions & 10 deletions packages/sdk/ai/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Mustache from 'mustache';

import { LDClient, LDContext } from '@launchdarkly/node-server-sdk';

import { LDAIConfigTracker } from './LDAIConfigTracker';
import { LDAIConfig } from './api/config';
import { LDAIConfigTracker } from './LDAIConfigTracker';

export class AIClient {
private ldClient: LDClient;
Expand Down Expand Up @@ -41,7 +41,6 @@ export class AIClient {
* const defaultValue = {}};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this defaultValue line have a typo?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there were a number of problems here, but they are updated now. I am going to merge this to the feature PR and work from there.

*
* const result = modelConfig(key, context, defaultValue, variables);
* console.log(result);
* // Output:
* {
* modelId: "gpt-4o",
Expand All @@ -67,7 +66,7 @@ export class AIClient {
defaultValue: string,
variables?: Record<string, unknown>,
): Promise<LDAIConfig> {
const detail = await this.ldClient.variationDetail(key, context, defaultValue);
const detail = await this.ldClient.variation(key, context, defaultValue);

const allVariables = { ldctx: context, ...variables };

Expand All @@ -78,12 +77,9 @@ export class AIClient {

return {
config: detail.value,
tracker: new LDAIConfigTracker(
this.ldClient,
key,
detail.value["_ldMeta"]["variationId"],
context,
),
// eslint-disable-next-line no-underscore-dangle
tracker: new LDAIConfigTracker(this.ldClient, key, detail.value._ldMeta.variationId, context),
noConfiguration: Object.keys(detail).length === 0,
};
}
}
Expand All @@ -92,4 +88,5 @@ export function init(ldClient: LDClient): AIClient {
return new AIClient(ldClient);
}

export { LDAIConfigTracker } from './LDAIConfigTracker';
export * from './api/config/LDAIConfigTracker';
export * from './api/metrics';
2 changes: 1 addition & 1 deletion packages/sdk/ai/src/trackUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BedrockTokenUsage, TokenMetrics, TokenUsage, UnderscoreTokenUsage } from './types';
import { BedrockTokenUsage, TokenMetrics, TokenUsage, UnderscoreTokenUsage } from './api/metrics';

export function usageToTokenMetrics(
usage: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage,
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/ai/tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["/**/*.ts"],
"exclude": ["node_modules"]
}
Loading