-
Notifications
You must be signed in to change notification settings - Fork 31
AI Tracking Updates #634
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
AI Tracking Updates #634
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,88 @@ | ||
| import { LDClient, LDContext } from '@launchdarkly/node-server-sdk'; | ||
|
|
||
| import { BedrockTokenUsage, FeedbackKind, TokenUsage, UnderscoreTokenUsage } from './api/metrics'; | ||
| import { usageToTokenMetrics } from './trackUtils'; | ||
| import { | ||
| BedrockTokenUsage, | ||
| FeedbackKind, | ||
| OpenAITokenUsage, | ||
| TokenUsage, | ||
| UnderscoreTokenUsage, | ||
| } from './api/metrics'; | ||
|
|
||
| export class LDAIConfigTracker { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to check the base PR, but this should be implementing an interface, and the interface is what the customer is aware of. |
||
| 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: Function, ...args: any[]): Promise<any> { | ||
InTheCloudDan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| trackTokens(tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage) { | ||
| console.log('tracking LLM tokens', tokens); | ||
| const tokenMetrics = usageToTokenMetrics(tokens); | ||
| console.log('token metrics', tokenMetrics); | ||
| async trackOpenAI(func: Function, ...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: any): Promise<any> { | ||
InTheCloudDan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (res.$metadata?.httpStatusCode === 200) { | ||
| this.trackGeneration(1); | ||
| } else if (res.$metadata?.httpStatusCode >= 400) { | ||
| this.trackError(res.$metadata.httpStatusCode); | ||
| } | ||
| if (res.metrics) { | ||
| this.trackDuration(res.metrics.latencyMs); | ||
| } | ||
| if (res.usage) { | ||
| this.trackTokens(new BedrockTokenUsage(res.usage)); | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| trackTokens(tokens: TokenUsage | UnderscoreTokenUsage | BedrockTokenUsage): void { | ||
| const tokenMetrics = tokens.toMetrics(); | ||
| if (tokenMetrics.total > 0) { | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:total', | ||
|
|
@@ -40,7 +92,6 @@ export class LDAIConfigTracker { | |
| ); | ||
| } | ||
| if (tokenMetrics.input > 0) { | ||
| console.log('tracking input tokens', tokenMetrics.input); | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:input', | ||
| this.context, | ||
|
|
@@ -49,7 +100,6 @@ export class LDAIConfigTracker { | |
| ); | ||
| } | ||
| if (tokenMetrics.output > 0) { | ||
| console.log('tracking output tokens', tokenMetrics.output); | ||
| this.ldClient.track( | ||
| '$ld:ai:tokens:output', | ||
| this.context, | ||
|
|
@@ -58,20 +108,4 @@ 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); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| export * from './LDAIConfig'; | ||
| export * from './LDAIConfigTracker'; | ||
| export { LDAIConfigTracker } from './LDAIConfigTracker'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,19 @@ | ||
| export interface BedrockTokenUsage { | ||
| export class BedrockTokenUsage { | ||
| totalTokens: number; | ||
| inputTokens: number; | ||
| outputTokens: number; | ||
| totalTokens: number; | ||
|
|
||
| constructor(data: any) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A type of some kind would be good here. Typescript is structural, so it just needs to have the stuff that we need to understand. |
||
| this.totalTokens = data.totalTokens || 0; | ||
| this.inputTokens = data.inputTokens || 0; | ||
| this.outputTokens = data.outputTokens || 0; | ||
| } | ||
|
|
||
| toMetrics() { | ||
| return { | ||
| total: this.totalTokens, | ||
| input: this.inputTokens, | ||
| output: this.outputTokens, | ||
| }; | ||
| } | ||
| } | ||
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as others.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am going to stop tagging now. But generally we want to avoid any, unless we really need to. Here we know several fields, so we should be able to say we need those. |
||
| 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, | ||
| }; | ||
| } | ||
| } | ||
| 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, | ||
| }; | ||
| } | ||
| } |
| 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, | ||
| }; | ||
| } | ||
| } |
| 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'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "include": ["/**/*.ts"], | ||
| "exclude": ["node_modules"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the package name should follow our normal conventions, likely
@launchdarkly/node-server-sdk-ai. I can see this might require some discussion.