@pwshub/aisdk 0.0.3 → 0.0.5
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/README.md +139 -42
- package/index.d.ts +18 -8
- package/package.json +8 -6
- package/src/coerce.test.js +142 -0
- package/src/config.js +30 -0
- package/src/config.test.js +142 -0
- package/src/errors.js +1 -1
- package/src/index.js +26 -14
- package/src/models.js +401 -0
- package/src/providers.js +109 -11
- package/src/registry.js +114 -37
- package/src/registry.test.js +314 -0
- package/src/validation.js +9 -3
- package/src/validation.test.js +410 -0
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @pwshub/aisdk
|
|
2
2
|
|
|
3
|
-
A thin, unified AI client for OpenAI, Anthropic, Google, DashScope, and
|
|
3
|
+
A thin, unified AI client for OpenAI, Anthropic, Google, DashScope, DeepSeek, and Mistral with automatic parameter normalization and fallback support.
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/js/@pwshub%2Faisdk)
|
|
6
|
+

|
|
7
|
+

|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
@@ -41,7 +45,7 @@ const ai = createAi()
|
|
|
41
45
|
|
|
42
46
|
// Basic usage
|
|
43
47
|
const result = await ai.ask({
|
|
44
|
-
model: 'gpt-4o',
|
|
48
|
+
model: 'openai/gpt-4o',
|
|
45
49
|
apikey: 'your-api-key-here',
|
|
46
50
|
prompt: 'What is the capital of Vietnam?',
|
|
47
51
|
temperature: 0.5,
|
|
@@ -69,11 +73,11 @@ Creates an AI client instance.
|
|
|
69
73
|
Sends a text generation request.
|
|
70
74
|
|
|
71
75
|
**Parameters:**
|
|
72
|
-
- `model` (string, required):
|
|
73
|
-
- `apikey` (string, required): API key for the provider
|
|
76
|
+
- `model` (string, required): Use `provider/name` format (e.g., `anthropic/claude-sonnet-4-6`)
|
|
77
|
+
- `apikey` (string, required): API key for the provider. With ollama local, set to any string.
|
|
74
78
|
- `prompt` (string, required): The user message
|
|
75
79
|
- `system` (string, optional): Optional system prompt
|
|
76
|
-
- `fallbacks` (string[], optional): Ordered list of fallback model
|
|
80
|
+
- `fallbacks` (string[], optional): Ordered list of fallback models (same format as `model`)
|
|
77
81
|
- `providerOptions` (object, optional): Provider-specific options
|
|
78
82
|
- `temperature` (number, optional): Sampling temperature
|
|
79
83
|
- `maxTokens` (number, optional): Maximum output tokens
|
|
@@ -111,7 +115,7 @@ import { createAi } from '@pwshub/aisdk'
|
|
|
111
115
|
const ai = createAi()
|
|
112
116
|
|
|
113
117
|
const result = await ai.ask({
|
|
114
|
-
model: 'gpt-4o',
|
|
118
|
+
model: 'openai/gpt-4o',
|
|
115
119
|
apikey: process.env.OPENAI_API_KEY,
|
|
116
120
|
prompt: 'Explain quantum entanglement',
|
|
117
121
|
temperature: 0.7,
|
|
@@ -123,7 +127,7 @@ const result = await ai.ask({
|
|
|
123
127
|
|
|
124
128
|
```javascript
|
|
125
129
|
const result = await ai.ask({
|
|
126
|
-
model: 'claude-sonnet-4-6',
|
|
130
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
127
131
|
apikey: process.env.ANTHROPIC_API_KEY,
|
|
128
132
|
prompt: 'Write a haiku about TypeScript',
|
|
129
133
|
temperature: 0.5,
|
|
@@ -134,7 +138,7 @@ const result = await ai.ask({
|
|
|
134
138
|
|
|
135
139
|
```javascript
|
|
136
140
|
const result = await ai.ask({
|
|
137
|
-
model: 'gemini-2.5-flash',
|
|
141
|
+
model: 'google/gemini-2.5-flash',
|
|
138
142
|
apikey: process.env.GOOGLE_API_KEY,
|
|
139
143
|
prompt: 'What is 2+2?',
|
|
140
144
|
providerOptions: {
|
|
@@ -151,7 +155,7 @@ Gemini 2.5 Pro and other reasoning models use thinking tokens by default. Disabl
|
|
|
151
155
|
|
|
152
156
|
```javascript
|
|
153
157
|
const result = await ai.ask({
|
|
154
|
-
model: 'gemini-2.5-pro',
|
|
158
|
+
model: 'google/gemini-2.5-pro',
|
|
155
159
|
apikey: process.env.GOOGLE_API_KEY,
|
|
156
160
|
prompt: 'What is the capital of Vietnam?',
|
|
157
161
|
maxTokens: 256,
|
|
@@ -171,10 +175,10 @@ const result = await ai.ask({
|
|
|
171
175
|
```javascript
|
|
172
176
|
try {
|
|
173
177
|
const result = await ai.ask({
|
|
174
|
-
model: 'gpt-4o',
|
|
178
|
+
model: 'openai/gpt-4o',
|
|
175
179
|
apikey: process.env.OPENAI_API_KEY,
|
|
176
180
|
prompt: 'Hello',
|
|
177
|
-
fallbacks: ['gpt-4o-mini', 'claude-haiku-4-5'],
|
|
181
|
+
fallbacks: ['openai/gpt-4o-mini', 'anthropic/claude-haiku-4-5'],
|
|
178
182
|
})
|
|
179
183
|
|
|
180
184
|
if (result.model !== 'gpt-4o') {
|
|
@@ -193,7 +197,7 @@ try {
|
|
|
193
197
|
|
|
194
198
|
```javascript
|
|
195
199
|
const result = await ai.ask({
|
|
196
|
-
model: 'qwen3.5-plus',
|
|
200
|
+
model: 'dashscope/qwen3.5-plus',
|
|
197
201
|
apikey: process.env.DASHSCOPE_API_KEY,
|
|
198
202
|
prompt: 'Hello',
|
|
199
203
|
})
|
|
@@ -223,7 +227,7 @@ const aiCN = createAi({
|
|
|
223
227
|
|
|
224
228
|
// Use the regional client
|
|
225
229
|
const result = await aiSingapore.ask({
|
|
226
|
-
model: 'qwen3.5-plus',
|
|
230
|
+
model: 'dashscope/qwen3.5-plus',
|
|
227
231
|
apikey: process.env.DASHSCOPE_API_KEY,
|
|
228
232
|
prompt: 'Hello from Singapore!',
|
|
229
233
|
})
|
|
@@ -233,56 +237,146 @@ const result = await aiSingapore.ask({
|
|
|
233
237
|
|
|
234
238
|
```javascript
|
|
235
239
|
const result = await ai.ask({
|
|
236
|
-
model: 'deepseek-chat',
|
|
240
|
+
model: 'deepseek/deepseek-chat',
|
|
237
241
|
apikey: process.env.DEEPSEEK_API_KEY,
|
|
238
242
|
prompt: 'Hello',
|
|
239
243
|
})
|
|
240
244
|
```
|
|
241
245
|
|
|
246
|
+
### Mistral
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
const result = await ai.ask({
|
|
250
|
+
model: 'mistral/mistral-large-latest',
|
|
251
|
+
apikey: process.env.MISTRAL_API_KEY,
|
|
252
|
+
prompt: 'Hello',
|
|
253
|
+
temperature: 0.7,
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Mistral with Random Seed
|
|
258
|
+
|
|
259
|
+
For reproducible results, use `randomSeed`:
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
const result = await ai.ask({
|
|
263
|
+
model: 'mistral/mistral-medium-latest',
|
|
264
|
+
apikey: process.env.MISTRAL_API_KEY,
|
|
265
|
+
prompt: 'Write a poem',
|
|
266
|
+
randomSeed: 42,
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
242
270
|
## Supported Models
|
|
243
271
|
|
|
244
|
-
|
|
272
|
+
The library comes with just a few popular models configured in src/models.js
|
|
273
|
+
|
|
274
|
+
## Model Management
|
|
245
275
|
|
|
246
|
-
-
|
|
247
|
-
- **Anthropic**: Any Anthropic model
|
|
248
|
-
- **Google**: Any Google model
|
|
249
|
-
- **DashScope**: Any DashScope model
|
|
250
|
-
- **DeepSeek**: Any DeepSeek model
|
|
276
|
+
Models are automatically loaded from the built-in registry when the library is imported. You can add custom models or replace the entire list with your own (e.g., from a CMS).
|
|
251
277
|
|
|
252
|
-
###
|
|
278
|
+
### Adding Custom Models
|
|
253
279
|
|
|
254
|
-
|
|
280
|
+
Use `addModels()` to add models to the existing registry. Only `name` and `provider` are required — other fields get sensible defaults:
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
import { createAi, addModels, listModels } from '@pwshub/aisdk'
|
|
284
|
+
|
|
285
|
+
// Add minimal model records (auto-generates ID and sets defaults)
|
|
286
|
+
addModels([
|
|
287
|
+
{ name: 'llama3.2', provider: 'ollama' },
|
|
288
|
+
{ name: 'mistral', provider: 'ollama' },
|
|
289
|
+
{ name: 'gemma3', provider: 'ollama' },
|
|
290
|
+
])
|
|
291
|
+
|
|
292
|
+
// Add models with custom pricing
|
|
293
|
+
addModels([
|
|
294
|
+
{
|
|
295
|
+
name: 'my-custom-model',
|
|
296
|
+
provider: 'openai',
|
|
297
|
+
input_price: 0.5,
|
|
298
|
+
output_price: 1.5,
|
|
299
|
+
max_in: 128000,
|
|
300
|
+
max_out: 16384,
|
|
301
|
+
},
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
// View all available models
|
|
305
|
+
console.log(listModels())
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Default values for missing fields:**
|
|
309
|
+
- `id`: Auto-generated as `${provider}_${name}` (e.g., `ollama_llama3.2`)
|
|
310
|
+
- `input_price`, `output_price`, `cache_price`: `0`
|
|
311
|
+
- `max_in`: `32000`
|
|
312
|
+
- `max_out`: `8000`
|
|
313
|
+
- `enable`: `true`
|
|
314
|
+
|
|
315
|
+
### Loading Models from CMS
|
|
316
|
+
|
|
317
|
+
Use `setModels()` to replace the entire registry with models from your CMS:
|
|
255
318
|
|
|
256
319
|
```javascript
|
|
257
320
|
import { createAi, setModels } from '@pwshub/aisdk'
|
|
258
321
|
|
|
259
|
-
//
|
|
322
|
+
// Fetch models from your CMS
|
|
260
323
|
const modelsFromCms = await fetch('https://cms.example.com/api/models').then(r => r.json())
|
|
324
|
+
|
|
325
|
+
// Expected format from CMS:
|
|
326
|
+
// [
|
|
327
|
+
// { id: 'uuid-123', name: 'llama3.2', provider: 'ollama', ... },
|
|
328
|
+
// { id: 'uuid-456', name: 'mistral', provider: 'ollama', ... }
|
|
329
|
+
// ]
|
|
330
|
+
|
|
261
331
|
setModels(modelsFromCms)
|
|
262
332
|
|
|
263
333
|
const ai = createAi()
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
> **Note:** Model `id` can be any unique string (UUID, slug, etc.). The library uses it for internal tracking. When using models from CMS, you reference them by `provider/name` format (see below).
|
|
337
|
+
|
|
338
|
+
### Using Models
|
|
339
|
+
|
|
340
|
+
Models MUST be referenced in `provider/name` format:
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
const ai = createAi()
|
|
344
|
+
|
|
345
|
+
// Correct: provider/name format
|
|
346
|
+
await ai.ask({
|
|
347
|
+
model: 'openai/gpt-4o',
|
|
348
|
+
apikey: process.env.OPENAI_API_KEY,
|
|
349
|
+
prompt: 'Hello',
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
// Correct: works for all providers
|
|
353
|
+
await ai.ask({
|
|
354
|
+
model: 'ollama/llama3.2',
|
|
355
|
+
apikey: '',
|
|
356
|
+
prompt: 'Hello',
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
await ai.ask({
|
|
360
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
361
|
+
apikey: process.env.ANTHROPIC_API_KEY,
|
|
362
|
+
prompt: 'Hello',
|
|
268
363
|
})
|
|
269
364
|
```
|
|
270
365
|
|
|
271
366
|
### Model Record Format
|
|
272
367
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
> **Note**: The `examples/` folder includes `models.json` as a reference for running evaluation scripts.
|
|
368
|
+
| Field | Required | Default | Description |
|
|
369
|
+
|-------|----------|---------|-------------|
|
|
370
|
+
| `name` | Yes | - | Model name used in API calls |
|
|
371
|
+
| `provider` | Yes | - | Provider ID (openai, anthropic, google, dashscope, deepseek, mistral, ollama) |
|
|
372
|
+
| `id` | No | `${provider}_${name}` | Unique identifier (auto-generated if not provided) |
|
|
373
|
+
| `input_price` | No | `0` | Price per 1M input tokens (USD) |
|
|
374
|
+
| `output_price` | No | `0` | Price per 1M output tokens (USD) |
|
|
375
|
+
| `cache_price` | No | `0` | Price per 1M cached tokens (USD) |
|
|
376
|
+
| `max_in` | No | `32000` | Maximum input tokens (context window) |
|
|
377
|
+
| `max_out` | No | `8000` | Maximum output tokens |
|
|
378
|
+
| `enable` | No | `true` | Enable/disable the model |
|
|
379
|
+
| `supportedParams` | No | Provider defaults | Array of supported parameter names |
|
|
286
380
|
|
|
287
381
|
## Error Handling
|
|
288
382
|
|
|
@@ -293,7 +387,7 @@ const ai = createAi()
|
|
|
293
387
|
|
|
294
388
|
try {
|
|
295
389
|
const result = await ai.ask({
|
|
296
|
-
model: 'gpt-4o',
|
|
390
|
+
model: 'openai/gpt-4o',
|
|
297
391
|
apikey: process.env.OPENAI_API_KEY,
|
|
298
392
|
prompt: 'Hello',
|
|
299
393
|
})
|
|
@@ -329,6 +423,9 @@ DASHSCOPE_API_KEY=your-key npm run eval:dashscope
|
|
|
329
423
|
|
|
330
424
|
# DeepSeek
|
|
331
425
|
DEEPSEEK_API_KEY=your-key npm run eval:deepseek
|
|
426
|
+
|
|
427
|
+
# Mistral
|
|
428
|
+
MISTRAL_API_KEY=your-key npm run eval:mistral
|
|
332
429
|
```
|
|
333
430
|
|
|
334
431
|
## Development
|
|
@@ -357,4 +454,4 @@ npm run lint:fix
|
|
|
357
454
|
|
|
358
455
|
## License
|
|
359
456
|
|
|
360
|
-
MIT
|
|
457
|
+
The MIT License (MIT)
|
package/index.d.ts
CHANGED
|
@@ -6,11 +6,17 @@ export interface AiOptions {
|
|
|
6
6
|
gatewayUrl?: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export interface Message {
|
|
10
|
+
role: 'user' | 'assistant' | 'system';
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
export interface AskParams {
|
|
10
15
|
model: string;
|
|
11
16
|
apikey: string;
|
|
12
|
-
prompt
|
|
17
|
+
prompt?: string;
|
|
13
18
|
system?: string;
|
|
19
|
+
messages?: Message[];
|
|
14
20
|
fallbacks?: string[];
|
|
15
21
|
providerOptions?: Record<string, unknown>;
|
|
16
22
|
temperature?: number;
|
|
@@ -19,6 +25,9 @@ export interface AskParams {
|
|
|
19
25
|
topK?: number;
|
|
20
26
|
frequencyPenalty?: number;
|
|
21
27
|
presencePenalty?: number;
|
|
28
|
+
randomSeed?: number;
|
|
29
|
+
seed?: number;
|
|
30
|
+
numPredict?: number;
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
export interface Usage {
|
|
@@ -36,15 +45,15 @@ export interface AskResult {
|
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
export interface ModelRecord {
|
|
39
|
-
id
|
|
48
|
+
id?: string;
|
|
40
49
|
name: string;
|
|
41
50
|
provider: string;
|
|
42
|
-
input_price
|
|
43
|
-
output_price
|
|
44
|
-
cache_price
|
|
45
|
-
max_in
|
|
46
|
-
max_out
|
|
47
|
-
enable
|
|
51
|
+
input_price?: number;
|
|
52
|
+
output_price?: number;
|
|
53
|
+
cache_price?: number;
|
|
54
|
+
max_in?: number;
|
|
55
|
+
max_out?: number;
|
|
56
|
+
enable?: boolean;
|
|
48
57
|
supportedParams?: string[];
|
|
49
58
|
}
|
|
50
59
|
|
|
@@ -70,5 +79,6 @@ export interface AiClient {
|
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
export function createAi(opts?: AiOptions): AiClient;
|
|
82
|
+
export function addModels(models: ModelRecord[]): void;
|
|
73
83
|
export function setModels(models: ModelRecord[]): void;
|
|
74
84
|
export function listModels(): ModelRecord[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pwshub/aisdk",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "A thin, unified AI client for OpenAI, Anthropic, Google, DashScope, and
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "A thin, unified AI client for OpenAI, Anthropic, Google, DashScope, DeepSeek, and Mistral with automatic param normalization and fallback support",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/pwshub/aisdk"
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
"index.d.ts"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"test": "node --test
|
|
26
|
-
"lint": "eslint src/
|
|
27
|
-
"lint:fix": "eslint src/
|
|
25
|
+
"test": "node --test src/*.test.js",
|
|
26
|
+
"lint": "eslint src/",
|
|
27
|
+
"lint:fix": "eslint src/ --fix",
|
|
28
28
|
"eval:openai": "node examples/openai.js",
|
|
29
29
|
"eval:anthropic": "node examples/anthropic.js",
|
|
30
30
|
"eval:google": "node examples/google.js",
|
|
31
31
|
"eval:dashscope": "node examples/dashscope.js",
|
|
32
|
-
"eval:deepseek": "node examples/deepseek.js"
|
|
32
|
+
"eval:deepseek": "node examples/deepseek.js",
|
|
33
|
+
"eval:mistral": "node examples/mistral.js"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@eslint/js": "^10.0.1",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"gpt",
|
|
48
49
|
"qwen",
|
|
49
50
|
"deepseek",
|
|
51
|
+
"mistral",
|
|
50
52
|
"chat",
|
|
51
53
|
"generation",
|
|
52
54
|
"sdk"
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for coerce module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
describe, it,
|
|
7
|
+
} from 'node:test'
|
|
8
|
+
import assert from 'node:assert'
|
|
9
|
+
import { coerceConfig } from '../src/coerce.js'
|
|
10
|
+
|
|
11
|
+
describe('coerceConfig', () => {
|
|
12
|
+
describe('openai', () => {
|
|
13
|
+
it('should clamp temperature to valid range [0, 2]', () => {
|
|
14
|
+
const config = { temperature: 3 }
|
|
15
|
+
const result = coerceConfig(config, 'openai')
|
|
16
|
+
assert.strictEqual(result.temperature, 2)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should clamp temperature below range', () => {
|
|
20
|
+
const config = { temperature: -1 }
|
|
21
|
+
const result = coerceConfig(config, 'openai')
|
|
22
|
+
assert.strictEqual(result.temperature, 0)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should not clamp temperature within range', () => {
|
|
26
|
+
const config = { temperature: 1 }
|
|
27
|
+
const result = coerceConfig(config, 'openai')
|
|
28
|
+
assert.strictEqual(result.temperature, 1)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should clamp topP to valid range [0, 1]', () => {
|
|
32
|
+
const config = { topP: 1.5 }
|
|
33
|
+
const result = coerceConfig(config, 'openai')
|
|
34
|
+
assert.strictEqual(result.topP, 1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should clamp frequencyPenalty to valid range [-2, 2]', () => {
|
|
38
|
+
const config = { frequencyPenalty: 3 }
|
|
39
|
+
const result = coerceConfig(config, 'openai')
|
|
40
|
+
assert.strictEqual(result.frequencyPenalty, 2)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should clamp presencePenalty to valid range [-2, 2]', () => {
|
|
44
|
+
const config = { presencePenalty: -3 }
|
|
45
|
+
const result = coerceConfig(config, 'openai')
|
|
46
|
+
assert.strictEqual(result.presencePenalty, -2)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('anthropic', () => {
|
|
51
|
+
it('should clamp temperature to valid range [0, 1]', () => {
|
|
52
|
+
const config = { temperature: 1.5 }
|
|
53
|
+
const result = coerceConfig(config, 'anthropic')
|
|
54
|
+
assert.strictEqual(result.temperature, 1)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should clamp topK to valid range [1, 100]', () => {
|
|
58
|
+
const config = { topK: 150 }
|
|
59
|
+
const result = coerceConfig(config, 'anthropic')
|
|
60
|
+
assert.strictEqual(result.topK, 100)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should clamp topK below range', () => {
|
|
64
|
+
const config = { topK: 0 }
|
|
65
|
+
const result = coerceConfig(config, 'anthropic')
|
|
66
|
+
assert.strictEqual(result.topK, 1)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('google', () => {
|
|
71
|
+
it('should clamp temperature to valid range [0, 2]', () => {
|
|
72
|
+
const config = { temperature: 3 }
|
|
73
|
+
const result = coerceConfig(config, 'google')
|
|
74
|
+
assert.strictEqual(result.temperature, 2)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should clamp topK to valid range [1, 100]', () => {
|
|
78
|
+
const config = { topK: 200 }
|
|
79
|
+
const result = coerceConfig(config, 'google')
|
|
80
|
+
assert.strictEqual(result.topK, 100)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('dashscope', () => {
|
|
85
|
+
it('should clamp temperature to valid range [0, 2]', () => {
|
|
86
|
+
const config = { temperature: 5 }
|
|
87
|
+
const result = coerceConfig(config, 'dashscope')
|
|
88
|
+
assert.strictEqual(result.temperature, 2)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should clamp topP to valid range [0, 1]', () => {
|
|
92
|
+
const config = { topP: 2 }
|
|
93
|
+
const result = coerceConfig(config, 'dashscope')
|
|
94
|
+
assert.strictEqual(result.topP, 1)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('deepseek', () => {
|
|
99
|
+
it('should clamp temperature to valid range [0, 2]', () => {
|
|
100
|
+
const config = { temperature: 3 }
|
|
101
|
+
const result = coerceConfig(config, 'deepseek')
|
|
102
|
+
assert.strictEqual(result.temperature, 2)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should clamp frequencyPenalty to valid range [-2, 2]', () => {
|
|
106
|
+
const config = { frequencyPenalty: 5 }
|
|
107
|
+
const result = coerceConfig(config, 'deepseek')
|
|
108
|
+
assert.strictEqual(result.frequencyPenalty, 2)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('edge cases', () => {
|
|
113
|
+
it('should return config unchanged for unknown provider', () => {
|
|
114
|
+
const config = { temperature: 100 }
|
|
115
|
+
const result = coerceConfig(config, 'unknown')
|
|
116
|
+
assert.strictEqual(result.temperature, 100)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should not clamp non-numeric values', () => {
|
|
120
|
+
const config = { temperature: 'hot' }
|
|
121
|
+
const result = coerceConfig(config, 'openai')
|
|
122
|
+
assert.strictEqual(result.temperature, 'hot')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should handle empty config', () => {
|
|
126
|
+
const result = coerceConfig({}, 'openai')
|
|
127
|
+
assert.deepStrictEqual(result, {})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should clamp multiple values at once', () => {
|
|
131
|
+
const config = {
|
|
132
|
+
temperature: 5,
|
|
133
|
+
topP: 2,
|
|
134
|
+
maxTokens: 100,
|
|
135
|
+
}
|
|
136
|
+
const result = coerceConfig(config, 'openai')
|
|
137
|
+
assert.strictEqual(result.temperature, 2)
|
|
138
|
+
assert.strictEqual(result.topP, 1)
|
|
139
|
+
assert.strictEqual(result.maxTokens, 100) // maxTokens has no range
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
})
|
package/src/config.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* @property {number} [topK]
|
|
22
22
|
* @property {number} [frequencyPenalty]
|
|
23
23
|
* @property {number} [presencePenalty]
|
|
24
|
+
* @property {number} [randomSeed]
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -147,6 +148,35 @@ const WIRE_KEYS = {
|
|
|
147
148
|
},
|
|
148
149
|
},
|
|
149
150
|
},
|
|
151
|
+
mistral: {
|
|
152
|
+
temperature: {
|
|
153
|
+
wireKey: 'temperature', range: {
|
|
154
|
+
min: 0, max: 2,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
maxTokens: { wireKey: 'max_tokens' },
|
|
158
|
+
topP: {
|
|
159
|
+
wireKey: 'top_p', range: {
|
|
160
|
+
min: 0, max: 1,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
randomSeed: { wireKey: 'random_seed' },
|
|
164
|
+
},
|
|
165
|
+
ollama: {
|
|
166
|
+
temperature: {
|
|
167
|
+
wireKey: 'temperature', range: {
|
|
168
|
+
min: 0, max: 2,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
maxTokens: { wireKey: 'num_predict' },
|
|
172
|
+
topP: {
|
|
173
|
+
wireKey: 'top_p', range: {
|
|
174
|
+
min: 0, max: 1,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
topK: { wireKey: 'top_k' },
|
|
178
|
+
seed: { wireKey: 'seed' },
|
|
179
|
+
},
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
/**
|