Skip to content

Commit 192315f

Browse files
jsonbaileyclaude
andcommitted
feat: introduce ManagedResult, RunnerResult, and LDAIMetricSummary (AIC-2388)
Adds RunnerResult (provider-level result type without evaluations), ManagedResult (managed-layer result with async evaluations promise), and LDAIMetricSummary (flat metric summary including resumptionToken). Adds toolCalls and durationMs to LDAIMetrics. TrackedChat.run() replaces invoke() returning ManagedResult with LDAIMetricSummary built from tracker. Adds createModel() to LDAIClient/LDAIClientImpl as the preferred replacement for createChat(). Updates chat-judge example. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c751ce6 commit 192315f

8 files changed

Lines changed: 192 additions & 7 deletions

File tree

packages/sdk/server-ai/examples/chat-judge/src/index.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ async function main() {
4343
enabled: false,
4444
};
4545

46-
const chat = await aiClient.createChat(aiConfigKey, context, defaultValue, {
46+
const model = await aiClient.createModel(aiConfigKey, context, defaultValue, {
4747
companyName: 'LaunchDarkly',
4848
});
4949

50-
if (!chat) {
50+
if (!model) {
5151
console.log('*** AI chat configuration is not enabled');
5252
process.exit(0);
5353
}
@@ -56,15 +56,14 @@ async function main() {
5656
const userInput = 'How can LaunchDarkly help me?';
5757
console.log('User Input:', userInput);
5858

59-
// The invoke method will automatically evaluate the chat response with any judges defined
60-
// in the AI config.
61-
const chatResponse = await chat.invoke(userInput);
62-
console.log('Chat Response:', chatResponse.message.content);
59+
// The run() method invokes the model and returns a ManagedResult.
60+
const result = await model.run(userInput);
61+
console.log('Chat Response:', result.content);
6362

6463
// Judge evaluations run asynchronously and do not block your application.
6564
// Results are automatically sent to LaunchDarkly for AI config metrics.
6665
// You only need to await if you want to access the evaluation results in your code.
67-
const evalResults = await chatResponse.evaluations;
66+
const evalResults = await result.evaluations;
6867
console.log('Judge results:', JSON.stringify(evalResults, null, 2));
6968

7069
console.log('Success.');

packages/sdk/server-ai/src/LDAIClientImpl.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,16 @@ export class LDAIClientImpl implements LDAIClient {
410410
}
411411
}
412412

413+
async createModel(
414+
key: string,
415+
context: LDContext,
416+
defaultValue?: LDAICompletionConfigDefault,
417+
variables?: Record<string, unknown>,
418+
defaultAiProvider?: SupportedAIProvider,
419+
): Promise<TrackedChat | undefined> {
420+
return this.createChat(key, context, defaultValue, variables, defaultAiProvider);
421+
}
422+
413423
/**
414424
* @deprecated Use `createChat` instead. This method will be removed in a future version.
415425
*/

packages/sdk/server-ai/src/api/LDAIClient.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,25 @@ export interface LDAIClient {
276276
defaultAiProvider?: SupportedAIProvider,
277277
): Promise<TrackedChat | undefined>;
278278

279+
/**
280+
* Creates and returns a new ManagedModel (TrackedChat) instance for chat interactions.
281+
* This is the preferred replacement for `createChat()`.
282+
*
283+
* @param key The key identifying the AI chat configuration to use.
284+
* @param context The standard LDContext used when evaluating flags.
285+
* @param defaultValue Optional fallback when the configuration is not available from LaunchDarkly.
286+
* @param variables Dictionary of values for instruction interpolation.
287+
* @param defaultAiProvider Optional default AI provider to use.
288+
* @returns A promise that resolves to the TrackedChat instance, or undefined if disabled.
289+
*/
290+
createModel(
291+
key: string,
292+
context: LDContext,
293+
defaultValue?: LDAICompletionConfigDefault,
294+
variables?: Record<string, unknown>,
295+
defaultAiProvider?: SupportedAIProvider,
296+
): Promise<TrackedChat | undefined>;
297+
279298
/**
280299
* @deprecated Use `createChat` instead. This method will be removed in a future version.
281300
*/

packages/sdk/server-ai/src/api/chat/TrackedChat.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { LDLogger } from '@launchdarkly/js-server-sdk-common';
33
import { LDAICompletionConfig, LDMessage } from '../config/types';
44
import { Judge } from '../judge/Judge';
55
import { LDJudgeResult } from '../judge/types';
6+
import { LDAIMetricSummary, ManagedResult } from '../model/types';
67
import { AIProvider } from '../providers/AIProvider';
78
import { ChatResponse } from './types';
89

@@ -24,9 +25,56 @@ export class TrackedChat {
2425
this.messages = [];
2526
}
2627

28+
/**
29+
* Invoke the chat model with a prompt string and return a ManagedResult.
30+
* This is the primary entry point for model invocation. Judge evaluations are
31+
* wired asynchronously and exposed via ManagedResult.evaluations.
32+
*/
33+
async run(prompt: string): Promise<ManagedResult> {
34+
const tracker = this.aiConfig.createTracker!();
35+
36+
// Convert prompt string to LDMessage with role 'user' and add to conversation history
37+
const userMessage: LDMessage = {
38+
role: 'user',
39+
content: prompt,
40+
};
41+
this.messages.push(userMessage);
42+
43+
// Prepend config messages to conversation history for model invocation
44+
const configMessages = this.aiConfig.messages || [];
45+
const allMessages = [...configMessages, ...this.messages];
46+
47+
// Delegate to provider-specific implementation with tracking
48+
const response = await tracker.trackMetricsOf(
49+
(result: ChatResponse) => result.metrics,
50+
() => this.provider.invokeModel(allMessages),
51+
);
52+
53+
this.messages.push(response.message);
54+
55+
// Build the metric summary from response metrics + resumption token
56+
const metrics: LDAIMetricSummary = {
57+
success: response.metrics.success,
58+
usage: response.metrics.usage,
59+
toolCalls: response.metrics.toolCalls,
60+
durationMs: response.metrics.durationMs,
61+
resumptionToken: tracker.resumptionToken,
62+
};
63+
64+
// Evaluations are wired in the managed layer (PR 3). For now, resolve empty.
65+
const evaluations: Promise<LDJudgeResult[]> = Promise.resolve([]);
66+
67+
return {
68+
content: response.message.content,
69+
metrics,
70+
evaluations,
71+
};
72+
}
73+
2774
/**
2875
* Invoke the chat model with a prompt string.
2976
* This method handles conversation management and tracking, delegating to the provider's invokeModel method.
77+
* @deprecated Use `run()` instead.
3078
*/
3179
async invoke(prompt: string): Promise<ChatResponse> {
3280
const tracker = this.aiConfig.createTracker!();

packages/sdk/server-ai/src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export * from './chat';
33
export * from './graph';
44
export * from './judge';
55
export * from './metrics';
6+
export * from './model';
67
export * from './LDAIClient';
78
export * from './providers';

packages/sdk/server-ai/src/api/metrics/LDAIMetrics.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,16 @@ export interface LDAIMetrics {
1515
* This will be undefined if no token usage data is available.
1616
*/
1717
usage?: LDTokenUsage;
18+
19+
/**
20+
* List of tool call identifiers made during the operation.
21+
* This will be undefined if no tool calls were made.
22+
*/
23+
toolCalls?: string[];
24+
25+
/**
26+
* Duration of the operation in milliseconds.
27+
* This will be undefined if duration was not tracked.
28+
*/
29+
durationMs?: number;
1830
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { LDAIMetricSummary, ManagedResult, RunnerResult } from './types';
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { LDJudgeResult } from '../judge/types';
2+
import { LDAIMetrics } from '../metrics/LDAIMetrics';
3+
import { LDTokenUsage } from '../metrics/LDTokenUsage';
4+
5+
/**
6+
* Summary metrics returned in a ManagedResult or ManagedGraphResult.
7+
* Provides a flat view of the key metrics for the completed operation.
8+
*/
9+
export interface LDAIMetricSummary {
10+
/**
11+
* Whether the AI operation was successful.
12+
*/
13+
success: boolean;
14+
15+
/**
16+
* Token usage information, if available.
17+
*/
18+
usage?: LDTokenUsage;
19+
20+
/**
21+
* List of tool call identifiers made during the operation, if any.
22+
*/
23+
toolCalls?: string[];
24+
25+
/**
26+
* Duration of the operation in milliseconds, if tracked.
27+
*/
28+
durationMs?: number;
29+
30+
/**
31+
* Resumption token for deferred feedback association.
32+
*/
33+
resumptionToken?: string;
34+
}
35+
36+
/**
37+
* The result returned by a Runner (provider-level) invocation.
38+
* Providers implement Runner and return RunnerResult from run().
39+
* This type does NOT include evaluations — those are wired in the managed layer.
40+
*/
41+
export interface RunnerResult {
42+
/**
43+
* The text content of the model's response.
44+
*/
45+
content: string;
46+
47+
/**
48+
* Metrics information for the operation.
49+
*/
50+
metrics: LDAIMetrics;
51+
52+
/**
53+
* The raw response object from the provider, if available.
54+
*/
55+
raw?: unknown;
56+
57+
/**
58+
* Parsed structured output, if the provider returned structured data.
59+
*/
60+
parsed?: Record<string, unknown>;
61+
}
62+
63+
/**
64+
* The result returned by a managed model invocation (ManagedModel.run()).
65+
* Includes a promise for asynchronous judge evaluations.
66+
*/
67+
export interface ManagedResult {
68+
/**
69+
* The text content of the model's response.
70+
*/
71+
content: string;
72+
73+
/**
74+
* Summarized metrics for this invocation.
75+
*/
76+
metrics: LDAIMetricSummary;
77+
78+
/**
79+
* The raw response object from the provider, if available.
80+
*/
81+
raw?: unknown;
82+
83+
/**
84+
* Parsed structured output, if available.
85+
*/
86+
parsed?: Record<string, unknown>;
87+
88+
/**
89+
* Promise that resolves to the judge evaluation results.
90+
* This promise encapsulates both evaluation and tracking
91+
* (tracker.trackJudgeResult is called when it resolves).
92+
* Awaiting this promise guarantees both evaluation and tracking are complete.
93+
*/
94+
evaluations: Promise<LDJudgeResult[]>;
95+
}

0 commit comments

Comments
 (0)