@launchdarkly/server-sdk-ai 0.19.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,147 +31,127 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AIProvider: () => AIProvider,
34
- AIProviderFactory: () => AIProviderFactory,
35
34
  AgentGraphDefinition: () => AgentGraphDefinition,
36
35
  AgentGraphNode: () => AgentGraphNode,
37
36
  Judge: () => Judge,
38
37
  LDFeedbackKind: () => LDFeedbackKind,
39
38
  LDGraphTrackerImpl: () => LDGraphTrackerImpl,
39
+ ManagedAgent: () => ManagedAgent,
40
+ ManagedAgentGraph: () => ManagedAgentGraph,
41
+ ManagedModel: () => ManagedModel,
42
+ RunnerFactory: () => RunnerFactory,
40
43
  SUPPORTED_AI_PROVIDERS: () => SUPPORTED_AI_PROVIDERS,
41
- TrackedChat: () => TrackedChat,
42
- createBedrockTokenUsage: () => createBedrockTokenUsage,
43
- createOpenAiUsage: () => createOpenAiUsage,
44
- createVercelAISDKTokenUsage: () => createVercelAISDKTokenUsage,
45
44
  initAi: () => initAi
46
45
  });
47
46
  module.exports = __toCommonJS(src_exports);
48
47
 
49
48
  // src/LDAIClientImpl.ts
50
- var import_mustache2 = __toESM(require("mustache"), 1);
49
+ var import_mustache = __toESM(require("mustache"), 1);
51
50
  var import_node_crypto = require("crypto");
52
51
 
