modelmix 4.6.0 → 4.6.2
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/MODELS.md +9 -0
- package/README.md +4 -0
- package/demo/default.env +1 -0
- package/index.js +66 -6
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +5 -2
- package/test/kimi.test.js +144 -1
package/MODELS.md
CHANGED
|
@@ -344,6 +344,15 @@ All providers inherit from `MixCustom` base class which provides common function
|
|
|
344
344
|
- **Output Format**: Same as OpenAI
|
|
345
345
|
- **Special Notes**: Uses OpenAI-compatible format
|
|
346
346
|
|
|
347
|
+
### Moonshot (MixKimi)
|
|
348
|
+
- **Base URL**: `https://api.moonshot.ai/v1/chat/completions`
|
|
349
|
+
- **Input Format**: Same as OpenAI
|
|
350
|
+
- **Output Format**: Same as OpenAI
|
|
351
|
+
- **Special Notes**:
|
|
352
|
+
- Requires `MOONSHOT_API_KEY` environment variable
|
|
353
|
+
- Available model: `kimi-k3`
|
|
354
|
+
- K3 always uses thinking and requires fixed sampling parameters to be omitted
|
|
355
|
+
|
|
347
356
|
### MiniMax (MixMiniMax)
|
|
348
357
|
- **Base URL**: `https://api.minimax.io/v1/chat/completions`
|
|
349
358
|
- **Input Format**: Same as OpenAI
|
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ Only the API keys you plan to use are required.
|
|
|
32
32
|
ANTHROPIC_API_KEY="sk-ant-..."
|
|
33
33
|
OPENAI_API_KEY="sk-proj-..."
|
|
34
34
|
OPENROUTER_API_KEY="sk-or-..."
|
|
35
|
+
MOONSHOT_API_KEY="your-moonshot-key..."
|
|
35
36
|
MINIMAX_API_KEY="your-minimax-key..."
|
|
36
37
|
NVIDIA_API_KEY="nvapi-..."
|
|
37
38
|
...
|
|
@@ -175,6 +176,7 @@ Here's a comprehensive list of available methods:
|
|
|
175
176
|
| `sonar()` | Perplexity | sonar | [\$1.00 / \$1.00][4] |
|
|
176
177
|
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
|
|
177
178
|
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
|
|
179
|
+
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00 / \$15.00][11] |
|
|
178
180
|
| `kimiK25think()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
|
|
179
181
|
| `kimiK26think()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
|
|
180
182
|
|
|
@@ -188,10 +190,12 @@ Here's a comprehensive list of available methods:
|
|
|
188
190
|
[8]: https://lambda.ai/inference "Lambda Pricing"
|
|
189
191
|
[9]: https://platform.minimax.io/docs/api-reference/anthropic-api-compatible-cache#supported-models-and-pricing "MiniMax Pricing"
|
|
190
192
|
[10]: https://fireworks.ai/pricing#serverless-pricing "Fireworks Pricing"
|
|
193
|
+
[11]: https://platform.kimi.ai/docs/guide/kimi-k3-pricing "Kimi K3 Pricing"
|
|
191
194
|
|
|
192
195
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
193
196
|
For NVIDIA on DeepSeek V4 Pro, use `deepseekV4Pro({ mix: { nvidia: true } })`.
|
|
194
197
|
For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
|
|
198
|
+
For OpenRouter instead of Moonshot's native API, use `kimiK3({ mix: { moonshot: false, openrouter: true } })`.
|
|
195
199
|
|
|
196
200
|
```javascript
|
|
197
201
|
const result = await ModelMix.new({
|
package/demo/default.env
CHANGED
package/index.js
CHANGED
|
@@ -118,6 +118,9 @@ const MODEL_PRICING = {
|
|
|
118
118
|
// Kimi K2.5 (Together/Fireworks/OpenRouter)
|
|
119
119
|
'moonshotai/Kimi-K2.5': [0.50, 2.80],
|
|
120
120
|
'moonshotai/kimi-k2.5': [0.50, 2.80],
|
|
121
|
+
// Kimi K3
|
|
122
|
+
'kimi-k3': [3.00, 15.00],
|
|
123
|
+
'moonshotai/kimi-k3': [3.00, 15.00],
|
|
121
124
|
// DeepSeek V3.2 (OpenRouter)
|
|
122
125
|
'deepseek/deepseek-v3.2': [0.56, 1.68],
|
|
123
126
|
// GLM 4.7 (OpenRouter/Cerebras)
|
|
@@ -523,6 +526,13 @@ class ModelMix {
|
|
|
523
526
|
return this;
|
|
524
527
|
}
|
|
525
528
|
|
|
529
|
+
kimiK3({ options = {}, config = {}, mix = { moonshot: true, openrouter: false } } = {}) {
|
|
530
|
+
mix = { ...this.mix, ...mix };
|
|
531
|
+
if (mix.moonshot) this.attach('kimi-k3', new MixKimi({ options, config }));
|
|
532
|
+
if (mix.openrouter) this.attach('moonshotai/kimi-k3', new MixOpenRouter({ options, config }));
|
|
533
|
+
return this;
|
|
534
|
+
}
|
|
535
|
+
|
|
526
536
|
kimiK25think({ options = {}, config = {}, mix = { together: true } } = {}) {
|
|
527
537
|
mix = { ...this.mix, ...mix };
|
|
528
538
|
if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
|
|
@@ -1079,8 +1089,9 @@ class ModelMix {
|
|
|
1079
1089
|
}
|
|
1080
1090
|
|
|
1081
1091
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
1082
|
-
|
|
1083
|
-
|
|
1092
|
+
if (result.assistantMessage) {
|
|
1093
|
+
this.messages.push(result.assistantMessage);
|
|
1094
|
+
} else if (result.message) {
|
|
1084
1095
|
if (result.signature) {
|
|
1085
1096
|
this.messages.push({
|
|
1086
1097
|
role: "assistant", content: [{
|
|
@@ -1094,7 +1105,9 @@ class ModelMix {
|
|
|
1094
1105
|
}
|
|
1095
1106
|
}
|
|
1096
1107
|
|
|
1097
|
-
|
|
1108
|
+
if (!result.assistantMessage) {
|
|
1109
|
+
this.messages.push({ role: "assistant", content: null, tool_calls: result.toolCalls });
|
|
1110
|
+
}
|
|
1098
1111
|
|
|
1099
1112
|
const toolResults = await this.processToolCalls(result.toolCalls);
|
|
1100
1113
|
for (const toolResult of toolResults) {
|
|
@@ -1153,7 +1166,9 @@ class ModelMix {
|
|
|
1153
1166
|
this.messages = [];
|
|
1154
1167
|
} else if (result.message) {
|
|
1155
1168
|
// Persist assistant response for multi-turn conversations
|
|
1156
|
-
if (result.
|
|
1169
|
+
if (result.assistantMessage) {
|
|
1170
|
+
this.messages.push(result.assistantMessage);
|
|
1171
|
+
} else if (result.signature) {
|
|
1157
1172
|
this.messages.push({
|
|
1158
1173
|
role: "assistant", content: [{
|
|
1159
1174
|
type: "thinking",
|
|
@@ -1653,7 +1668,12 @@ class MixOpenAI extends MixCustom {
|
|
|
1653
1668
|
for (const message of messages) {
|
|
1654
1669
|
|
|
1655
1670
|
if (message.tool_calls) {
|
|
1656
|
-
results.push({
|
|
1671
|
+
results.push({
|
|
1672
|
+
role: 'assistant',
|
|
1673
|
+
content: message.content ?? null,
|
|
1674
|
+
...(message.reasoning_content && { reasoning_content: message.reasoning_content }),
|
|
1675
|
+
tool_calls: message.tool_calls
|
|
1676
|
+
})
|
|
1657
1677
|
continue;
|
|
1658
1678
|
}
|
|
1659
1679
|
|
|
@@ -2102,6 +2122,46 @@ class MixOpenRouter extends MixOpenAI {
|
|
|
2102
2122
|
}
|
|
2103
2123
|
}
|
|
2104
2124
|
|
|
2125
|
+
class MixKimi extends MixOpenAI {
|
|
2126
|
+
getDefaultConfig(customConfig) {
|
|
2127
|
+
if (!process.env.MOONSHOT_API_KEY) {
|
|
2128
|
+
throw new Error('Moonshot API key not found. Please provide it in config or set MOONSHOT_API_KEY environment variable.');
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
return MixCustom.prototype.getDefaultConfig.call(this, {
|
|
2132
|
+
url: 'https://api.moonshot.ai/v1/chat/completions',
|
|
2133
|
+
apiKey: process.env.MOONSHOT_API_KEY,
|
|
2134
|
+
...customConfig
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
async create({ config = {}, options = {} } = {}) {
|
|
2139
|
+
if (Object.hasOwn(options, 'max_tokens')) {
|
|
2140
|
+
options.max_completion_tokens = options.max_tokens;
|
|
2141
|
+
delete options.max_tokens;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
delete options.temperature;
|
|
2145
|
+
delete options.top_p;
|
|
2146
|
+
delete options.n;
|
|
2147
|
+
delete options.presence_penalty;
|
|
2148
|
+
delete options.frequency_penalty;
|
|
2149
|
+
|
|
2150
|
+
return super.create({ config, options });
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
extractDelta(data) {
|
|
2154
|
+
return data?.choices?.[0]?.delta?.content || '';
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
processResponse(response) {
|
|
2158
|
+
return {
|
|
2159
|
+
...super.processResponse(response),
|
|
2160
|
+
assistantMessage: response.data?.choices?.[0]?.message
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2105
2165
|
class MixAnthropic extends MixCustom {
|
|
2106
2166
|
|
|
2107
2167
|
static thinkingOptions = {
|
|
@@ -2931,4 +2991,4 @@ class MixGoogle extends MixCustom {
|
|
|
2931
2991
|
}
|
|
2932
2992
|
}
|
|
2933
2993
|
|
|
2934
|
-
module.exports = { MixCustom, ModelMix, MixAnthropic, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA };
|
|
2994
|
+
module.exports = { MixCustom, ModelMix, MixAnthropic, MixKimi, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA };
|
package/package.json
CHANGED
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: modelmix
|
|
3
|
-
description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when writing code that calls AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, MiniMax, Fireworks, Together, Lambda, Cerebras, OpenRouter, Ollama, LM Studio), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, managing multi-provider AI workflows, round-robin load balancing, or rate limiting API requests in Node.js. Also use when the user mentions "modelmix", "ModelMix", asks to "call an LLM", "query a model", "add AI to my app", or wants to integrate any supported provider.
|
|
3
|
+
description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when writing code that calls AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, Moonshot, MiniMax, Fireworks, Together, Lambda, Cerebras, OpenRouter, Ollama, LM Studio), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, managing multi-provider AI workflows, round-robin load balancing, or rate limiting API requests in Node.js. Also use when the user mentions "modelmix", "ModelMix", asks to "call an LLM", "query a model", "add AI to my app", or wants to integrate any supported provider.
|
|
4
4
|
metadata:
|
|
5
5
|
tags: [llm, ai, openai, anthropic, google, groq, perplexity, grok, mcp, streaming, json-output]
|
|
6
6
|
---
|
|
@@ -112,6 +112,9 @@ Thinking variants: append `think` — e.g. `fable5think()` `opus48think()` `opus
|
|
|
112
112
|
### Together
|
|
113
113
|
`qwen36plus()` `GLM52()` `kimiK25think()` `gptOss()`
|
|
114
114
|
|
|
115
|
+
### Moonshot
|
|
116
|
+
`kimiK3()` — requires `MOONSHOT_API_KEY`; use `{ mix: { moonshot: false, openrouter: true } }` for OpenRouter.
|
|
117
|
+
|
|
115
118
|
### MiniMax
|
|
116
119
|
`minimaxM25()` `minimaxM27()` `minimaxM3()`
|
|
117
120
|
|
|
@@ -471,7 +474,7 @@ const model = ModelMix.new({
|
|
|
471
474
|
|
|
472
475
|
## Available Provider Classes
|
|
473
476
|
|
|
474
|
-
`MixOpenAI` `MixAnthropic` `MixGoogle` `MixPerplexity` `MixGroq` `MixTogether` `MixGrok` `MixOpenRouter` `MixOllama` `MixLMStudio` `MixCustom` `MixCerebras` `MixFireworks` `MixMiniMax` `MixLambda`
|
|
477
|
+
`MixOpenAI` `MixAnthropic` `MixGoogle` `MixPerplexity` `MixGroq` `MixTogether` `MixGrok` `MixOpenRouter` `MixOllama` `MixLMStudio` `MixCustom` `MixCerebras` `MixFireworks` `MixKimi` `MixMiniMax` `MixLambda`
|
|
475
478
|
|
|
476
479
|
## Troubleshooting
|
|
477
480
|
|
package/test/kimi.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { expect } = require('chai');
|
|
2
|
-
const
|
|
2
|
+
const nock = require('nock');
|
|
3
|
+
const { ModelMix, MixKimi, MixOpenRouter, MixTogether } = require('../index.js');
|
|
3
4
|
|
|
4
5
|
describe('Kimi Model Registration Tests', () => {
|
|
5
6
|
it('should register Together Kimi K2.7 Code by default', () => {
|
|
@@ -10,4 +11,146 @@ describe('Kimi Model Registration Tests', () => {
|
|
|
10
11
|
expect(model.models[0].key).to.equal('moonshotai/Kimi-K2.7-Code');
|
|
11
12
|
expect(model.models[0].provider).to.be.instanceOf(MixTogether);
|
|
12
13
|
});
|
|
14
|
+
|
|
15
|
+
it('should register Kimi K3 with the native Moonshot provider by default', () => {
|
|
16
|
+
const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
|
|
17
|
+
process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const model = ModelMix.new().kimiK3();
|
|
21
|
+
|
|
22
|
+
expect(model.models).to.have.length(1);
|
|
23
|
+
expect(model.models[0].key).to.equal('kimi-k3');
|
|
24
|
+
expect(model.models[0].provider).to.be.instanceOf(MixKimi);
|
|
25
|
+
expect(model.models[0].provider.config.url).to.equal('https://api.moonshot.ai/v1/chat/completions');
|
|
26
|
+
} finally {
|
|
27
|
+
if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
|
|
28
|
+
else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should support Kimi K3 through OpenRouter when requested', () => {
|
|
33
|
+
const originalOpenRouterApiKey = process.env.OPENROUTER_API_KEY;
|
|
34
|
+
process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const model = ModelMix.new().kimiK3({ mix: { moonshot: false, openrouter: true } });
|
|
38
|
+
|
|
39
|
+
expect(model.models).to.have.length(1);
|
|
40
|
+
expect(model.models[0].key).to.equal('moonshotai/kimi-k3');
|
|
41
|
+
expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
|
|
42
|
+
} finally {
|
|
43
|
+
if (originalOpenRouterApiKey === undefined) delete process.env.OPENROUTER_API_KEY;
|
|
44
|
+
else process.env.OPENROUTER_API_KEY = originalOpenRouterApiKey;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should adapt Kimi K3 requests to its fixed sampling API', async () => {
|
|
49
|
+
const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
|
|
50
|
+
process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const provider = new MixKimi();
|
|
54
|
+
let requestBody;
|
|
55
|
+
nock('https://api.moonshot.ai')
|
|
56
|
+
.post('/v1/chat/completions', body => {
|
|
57
|
+
requestBody = body;
|
|
58
|
+
return true;
|
|
59
|
+
})
|
|
60
|
+
.reply(200, {
|
|
61
|
+
choices: [{ message: { content: 'Done' } }]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await provider.create({
|
|
65
|
+
config: { system: 'You are an assistant.' },
|
|
66
|
+
options: {
|
|
67
|
+
model: 'kimi-k3',
|
|
68
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
69
|
+
max_tokens: 1000,
|
|
70
|
+
temperature: 0.5,
|
|
71
|
+
top_p: 0.9,
|
|
72
|
+
n: 2,
|
|
73
|
+
presence_penalty: 0.2,
|
|
74
|
+
frequency_penalty: 0.3
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(requestBody.max_completion_tokens).to.equal(1000);
|
|
79
|
+
expect(requestBody).to.not.have.property('max_tokens');
|
|
80
|
+
expect(requestBody).to.not.have.property('temperature');
|
|
81
|
+
expect(requestBody).to.not.have.property('top_p');
|
|
82
|
+
expect(requestBody).to.not.have.property('n');
|
|
83
|
+
expect(requestBody).to.not.have.property('presence_penalty');
|
|
84
|
+
expect(requestBody).to.not.have.property('frequency_penalty');
|
|
85
|
+
} finally {
|
|
86
|
+
if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
|
|
87
|
+
else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should preserve Kimi K3 reasoning content in tool-call continuations', async () => {
|
|
92
|
+
const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
|
|
93
|
+
process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
let continuationBody;
|
|
97
|
+
nock('https://api.moonshot.ai')
|
|
98
|
+
.post('/v1/chat/completions')
|
|
99
|
+
.reply(200, {
|
|
100
|
+
choices: [{
|
|
101
|
+
message: {
|
|
102
|
+
role: 'assistant',
|
|
103
|
+
content: 'I will calculate that.',
|
|
104
|
+
reasoning_content: 'I need the calculator tool.',
|
|
105
|
+
tool_calls: [{
|
|
106
|
+
id: 'call_calculate',
|
|
107
|
+
type: 'function',
|
|
108
|
+
function: {
|
|
109
|
+
name: 'calculate',
|
|
110
|
+
arguments: '{"expression":"2 + 2"}'
|
|
111
|
+
}
|
|
112
|
+
}]
|
|
113
|
+
}
|
|
114
|
+
}]
|
|
115
|
+
})
|
|
116
|
+
.post('/v1/chat/completions', body => {
|
|
117
|
+
continuationBody = body;
|
|
118
|
+
return true;
|
|
119
|
+
})
|
|
120
|
+
.reply(200, {
|
|
121
|
+
choices: [{ message: { role: 'assistant', content: 'The answer is 4.' } }]
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const model = ModelMix.new().kimiK3();
|
|
125
|
+
model.addTool({
|
|
126
|
+
name: 'calculate',
|
|
127
|
+
description: 'Evaluates an expression.',
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: { expression: { type: 'string' } },
|
|
131
|
+
required: ['expression']
|
|
132
|
+
}
|
|
133
|
+
}, ({ expression }) => expression === '2 + 2' ? 4 : null);
|
|
134
|
+
model.addText('What is 2 + 2?');
|
|
135
|
+
|
|
136
|
+
expect(await model.message()).to.equal('The answer is 4.');
|
|
137
|
+
const assistantMessage = continuationBody.messages.find(message => message.role === 'assistant');
|
|
138
|
+
expect(assistantMessage).to.deep.equal({
|
|
139
|
+
role: 'assistant',
|
|
140
|
+
content: 'I will calculate that.',
|
|
141
|
+
reasoning_content: 'I need the calculator tool.',
|
|
142
|
+
tool_calls: [{
|
|
143
|
+
id: 'call_calculate',
|
|
144
|
+
type: 'function',
|
|
145
|
+
function: {
|
|
146
|
+
name: 'calculate',
|
|
147
|
+
arguments: '{"expression":"2 + 2"}'
|
|
148
|
+
}
|
|
149
|
+
}]
|
|
150
|
+
});
|
|
151
|
+
} finally {
|
|
152
|
+
if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
|
|
153
|
+
else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
13
156
|
});
|