@pwshub/aisdk 0.0.4 → 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 +126 -51
- package/index.d.ts +17 -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 +8 -8
- package/src/models.js +56 -0
- package/src/providers.js +72 -2
- package/src/registry.js +75 -34
- package/src/registry.test.js +314 -0
- package/src/validation.test.js +410 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
4
|
|
|
5
5
|
[](https://badge.fury.io/js/@pwshub%2Faisdk)
|
|
6
6
|

|
|
@@ -45,7 +45,7 @@ const ai = createAi()
|
|
|
45
45
|
|
|
46
46
|
// Basic usage
|
|
47
47
|
const result = await ai.ask({
|
|
48
|
-
model: 'gpt-4o',
|
|
48
|
+
model: 'openai/gpt-4o',
|
|
49
49
|
apikey: 'your-api-key-here',
|
|
50
50
|
prompt: 'What is the capital of Vietnam?',
|
|
51
51
|
temperature: 0.5,
|
|
@@ -73,11 +73,11 @@ Creates an AI client instance.
|
|
|
73
73
|
Sends a text generation request.
|
|
74
74
|
|
|
75
75
|
**Parameters:**
|
|
76
|
-
- `model` (string, required):
|
|
77
|
-
- `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.
|
|
78
78
|
- `prompt` (string, required): The user message
|
|
79
79
|
- `system` (string, optional): Optional system prompt
|
|
80
|
-
- `fallbacks` (string[], optional): Ordered list of fallback model
|
|
80
|
+
- `fallbacks` (string[], optional): Ordered list of fallback models (same format as `model`)
|
|
81
81
|
- `providerOptions` (object, optional): Provider-specific options
|
|
82
82
|
- `temperature` (number, optional): Sampling temperature
|
|
83
83
|
- `maxTokens` (number, optional): Maximum output tokens
|
|
@@ -115,7 +115,7 @@ import { createAi } from '@pwshub/aisdk'
|
|
|
115
115
|
const ai = createAi()
|
|
116
116
|
|
|
117
117
|
const result = await ai.ask({
|
|
118
|
-
model: 'gpt-4o',
|
|
118
|
+
model: 'openai/gpt-4o',
|
|
119
119
|
apikey: process.env.OPENAI_API_KEY,
|
|
120
120
|
prompt: 'Explain quantum entanglement',
|
|
121
121
|
temperature: 0.7,
|
|
@@ -127,7 +127,7 @@ const result = await ai.ask({
|
|
|
127
127
|
|
|
128
128
|
```javascript
|
|
129
129
|
const result = await ai.ask({
|
|
130
|
-
model: 'claude-sonnet-4-6',
|
|
130
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
131
131
|
apikey: process.env.ANTHROPIC_API_KEY,
|
|
132
132
|
prompt: 'Write a haiku about TypeScript',
|
|
133
133
|
temperature: 0.5,
|
|
@@ -138,7 +138,7 @@ const result = await ai.ask({
|
|
|
138
138
|
|
|
139
139
|
```javascript
|
|
140
140
|
const result = await ai.ask({
|
|
141
|
-
model: 'gemini-2.5-flash',
|
|
141
|
+
model: 'google/gemini-2.5-flash',
|
|
142
142
|
apikey: process.env.GOOGLE_API_KEY,
|
|
143
143
|
prompt: 'What is 2+2?',
|
|
144
144
|
providerOptions: {
|
|
@@ -155,7 +155,7 @@ Gemini 2.5 Pro and other reasoning models use thinking tokens by default. Disabl
|
|
|
155
155
|
|
|
156
156
|
```javascript
|
|
157
157
|
const result = await ai.ask({
|
|
158
|
-
model: 'gemini-2.5-pro',
|
|
158
|
+
model: 'google/gemini-2.5-pro',
|
|
159
159
|
apikey: process.env.GOOGLE_API_KEY,
|
|
160
160
|
prompt: 'What is the capital of Vietnam?',
|
|
161
161
|
maxTokens: 256,
|
|
@@ -175,10 +175,10 @@ const result = await ai.ask({
|
|
|
175
175
|
```javascript
|
|
176
176
|
try {
|
|
177
177
|
const result = await ai.ask({
|
|
178
|
-
model: 'gpt-4o',
|
|
178
|
+
model: 'openai/gpt-4o',
|
|
179
179
|
apikey: process.env.OPENAI_API_KEY,
|
|
180
180
|
prompt: 'Hello',
|
|
181
|
-
fallbacks: ['gpt-4o-mini', 'claude-haiku-4-5'],
|
|
181
|
+
fallbacks: ['openai/gpt-4o-mini', 'anthropic/claude-haiku-4-5'],
|
|
182
182
|
})
|
|
183
183
|
|
|
184
184
|
if (result.model !== 'gpt-4o') {
|
|
@@ -197,7 +197,7 @@ try {
|
|
|
197
197
|
|
|
198
198
|
```javascript
|
|
199
199
|
const result = await ai.ask({
|
|
200
|
-
model: 'qwen3.5-plus',
|
|
200
|
+
model: 'dashscope/qwen3.5-plus',
|
|
201
201
|
apikey: process.env.DASHSCOPE_API_KEY,
|
|
202
202
|
prompt: 'Hello',
|
|
203
203
|
})
|
|
@@ -227,7 +227,7 @@ const aiCN = createAi({
|
|
|
227
227
|
|
|
228
228
|
// Use the regional client
|
|
229
229
|
const result = await aiSingapore.ask({
|
|
230
|
-
model: 'qwen3.5-plus',
|
|
230
|
+
model: 'dashscope/qwen3.5-plus',
|
|
231
231
|
apikey: process.env.DASHSCOPE_API_KEY,
|
|
232
232
|
prompt: 'Hello from Singapore!',
|
|
233
233
|
})
|
|
@@ -237,74 +237,146 @@ const result = await aiSingapore.ask({
|
|
|
237
237
|
|
|
238
238
|
```javascript
|
|
239
239
|
const result = await ai.ask({
|
|
240
|
-
model: 'deepseek-chat',
|
|
240
|
+
model: 'deepseek/deepseek-chat',
|
|
241
241
|
apikey: process.env.DEEPSEEK_API_KEY,
|
|
242
242
|
prompt: 'Hello',
|
|
243
243
|
})
|
|
244
244
|
```
|
|
245
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
|
+
|
|
246
270
|
## Supported Models
|
|
247
271
|
|
|
248
|
-
The library comes with
|
|
272
|
+
The library comes with just a few popular models configured in src/models.js
|
|
273
|
+
|
|
274
|
+
## Model Management
|
|
249
275
|
|
|
250
|
-
|
|
251
|
-
- **Anthropic**: claude-haiku-4-5, claude-sonnet-4-6, claude-sonnet-4-5, claude-opus-4-6
|
|
252
|
-
- **Google**: gemini-2.5-flash, gemini-2.5-flash-lite, gemini-2.5-pro, gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview
|
|
253
|
-
- **DashScope**: qwen-flash, qwen3.5-flash, qwen-plus, qwen3.5-plus, qwen-max, qwen3-max
|
|
254
|
-
- **DeepSeek**: deepseek-chat, deepseek-reasoner
|
|
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).
|
|
255
277
|
|
|
256
|
-
###
|
|
278
|
+
### Adding Custom Models
|
|
257
279
|
|
|
258
|
-
|
|
280
|
+
Use `addModels()` to add models to the existing registry. Only `name` and `provider` are required — other fields get sensible defaults:
|
|
259
281
|
|
|
260
282
|
```javascript
|
|
261
|
-
import { createAi, addModels,
|
|
283
|
+
import { createAi, addModels, listModels } from '@pwshub/aisdk'
|
|
262
284
|
|
|
263
|
-
//
|
|
264
|
-
|
|
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
|
+
])
|
|
265
291
|
|
|
266
|
-
// Add
|
|
292
|
+
// Add models with custom pricing
|
|
267
293
|
addModels([
|
|
268
294
|
{
|
|
269
|
-
id: 'my-custom-model',
|
|
270
295
|
name: 'my-custom-model',
|
|
271
296
|
provider: 'openai',
|
|
272
|
-
input_price:
|
|
273
|
-
output_price:
|
|
274
|
-
cache_price: 0.5,
|
|
297
|
+
input_price: 0.5,
|
|
298
|
+
output_price: 1.5,
|
|
275
299
|
max_in: 128000,
|
|
276
300
|
max_out: 16384,
|
|
277
|
-
enable: true,
|
|
278
301
|
},
|
|
279
302
|
])
|
|
280
303
|
|
|
281
|
-
//
|
|
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:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
import { createAi, setModels } from '@pwshub/aisdk'
|
|
321
|
+
|
|
322
|
+
// Fetch models from your CMS
|
|
282
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
|
+
|
|
283
331
|
setModels(modelsFromCms)
|
|
284
332
|
|
|
285
333
|
const ai = createAi()
|
|
286
|
-
const result = await ai.ask({
|
|
287
|
-
model: 'gemini-2.5-flash',
|
|
288
|
-
apikey: 'your-api-key',
|
|
289
|
-
prompt: 'Hello!',
|
|
290
|
-
})
|
|
291
334
|
```
|
|
292
335
|
|
|
293
|
-
> **Note:**
|
|
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',
|
|
363
|
+
})
|
|
364
|
+
```
|
|
294
365
|
|
|
295
366
|
### Model Record Format
|
|
296
367
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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 |
|
|
308
380
|
|
|
309
381
|
## Error Handling
|
|
310
382
|
|
|
@@ -315,7 +387,7 @@ const ai = createAi()
|
|
|
315
387
|
|
|
316
388
|
try {
|
|
317
389
|
const result = await ai.ask({
|
|
318
|
-
model: 'gpt-4o',
|
|
390
|
+
model: 'openai/gpt-4o',
|
|
319
391
|
apikey: process.env.OPENAI_API_KEY,
|
|
320
392
|
prompt: 'Hello',
|
|
321
393
|
})
|
|
@@ -351,6 +423,9 @@ DASHSCOPE_API_KEY=your-key npm run eval:dashscope
|
|
|
351
423
|
|
|
352
424
|
# DeepSeek
|
|
353
425
|
DEEPSEEK_API_KEY=your-key npm run eval:deepseek
|
|
426
|
+
|
|
427
|
+
# Mistral
|
|
428
|
+
MISTRAL_API_KEY=your-key npm run eval:mistral
|
|
354
429
|
```
|
|
355
430
|
|
|
356
431
|
## Development
|
|
@@ -379,4 +454,4 @@ npm run lint:fix
|
|
|
379
454
|
|
|
380
455
|
## License
|
|
381
456
|
|
|
382
|
-
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
|
|
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
|
/**
|