53
- // src/api/chat/TrackedChat.ts
54
- var TrackedChat = class {
55
- constructor(aiConfig, provider, judges = {}, _logger) {
56
- this.aiConfig = aiConfig;
57
- this.provider = provider;
58
- this.judges = judges;
52
+ // src/api/ManagedAgent.ts
53
+ var ManagedAgent = class {
54
+ constructor(aiAgentConfig, runner, _logger) {
55
+ this.aiAgentConfig = aiAgentConfig;
56
+ this.runner = runner;
59
57
  this._logger = _logger;
60
- this.messages = [];
61
58
  }
62
59
  /**
63
- * Invoke the chat model with a prompt string.
64
- * This method handles conversation management and tracking, delegating to the provider's invokeModel method.
60
+ * Invoke the agent with a prompt string and return a ManagedResult.
61
+ *
62
+ * `run()` resolves before `ManagedResult.evaluations` resolves. Awaiting
63
+ * `evaluations` guarantees both judge evaluation and tracker.trackJudgeResult()
64
+ * are complete.
65
+ *
66
+ * @param prompt The user input to send to the agent.
67
+ * @returns Promise resolving to ManagedResult (before evaluations settle).
65
68
  */
66
- async invoke(prompt) {
67
- const tracker = this.aiConfig.createTracker();
68
- const userMessage = {
69
- role: "user",
70
- content: prompt
71
- };
72
- this.messages.push(userMessage);
73
- const configMessages = this.aiConfig.messages || [];
74
- const allMessages = [...configMessages, ...this.messages];
75
- const response = await tracker.trackMetricsOf(
76
- (result) => result.metrics,
77
- () => this.provider.invokeModel(allMessages)
69
+ async run(prompt) {
70
+ const tracker = this.aiAgentConfig.createTracker();
71
+ const result = await tracker.trackMetricsOf(
72
+ (r) => r.metrics,
73
+ () => this.runner.run(prompt)
78
74
  );
79
- if (this.aiConfig.judgeConfiguration?.judges && this.aiConfig.judgeConfiguration.judges.length > 0) {
80
- response.evaluations = this._evaluateWithJudges(this.messages, response).then(
81
- (evaluations) => {
82
- evaluations.forEach((judgeResult) => {
83
- tracker.trackJudgeResult(judgeResult);
84
- });
85
- return evaluations;
75
+ const metrics = tracker.getSummary();
76
+ const output = result.content;
77
+ const evaluations = this.aiAgentConfig.evaluator.evaluate(prompt, output).then((results) => {
78
+ results.forEach((judgeResult) => {
79
+ if (!judgeResult.sampled) {
80
+ return;
86
81
  }
87
- );
88
- }
89
- this.messages.push(response.message);
90
- return response;
91
- }
92
- /**
93
- * Evaluates the response with all configured judges.
94
- * Returns a promise that resolves to an array of evaluation results.
95
- *
96
- * @param messages Array of messages representing the conversation history
97
- * @param response The AI response to be evaluated
98
- * @returns Promise resolving to array of judge evaluation results
99
- */
100
- async _evaluateWithJudges(messages, response) {
101
- const judgeConfigs = this.aiConfig.judgeConfiguration.judges;
102
- const evaluationPromises = judgeConfigs.map(async (judgeConfig) => {
103
- const judge = this.judges[judgeConfig.key];
104
- if (!judge) {
105
- this._logger?.warn(
106
- `Judge configuration is not enabled for ${judgeConfig.key} in ${this.aiConfig.key}`
107
- );
108
- const result = {
109
- success: false,
110
- sampled: true,
111
- errorMessage: `Judge configuration is not enabled for ${judgeConfig.key}`
112
- };
113
- return result;
114
- }
115
- return judge.evaluateMessages(messages, response, judgeConfig.samplingRate);
116
- });
117
- const results = await Promise.allSettled(evaluationPromises);
118
- return results.map((settled) => {
119
- if (settled.status === "fulfilled") {
120
- return settled.value;
121
- }
122
- const result = {
123
- success: false,
124
- sampled: true,
125
- errorMessage: "Judge evaluation failed"
126
- };
127
- return result;
82
+ tracker.trackJudgeResult(judgeResult);
83
+ });
84
+ return results;
85
+ }).catch((err) => {
86
+ this._logger?.warn("Judge evaluation failed unexpectedly:", err);
87
+ return [];
128
88
  });
89
+ return {
90
+ content: output,
91
+ metrics,
92
+ raw: result.raw,
93
+ parsed: result.parsed,
94
+ evaluations
95
+ };
129
96
  }
130
97
  /**
131
- * Get the underlying AI configuration used to initialize this TrackedChat.
98
+ * Get the underlying AI agent configuration used to initialize this ManagedAgent.
132
99
  */
133
100
  getConfig() {
134
- return this.aiConfig;
101
+ return this.aiAgentConfig;
135
102
  }
136
- /**
137
- * Get the underlying AI provider instance.
138
- * This provides direct access to the provider for advanced use cases.
139
- */
140
- getProvider() {
141
- return this.provider;
142
- }
143
- /**
144
- * Get the judges associated with this TrackedChat.
145
- * Returns a record of judge instances keyed by their configuration keys.
146
- */
147
- getJudges() {
148
- return this.judges;
103
+ };
104
+
105
+ // src/api/ManagedModel.ts
106
+ var ManagedModel = class {
107
+ constructor(aiConfig, runner, _logger) {
108
+ this.aiConfig = aiConfig;
109
+ this.runner = runner;
110
+ this._logger = _logger;
149
111
  }
150
112
  /**
151
- * Append messages to the conversation history.
152
- * Adds messages to the conversation history without invoking the model,
153
- * which is useful for managing multi-turn conversations or injecting context.
113
+ * Invoke the model with a prompt string and return a ManagedResult.
114
+ *
115
+ * `run()` resolves before `ManagedResult.evaluations` resolves. Awaiting
116
+ * `evaluations` guarantees both judge evaluation and tracker.trackJudgeResult()
117
+ * are complete.
154
118
  *
155
- * @param messages Array of messages to append to the conversation history
119
+ * @param prompt The user input to send to the model.
120
+ * @returns Promise resolving to ManagedResult (before evaluations settle).
156
121
  */
157
- appendMessages(messages) {
158
- this.messages.push(...messages);
122
+ async run(prompt) {
123
+ const tracker = this.aiConfig.createTracker();
124
+ const result = await tracker.trackMetricsOf(
125
+ (r) => r.metrics,
126
+ () => this.runner.run(prompt)
127
+ );
128
+ const metrics = tracker.getSummary();
129
+ const output = result.content;
130
+ const evaluations = this.aiConfig.evaluator.evaluate(prompt, output).then((results) => {
131
+ results.forEach((judgeResult) => {
132
+ if (!judgeResult.sampled) {
133
+ return;
134
+ }
135
+ tracker.trackJudgeResult(judgeResult);
136
+ });
137
+ return results;
138
+ }).catch((err) => {
139
+ this._logger?.warn("Judge evaluation failed unexpectedly:", err);
140
+ return [];
141
+ });
142
+ return {
143
+ content: output,
144
+ metrics,
145
+ raw: result.raw,
146
+ parsed: result.parsed,
147
+ evaluations
148
+ };
159
149
  }
160
150
  /**
161
- * Get all messages in the conversation history.
162
- *
163
- * @param includeConfigMessages Whether to include the config messages from the AIConfig.
164
- * Defaults to false.
165
- * @returns Array of messages. When includeConfigMessages is true, returns both config
166
- * messages and conversation history with config messages prepended. When false,
167
- * returns only the conversation history messages.
151
+ * Get the underlying AI configuration used to initialize this ManagedModel.
168
152
  */
169
- getMessages(includeConfigMessages = false) {
170
- if (includeConfigMessages) {
171
- const configMessages = this.aiConfig.messages || [];
172
- return [...configMessages, ...this.messages];
173
- }
174
- return [...this.messages];
153
+ getConfig() {
154
+ return this.aiConfig;
175
155
  }
176
156
  };
177
157
 
@@ -206,9 +186,6 @@ var LDAIConfigUtils = class {
206
186
  if ("evaluationMetricKey" in config && config.evaluationMetricKey !== void 0) {
207
187
  flagValue.evaluationMetricKey = config.evaluationMetricKey;
208
188
  }
209
- if ("evaluationMetricKeys" in config && config.evaluationMetricKeys !== void 0) {
210
- flagValue.evaluationMetricKeys = config.evaluationMetricKeys;
211
- }
212
189
  if ("judgeConfiguration" in config && config.judgeConfiguration !== void 0) {
213
190
  flagValue.judgeConfiguration = config.judgeConfiguration;
214
191
  }
@@ -222,47 +199,53 @@ var LDAIConfigUtils = class {
222
199
  *
223
200
  * @param key The configuration key
224
201
  * @param flagValue The flag value from LaunchDarkly
225
- * @param trackerFactory A factory function that creates a new tracker for each execution
202
+ * @param trackerFactory A factory function that creates a new tracker for each AI run
203
+ * @param evaluator The evaluator to attach to completion and agent configs
226
204
  * @returns The appropriate AI configuration type
227
205
  */
228
- static fromFlagValue(key, flagValue, trackerFactory) {
206
+ static fromFlagValue(key, flagValue, trackerFactory, evaluator) {
229
207
  const flagValueMode = flagValue._ldMeta?.mode;
230
208
  switch (flagValueMode) {
231
209
  case "agent":
232
- return this.toAgentConfig(key, flagValue, trackerFactory);
210
+ return this.toAgentConfig(key, flagValue, trackerFactory, evaluator);
233
211
  case "judge":
234
212
  return this.toJudgeConfig(key, flagValue, trackerFactory);
235
213
  case "completion":
236
214
  default:
237
- return this.toCompletionConfig(key, flagValue, trackerFactory);
215
+ return this.toCompletionConfig(key, flagValue, trackerFactory, evaluator);
238
216
  }
239
217
  }
240
218
  /**
241
219
  * Creates a disabled configuration of the specified mode.
242
220
  *
221
+ * @param key The configuration key
243
222
  * @param mode The mode for the disabled config
223
+ * @param createTracker A factory function that creates a new tracker for each AI run
224
+ * @param evaluator The evaluator to attach to completion and agent configs
244
225
  * @returns A disabled config of the appropriate type
245
226
  */
246
- static createDisabledConfig(key, mode) {
227
+ static createDisabledConfig(key, mode, createTracker, evaluator) {
247
228
  switch (mode) {
248
229
  case "agent":
249
230
  return {
250
231
  key,
251
232
  enabled: false,
252
- createTracker: void 0
233
+ createTracker,
234
+ evaluator
253
235
  };
254
236
  case "judge":
255
237
  return {
256
238
  key,
257
239
  enabled: false,
258
- createTracker: void 0
240
+ createTracker
259
241
  };
260
242
  case "completion":
261
243
  default:
262
244
  return {
263
245
  key,
264
246
  enabled: false,
265
- createTracker: void 0
247
+ createTracker,
248
+ evaluator
266
249
  };
267
250
  }
268
251
  }
@@ -303,13 +286,15 @@ var LDAIConfigUtils = class {
303
286
  *
304
287
  * @param key The configuration key
305
288
  * @param flagValue The flag value from LaunchDarkly
306
- * @param trackerFactory A factory function that creates a new tracker for each execution
289
+ * @param trackerFactory A factory function that creates a new tracker for each AI run
290
+ * @param evaluator The evaluator for this completion config
307
291
  * @returns A completion configuration
308
292
  */
309
- static toCompletionConfig(key, flagValue, trackerFactory) {
293
+ static toCompletionConfig(key, flagValue, trackerFactory, evaluator) {
310
294
  return {
311
295
  ...this._toBaseConfig(key, flagValue),
312
296
  createTracker: trackerFactory,
297
+ evaluator,
313
298
  messages: flagValue.messages,
314
299
  judgeConfiguration: flagValue.judgeConfiguration,
315
300
  tools: this._resolveTools(flagValue)
@@ -320,13 +305,15 @@ var LDAIConfigUtils = class {
320
305
  *
321
306
  * @param key The configuration key
322
307
  * @param flagValue The flag value from LaunchDarkly
323
- * @param trackerFactory A factory function that creates a new tracker for each execution
308
+ * @param trackerFactory A factory function that creates a new tracker for each AI run
309
+ * @param evaluator The evaluator for this agent config
324
310
  * @returns An agent configuration
325
311
  */
326
- static toAgentConfig(key, flagValue, trackerFactory) {
312
+ static toAgentConfig(key, flagValue, trackerFactory, evaluator) {
327
313
  return {
328
314
  ...this._toBaseConfig(key, flagValue),
329
315
  createTracker: trackerFactory,
316
+ evaluator,
330
317
  instructions: flagValue.instructions,
331
318
  judgeConfiguration: flagValue.judgeConfiguration,
332
319
  tools: this._resolveTools(flagValue)
@@ -337,7 +324,7 @@ var LDAIConfigUtils = class {
337
324
  *
338
325
  * @param key The configuration key
339
326
  * @param flagValue The flag value from LaunchDarkly
340
- * @param trackerFactory A factory function that creates a new tracker for each execution
327
+ * @param trackerFactory A factory function that creates a new tracker for each AI run
341
328
  * @returns A judge configuration
342
329
  */
343
330
  static toJudgeConfig(key, flagValue, trackerFactory) {
@@ -470,10 +457,10 @@ var AgentGraphDefinition = class _AgentGraphDefinition {
470
457
  return this._agentGraph;
471
458
  }
472
459
  /**
473
- * Returns a new {@link LDGraphTracker} for this graph invocation.
460
+ * Returns a new {@link LDGraphTracker} for a fresh graph run.
474
461
  *
475
- * Call this once per invocation. Each call produces a tracker with a fresh `runId`
476
- * that groups all events for that invocation.
462
+ * Call this once per graph run. Each call produces a tracker with a fresh `runId`
463
+ * that groups all events for that run.
477
464
  */
478
465
  createTracker() {
479
466
  return this._createTracker();
@@ -591,8 +578,23 @@ var AgentGraphDefinition = class _AgentGraphDefinition {
591
578
  }
592
579
  };
593
580
 
581
+ // src/api/judge/Evaluator.ts
582
+ var Evaluator = class _Evaluator {
583
+ constructor(_judges) {
584
+ this._judges = _judges;
585
+ }
586
+ static noop() {
587
+ return new _Evaluator([]);
588
+ }
589
+ async evaluate(input, output) {
590
+ if (this._judges.length === 0) {
591
+ return [];
592
+ }
593
+ return Promise.all(this._judges.map((judge) => judge.evaluate(input, output)));
594
+ }
595
+ };
596
+
594
597
  // src/api/judge/Judge.ts
595
- var import_mustache = __toESM(require("mustache"), 1);
596
598
  var EVALUATION_SCHEMA = {
597
599
  type: "object",
598
600
  properties: {
@@ -610,15 +612,27 @@ var EVALUATION_SCHEMA = {
610
612
  required: ["score", "reasoning"],
611
613
  additionalProperties: false
612
614
  };
615
+ function stripLegacyJudgeMessages(messages) {
616
+ return messages.filter(
617
+ (msg) => msg.role === "system" || !msg.content.includes("{{message_history}}") && !msg.content.includes("{{response_to_evaluate}}")
618
+ );
619
+ }
613
620
  var Judge = class {
614
- constructor(_aiConfig, _aiProvider, logger) {
621
+ constructor(_aiConfig, _runner, _sampleRate = 1, logger) {
615
622
  this._aiConfig = _aiConfig;
616
- this._aiProvider = _aiProvider;
623
+ this._runner = _runner;
624
+ this._sampleRate = _sampleRate;
617
625
  this._logger = logger;
618
626
  }
619
627
  /**
620
- * Gets the evaluation metric key, prioritizing evaluationMetricKey over evaluationMetricKeys.
621
- * Falls back to the first valid (non-empty, non-whitespace) value in evaluationMetricKeys if evaluationMetricKey is not provided.
628
+ * The default sampling rate baked in at construction. Used by `evaluate` /
629
+ * `evaluateMessages` when no per-call rate is supplied.
630
+ */
631
+ get sampleRate() {
632
+ return this._sampleRate;
633
+ }
634
+ /**
635
+ * Gets the evaluation metric key from the judge AI config.
622
636
  * Treats empty strings and whitespace-only strings as invalid.
623
637
  * @returns The evaluation metric key, or undefined if not available
624
638
  */
@@ -626,12 +640,6 @@ var Judge = class {
626
640
  if (this._aiConfig.evaluationMetricKey && this._aiConfig.evaluationMetricKey.trim().length > 0) {
627
641
  return this._aiConfig.evaluationMetricKey.trim();
628
642
  }
629
- if (this._aiConfig.evaluationMetricKeys && this._aiConfig.evaluationMetricKeys.length > 0) {
630
- const validKey = this._aiConfig.evaluationMetricKeys.find(
631
- (key) => key && key.trim().length > 0
632
- );
633
- return validKey ? validKey.trim() : void 0;
634
- }
635
643
  return void 0;
636
644
  }
637
645
  /**
@@ -639,10 +647,13 @@ var Judge = class {
639
647
  *
640
648
  * @param input The input prompt or question that was provided to the AI
641
649
  * @param output The AI-generated response to be evaluated
642
- * @param samplingRate Sampling rate (0-1) to determine if evaluation should be processed (defaults to 1)
650
+ * @param samplingRate Sampling rate (0-1) to determine if evaluation should be processed.
651
+ * When omitted, the Judge's constructor-default rate is used. An explicit `0` overrides
652
+ * the default — only `undefined` falls through.
643
653
  * @returns Promise that resolves to evaluation results
644
654
  */
645
- async evaluate(input, output, samplingRate = 1) {
655
+ async evaluate(input, output, samplingRate) {
656
+ const effectiveRate = samplingRate ?? this._sampleRate;
646
657
  const result = {
647
658
  success: false,
648
659
  sampled: false,
@@ -660,26 +671,20 @@ var Judge = class {
660
671
  result.errorMessage = "Judge configuration is missing required evaluation metric key";
661
672
  return result;
662
673
  }
663
- if (!this._aiConfig.messages) {
664
- this._logger?.warn("Judge configuration must include messages", tracker.getTrackData());
665
- result.sampled = true;
666
- result.errorMessage = "Judge configuration must include messages";
667
- return result;
668
- }
669
- if (Math.random() > samplingRate) {
670
- this._logger?.debug(`Judge evaluation skipped due to sampling rate: ${samplingRate}`);
674
+ if (Math.random() > effectiveRate) {
675
+ this._logger?.debug(`Judge evaluation skipped due to sampling rate: ${effectiveRate}`);
671
676
  return result;
672
677
  }
673
678
  result.sampled = true;
674
- const messages = this._constructEvaluationMessages(input, output);
679
+ const evaluationInput = this._buildEvaluationInput(input, output);
675
680
  const response = await tracker.trackMetricsOf(
676
681
  (r) => r.metrics,
677
- () => this._aiProvider.invokeStructuredModel(messages, EVALUATION_SCHEMA)
682
+ () => this._runner.run(evaluationInput, EVALUATION_SCHEMA)
678
683
  );
679
- const evalResult = this._parseEvaluationResponse(response.data);
684
+ const evalResult = this._parseEvaluationResponse(response.parsed);
680
685
  if (!evalResult) {
681
686
  this._logger?.warn(
682
- `Could not parse evaluation response: ${JSON.stringify(response.data)}`,
687
+ `Could not parse evaluation response: ${JSON.stringify(response.parsed)}`,
683
688
  tracker.getTrackData()
684
689
  );
685
690
  return result;
@@ -699,16 +704,21 @@ var Judge = class {
699
704
  }
700
705
  }
701
706
  /**
702
- * Evaluates an AI response from chat messages and response.
707
+ * Evaluates an AI response from chat messages and a runner result.
708
+ *
709
+ * Each message is rendered as `<role>: <content>` so the judge model can
710
+ * distinguish speakers in the message history. Messages are joined with a
711
+ * single newline.
703
712
  *
704
713
  * @param messages Array of messages representing the conversation history
705
- * @param response The AI response to be evaluated
706
- * @param samplingRatio Sampling ratio (0-1) to determine if evaluation should be processed (defaults to 1)
714
+ * @param response The runner result containing the AI-generated content to evaluate
715
+ * @param samplingRatio Sampling ratio (0-1). When omitted, the Judge's
716
+ * constructor-default rate is used.
707
717
  * @returns Promise that resolves to evaluation results
708
718
  */
709
- async evaluateMessages(messages, response, samplingRatio = 1) {
710
- const input = messages.length === 0 ? "" : messages.map((msg) => msg.content).join("\r\n");
711
- const output = response.message.content;
719
+ async evaluateMessages(messages, response, samplingRatio) {
720
+ const input = messages.length === 0 ? "" : messages.map((msg) => `${msg.role}: ${msg.content}`).join("\n");
721
+ const output = response.content;
712
722
  return this.evaluate(input, output, samplingRatio);
713
723
  }
714
724
  /**
@@ -718,29 +728,23 @@ var Judge = class {
718
728
  return this._aiConfig;
719
729
  }
720
730
  /**
721
- * Returns the AI provider used by this judge.
731
+ * Returns the runner used by this judge.
722
732
  */
723
- getProvider() {
724
- return this._aiProvider;
725
- }
726
- /**
727
- * Constructs evaluation messages by combining judge's config messages with input/output.
728
- */
729
- _constructEvaluationMessages(input, output) {
730
- const messages = this._aiConfig.messages.map((msg) => ({
731
- ...msg,
732
- content: this._interpolateMessage(msg.content, {
733
- message_history: input,
734
- response_to_evaluate: output
735
- })
736
- }));
737
- return messages;
733
+ getRunner() {
734
+ return this._runner;
738
735
  }
739
736
  /**
740
- * Interpolates message content with variables using Mustache templating.
737
+ * Builds the evaluation input string passed to the runner.
738
+ *
739
+ * Combines the original prompt and the response into a single, well-known
740
+ * format the judge model is expected to evaluate.
741
741
  */
742
- _interpolateMessage(content, variables) {
743
- return import_mustache.default.render(content, variables, void 0, { escape: (item) => item });
742
+ _buildEvaluationInput(input, output) {
743
+ return `MESSAGE HISTORY:
744
+ ${input}
745
+
746
+ RESPONSE TO EVALUATE:
747
+ ${output}`;
744
748
  }
745
749
  /**
746
750
  * Parses the structured evaluation response. Expects top-level {score, reasoning}.
@@ -766,115 +770,105 @@ var Judge = class {
766
770
  // src/api/providers/AIProvider.ts
767
771
  var AIProvider = class {
768
772
  constructor(logger) {
769
- this.logger = logger;
773
+ this._logger = logger;
770
774
  }
771
775
  /**
772
- * Invoke the chat model with an array of messages.
773
- * This method should convert messages to provider format, invoke the model,
774
- * and return a ChatResponse with the result and metrics.
776
+ * Create a Runner for a completion or judge AI Config.
775
777
  *
776
- * Default implementation takes no action and returns a placeholder response.
777
- * Provider implementations should override this method.
778
+ * Override in provider subclasses to return a configured {@link Runner}.
779
+ * Default implementation returns `undefined`.
778
780
  *
779
- * @param messages Array of LDMessage objects representing the conversation
780
- * @returns Promise that resolves to a ChatResponse containing the model's response
781
+ * @param config The completion or judge AI configuration.
782
+ * @param multiTurn Whether the runner should accumulate conversation history
783
+ * across successive `run()` calls. Defaults to `true` (chat semantics).
784
+ * Pass `false` for stateless runners such as judges where each call must
785
+ * start from the initial config messages.
786
+ * @returns Promise resolving to a {@link Runner}, or `undefined` if this
787
+ * provider does not support model creation.
781
788
  */
782
- async invokeModel(_messages) {
783
- this.logger?.warn("invokeModel not implemented by this provider");
784
- return {
785
- message: {
786
- role: "assistant",
787
- content: ""
788
- },
789
- metrics: {
790
- success: false,
791
- usage: {
792
- total: 0,
793
- input: 0,
794
- output: 0
795
- }
796
- }
797
- };
789
+ async createModel(_config, _multiTurn = true) {
790
+ return void 0;
798
791
  }
799
792
  /**
800
- * Invoke the chat model with structured output support.
801
- * This method should convert messages to provider format, invoke the model with
802
- * structured output configuration, and return a structured response.
793
+ * Create a Runner for an agent AI Config.
803
794
  *
804
- * Default implementation takes no action and returns a placeholder response.
805
- * Provider implementations should override this method.
795
+ * Override in provider subclasses to return a configured {@link Runner}.
796
+ * Default implementation returns `undefined`.
806
797
  *
807
- * @param messages Array of LDMessage objects representing the conversation
808
- * @param responseStructure Dictionary of output configurations keyed by output name
809
- * @returns Promise that resolves to a structured response
798
+ * @param config The agent AI configuration.
799
+ * @param tools Optional registry of callable tools.
800
+ * @returns Promise resolving to a {@link Runner}, or `undefined` if this
801
+ * provider does not support agent creation.
810
802
  */
811
- async invokeStructuredModel(_messages, _responseStructure) {
812
- this.logger?.warn("invokeStructuredModel not implemented by this provider");
813
- return {
814
- data: {},
815
- rawResponse: "",
816
- metrics: {
817
- success: false,
818
- usage: {
819
- total: 0,
820
- input: 0,
821
- output: 0
822
- }
823
- }
824
- };
803
+ async createAgent(_config, _tools) {
804
+ return void 0;
825
805
  }
826
806
  /**
827
- * Static method that constructs an instance of the provider.
828
- * Each provider implementation must provide their own static create method
829
- * that accepts an AIConfig and returns a configured instance.
807
+ * Create an AgentGraphRunner for an agent graph definition.
808
+ *
809
+ * Override in provider subclasses to return a configured {@link AgentGraphRunner}.
810
+ * Default implementation returns `undefined`.
830
811
  *
831
- * @param aiConfig The LaunchDarkly AI configuration
832
- * @param logger Optional logger for the provider
833
- * @returns Promise that resolves to a configured provider instance
812
+ * @param graphDef The agent graph definition.
813
+ * @param tools Optional registry of callable tools.
814
+ * @returns Promise resolving to an {@link AgentGraphRunner}, or `undefined` if
815
+ * this provider does not support graph execution.
834
816
  */
835
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
836
- static async create(aiConfig, logger) {
837
- throw new Error("Provider implementations must override the static create method");
817
+ async createAgentGraph(_graphDef, _tools) {
818
+ return void 0;
838
819
  }
839
820
  };
840
821
 
841
- // src/api/providers/AIProviderFactory.ts
822
+ // src/api/providers/RunnerFactory.ts
842
823
  var SUPPORTED_AI_PROVIDERS = [
843
824
  "openai",
844
825
  // Multi-provider packages should be last in the list
845
826
  "langchain",
846
827
  "vercel"
847
828
  ];
848
- var AIProviderFactory = class {
829
+ var RunnerFactory = class _RunnerFactory {
849
830
  /**
850
- * Create an AIProvider instance based on the AI configuration.
851
- * This method attempts to load provider-specific implementations dynamically.
852
- * Returns undefined if the provider is not supported.
831
+ * Load and return the AIProvider factory for the given provider type.
853
832
  *
854
- * @param aiConfig The AI configuration
855
- * @param logger Optional logger for logging provider initialization
856
- * @param defaultAiProvider Optional default AI provider to use
833
+ * This is the single place in the codebase that knows provider package names.
834
+ * Each supported provider package exports a `*RunnerFactory` class that
835
+ * extends {@link AIProvider}; this method instantiates it directly.
836
+ *
837
+ * @param providerType One of the {@link SUPPORTED_AI_PROVIDERS} values.
838
+ * @param logger Optional logger forwarded to the provider factory.
839
+ * @returns A configured {@link AIProvider} instance, or `undefined` if the
840
+ * package cannot be loaded.
857
841
  */
858
- static async create(aiConfig, logger, defaultAiProvider) {
859
- const providerName = aiConfig.provider?.name?.toLowerCase();
860
- const providersToTry = this._getProvidersToTry(defaultAiProvider, providerName);
861
- for (const providerType of providersToTry) {
862
- logger?.debug(
863
- `Attempting to create AIProvider for: ${aiConfig.provider?.name} with provider type: ${providerType}`
864
- );
865
- const provider = await this._tryCreateProvider(providerType, aiConfig, logger);
866
- if (provider) {
867
- logger?.debug(`Successfully created AIProvider for: ${aiConfig.provider?.name}`);
868
- return provider;
842
+ static async _getProviderFactory(providerType, logger) {
843
+ try {
844
+ let module2;
845
+ switch (providerType) {
846
+ case "openai": {
847
+ module2 = await import("@launchdarkly/server-sdk-ai-openai");
848
+ return new module2.OpenAIRunnerFactory(logger);
849
+ }
850
+ case "langchain": {
851
+ module2 = await import("@launchdarkly/server-sdk-ai-langchain");
852
+ return new module2.LangChainRunnerFactory(logger);
853
+ }
854
+ case "vercel": {
855
+ module2 = await import("@launchdarkly/server-sdk-ai-vercel");
856
+ return new module2.VercelRunnerFactory(logger);
857
+ }
858
+ default:
859
+ return void 0;
869
860
  }
861
+ } catch (error) {
862
+ logger?.warn(
863
+ `Unable to load provider package. Check that you have installed the correct package. ${error.message}`
864
+ );
865
+ return void 0;
870
866
  }
871
- logger?.warn(
872
- `Provider is not supported or failed to initialize: ${aiConfig.provider?.name ?? "unknown"}`
873
- );
874
- return void 0;
875
867
  }
876
868
  /**
877
869
  * Determine which providers to try based on defaultAiProvider and providerName.
870
+ *
871
+ * Mirrors Python's `_get_providers_to_try` helper.
878
872
  */
879
873
  static _getProvidersToTry(defaultAiProvider, providerName) {
880
874
  if (defaultAiProvider) {
@@ -891,57 +885,118 @@ var AIProviderFactory = class {
891
885
  return Array.from(providerSet);
892
886
  }
893
887
  /**
894
- * Try to create a provider of the specified type.
888
+ * Try each provider in order and return the first non-undefined result.
889
+ *
890
+ * Mirrors Python's `_with_fallback` helper. Loads each provider factory via
891
+ * {@link _getProviderFactory} and calls `fn` with it. Returns the first
892
+ * truthy result, or `undefined` if no provider succeeds.
893
+ *
894
+ * @param providers Ordered list of provider types to try.
895
+ * @param fn Callback that calls the appropriate factory method on the provider.
896
+ * @param logger Optional logger forwarded to each provider factory.
895
897
  */
896
- static async _tryCreateProvider(providerType, aiConfig, logger) {
897
- try {
898
- let module2;
899
- switch (providerType) {
900
- case "openai": {
901
- module2 = await import("@launchdarkly/server-sdk-ai-openai");
902
- const provider = await module2.OpenAIProvider.create(aiConfig, logger);
903
- return provider;
904
- }
905
- case "langchain": {
906
- module2 = await import("@launchdarkly/server-sdk-ai-langchain");
907
- const provider = await module2.LangChainProvider.create(aiConfig, logger);
908
- return provider;
898
+ static async _withFallback(providers, fn, logger) {
899
+ for (const providerType of providers) {
900
+ logger?.debug(`Attempting to create runner with provider: ${providerType}`);
901
+ const factory = await _RunnerFactory._getProviderFactory(providerType, logger);
902
+ if (factory) {
903
+ const result = await fn(factory);
904
+ if (result) {
905
+ logger?.debug(`Successfully created runner with provider: ${providerType}`);
906
+ return result;
909
907
  }
910
- case "vercel": {
911
- module2 = await import("@launchdarkly/server-sdk-ai-vercel");
912
- const provider = await module2.VercelProvider.create(aiConfig, logger);
913
- return provider;
914
- }
915
- default:
916
- return void 0;
917
908
  }
918
- } catch (error) {
909
+ }
910
+ return void 0;
911
+ }
912
+ /**
913
+ * Create a Runner for the given AI configuration.
914
+ *
915
+ * Suitable for completion, judge, and agent config modes. Dynamically
916
+ * loads the matching provider package via {@link _getProviderFactory} and
917
+ * delegates to its {@link AIProvider.createModel} method.
918
+ *
919
+ * @param config The AI configuration (completion, agent, or judge).
920
+ * @param logger Optional logger forwarded to the underlying provider.
921
+ * @param defaultAiProvider Optional provider override
922
+ * ('openai', 'langchain', 'vercel', …). When set, only that provider is
923
+ * tried. When omitted, providers are tried in priority order based on the
924
+ * provider name in the config.
925
+ * @param multiTurn Whether the runner should accumulate conversation history
926
+ * across successive `run()` calls. Defaults to `true` (chat semantics).
927
+ * Judges pass `false` so each evaluation starts from the initial config
928
+ * messages.
929
+ * @returns A configured {@link Runner} ready to invoke the model, or
930
+ * `undefined` if no suitable provider could be loaded.
931
+ */
932
+ static async createModel(config, logger, defaultAiProvider, multiTurn = true) {
933
+ const providerName = config.provider?.name?.toLowerCase();
934
+ const providers = _RunnerFactory._getProvidersToTry(defaultAiProvider, providerName);
935
+ const runner = await _RunnerFactory._withFallback(
936
+ providers,
937
+ (factory) => factory.createModel(config, multiTurn),
938
+ logger
939
+ );
940
+ if (!runner) {
919
941
  logger?.warn(
920
- `Unable to create AIProvider. Check that you have installed the correct package. ${error.message}`
942
+ `Provider is not supported or failed to initialize: ${config.provider?.name ?? "unknown"}`
921
943
  );
922
- return void 0;
923
944
  }
945
+ return runner;
946
+ }
947
+ /**
948
+ * Create a Runner for an agent AI Config.
949
+ *
950
+ * Delegates to the provider factory's {@link AIProvider.createAgent} method.
951
+ *
952
+ * @param config The agent AI configuration.
953
+ * @param tools Optional registry of callable tools.
954
+ * @param logger Optional logger forwarded to the underlying provider.
955
+ * @param defaultAiProvider Optional provider override.
956
+ * @returns A configured {@link Runner}, or `undefined` if no suitable
957
+ * provider could be loaded.
958
+ */
959
+ static async createAgent(config, tools, logger, defaultAiProvider) {
960
+ const providerName = config.provider?.name?.toLowerCase();
961
+ const providers = _RunnerFactory._getProvidersToTry(defaultAiProvider, providerName);
962
+ const runner = await _RunnerFactory._withFallback(
963
+ providers,
964
+ (factory) => factory.createAgent(config, tools),
965
+ logger
966
+ );
967
+ if (!runner) {
968
+ logger?.warn(
969
+ `Provider is not supported or failed to initialize: ${config.provider?.name ?? "unknown"}`
970
+ );
971
+ }
972
+ return runner;
973
+ }
974
+ /**
975
+ * Create an AgentGraphRunner for the given agent graph definition.
976
+ *
977
+ * Delegates to the provider factory's {@link AIProvider.createAgentGraph} method.
978
+ *
979
+ * @param graphDef The agent graph definition.
980
+ * @param tools Optional registry of callable tools.
981
+ * @param logger Optional logger forwarded to the underlying provider.
982
+ * @param defaultAiProvider Optional provider override.
983
+ * @returns A configured {@link AgentGraphRunner}, or `undefined` if no
984
+ * suitable provider could be loaded.
985
+ */
986
+ static async createAgentGraph(graphDef, tools, logger, defaultAiProvider) {
987
+ const providers = _RunnerFactory._getProvidersToTry(defaultAiProvider);
988
+ const runner = await _RunnerFactory._withFallback(
989
+ providers,
990
+ (factory) => factory.createAgentGraph(graphDef, tools),
991
+ logger
992
+ );
993
+ if (!runner) {
994
+ logger?.warn(`No provider could create an AgentGraphRunner for the given graph definition.`);
995
+ }
996
+ return runner;
924
997
  }
925
998
  };
926
999
 
927
- // src/api/metrics/BedrockTokenUsage.ts
928
- function createBedrockTokenUsage(data) {
929
- return {
930
- total: data.totalTokens || 0,
931
- input: data.inputTokens || 0,
932
- output: data.outputTokens || 0
933
- };
934
- }
935
-
936
- // src/api/metrics/OpenAiUsage.ts
937
- function createOpenAiUsage(data) {
938
- return {
939
- total: data.total_tokens ?? 0,
940
- input: data.prompt_tokens ?? 0,
941
- output: data.completion_tokens ?? 0
942
- };
943
- }
944
-
945
1000
  // src/api/metrics/LDFeedbackKind.ts
946
1001
  var LDFeedbackKind = /* @__PURE__ */ ((LDFeedbackKind2) => {
947
1002
  LDFeedbackKind2["Positive"] = "positive";
@@ -949,15 +1004,6 @@ var LDFeedbackKind = /* @__PURE__ */ ((LDFeedbackKind2) => {
949
1004
  return LDFeedbackKind2;
950
1005
  })(LDFeedbackKind || {});
951
1006
 
952
- // src/api/metrics/VercelAISDKTokenUsage.ts
953
- function createVercelAISDKTokenUsage(data) {
954
- return {
955
- total: data.totalTokens ?? 0,
956
- input: data.inputTokens ?? data.promptTokens ?? 0,
957
- output: data.outputTokens ?? data.completionTokens ?? 0
958
- };
959
- }
960
-
961
1007
  // src/LDAIConfigTrackerImpl.ts
962
1008
  var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
963
1009
  constructor(_ldClient, _runId, _configKey, _variationKey, _version, _modelName, _providerName, _context, _graphKey) {
@@ -971,6 +1017,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
971
1017
  this._context = _context;
972
1018
  this._graphKey = _graphKey;
973
1019
  this._trackedMetrics = {};
1020
+ this._trackedMetrics.resumptionToken = this.resumptionToken;
974
1021
  }
975
1022
  getTrackData() {
976
1023
  return {
@@ -1011,7 +1058,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1011
1058
  trackDuration(duration) {
1012
1059
  if (this._trackedMetrics.durationMs !== void 0) {
1013
1060
  this._ldClient.logger?.warn(
1014
- "Duration has already been tracked for this execution. Use createTracker() for a new execution."
1061
+ "Skipping trackDuration: duration already recorded on this tracker. Call createTracker on the AI Config for a new run."
1015
1062
  );
1016
1063
  return;
1017
1064
  }
@@ -1032,7 +1079,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1032
1079
  trackTimeToFirstToken(timeToFirstTokenMs) {
1033
1080
  if (this._trackedMetrics.timeToFirstTokenMs !== void 0) {
1034
1081
  this._ldClient.logger?.warn(
1035
- "Time to first token has already been tracked for this execution. Use createTracker() for a new execution."
1082
+ "Skipping trackTimeToFirstToken: time-to-first-token already recorded on this tracker. Call createTracker on the AI Config for a new run."
1036
1083
  );
1037
1084
  return;
1038
1085
  }
@@ -1054,6 +1101,10 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1054
1101
  }
1055
1102
  }
1056
1103
  trackToolCall(toolKey) {
1104
+ if (!this._trackedMetrics.toolCalls) {
1105
+ this._trackedMetrics.toolCalls = [];
1106
+ }
1107
+ this._trackedMetrics.toolCalls.push(toolKey);
1057
1108
  this._ldClient.track("$ld:ai:tool_call", this._context, { ...this.getTrackData(), toolKey }, 1);
1058
1109
  }
1059
1110
  trackToolCalls(toolKeys) {
@@ -1064,7 +1115,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1064
1115
  trackFeedback(feedback) {
1065
1116
  if (this._trackedMetrics.feedback !== void 0) {
1066
1117
  this._ldClient.logger?.warn(
1067
- "Feedback has already been tracked for this execution. Use createTracker() for a new execution."
1118
+ "Skipping trackFeedback: feedback already recorded on this tracker. Call createTracker on the AI Config for a new run."
1068
1119
  );
1069
1120
  return;
1070
1121
  }
@@ -1078,7 +1129,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1078
1129
  trackSuccess() {
1079
1130
  if (this._trackedMetrics.success !== void 0) {
1080
1131
  this._ldClient.logger?.warn(
1081
- "Generation result has already been tracked for this execution. Use createTracker() for a new execution."
1132
+ "Skipping trackSuccess: success/error already recorded on this tracker. Call createTracker on the AI Config for a new run."
1082
1133
  );
1083
1134
  return;
1084
1135
  }
@@ -1088,7 +1139,7 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1088
1139
  trackError() {
1089
1140
  if (this._trackedMetrics.success !== void 0) {
1090
1141
  this._ldClient.logger?.warn(
1091
- "Generation result has already been tracked for this execution. Use createTracker() for a new execution."
1142
+ "Skipping trackError: success/error already recorded on this tracker. Call createTracker on the AI Config for a new run."
1092
1143
  );
1093
1144
  return;
1094
1145
  }
@@ -1109,8 +1160,11 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1109
1160
  } else {
1110
1161
  this.trackError();
1111
1162
  }
1112
- if (metrics.usage) {
1113
- this.trackTokens(metrics.usage);
1163
+ if (metrics.tokens) {
1164
+ this.trackTokens(metrics.tokens);
1165
+ }
1166
+ if (metrics.toolCalls?.length) {
1167
+ this.trackToolCalls(metrics.toolCalls);
1114
1168
  }
1115
1169
  return result;
1116
1170
  }
@@ -1134,8 +1188,11 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1134
1188
  } else {
1135
1189
  this.trackError();
1136
1190
  }
1137
- if (metrics.usage) {
1138
- this.trackTokens(metrics.usage);
1191
+ if (metrics.tokens) {
1192
+ this.trackTokens(metrics.tokens);
1193
+ }
1194
+ if (metrics.toolCalls?.length) {
1195
+ this.trackToolCalls(metrics.toolCalls);
1139
1196
  }
1140
1197
  } catch (error) {
1141
1198
  this.trackError();
@@ -1143,50 +1200,10 @@ var LDAIConfigTrackerImpl = class _LDAIConfigTrackerImpl {
1143
1200
  this.trackDuration(Date.now() - startTime);
1144
1201
  }
1145
1202
  }
1146
- async trackOpenAIMetrics(func) {
1147
- try {
1148
- const result = await this.trackDurationOf(func);
1149
- this.trackSuccess();
1150
- if (result.usage) {
1151
- this.trackTokens(createOpenAiUsage(result.usage));
1152
- }
1153
- return result;
1154
- } catch (err) {
1155
- this.trackError();
1156
- throw err;
1157
- }
1158
- }
1159
- trackBedrockConverseMetrics(res) {
1160
- if (res.$metadata?.httpStatusCode === 200) {
1161
- this.trackSuccess();
1162
- } else if (res.$metadata?.httpStatusCode && res.$metadata.httpStatusCode >= 400) {
1163
- this.trackError();
1164
- }
1165
- if (res.metrics && res.metrics.latencyMs) {
1166
- this.trackDuration(res.metrics.latencyMs);
1167
- }
1168
- if (res.usage) {
1169
- this.trackTokens(createBedrockTokenUsage(res.usage));
1170
- }
1171
- return res;
1172
- }
1173
- async trackVercelAISDKGenerateTextMetrics(func) {
1174
- try {
1175
- const result = await this.trackDurationOf(func);
1176
- this.trackSuccess();
1177
- if (result.usage) {
1178
- this.trackTokens(createVercelAISDKTokenUsage(result.usage));
1179
- }
1180
- return result;
1181
- } catch (err) {
1182
- this.trackError();
1183
- throw err;
1184
- }
1185
- }
1186
1203
  trackTokens(tokens) {
1187
1204
  if (this._trackedMetrics.tokens !== void 0) {
1188
1205
  this._ldClient.logger?.warn(
1189
- "Token usage has already been tracked for this execution. Use createTracker() for a new execution."
1206
+ "Skipping trackTokens: token usage already recorded on this tracker. Call createTracker on the AI Config for a new run."
1190
1207
  );
1191
1208
  return;
1192
1209
  }
@@ -1220,6 +1237,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1220
1237
  this._version = _version;
1221
1238
  this._context = _context;
1222
1239
  this._summary = {};
1240
+ this._summary.resumptionToken = this.resumptionToken;
1223
1241
  }
1224
1242
  /**
1225
1243
  * Reconstructs an {@link LDGraphTrackerImpl} from a resumption token, preserving
@@ -1245,15 +1263,12 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1245
1263
  );
1246
1264
  }
1247
1265
  getTrackData() {
1248
- const data = {
1266
+ return {
1249
1267
  runId: this._runId,
1250
1268
  graphKey: this._graphKey,
1251
- version: this._version
1269
+ version: this._version,
1270
+ ...this._variationKey !== void 0 ? { variationKey: this._variationKey } : {}
1252
1271
  };
1253
- if (this._variationKey !== void 0) {
1254
- data.variationKey = this._variationKey;
1255
- }
1256
- return data;
1257
1272
  }
1258
1273
  getSummary() {
1259
1274
  return { ...this._summary };
@@ -1273,7 +1288,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1273
1288
  trackInvocationSuccess() {
1274
1289
  if (this._summary.success !== void 0) {
1275
1290
  this._ldClient.logger?.warn(
1276
- "LDGraphTracker: invocation success/failure already recorded for this run \u2014 dropping duplicate call."
1291
+ "Skipping trackInvocationSuccess: invocation result already recorded on this graph tracker. Call createTracker on the agent graph for a new run."
1277
1292
  );
1278
1293
  return;
1279
1294
  }
@@ -1283,7 +1298,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1283
1298
  trackInvocationFailure() {
1284
1299
  if (this._summary.success !== void 0) {
1285
1300
  this._ldClient.logger?.warn(
1286
- "LDGraphTracker: invocation success/failure already recorded for this run \u2014 dropping duplicate call."
1301
+ "Skipping trackInvocationFailure: invocation result already recorded on this graph tracker. Call createTracker on the agent graph for a new run."
1287
1302
  );
1288
1303
  return;
1289
1304
  }
@@ -1293,7 +1308,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1293
1308
  trackDuration(durationMs) {
1294
1309
  if (this._summary.durationMs !== void 0) {
1295
1310
  this._ldClient.logger?.warn(
1296
- "LDGraphTracker: trackDuration already called for this run \u2014 dropping duplicate call."
1311
+ "Skipping trackDuration: duration already recorded on this graph tracker. Call createTracker on the agent graph for a new run."
1297
1312
  );
1298
1313
  return;
1299
1314
  }
@@ -1308,7 +1323,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1308
1323
  trackTotalTokens(tokens) {
1309
1324
  if (this._summary.tokens !== void 0) {
1310
1325
  this._ldClient.logger?.warn(
1311
- "LDGraphTracker: trackTotalTokens already called for this run \u2014 dropping duplicate call."
1326
+ "Skipping trackTotalTokens: tokens already recorded on this graph tracker. Call createTracker on the agent graph for a new run."
1312
1327
  );
1313
1328
  return;
1314
1329
  }
@@ -1323,7 +1338,7 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1323
1338
  trackPath(path) {
1324
1339
  if (this._summary.path !== void 0) {
1325
1340
  this._ldClient.logger?.warn(
1326
- "LDGraphTracker: trackPath already called for this run \u2014 dropping duplicate call."
1341
+ "Skipping trackPath: path already recorded on this graph tracker. Call createTracker on the agent graph for a new run."
1327
1342
  );
1328
1343
  return;
1329
1344
  }
@@ -1358,13 +1373,14 @@ var LDGraphTrackerImpl = class _LDGraphTrackerImpl {
1358
1373
 
1359
1374
  // src/sdkInfo.ts
1360
1375
  var aiSdkName = "@launchdarkly/server-sdk-ai";
1361
- var aiSdkVersion = "0.19.1";
1376
+ var aiSdkVersion = "1.0.0";
1362
1377
  var aiSdkLanguage = "javascript";
1363
1378
 
1364
1379
  // src/LDAIClientImpl.ts
1365
1380
  var TRACK_SDK_INFO = "$ld:ai:sdk:info";
1366
1381
  var TRACK_USAGE_COMPLETION_CONFIG = "$ld:ai:usage:completion-config";
1367
1382
  var TRACK_USAGE_CREATE_CHAT = "$ld:ai:usage:create-chat";
1383
+ var TRACK_USAGE_CREATE_AGENT = "$ld:ai:usage:create-agent";
1368
1384
  var TRACK_USAGE_JUDGE_CONFIG = "$ld:ai:usage:judge-config";
1369
1385
  var TRACK_USAGE_CREATE_JUDGE = "$ld:ai:usage:create-judge";
1370
1386
  var TRACK_USAGE_AGENT_CONFIG = "$ld:ai:usage:agent-config";
@@ -1392,18 +1408,11 @@ var LDAIClientImpl = class {
1392
1408
  );
1393
1409
  }
1394
1410
  _interpolateTemplate(template, variables) {
1395
- return import_mustache2.default.render(template, variables, void 0, { escape: (item) => item });
1411
+ return import_mustache.default.render(template, variables, void 0, { escape: (item) => item });
1396
1412
  }
1397
- async _evaluate(key, context, defaultValue, mode, variables, graphKey) {
1413
+ async _evaluate(key, context, defaultValue, mode, variables, graphKey, defaultAiProvider) {
1398
1414
  const ldFlagValue = LDAIConfigUtils.toFlagValue(defaultValue, mode);
1399
1415
  const value = await this._ldClient.variation(key, context, ldFlagValue);
1400
- const flagMode = value._ldMeta?.mode ?? "completion";
1401
- if (flagMode !== mode) {
1402
- this._logger?.warn(
1403
- `AI Config mode mismatch for ${key}: expected ${mode}, got ${flagMode}. Returning disabled config.`
1404
- );
1405
- return LDAIConfigUtils.createDisabledConfig(key, mode);
1406
- }
1407
1416
  const trackerFactory = () => new LDAIConfigTrackerImpl(
1408
1417
  this._ldClient,
1409
1418
  (0, import_node_crypto.randomUUID)(),
@@ -1417,7 +1426,23 @@ var LDAIClientImpl = class {
1417
1426
  context,
1418
1427
  graphKey
1419
1428
  );
1420
- const config = LDAIConfigUtils.fromFlagValue(key, value, trackerFactory);
1429
+ const flagMode = value._ldMeta?.mode ?? "completion";
1430
+ let evaluator = Evaluator.noop();
1431
+ if (flagMode !== mode) {
1432
+ this._logger?.warn(
1433
+ `AI Config mode mismatch for ${key}: expected ${mode}, got ${flagMode}. Returning disabled config.`
1434
+ );
1435
+ return LDAIConfigUtils.createDisabledConfig(key, mode, trackerFactory, evaluator);
1436
+ }
1437
+ if (flagMode !== "judge") {
1438
+ evaluator = await this._buildEvaluator(
1439
+ value.judgeConfiguration?.judges ?? [],
1440
+ context,
1441
+ variables,
1442
+ defaultAiProvider
1443
+ );
1444
+ }
1445
+ const config = LDAIConfigUtils.fromFlagValue(key, value, trackerFactory, evaluator);
1421
1446
  return this._applyInterpolation(config, context, variables);
1422
1447
  }
1423
1448
  _applyInterpolation(config, context, variables) {
@@ -1439,61 +1464,98 @@ var LDAIClientImpl = class {
1439
1464
  }
1440
1465
  return config;
1441
1466
  }
1442
- async _initializeJudges(judgeConfigs, context, variables, defaultAiProvider) {
1443
- const judges = {};
1444
- const judgePromises = judgeConfigs.map(async (judgeConfig) => {
1445
- const judge = await this.createJudge(
1446
- judgeConfig.key,
1447
- context,
1448
- void 0,
1449
- variables,
1450
- defaultAiProvider
1451
- );
1452
- return judge ? { key: judgeConfig.key, judge } : null;
1453
- });
1454
- const results = await Promise.all(judgePromises);
1455
- results.forEach((result) => {
1456
- if (result) {
1457
- judges[result.key] = result.judge;
1458
- }
1459
- });
1460
- return judges;
1461
- }
1462
- async _completionConfig(key, context, defaultValue, variables) {
1463
- const config = await this._evaluate(key, context, defaultValue, "completion", variables);
1464
- return config;
1467
+ async _buildEvaluator(judgeConfigs, context, variables, defaultAiProvider) {
1468
+ if (judgeConfigs.length === 0) {
1469
+ return Evaluator.noop();
1470
+ }
1471
+ const judgeInstances = (await Promise.all(
1472
+ judgeConfigs.map(
1473
+ (jc) => this._createJudgeInstance(
1474
+ jc.key,
1475
+ context,
1476
+ void 0,
1477
+ variables,
1478
+ defaultAiProvider,
1479
+ jc.samplingRate
1480
+ )
1481
+ )
1482
+ )).filter((j) => j !== void 0);
1483
+ return new Evaluator(judgeInstances);
1484
+ }
1485
+ async _completionConfig(key, context, defaultValue, variables, defaultAiProvider) {
1486
+ return await this._evaluate(
1487
+ key,
1488
+ context,
1489
+ defaultValue,
1490
+ "completion",
1491
+ variables,
1492
+ void 0,
1493
+ defaultAiProvider
1494
+ );
1465
1495
  }
1466
- async completionConfig(key, context, defaultValue, variables) {
1496
+ async completionConfig(key, context, defaultValue, variables, defaultAiProvider) {
1467
1497
  this._ldClient.track(TRACK_USAGE_COMPLETION_CONFIG, context, key, 1);
1468
- return this._completionConfig(key, context, defaultValue ?? disabledAIConfig, variables);
1469
- }
1470
- /**
1471
- * @deprecated Use `completionConfig` instead. This method will be removed in a future version.
1472
- */
1473
- async config(key, context, defaultValue, variables) {
1474
- return this.completionConfig(key, context, defaultValue, variables);
1498
+ return this._completionConfig(
1499
+ key,
1500
+ context,
1501
+ defaultValue ?? disabledAIConfig,
1502
+ variables,
1503
+ defaultAiProvider
1504
+ );
1475
1505
  }
1476
1506
  async _judgeConfig(key, context, defaultValue, variables) {
1477
- const config = await this._evaluate(key, context, defaultValue, "judge", variables);
1507
+ if (variables?.message_history !== void 0) {
1508
+ this._logger?.warn(
1509
+ "The variable 'message_history' is reserved by the judge and will be ignored."
1510
+ );
1511
+ }
1512
+ if (variables?.response_to_evaluate !== void 0) {
1513
+ this._logger?.warn(
1514
+ "The variable 'response_to_evaluate' is reserved by the judge and will be ignored."
1515
+ );
1516
+ }
1517
+ const extendedVariables = {
1518
+ ...variables,
1519
+ message_history: "{{message_history}}",
1520
+ response_to_evaluate: "{{response_to_evaluate}}"
1521
+ };
1522
+ const config = await this._evaluate(
1523
+ key,
1524
+ context,
1525
+ defaultValue,
1526
+ "judge",
1527
+ extendedVariables
1528
+ );
1529
+ if (config.messages) {
1530
+ return { ...config, messages: stripLegacyJudgeMessages(config.messages) };
1531
+ }
1478
1532
  return config;
1479
1533
  }
1480
1534
  async judgeConfig(key, context, defaultValue, variables) {
1481
1535
  this._ldClient.track(TRACK_USAGE_JUDGE_CONFIG, context, key, 1);
1482
1536
  return this._judgeConfig(key, context, defaultValue ?? disabledAIConfig, variables);
1483
1537
  }
1484
- async _agentConfig(key, context, defaultValue, variables, graphKey) {
1485
- const config = await this._evaluate(key, context, defaultValue, "agent", variables, graphKey);
1486
- return config;
1538
+ async _agentConfig(key, context, defaultValue, variables, graphKey, defaultAiProvider) {
1539
+ return await this._evaluate(
1540
+ key,
1541
+ context,
1542
+ defaultValue,
1543
+ "agent",
1544
+ variables,
1545
+ graphKey,
1546
+ defaultAiProvider
1547
+ );
1487
1548
  }
1488
- async agentConfig(key, context, defaultValue, variables) {
1549
+ async agentConfig(key, context, defaultValue, variables, defaultAiProvider) {
1489
1550
  this._ldClient.track(TRACK_USAGE_AGENT_CONFIG, context, key, 1);
1490
- return this._agentConfig(key, context, defaultValue ?? disabledAIConfig, variables);
1491
- }
1492
- /**
1493
- * @deprecated Use `agentConfig` instead. This method will be removed in a future version.
1494
- */
1495
- async agent(key, context, defaultValue, variables) {
1496
- return this.agentConfig(key, context, defaultValue, variables);
1551
+ return this._agentConfig(
1552
+ key,
1553
+ context,
1554
+ defaultValue ?? disabledAIConfig,
1555
+ variables,
1556
+ void 0,
1557
+ defaultAiProvider
1558
+ );
1497
1559
  }
1498
1560
  async agentConfigs(agentConfigs, context) {
1499
1561
  this._ldClient.track(
@@ -1516,79 +1578,82 @@ var LDAIClientImpl = class {
1516
1578
  );
1517
1579
  return agents;
1518
1580
  }
1519
- /**
1520
- * @deprecated Use `agentConfigs` instead. This method will be removed in a future version.
1521
- */
1522
- async agents(agentConfigs, context) {
1523
- return this.agentConfigs(agentConfigs, context);
1524
- }
1525
- async createChat(key, context, defaultValue, variables, defaultAiProvider) {
1526
- this._ldClient.track(TRACK_USAGE_CREATE_CHAT, context, key, 1);
1527
- const config = await this._completionConfig(
1581
+ async createJudge(key, context, defaultValue, variables, defaultAiProvider, sampleRate = 1) {
1582
+ this._ldClient.track(TRACK_USAGE_CREATE_JUDGE, context, key, 1);
1583
+ return this._createJudgeInstance(
1528
1584
  key,
1529
1585
  context,
1530
- defaultValue ?? disabledAIConfig,
1531
- variables
1532
- );
1533
- if (!config.enabled) {
1534
- this._logger?.info(`Chat configuration is disabled: ${key}`);
1535
- return void 0;
1536
- }
1537
- const provider = await AIProviderFactory.create(config, this._logger, defaultAiProvider);
1538
- if (!provider) {
1539
- return void 0;
1540
- }
1541
- const judges = await this._initializeJudges(
1542
- config.judgeConfiguration?.judges ?? [],
1543
- context,
1586
+ defaultValue,
1544
1587
  variables,
1545
- defaultAiProvider
1588
+ defaultAiProvider,
1589
+ sampleRate
1546
1590
  );
1547
- return new TrackedChat(config, provider, judges, this._logger);
1548
1591
  }
1549
- async createJudge(key, context, defaultValue, variables, defaultAiProvider) {
1550
- this._ldClient.track(TRACK_USAGE_CREATE_JUDGE, context, key, 1);
1592
+ async _createJudgeInstance(key, context, defaultValue, variables, defaultAiProvider, sampleRate = 1) {
1551
1593
  try {
1552
- if (variables?.message_history !== void 0) {
1553
- this._logger?.warn(
1554
- "The variable 'message_history' is reserved by the judge and will be ignored."
1555
- );
1556
- }
1557
- if (variables?.response_to_evaluate !== void 0) {
1558
- this._logger?.warn(
1559
- "The variable 'response_to_evaluate' is reserved by the judge and will be ignored."
1560
- );
1561
- }
1562
- const extendedVariables = {
1563
- ...variables,
1564
- message_history: "{{message_history}}",
1565
- response_to_evaluate: "{{response_to_evaluate}}"
1566
- };
1567
1594
  const judgeConfig = await this._judgeConfig(
1568
1595
  key,
1569
1596
  context,
1570
1597
  defaultValue ?? disabledAIConfig,
1571
- extendedVariables
1598
+ variables
1572
1599
  );
1573
1600
  if (!judgeConfig.enabled) {
1574
1601
  this._logger?.info(`Judge configuration is disabled: ${key}`);
1575
1602
  return void 0;
1576
1603
  }
1577
- const provider = await AIProviderFactory.create(judgeConfig, this._logger, defaultAiProvider);
1578
- if (!provider) {
1604
+ const runner = await RunnerFactory.createModel(
1605
+ judgeConfig,
1606
+ this._logger,
1607
+ defaultAiProvider,
1608
+ false
1609
+ );
1610
+ if (!runner) {
1579
1611
  return void 0;
1580
1612
  }
1581
- return new Judge(judgeConfig, provider, this._logger);
1613
+ return new Judge(judgeConfig, runner, sampleRate, this._logger);
1582
1614
  } catch (error) {
1583
1615
  this._logger?.error(`Failed to initialize judge ${key}:`, error);
1584
1616
  return void 0;
1585
1617
  }
1586
1618
  }
1587
- /**
1588
- * @deprecated Use `createChat` instead. This method will be removed in a future version.
1589
- */
1590
- async initChat(key, context, defaultValue, variables, defaultAiProvider) {
1591
- return this.createChat(key, context, defaultValue, variables, defaultAiProvider);
1619
+ async createModel(key, context, defaultValue, variables, defaultAiProvider) {
1620
+ this._ldClient.track(TRACK_USAGE_CREATE_CHAT, context, key, 1);
1621
+ const config = await this._completionConfig(
1622
+ key,
1623
+ context,
1624
+ defaultValue ?? disabledAIConfig,
1625
+ variables,
1626
+ defaultAiProvider
1627
+ );
1628
+ if (!config.enabled) {
1629
+ this._logger?.info(`Completion configuration is disabled: ${key}`);
1630
+ return void 0;
1631
+ }
1632
+ const runner = await RunnerFactory.createModel(config, this._logger, defaultAiProvider);
1633
+ if (!runner) {
1634
+ return void 0;
1635
+ }
1636
+ return new ManagedModel(config, runner, this._logger);
1637
+ }
1638
+ async createAgent(key, context, defaultValue, variables, defaultAiProvider) {
1639
+ this._ldClient.track(TRACK_USAGE_CREATE_AGENT, context, key, 1);
1640
+ const config = await this._agentConfig(
1641
+ key,
1642
+ context,
1643
+ defaultValue ?? disabledAIConfig,
1644
+ variables,
1645
+ void 0,
1646
+ defaultAiProvider
1647
+ );
1648
+ if (!config.enabled) {
1649
+ this._logger?.info(`Agent configuration is disabled: ${key}`);
1650
+ return void 0;
1651
+ }
1652
+ const runner = await RunnerFactory.createAgent(config, void 0, this._logger, defaultAiProvider);
1653
+ if (!runner) {
1654
+ return void 0;
1655
+ }
1656
+ return new ManagedAgent(config, runner, this._logger);
1592
1657
  }
1593
1658
  createTracker(token, context) {
1594
1659
  return LDAIConfigTrackerImpl.fromResumptionToken(token, this._ldClient, context);
@@ -1667,6 +1732,81 @@ var LDAIClientImpl = class {
1667
1732
  }
1668
1733
  };
1669
1734
 
1735
+ // src/api/ManagedAgentGraph.ts
1736
+ var ManagedAgentGraph = class {
1737
+ constructor(_graphDefinition, _logger) {
1738
+ this._graphDefinition = _graphDefinition;
1739
+ this._logger = _logger;
1740
+ }
1741
+ /**
1742
+ * Runs the agent graph using the provided runner function and returns a ManagedGraphResult.
1743
+ *
1744
+ * The runner function receives the graph tracker and AgentGraphDefinition,
1745
+ * executes the graph, and returns an AgentGraphRunnerResult.
1746
+ *
1747
+ * run() returns before ManagedGraphResult.evaluations resolves.
1748
+ *
1749
+ * @param runner Async function that executes the graph and returns AgentGraphRunnerResult.
1750
+ * @returns ManagedGraphResult with LDAIGraphMetricSummary and evaluations promise.
1751
+ */
1752
+ async run(runner) {
1753
+ const graphTracker = this._graphDefinition.createTracker();
1754
+ const runnerResult = await runner(this._graphDefinition, graphTracker);
1755
+ const metrics = {
1756
+ success: runnerResult.metrics.success,
1757
+ path: runnerResult.metrics.path,
1758
+ durationMs: runnerResult.metrics.durationMs,
1759
+ tokens: runnerResult.metrics.tokens,
1760
+ nodeMetrics: this._trackNodeMetrics(runnerResult.metrics.nodeMetrics),
1761
+ resumptionToken: graphTracker.resumptionToken
1762
+ };
1763
+ const evaluations = Promise.resolve([]);
1764
+ return {
1765
+ content: runnerResult.content,
1766
+ metrics,
1767
+ raw: runnerResult.raw,
1768
+ evaluations
1769
+ };
1770
+ }
1771
+ /**
1772
+ * Converts per-node LDAIMetrics from the runner into LDAIMetricSummary by
1773
+ * creating a per-node tracker, firing tracking events, and calling getSummary().
1774
+ */
1775
+ _trackNodeMetrics(nodeMetrics) {
1776
+ const summaries = {};
1777
+ for (const [nodeKey, metrics] of Object.entries(nodeMetrics)) {
1778
+ const node = this._graphDefinition.getNode(nodeKey);
1779
+ if (!node) {
1780
+ this._logger?.warn(`ManagedAgentGraph: no node found for key "${nodeKey}", skipping metrics`);
1781
+ continue;
1782
+ }
1783
+ const tracker = node.getConfig().createTracker();
1784
+ if (metrics.tokens) {
1785
+ tracker.trackTokens(metrics.tokens);
1786
+ }
1787
+ if (metrics.durationMs !== void 0) {
1788
+ tracker.trackDuration(metrics.durationMs);
1789
+ }
1790
+ if (metrics.toolCalls?.length) {
1791
+ tracker.trackToolCalls(metrics.toolCalls);
1792
+ }
1793
+ if (metrics.success) {
1794
+ tracker.trackSuccess();
1795
+ } else {
1796
+ tracker.trackError();
1797
+ }
1798
+ summaries[nodeKey] = tracker.getSummary();
1799
+ }
1800
+ return summaries;
1801
+ }
1802
+ /**
1803
+ * Returns the underlying AgentGraphDefinition.
1804
+ */
1805
+ getGraphDefinition() {
1806
+ return this._graphDefinition;
1807
+ }
1808
+ };
1809
+
1670
1810
  // src/index.ts
1671
1811
  function initAi(ldClient) {
1672
1812
  return new LDAIClientImpl(ldClient);
@@ -1674,17 +1814,16 @@ function initAi(ldClient) {
1674
1814
  // Annotate the CommonJS export names for ESM import in node:
1675
1815
  0 && (module.exports = {
1676
1816
  AIProvider,
1677
- AIProviderFactory,
1678
1817
  AgentGraphDefinition,
1679
1818
  AgentGraphNode,
1680
1819
  Judge,
1681
1820
  LDFeedbackKind,
1682
1821
  LDGraphTrackerImpl,
1822
+ ManagedAgent,
1823
+ ManagedAgentGraph,
1824
+ ManagedModel,
1825
+ RunnerFactory,
1683
1826
  SUPPORTED_AI_PROVIDERS,
1684
- TrackedChat,
1685
- createBedrockTokenUsage,
1686
- createOpenAiUsage,
1687
- createVercelAISDKTokenUsage,
1688
1827
  initAi
1689
1828
  });
1690
1829
  //# sourceMappingURL=index.cjs.map