Skip to content

Commit 9393389

Browse files
committed
feat: Add toVercelAISDK method to support easy model creation
fix!: VercelProvider now requires type safe parameters for Vercel models fix: Properly convert LD model parameters to Vercel model parameters
1 parent 8d57904 commit 9393389

File tree

3 files changed

+207
-10
lines changed

3 files changed

+207
-10
lines changed

packages/ai-providers/server-ai-vercel/src/VercelProvider.ts

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,50 @@ import type {
1010
LDTokenUsage,
1111
} from '@launchdarkly/server-sdk-ai';
1212

13+
import type {
14+
VercelAIModelParameters,
15+
VercelAISDKConfig,
16+
VercelAISDKMapOptions,
17+
VercelAISDKProvider,
18+
} from './types';
19+
1320
/**
1421
* Vercel AI implementation of AIProvider.
1522
* This provider integrates Vercel AI SDK with LaunchDarkly's tracking capabilities.
1623
*/
1724
export class VercelProvider extends AIProvider {
1825
private _model: LanguageModel;
19-
private _parameters: Record<string, unknown>;
26+
private _parameters: VercelAIModelParameters;
2027

21-
constructor(model: LanguageModel, parameters: Record<string, unknown>, logger?: LDLogger) {
28+
/**
29+
* Constructor for the VercelProvider.
30+
* @param model - The Vercel AI model to use.
31+
* @param parameters - The Vercel AI model parameters.
32+
* @param logger - The logger to use for the Vercel AI provider.
33+
*/
34+
constructor(model: LanguageModel, parameters: VercelAIModelParameters, logger?: LDLogger) {
2235
super(logger);
2336
this._model = model;
2437
this._parameters = parameters;
2538
}
2639

2740
// =============================================================================
28-
// MAIN FACTORY METHOD
41+
// MAIN FACTORY METHODS
2942
// =============================================================================
3043

3144
/**
3245
* Static factory method to create a Vercel AIProvider from an AI configuration.
46+
* This method auto-detects the provider and creates the model.
47+
* Note: Messages from the AI config are not included in the provider - messages
48+
* should be passed at invocation time via invokeModel().
49+
*
50+
* @param aiConfig The LaunchDarkly AI configuration
51+
* @param logger Optional logger
52+
* @returns A Promise that resolves to a configured VercelProvider
3353
*/
3454
static async create(aiConfig: LDAIConfig, logger?: LDLogger): Promise<VercelProvider> {
3555
const model = await VercelProvider.createVercelModel(aiConfig);
36-
const parameters = aiConfig.model?.parameters || {};
56+
const parameters = VercelProvider.mapParameters(aiConfig.model?.parameters);
3757
return new VercelProvider(model, parameters, logger);
3858
}
3959

@@ -46,8 +66,6 @@ export class VercelProvider extends AIProvider {
4666
*/
4767
async invokeModel(messages: LDMessage[]): Promise<ChatResponse> {
4868
// Call Vercel AI generateText
49-
// Type assertion: our MinLanguageModel is compatible with the expected LanguageModel interface
50-
// The generateText function will work with any object that has the required properties
5169
const result = await generateText({
5270
model: this._model,
5371
messages,
@@ -121,19 +139,151 @@ export class VercelProvider extends AIProvider {
121139
};
122140
}
123141

142+
/**
143+
* Create a metrics extractor for Vercel AI SDK streaming results.
144+
* Use this with tracker.trackStreamMetricsOf() for streaming operations like streamText.
145+
*
146+
* The extractor waits for the stream's response promise to resolve, then extracts
147+
* metrics from the completed response.
148+
*
149+
* @returns A metrics extractor function for streaming results
150+
*
151+
* @example
152+
* const stream = aiConfig.tracker.trackStreamMetricsOf(
153+
* () => streamText(vercelConfig),
154+
* VercelProvider.createStreamMetricsExtractor()
155+
* );
156+
*
157+
* for await (const chunk of stream.textStream) {
158+
* process.stdout.write(chunk);
159+
* }
160+
*/
161+
static createStreamMetricsExtractor() {
162+
return async (stream: any): Promise<LDAIMetrics> => {
163+
// Wait for stream to complete
164+
const result = await stream.response;
165+
// Extract metrics from completed response
166+
return VercelProvider.createAIMetrics(result);
167+
};
168+
}
169+
170+
/**
171+
* Map LaunchDarkly model parameters to Vercel AI SDK parameters.
172+
*
173+
* Parameter mappings:
174+
* - max_tokens → maxTokens
175+
* - max_completion_tokens → maxOutputTokens
176+
* - temperature → temperature
177+
* - top_p → topP
178+
* - top_k → topK
179+
* - presence_penalty → presencePenalty
180+
* - frequency_penalty → frequencyPenalty
181+
* - stop → stopSequences
182+
* - seed → seed
183+
*
184+
* @param parameters The LaunchDarkly model parameters to map
185+
* @returns An object containing mapped Vercel AI SDK parameters
186+
*/
187+
static mapParameters(parameters?: { [index: string]: unknown }): VercelAIModelParameters {
188+
if (!parameters) {
189+
return {};
190+
}
191+
192+
const params: VercelAIModelParameters = {};
193+
194+
// Map token limits
195+
if (parameters.max_tokens !== undefined) {
196+
params.maxTokens = parameters.max_tokens as number;
197+
}
198+
if (parameters.max_completion_tokens !== undefined) {
199+
params.maxOutputTokens = parameters.max_completion_tokens as number;
200+
}
201+
202+
// Map remaining parameters
203+
if (parameters.temperature !== undefined) {
204+
params.temperature = parameters.temperature as number;
205+
}
206+
if (parameters.top_p !== undefined) {
207+
params.topP = parameters.top_p as number;
208+
}
209+
if (parameters.top_k !== undefined) {
210+
params.topK = parameters.top_k as number;
211+
}
212+
if (parameters.presence_penalty !== undefined) {
213+
params.presencePenalty = parameters.presence_penalty as number;
214+
}
215+
if (parameters.frequency_penalty !== undefined) {
216+
params.frequencyPenalty = parameters.frequency_penalty as number;
217+
}
218+
if (parameters.stop !== undefined) {
219+
params.stopSequences = parameters.stop as string[];
220+
}
221+
if (parameters.seed !== undefined) {
222+
params.seed = parameters.seed as number;
223+
}
224+
225+
return params;
226+
}
227+
228+
/**
229+
* Convert an AI configuration to Vercel AI SDK parameters.
230+
* This static method allows converting an LDAIConfig to VercelAISDKConfig without
231+
* requiring an instance of VercelProvider.
232+
*
233+
* @param aiConfig The LaunchDarkly AI configuration
234+
* @param provider A Vercel AI SDK Provider or a map of provider names to Vercel AI SDK Providers
235+
* @param options Optional mapping options
236+
* @returns A configuration directly usable in Vercel AI SDK generateText() and streamText()
237+
* @throws {Error} if a Vercel AI SDK model cannot be determined from the given provider parameter
238+
*/
239+
static toVercelAISDK<TMod>(
240+
aiConfig: LDAIConfig,
241+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
242+
options?: VercelAISDKMapOptions | undefined,
243+
): VercelAISDKConfig<TMod> {
244+
// Determine the model from the provider
245+
let model: TMod | undefined;
246+
if (typeof provider === 'function') {
247+
model = provider(aiConfig.model?.name ?? '');
248+
} else {
249+
model = provider[aiConfig.provider?.name ?? '']?.(aiConfig.model?.name ?? '');
250+
}
251+
if (!model) {
252+
throw new Error(
253+
'Vercel AI SDK model cannot be determined from the supplied provider parameter.',
254+
);
255+
}
256+
257+
// Merge messages from config and options
258+
let messages: LDMessage[] | undefined;
259+
const configMessages = ('messages' in aiConfig ? aiConfig.messages : undefined) as
260+
| LDMessage[]
261+
| undefined;
262+
if (configMessages || options?.nonInterpolatedMessages) {
263+
messages = [...(configMessages ?? []), ...(options?.nonInterpolatedMessages ?? [])];
264+
}
265+
266+
// Map parameters using the shared mapping method
267+
const params = VercelProvider.mapParameters(aiConfig.model?.parameters);
268+
269+
// Build and return the Vercel AI SDK configuration
270+
return {
271+
model,
272+
messages,
273+
...params,
274+
};
275+
}
276+
124277
/**
125278
* Create a Vercel AI model from an AI configuration.
126-
* This method creates a Vercel AI model based on the provider configuration.
279+
* This method auto-detects the provider and creates the model instance.
127280
*
128281
* @param aiConfig The LaunchDarkly AI configuration
129282
* @returns A Promise that resolves to a configured Vercel AI model
130283
*/
131284
static async createVercelModel(aiConfig: LDAIConfig): Promise<LanguageModel> {
132285
const providerName = VercelProvider.mapProvider(aiConfig.provider?.name || '');
133286
const modelName = aiConfig.model?.name || '';
134-
// Parameters are not used in model creation but kept for future use
135-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
136-
const parameters = aiConfig.model?.parameters || {};
137287

138288
// Map provider names to their corresponding Vercel AI SDK imports
139289
switch (providerName) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
export { VercelProvider } from './VercelProvider';
2+
export type {
3+
VercelAIModelParameters,
4+
VercelAISDKConfig,
5+
VercelAISDKMapOptions,
6+
VercelAISDKProvider,
7+
} from './types';
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { LDMessage } from '@launchdarkly/server-sdk-ai';
2+
3+
/**
4+
* Vercel AI SDK Provider type - a function that takes a model name and returns a model instance.
5+
*/
6+
export type VercelAISDKProvider<TMod> = (modelName: string) => TMod;
7+
8+
/**
9+
* Options for mapping to Vercel AI SDK configuration.
10+
*/
11+
export interface VercelAISDKMapOptions {
12+
/**
13+
* Additional messages that should not be interpolated.
14+
*/
15+
nonInterpolatedMessages?: LDMessage[] | undefined;
16+
}
17+
18+
/**
19+
* Vercel AI SDK model parameters.
20+
* These are the parameters that can be passed to Vercel AI SDK methods like generateText() and streamText().
21+
*/
22+
export interface VercelAIModelParameters {
23+
maxTokens?: number | undefined;
24+
maxOutputTokens?: number | undefined;
25+
temperature?: number | undefined;
26+
topP?: number | undefined;
27+
topK?: number | undefined;
28+
presencePenalty?: number | undefined;
29+
frequencyPenalty?: number | undefined;
30+
stopSequences?: string[] | undefined;
31+
seed?: number | undefined;
32+
}
33+
34+
/**
35+
* Configuration format compatible with Vercel AI SDK's generateText() and streamText() methods.
36+
*/
37+
export interface VercelAISDKConfig<TMod> extends VercelAIModelParameters {
38+
model: TMod;
39+
messages?: LDMessage[] | undefined;
40+
}
41+

0 commit comments

Comments
 (0)