@@ -5,16 +5,14 @@ import { LDContext, LDLogger } from '@launchdarkly/js-server-sdk-common';
55import { TrackedChat } from './api/chat' ;
66import {
77 LDAIAgentConfig ,
8+ LDAIAgentConfigDefault ,
89 LDAIAgentRequestConfig ,
9- LDAIConfig ,
10- LDAIConfigKind ,
11- LDAIConfigTracker ,
10+ LDAIConfigKindDefault ,
11+ LDAIConversationConfig ,
12+ LDAIConversationConfigDefault ,
1213 LDAIJudgeConfig ,
14+ LDAIJudgeConfigDefault ,
1315 LDMessage ,
14- LDTrackedAgent ,
15- LDTrackedAgents ,
16- LDTrackedConfig ,
17- LDTrackedJudge ,
1816 VercelAISDKConfig ,
1917 VercelAISDKMapOptions ,
2018 VercelAISDKProvider ,
@@ -27,14 +25,6 @@ import { LDAIConfigMapper } from './LDAIConfigMapper';
2725import { LDAIConfigTrackerImpl } from './LDAIConfigTrackerImpl' ;
2826import { LDClientMin } from './LDClientMin' ;
2927
30- /**
31- * The result of evaluating a configuration.
32- */
33- interface EvaluationResult {
34- tracker : LDAIConfigTracker ;
35- config : LDAIConfigKind ;
36- }
37-
3828export class LDAIClientImpl implements LDAIClient {
3929 private _logger ?: LDLogger ;
4030
@@ -49,14 +39,25 @@ export class LDAIClientImpl implements LDAIClient {
4939 private async _evaluate (
5040 key : string ,
5141 context : LDContext ,
52- defaultValue : LDAIConfigKind ,
53- variables ?: Record < string , any > ,
54- ) : Promise < EvaluationResult > {
42+ defaultValue : LDAIConfigKindDefault ,
43+ mode : 'completion' | 'agent' | 'judge' ,
44+ variables ?: Record < string , unknown > ,
45+ ) : Promise < LDAIConversationConfig | LDAIAgentConfig | LDAIJudgeConfig > {
5546 // Convert default value to LDFlagValue format
56- const ldFlagValue = LDAIConfigUtils . toFlagValue ( defaultValue ) ;
47+ const ldFlagValue = LDAIConfigUtils . toFlagValue ( defaultValue , mode ) ;
5748
5849 const value : LDAIConfigFlagValue = await this . _ldClient . variation ( key , context , ldFlagValue ) ;
5950
51+ // Validate mode match
52+ // eslint-disable-next-line no-underscore-dangle
53+ const flagMode = value . _ldMeta ?. mode ;
54+ if ( flagMode !== mode ) {
55+ this . _logger ?. warn (
56+ `AI Config mode mismatch for ${ key } : expected ${ mode } , got ${ flagMode } . Returning disabled config.` ,
57+ ) ;
58+ return LDAIConfigUtils . createDisabledConfig ( mode ) ;
59+ }
60+
6061 const tracker = new LDAIConfigTrackerImpl (
6162 this . _ldClient ,
6263 key ,
@@ -69,133 +70,96 @@ export class LDAIClientImpl implements LDAIClient {
6970 context ,
7071 ) ;
7172
72- // Convert the flag value directly to the appropriate config type
73- const config = LDAIConfigUtils . fromFlagValue ( value ) ;
73+ // Convert the flag value to the appropriate config type
74+ const config = LDAIConfigUtils . fromFlagValue ( value , tracker ) ;
7475
75- // Apply variable interpolation if variables are provided
76- if ( variables ) {
77- const allVariables = { ... variables , ldctx : context } ;
76+ // Apply variable interpolation (always needed for ldctx)
77+ return this . _applyInterpolation ( config , context , variables ) ;
78+ }
7879
79- // Apply variable interpolation to messages if they exist
80- if ( 'messages' in config && config . messages ) {
81- config . messages = config . messages . map ( ( entry : LDMessage ) => ( {
80+ private _applyInterpolation (
81+ config : LDAIConversationConfig | LDAIAgentConfig | LDAIJudgeConfig ,
82+ context : LDContext ,
83+ variables ?: Record < string , unknown > ,
84+ ) : LDAIConversationConfig | LDAIAgentConfig | LDAIJudgeConfig {
85+ const allVariables = { ...variables , ldctx : context } ;
86+
87+ // Apply variable interpolation to messages if they exist
88+ if ( 'messages' in config && config . messages ) {
89+ return {
90+ ...config ,
91+ messages : config . messages . map ( ( entry : LDMessage ) => ( {
8292 ...entry ,
8393 content : this . _interpolateTemplate ( entry . content , allVariables ) ,
84- } ) ) ;
85- }
94+ } ) ) ,
95+ } ;
96+ }
8697
87- // Apply variable interpolation to instructions if they exist
88- if ( 'instructions' in config && config . instructions ) {
89- config . instructions = this . _interpolateTemplate ( config . instructions , allVariables ) ;
90- }
98+ // Apply variable interpolation to instructions if they exist
99+ if ( 'instructions' in config && config . instructions ) {
100+ return {
101+ ...config ,
102+ instructions : this . _interpolateTemplate ( config . instructions , allVariables ) ,
103+ } ;
91104 }
92105
106+ return config ;
107+ }
108+
109+ private _addVercelAISDKSupport ( config : LDAIConversationConfig ) : LDAIConversationConfig {
110+ const { messages } = config ;
111+ const mapper = new LDAIConfigMapper ( config . model , config . provider , messages ) ;
112+
93113 return {
94- tracker,
95- config,
114+ ...config ,
115+ toVercelAISDK : < TMod > (
116+ sdkProvider : VercelAISDKProvider < TMod > | Record < string , VercelAISDKProvider < TMod > > ,
117+ options ?: VercelAISDKMapOptions | undefined ,
118+ ) : VercelAISDKConfig < TMod > => mapper . toVercelAISDK ( sdkProvider , options ) ,
96119 } ;
97120 }
98121
99122 async config (
100123 key : string ,
101124 context : LDContext ,
102- defaultValue : LDAIConfigKind ,
125+ defaultValue : LDAIConversationConfigDefault ,
103126 variables ?: Record < string , unknown > ,
104- ) : Promise < LDTrackedConfig > {
127+ ) : Promise < LDAIConversationConfig > {
105128 this . _ldClient . track ( '$ld:ai:config:function:single' , context , key , 1 ) ;
106129
107- const { tracker, config } = await this . _evaluate ( key , context , defaultValue , variables ) ;
108-
109- // Cast to LDAIConfig since config method only accepts completion configs
110- const completionConfig = config as LDAIConfig ;
111-
112- // Create the mapper for toVercelAISDK functionality
113- const messages = 'messages' in completionConfig ? completionConfig . messages : undefined ;
114- const mapper = new LDAIConfigMapper (
115- completionConfig . model ,
116- completionConfig . provider ,
117- messages ,
118- ) ;
119-
120- return {
121- tracker,
122- config : {
123- ...completionConfig ,
124- toVercelAISDK : < TMod > (
125- sdkProvider : VercelAISDKProvider < TMod > | Record < string , VercelAISDKProvider < TMod > > ,
126- options ?: VercelAISDKMapOptions | undefined ,
127- ) : VercelAISDKConfig < TMod > => mapper . toVercelAISDK ( sdkProvider , options ) ,
128- } ,
129- } ;
130+ const config = await this . _evaluate ( key , context , defaultValue , 'completion' , variables ) ;
131+ return this . _addVercelAISDKSupport ( config as LDAIConversationConfig ) ;
130132 }
131133
132134 async judge (
133135 key : string ,
134136 context : LDContext ,
135- defaultValue : LDAIJudgeConfig ,
137+ defaultValue : LDAIJudgeConfigDefault ,
136138 variables ?: Record < string , unknown > ,
137- ) : Promise < LDTrackedJudge > {
139+ ) : Promise < LDAIJudgeConfig > {
138140 this . _ldClient . track ( '$ld:ai:judge:function:single' , context , key , 1 ) ;
139141
140- const { tracker, config } = await this . _evaluate ( key , context , defaultValue , variables ) ;
141-
142- // Cast to judge config since this method only accepts judge configs
143- const judgeConfig = config as LDAIJudgeConfig ;
144-
145- // Create the mapper for toVercelAISDK functionality
146- const messages = 'messages' in judgeConfig ? judgeConfig . messages : undefined ;
147- const mapper = new LDAIConfigMapper ( judgeConfig . model , judgeConfig . provider , messages ) ;
148-
149- return {
150- tracker,
151- judge : {
152- ...judgeConfig ,
153- toVercelAISDK : < TMod > (
154- sdkProvider : VercelAISDKProvider < TMod > | Record < string , VercelAISDKProvider < TMod > > ,
155- options ?: VercelAISDKMapOptions | undefined ,
156- ) : VercelAISDKConfig < TMod > => mapper . toVercelAISDK ( sdkProvider , options ) ,
157- } ,
158- } ;
142+ const config = await this . _evaluate ( key , context , defaultValue , 'judge' , variables ) ;
143+ return config as LDAIJudgeConfig ;
159144 }
160145
161146 async agent (
162147 key : string ,
163148 context : LDContext ,
164- defaultValue : LDAIAgentConfig ,
149+ defaultValue : LDAIAgentConfigDefault ,
165150 variables ?: Record < string , unknown > ,
166- ) : Promise < LDTrackedAgent > {
151+ ) : Promise < LDAIAgentConfig > {
167152 // Track agent usage
168153 this . _ldClient . track ( '$ld:ai:agent:function:single' , context , key , 1 ) ;
169154
170- const { tracker, config } = await this . _evaluate ( key , context , defaultValue , variables ) ;
171-
172- // Check if we have an agent config, log warning and return disabled config if not
173- if ( config . mode !== 'agent' ) {
174- this . _logger ?. warn (
175- `Configuration is not an agent (mode: ${ config . mode } ), returning disabled config` ,
176- ) ;
177- return {
178- tracker,
179- agent : {
180- ...config ,
181- enabled : false ,
182- mode : 'agent' as const ,
183- } as LDAIAgentConfig ,
184- } ;
185- }
186-
187- const agent = config as LDAIAgentConfig ;
188-
189- return {
190- tracker,
191- agent,
192- } ;
155+ const config = await this . _evaluate ( key , context , defaultValue , 'agent' , variables ) ;
156+ return config as LDAIAgentConfig ;
193157 }
194158
195159 async agents < const T extends readonly LDAIAgentRequestConfig [ ] > (
196160 agentConfigs : T ,
197161 context : LDContext ,
198- ) : Promise < LDTrackedAgents > {
162+ ) : Promise < Record < T [ number ] [ 'key' ] , LDAIAgentConfig > > {
199163 // Track multiple agents usage
200164 this . _ldClient . track (
201165 '$ld:ai:agent:function:multiple' ,
@@ -205,90 +169,55 @@ export class LDAIClientImpl implements LDAIClient {
205169 ) ;
206170
207171 const agents = { } as Record < T [ number ] [ 'key' ] , LDAIAgentConfig > ;
208- let tracker : LDAIConfigTracker | undefined ;
209172
210173 await Promise . all (
211174 agentConfigs . map ( async ( config ) => {
212- const result = await this . _evaluate (
175+ const agent = await this . _evaluate (
213176 config . key ,
214177 context ,
215178 config . defaultValue ,
179+ 'agent' ,
216180 config . variables ,
217181 ) ;
218-
219- // Check if we have an agent config, log warning and return disabled config if not
220- if ( result . config . mode !== 'agent' ) {
221- this . _logger ?. warn (
222- `Configuration ${ config . key } is not an agent (mode: ${ result . config . mode } ), returning disabled config` ,
223- ) ;
224- agents [ config . key as T [ number ] [ 'key' ] ] = {
225- ...result . config ,
226- enabled : false ,
227- mode : 'agent' as const ,
228- } as LDAIAgentConfig ;
229- if ( ! tracker ) {
230- tracker = result . tracker ;
231- }
232- return ;
233- }
234-
235- const agent = result . config as LDAIAgentConfig ;
236- agents [ config . key as T [ number ] [ 'key' ] ] = agent ;
237- if ( ! tracker ) {
238- tracker = result . tracker ;
239- }
182+ agents [ config . key as T [ number ] [ 'key' ] ] = agent as LDAIAgentConfig ;
240183 } ) ,
241184 ) ;
242185
243- return {
244- tracker : tracker ! ,
245- agents,
246- } ;
186+ return agents ;
247187 }
248188
249189 async initChat (
250190 key : string ,
251191 context : LDContext ,
252- defaultValue : LDAIConfig ,
192+ defaultValue : LDAIConversationConfigDefault ,
253193 variables ?: Record < string , unknown > ,
254194 defaultAiProvider ?: SupportedAIProvider ,
255195 ) : Promise < TrackedChat | undefined > {
256196 // Track chat initialization
257197 this . _ldClient . track ( '$ld:ai:config:function:initChat' , context , key , 1 ) ;
258198
259- const result = await this . config ( key , context , defaultValue , variables ) ;
199+ const config = await this . config ( key , context , defaultValue , variables ) ;
260200
261201 // Return undefined if the configuration is disabled
262- if ( ! result . config . enabled ) {
202+ if ( ! config . enabled || ! config . tracker ) {
263203 this . _logger ?. info ( `Chat configuration is disabled: ${ key } ` ) ;
264204 return undefined ;
265205 }
266206
267- // Check if we have a completion config, log warning and return undefined if not
268- if ( result . config . mode && result . config . mode !== 'completion' ) {
269- this . _logger ?. warn (
270- `Configuration ${ key } is not a completion config (mode: ${ result . config . mode } ), returning undefined` ,
271- ) ;
272- return undefined ;
273- }
274-
275- // Cast to LDAIConfig since initChat only accepts completion configs
276- const config = result . config as LDAIConfig ;
277-
278207 // Create the AIProvider instance
279208 const provider = await AIProviderFactory . create ( config , this . _logger , defaultAiProvider ) ;
280209 if ( ! provider ) {
281210 return undefined ;
282211 }
283212
284213 // Create the TrackedChat instance with the provider
285- return new TrackedChat ( config , result . tracker , provider ) ;
214+ return new TrackedChat ( config , config . tracker , provider ) ;
286215 }
287216
288217 async initJudge (
289218 key : string ,
290219 context : LDContext ,
291- defaultValue : LDAIJudgeConfig ,
220+ defaultValue : LDAIJudgeConfigDefault ,
292221 variables ?: Record < string , unknown > ,
293222 defaultAiProvider ?: SupportedAIProvider ,
294223 ) : Promise < Judge | undefined > {
@@ -297,38 +226,22 @@ export class LDAIClientImpl implements LDAIClient {
297226
298227 try {
299228 // Retrieve the judge AI Config using the new judge method
300- const result = await this . judge ( key , context , defaultValue , variables ) ;
229+ const judgeConfig = await this . judge ( key , context , defaultValue , variables ) ;
301230
302231 // Return undefined if the configuration is disabled
303- if ( ! result . judge . enabled ) {
232+ if ( ! judgeConfig . enabled || ! judgeConfig . tracker ) {
304233 this . _logger ?. info ( `Judge configuration is disabled: ${ key } ` ) ;
305234 return undefined ;
306235 }
307236
308- // Validate that this is a judge configuration
309- if ( result . judge . mode !== 'judge' ) {
310- this . _logger ?. warn ( `Configuration ${ key } is not a judge (mode: ${ result . judge . mode } )` ) ;
311- return undefined ;
312- }
313-
314- // Validate that the judge configuration has the required evaluation metric keys
315- if ( ! result . judge . evaluationMetricKeys || result . judge . evaluationMetricKeys . length === 0 ) {
316- this . _logger ?. error ( `Judge configuration ${ key } is missing required evaluationMetricKeys` ) ;
317- return undefined ;
318- }
319-
320237 // Create the AIProvider instance
321- const provider = await AIProviderFactory . create (
322- result . judge ,
323- this . _logger ,
324- defaultAiProvider ,
325- ) ;
238+ const provider = await AIProviderFactory . create ( judgeConfig , this . _logger , defaultAiProvider ) ;
326239 if ( ! provider ) {
327240 return undefined ;
328241 }
329242
330243 // Create and return the Judge instance
331- return new Judge ( result . judge , result . tracker , provider , this . _logger ) ;
244+ return new Judge ( judgeConfig , judgeConfig . tracker , provider , this . _logger ) ;
332245 } catch ( error ) {
333246 this . _logger ?. error ( `Failed to initialize judge ${ key } :` , error ) ;
334247 return undefined ;
0 commit comments