@modelence/ai 0.1.5 → 0.1.6-dev.1
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.d.ts +9 -11
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/jest.config.js +17 -0
- package/package.json +14 -8
- package/src/index.test.ts +190 -0
- package/src/index.ts +17 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as ai from 'ai';
|
|
2
1
|
import { generateText as generateText$1 } from 'ai';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -6,18 +5,17 @@ import { generateText as generateText$1 } from 'ai';
|
|
|
6
5
|
*/
|
|
7
6
|
type Provider = 'openai' | 'anthropic' | 'google';
|
|
8
7
|
type OriginalGenerateTextParams = Parameters<typeof generateText$1>[0];
|
|
8
|
+
type ModelenceGenerateTextOptions<T> = T extends unknown ? Omit<T, 'model'> & {
|
|
9
|
+
provider: Provider;
|
|
10
|
+
model: string;
|
|
11
|
+
} : never;
|
|
9
12
|
/**
|
|
10
13
|
* Options for the Modelence generateText function.
|
|
11
14
|
*
|
|
12
|
-
* This
|
|
15
|
+
* This type extends all the standard AI SDK generateText options,
|
|
13
16
|
* but replaces the model parameter with separate provider and model parameters.
|
|
14
17
|
*/
|
|
15
|
-
|
|
16
|
-
/** The AI provider name */
|
|
17
|
-
provider: Provider;
|
|
18
|
-
/** The specific model name */
|
|
19
|
-
model: string;
|
|
20
|
-
}
|
|
18
|
+
type GenerateTextOptions = ModelenceGenerateTextOptions<OriginalGenerateTextParams>;
|
|
21
19
|
/**
|
|
22
20
|
* Generates text using AI models with built-in Modelence configuration and telemetry.
|
|
23
21
|
*
|
|
@@ -32,8 +30,8 @@ interface GenerateTextOptions extends Omit<OriginalGenerateTextParams, 'model'>
|
|
|
32
30
|
* import { generateText } from '@modelence/ai';
|
|
33
31
|
*
|
|
34
32
|
* const response = await generateText({
|
|
35
|
-
* provider: '
|
|
36
|
-
* model: '
|
|
33
|
+
* provider: 'anthropic',
|
|
34
|
+
* model: 'claude-sonnet-4-6',
|
|
37
35
|
* messages: [
|
|
38
36
|
* { role: 'user', content: 'Write a haiku about programming' }
|
|
39
37
|
* ],
|
|
@@ -43,6 +41,6 @@ interface GenerateTextOptions extends Omit<OriginalGenerateTextParams, 'model'>
|
|
|
43
41
|
* console.log(response.text);
|
|
44
42
|
* ```
|
|
45
43
|
*/
|
|
46
|
-
declare function generateText(options: GenerateTextOptions): Promise<
|
|
44
|
+
declare function generateText(options: GenerateTextOptions): Promise<Awaited<ReturnType<typeof generateText$1>>>;
|
|
47
45
|
|
|
48
46
|
export { type GenerateTextOptions, generateText };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {getConfig}from'modelence/server';import {startTransaction,captureError}from'modelence/telemetry';import {generateText}from'ai';import {createOpenAI}from'@ai-sdk/openai';import {createAnthropic}from'@ai-sdk/anthropic';import {createGoogleGenerativeAI}from'@ai-sdk/google';function l(e,
|
|
1
|
+
import {getConfig}from'modelence/server';import {startTransaction,captureError}from'modelence/telemetry';import {generateText}from'ai';import {createOpenAI}from'@ai-sdk/openai';import {createAnthropic}from'@ai-sdk/anthropic';import {createGoogleGenerativeAI}from'@ai-sdk/google';function l(e,r){switch(e){case "openai":return createOpenAI({apiKey:String(getConfig("_system.openai.apiKey"))})(r);case "anthropic":return createAnthropic({apiKey:String(getConfig("_system.anthropic.apiKey"))})(r);case "google":return createGoogleGenerativeAI({apiKey:String(getConfig("_system.gemini.apiKey"))})(r);default:throw new Error(`Unsupported provider: ${e}`)}}async function v(e){let{provider:r,model:a,...i}=e,s=l(r,a),n=startTransaction("ai","ai:generateText",{provider:r,model:a,messageCount:Array.isArray(e.messages)?e.messages.length:0,temperature:e.temperature});try{let t=await generateText({...i,model:s});return "setContext"in n?n.end("success",{context:{usage:{promptTokens:t.usage.inputTokens,completionTokens:t.usage.outputTokens,totalTokens:t.usage.totalTokens}}}):n.end("success"),t}catch(t){throw captureError(t),n.end("error"),t}}export{v as generateText};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["getProviderModel","provider","model","createOpenAI","getConfig","createAnthropic","createGoogleGenerativeAI","generateText","options","restOptions","transaction","startTransaction","result","originalGenerateText","error","captureError"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["getProviderModel","provider","model","createOpenAI","getConfig","createAnthropic","createGoogleGenerativeAI","generateText","options","restOptions","providerModel","transaction","startTransaction","result","originalGenerateText","error","captureError"],"mappings":"uRA6BA,SAASA,CAAAA,CAAiBC,CAAAA,CAAoBC,EAAe,CAC3D,OAAQD,GACN,KAAK,SACH,OAAOE,YAAAA,CAAa,CAClB,MAAA,CAAQ,MAAA,CAAOC,UAAU,uBAAuB,CAAC,CACnD,CAAC,CAAA,CAAEF,CAAK,CAAA,CAEV,KAAK,YACH,OAAOG,eAAAA,CAAgB,CACrB,MAAA,CAAQ,MAAA,CAAOD,UAAU,0BAA0B,CAAC,CACtD,CAAC,CAAA,CAAEF,CAAK,CAAA,CAEV,KAAK,SACH,OAAOI,wBAAAA,CAAyB,CAC9B,MAAA,CAAQ,MAAA,CAAOF,UAAU,uBAAuB,CAAC,CACnD,CAAC,CAAA,CAAEF,CAAK,CAAA,CAEV,QACE,MAAM,IAAI,KAAA,CAAM,yBAAyBD,CAAQ,CAAA,CAAE,CACvD,CACF,CA2BA,eAAsBM,CAAAA,CACpBC,CAAAA,CAC2D,CAC3D,GAAM,CAAE,SAAAP,CAAAA,CAAU,KAAA,CAAAC,EAAO,GAAGO,CAAY,EAAID,CAAAA,CACtCE,CAAAA,CAAgBV,EAAiBC,CAAAA,CAAUC,CAAK,EAEhDS,CAAAA,CAAcC,gBAAAA,CAAiB,KAAM,iBAAA,CAAmB,CAC5D,SAAAX,CAAAA,CACA,KAAA,CAAAC,EACA,YAAA,CAAc,KAAA,CAAM,QAAQM,CAAAA,CAAQ,QAAQ,EAAIA,CAAAA,CAAQ,QAAA,CAAS,OAAS,CAAA,CAC1E,WAAA,CAAaA,EAAQ,WACvB,CAAC,EAED,GAAI,CACF,IAAMK,CAAAA,CAAS,MAAMC,aAAqB,CACxC,GAAGL,EACH,KAAA,CAAOC,CACT,CAAC,CAAA,CAED,OAAI,eAAgBC,CAAAA,CAClBA,CAAAA,CAAY,IAAI,SAAA,CAAW,CACzB,QAAS,CACP,KAAA,CAAO,CACL,YAAA,CAAcE,CAAAA,CAAO,MAAM,WAAA,CAC3B,gBAAA,CAAkBA,EAAO,KAAA,CAAM,YAAA,CAC/B,YAAaA,CAAAA,CAAO,KAAA,CAAM,WAC5B,CACF,CACF,CAAC,CAAA,CAIDF,CAAAA,CAAY,IAAI,SAAS,CAAA,CAEpBE,CACT,CAAA,MAASE,CAAAA,CAAO,CACd,MAAAC,YAAAA,CAAaD,CAAc,CAAA,CAC3BJ,CAAAA,CAAY,IAAI,OAAO,CAAA,CACjBI,CACR,CACF","file":"index.js","sourcesContent":["import { getConfig } from 'modelence/server';\nimport { startTransaction, captureError } from 'modelence/telemetry';\nimport { generateText as originalGenerateText } from 'ai';\nimport { createOpenAI } from '@ai-sdk/openai';\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport { createGoogleGenerativeAI } from '@ai-sdk/google';\n\n/**\n * Supported AI providers for text generation.\n */\ntype Provider = 'openai' | 'anthropic' | 'google';\n\n// Extract the original generateText parameters and override the model property\ntype OriginalGenerateTextParams = Parameters<typeof originalGenerateText>[0];\ntype ModelenceGenerateTextOptions<T> = T extends unknown\n ? Omit<T, 'model'> & {\n provider: Provider;\n model: string;\n }\n : never;\n\n/**\n * Options for the Modelence generateText function.\n * \n * This type extends all the standard AI SDK generateText options,\n * but replaces the model parameter with separate provider and model parameters.\n */\nexport type GenerateTextOptions = ModelenceGenerateTextOptions<OriginalGenerateTextParams>;\n\nfunction getProviderModel(provider: Provider, model: string) {\n switch (provider) {\n case 'openai':\n return createOpenAI({\n apiKey: String(getConfig('_system.openai.apiKey')),\n })(model);\n \n case 'anthropic':\n return createAnthropic({\n apiKey: String(getConfig('_system.anthropic.apiKey')),\n })(model);\n \n case 'google':\n return createGoogleGenerativeAI({\n apiKey: String(getConfig('_system.gemini.apiKey')),\n })(model);\n \n default:\n throw new Error(`Unsupported provider: ${provider}`);\n }\n}\n\n/**\n * Generates text using AI models with built-in Modelence configuration and telemetry.\n * \n * This is a wrapper around the AI SDK's generateText function that automatically\n * configures providers using Modelence's server-side configuration system.\n * \n * @param options - Configuration options for text generation\n * @returns A promise that resolves to the generated text result\n * \n * @example\n * ```typescript\n * import { generateText } from '@modelence/ai';\n * \n * const response = await generateText({\n * provider: 'anthropic',\n * model: 'claude-sonnet-4-6',\n * messages: [\n * { role: 'user', content: 'Write a haiku about programming' }\n * ],\n * temperature: 0.7\n * });\n * \n * console.log(response.text);\n * ```\n */\nexport async function generateText(\n options: GenerateTextOptions\n): Promise<Awaited<ReturnType<typeof originalGenerateText>>> {\n const { provider, model, ...restOptions } = options;\n const providerModel = getProviderModel(provider, model);\n \n const transaction = startTransaction('ai', 'ai:generateText', {\n provider, \n model,\n messageCount: Array.isArray(options.messages) ? options.messages.length : 0,\n temperature: options.temperature\n });\n\n try {\n const result = await originalGenerateText({\n ...restOptions,\n model: providerModel,\n });\n \n if ('setContext' in transaction) {\n transaction.end('success', {\n context: {\n usage: {\n promptTokens: result.usage.inputTokens,\n completionTokens: result.usage.outputTokens,\n totalTokens: result.usage.totalTokens,\n }\n }\n });\n } else {\n // Backwards compatibility for older versions of Modelence\n // @ts-ignore\n transaction.end('success');\n }\n return result;\n } catch (error) {\n captureError(error as Error);\n transaction.end('error');\n throw error;\n }\n}\n"]}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/src'],
|
|
5
|
+
testMatch: ['**/?(*.)+(spec|test).ts'],
|
|
6
|
+
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'src/**/*.ts',
|
|
9
|
+
'!src/**/*.d.ts',
|
|
10
|
+
'!src/**/*.test.ts',
|
|
11
|
+
'!src/**/*.spec.ts',
|
|
12
|
+
],
|
|
13
|
+
transform: {
|
|
14
|
+
'^.+\\.ts$': ['ts-jest', { useESM: true }],
|
|
15
|
+
},
|
|
16
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@modelence/ai",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-dev.1",
|
|
5
5
|
"description": "Modelence AI engine",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"dev": "tsup --watch",
|
|
17
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
18
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
|
|
19
|
+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
17
20
|
"prepublishOnly": "npm run build"
|
|
18
21
|
},
|
|
19
22
|
"repository": {
|
|
@@ -24,17 +27,20 @@
|
|
|
24
27
|
"author": "Modelence",
|
|
25
28
|
"license": "SEE LICENSE IN LICENSE",
|
|
26
29
|
"dependencies": {
|
|
27
|
-
"@ai-sdk/anthropic": "^
|
|
28
|
-
"@ai-sdk/google": "^
|
|
29
|
-
"@ai-sdk/openai": "^
|
|
30
|
-
"ai": "^
|
|
30
|
+
"@ai-sdk/anthropic": "^3.0.45",
|
|
31
|
+
"@ai-sdk/google": "^3.0.29",
|
|
32
|
+
"@ai-sdk/openai": "^3.0.29",
|
|
33
|
+
"ai": "^6.0.90"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"modelence": "*"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
|
+
"jest": "^30.2.0",
|
|
41
|
+
"modelence": "^0.12.0",
|
|
42
|
+
"ts-jest": "^29.4.6",
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
39
45
|
}
|
|
40
46
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
const mockGetConfig = jest.fn<(key: string) => unknown>();
|
|
4
|
+
const mockStartTransaction = jest.fn();
|
|
5
|
+
const mockCaptureError = jest.fn();
|
|
6
|
+
const mockGenerateText = jest.fn();
|
|
7
|
+
|
|
8
|
+
const mockOpenAIModelFactory = jest.fn();
|
|
9
|
+
const mockAnthropicModelFactory = jest.fn();
|
|
10
|
+
const mockGoogleModelFactory = jest.fn();
|
|
11
|
+
|
|
12
|
+
const mockCreateOpenAI = jest.fn(() => mockOpenAIModelFactory);
|
|
13
|
+
const mockCreateAnthropic = jest.fn(() => mockAnthropicModelFactory);
|
|
14
|
+
const mockCreateGoogleGenerativeAI = jest.fn(() => mockGoogleModelFactory);
|
|
15
|
+
|
|
16
|
+
jest.unstable_mockModule('modelence/server', () => ({
|
|
17
|
+
getConfig: mockGetConfig,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.unstable_mockModule('modelence/telemetry', () => ({
|
|
21
|
+
startTransaction: mockStartTransaction,
|
|
22
|
+
captureError: mockCaptureError,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.unstable_mockModule('ai', () => ({
|
|
26
|
+
generateText: mockGenerateText,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
jest.unstable_mockModule('@ai-sdk/openai', () => ({
|
|
30
|
+
createOpenAI: mockCreateOpenAI,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.unstable_mockModule('@ai-sdk/anthropic', () => ({
|
|
34
|
+
createAnthropic: mockCreateAnthropic,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
jest.unstable_mockModule('@ai-sdk/google', () => ({
|
|
38
|
+
createGoogleGenerativeAI: mockCreateGoogleGenerativeAI,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const { generateText } = await import('./index');
|
|
42
|
+
|
|
43
|
+
describe('@modelence/ai generateText', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
mockGetConfig.mockImplementation((key: string) => {
|
|
47
|
+
const values: Record<string, string> = {
|
|
48
|
+
'_system.openai.apiKey': 'openai-key',
|
|
49
|
+
'_system.anthropic.apiKey': 'anthropic-key',
|
|
50
|
+
'_system.gemini.apiKey': 'google-key',
|
|
51
|
+
};
|
|
52
|
+
return values[key];
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('uses OpenAI provider, forwards options, and ends transaction with usage context', async () => {
|
|
57
|
+
const transaction = {
|
|
58
|
+
end: jest.fn(),
|
|
59
|
+
setContext: jest.fn(),
|
|
60
|
+
};
|
|
61
|
+
mockStartTransaction.mockReturnValue(transaction);
|
|
62
|
+
|
|
63
|
+
const modelInstance = { provider: 'openai', model: 'gpt-4o' };
|
|
64
|
+
mockOpenAIModelFactory.mockReturnValue(modelInstance);
|
|
65
|
+
|
|
66
|
+
const result = {
|
|
67
|
+
text: 'hello',
|
|
68
|
+
usage: {
|
|
69
|
+
promptTokens: 11,
|
|
70
|
+
completionTokens: 7,
|
|
71
|
+
totalTokens: 18,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
mockGenerateText.mockResolvedValue(result as never);
|
|
75
|
+
|
|
76
|
+
const options = {
|
|
77
|
+
provider: 'openai',
|
|
78
|
+
model: 'gpt-4o',
|
|
79
|
+
messages: [{ role: 'user', content: 'Say hello' }],
|
|
80
|
+
temperature: 0.4,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const response = await generateText(options as never);
|
|
84
|
+
|
|
85
|
+
expect(mockStartTransaction).toHaveBeenCalledWith('ai', 'ai:generateText', {
|
|
86
|
+
provider: 'openai',
|
|
87
|
+
model: 'gpt-4o',
|
|
88
|
+
messageCount: 1,
|
|
89
|
+
temperature: 0.4,
|
|
90
|
+
});
|
|
91
|
+
expect(mockGetConfig).toHaveBeenCalledWith('_system.openai.apiKey');
|
|
92
|
+
expect(mockCreateOpenAI).toHaveBeenCalledWith({ apiKey: 'openai-key' });
|
|
93
|
+
expect(mockOpenAIModelFactory).toHaveBeenCalledWith('gpt-4o');
|
|
94
|
+
expect(mockGenerateText).toHaveBeenCalledWith({
|
|
95
|
+
model: modelInstance,
|
|
96
|
+
messages: [{ role: 'user', content: 'Say hello' }],
|
|
97
|
+
temperature: 0.4,
|
|
98
|
+
});
|
|
99
|
+
expect(transaction.end).toHaveBeenCalledWith('success', {
|
|
100
|
+
context: {
|
|
101
|
+
usage: {
|
|
102
|
+
promptTokens: 11,
|
|
103
|
+
completionTokens: 7,
|
|
104
|
+
totalTokens: 18,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(mockCaptureError).not.toHaveBeenCalled();
|
|
109
|
+
expect(response).toBe(result);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('supports older transaction API without setContext', async () => {
|
|
113
|
+
const transaction = {
|
|
114
|
+
end: jest.fn(),
|
|
115
|
+
};
|
|
116
|
+
mockStartTransaction.mockReturnValue(transaction);
|
|
117
|
+
|
|
118
|
+
const modelInstance = { provider: 'anthropic', model: 'claude-3-haiku' };
|
|
119
|
+
mockAnthropicModelFactory.mockReturnValue(modelInstance);
|
|
120
|
+
|
|
121
|
+
mockGenerateText.mockResolvedValue({
|
|
122
|
+
text: 'ok',
|
|
123
|
+
usage: {
|
|
124
|
+
promptTokens: 3,
|
|
125
|
+
completionTokens: 2,
|
|
126
|
+
totalTokens: 5,
|
|
127
|
+
},
|
|
128
|
+
} as never);
|
|
129
|
+
|
|
130
|
+
await generateText({
|
|
131
|
+
provider: 'anthropic',
|
|
132
|
+
model: 'claude-3-haiku',
|
|
133
|
+
prompt: 'Hello',
|
|
134
|
+
} as never);
|
|
135
|
+
|
|
136
|
+
expect(mockGetConfig).toHaveBeenCalledWith('_system.anthropic.apiKey');
|
|
137
|
+
expect(mockCreateAnthropic).toHaveBeenCalledWith({ apiKey: 'anthropic-key' });
|
|
138
|
+
expect(mockAnthropicModelFactory).toHaveBeenCalledWith('claude-3-haiku');
|
|
139
|
+
expect(transaction.end).toHaveBeenCalledWith('success');
|
|
140
|
+
expect(transaction.end).toHaveBeenCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('captures and rethrows provider/model errors', async () => {
|
|
144
|
+
const transaction = {
|
|
145
|
+
end: jest.fn(),
|
|
146
|
+
setContext: jest.fn(),
|
|
147
|
+
};
|
|
148
|
+
mockStartTransaction.mockReturnValue(transaction);
|
|
149
|
+
|
|
150
|
+
const modelInstance = { provider: 'google', model: 'gemini-1.5-pro' };
|
|
151
|
+
mockGoogleModelFactory.mockReturnValue(modelInstance);
|
|
152
|
+
|
|
153
|
+
const error = new Error('generation failed');
|
|
154
|
+
mockGenerateText.mockRejectedValue(error);
|
|
155
|
+
|
|
156
|
+
await expect(
|
|
157
|
+
generateText({
|
|
158
|
+
provider: 'google',
|
|
159
|
+
model: 'gemini-1.5-pro',
|
|
160
|
+
prompt: 'Hello',
|
|
161
|
+
} as never)
|
|
162
|
+
).rejects.toThrow('generation failed');
|
|
163
|
+
|
|
164
|
+
expect(mockGetConfig).toHaveBeenCalledWith('_system.gemini.apiKey');
|
|
165
|
+
expect(mockCreateGoogleGenerativeAI).toHaveBeenCalledWith({ apiKey: 'google-key' });
|
|
166
|
+
expect(mockGoogleModelFactory).toHaveBeenCalledWith('gemini-1.5-pro');
|
|
167
|
+
expect(mockCaptureError).toHaveBeenCalledWith(error);
|
|
168
|
+
expect(transaction.end).toHaveBeenCalledWith('error');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('captures unsupported provider failures and does not call AI SDK', async () => {
|
|
172
|
+
const transaction = {
|
|
173
|
+
end: jest.fn(),
|
|
174
|
+
setContext: jest.fn(),
|
|
175
|
+
};
|
|
176
|
+
mockStartTransaction.mockReturnValue(transaction);
|
|
177
|
+
|
|
178
|
+
await expect(
|
|
179
|
+
generateText({
|
|
180
|
+
provider: 'invalid',
|
|
181
|
+
model: 'some-model',
|
|
182
|
+
prompt: 'Hello',
|
|
183
|
+
} as never)
|
|
184
|
+
).rejects.toThrow('Unsupported provider: invalid');
|
|
185
|
+
|
|
186
|
+
expect(mockGenerateText).not.toHaveBeenCalled();
|
|
187
|
+
expect(mockCaptureError).toHaveBeenCalledWith(expect.any(Error));
|
|
188
|
+
expect(transaction.end).toHaveBeenCalledWith('error');
|
|
189
|
+
});
|
|
190
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -12,19 +12,20 @@ type Provider = 'openai' | 'anthropic' | 'google';
|
|
|
12
12
|
|
|
13
13
|
// Extract the original generateText parameters and override the model property
|
|
14
14
|
type OriginalGenerateTextParams = Parameters<typeof originalGenerateText>[0];
|
|
15
|
+
type ModelenceGenerateTextOptions<T> = T extends unknown
|
|
16
|
+
? Omit<T, 'model'> & {
|
|
17
|
+
provider: Provider;
|
|
18
|
+
model: string;
|
|
19
|
+
}
|
|
20
|
+
: never;
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Options for the Modelence generateText function.
|
|
18
24
|
*
|
|
19
|
-
* This
|
|
25
|
+
* This type extends all the standard AI SDK generateText options,
|
|
20
26
|
* but replaces the model parameter with separate provider and model parameters.
|
|
21
27
|
*/
|
|
22
|
-
export
|
|
23
|
-
/** The AI provider name */
|
|
24
|
-
provider: Provider;
|
|
25
|
-
/** The specific model name */
|
|
26
|
-
model: string;
|
|
27
|
-
}
|
|
28
|
+
export type GenerateTextOptions = ModelenceGenerateTextOptions<OriginalGenerateTextParams>;
|
|
28
29
|
|
|
29
30
|
function getProviderModel(provider: Provider, model: string) {
|
|
30
31
|
switch (provider) {
|
|
@@ -62,8 +63,8 @@ function getProviderModel(provider: Provider, model: string) {
|
|
|
62
63
|
* import { generateText } from '@modelence/ai';
|
|
63
64
|
*
|
|
64
65
|
* const response = await generateText({
|
|
65
|
-
* provider: '
|
|
66
|
-
* model: '
|
|
66
|
+
* provider: 'anthropic',
|
|
67
|
+
* model: 'claude-sonnet-4-6',
|
|
67
68
|
* messages: [
|
|
68
69
|
* { role: 'user', content: 'Write a haiku about programming' }
|
|
69
70
|
* ],
|
|
@@ -73,8 +74,11 @@ function getProviderModel(provider: Provider, model: string) {
|
|
|
73
74
|
* console.log(response.text);
|
|
74
75
|
* ```
|
|
75
76
|
*/
|
|
76
|
-
export async function generateText(
|
|
77
|
+
export async function generateText(
|
|
78
|
+
options: GenerateTextOptions
|
|
79
|
+
): Promise<Awaited<ReturnType<typeof originalGenerateText>>> {
|
|
77
80
|
const { provider, model, ...restOptions } = options;
|
|
81
|
+
const providerModel = getProviderModel(provider, model);
|
|
78
82
|
|
|
79
83
|
const transaction = startTransaction('ai', 'ai:generateText', {
|
|
80
84
|
provider,
|
|
@@ -85,16 +89,16 @@ export async function generateText(options: GenerateTextOptions) {
|
|
|
85
89
|
|
|
86
90
|
try {
|
|
87
91
|
const result = await originalGenerateText({
|
|
88
|
-
model: getProviderModel(provider, model),
|
|
89
92
|
...restOptions,
|
|
93
|
+
model: providerModel,
|
|
90
94
|
});
|
|
91
95
|
|
|
92
96
|
if ('setContext' in transaction) {
|
|
93
97
|
transaction.end('success', {
|
|
94
98
|
context: {
|
|
95
99
|
usage: {
|
|
96
|
-
promptTokens: result.usage.
|
|
97
|
-
completionTokens: result.usage.
|
|
100
|
+
promptTokens: result.usage.inputTokens,
|
|
101
|
+
completionTokens: result.usage.outputTokens,
|
|
98
102
|
totalTokens: result.usage.totalTokens,
|
|
99
103
|
}
|
|
100
104
|
}
|