flowmind 1.0.1 → 1.2.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/README_CN.md +248 -0
- package/bin/flowmind.js +638 -2
- package/config/ai-config.example.json +64 -0
- package/config/claude-mcp-config.example.json +12 -0
- package/core/ai/base-model.js +70 -0
- package/core/ai/index.js +29 -0
- package/core/ai/model-manager.js +320 -0
- package/core/ai/prompts/extraction.js +38 -0
- package/core/ai/prompts/index.js +11 -0
- package/core/ai/prompts/intent.js +43 -0
- package/core/ai/prompts/learning.js +46 -0
- package/core/ai/prompts/selection.js +38 -0
- package/core/ai/prompts/summary.js +35 -0
- package/core/ai/providers/anthropic.js +93 -0
- package/core/ai/providers/deepseek.js +80 -0
- package/core/ai/providers/ernie.js +111 -0
- package/core/ai/providers/glm.js +80 -0
- package/core/ai/providers/mimo.js +80 -0
- package/core/ai/providers/ollama.js +147 -0
- package/core/ai/providers/openai.js +82 -0
- package/core/ai/providers/qwen.js +80 -0
- package/core/event-bus.js +17 -0
- package/core/honor-engine.js +255 -0
- package/core/index.js +115 -13
- package/core/learning-engine.js +29 -1
- package/core/skill-loader.js +31 -9
- package/dashboard/app.jsx +29 -0
- package/dashboard/components/ActivityFeed.jsx +86 -0
- package/dashboard/components/DragonPanel.jsx +67 -0
- package/dashboard/components/McpStatusBar.jsx +43 -0
- package/dashboard/components/StatsRow.jsx +65 -0
- package/mcp/server.js +328 -0
- package/package.json +19 -7
- package/tui/app.jsx +69 -0
- package/tui/components/ChatPanel.jsx +72 -0
- package/tui/components/DragonTotem.jsx +108 -0
- package/tui/components/ResultPanel.jsx +33 -0
- package/tui/components/Sidebar.jsx +70 -0
- package/tui/components/StatusBar.jsx +49 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结果摘要提示词
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
system: `你是一个结果摘要专家。你的任务是将技术性的执行结果转换为用户友好的自然语言摘要。
|
|
7
|
+
|
|
8
|
+
摘要要求:
|
|
9
|
+
1. 简洁明了,突出关键信息
|
|
10
|
+
2. 使用用户能理解的语言
|
|
11
|
+
3. 如果有错误,清楚地说明问题所在
|
|
12
|
+
4. 如果有数据,提取关键指标和趋势
|
|
13
|
+
5. 如果有建议,给出明确的下一步操作
|
|
14
|
+
|
|
15
|
+
格式要求:
|
|
16
|
+
- 使用中文回复
|
|
17
|
+
- 使用 markdown 格式
|
|
18
|
+
- 重要信息使用加粗
|
|
19
|
+
- 列表使用有序或无序列表`,
|
|
20
|
+
|
|
21
|
+
user: (result, context) => {
|
|
22
|
+
let prompt = `请为以下执行结果生成摘要:\n\n`;
|
|
23
|
+
|
|
24
|
+
if (context.skill) {
|
|
25
|
+
prompt += `使用的技能: ${context.skill}\n`;
|
|
26
|
+
}
|
|
27
|
+
if (context.intent) {
|
|
28
|
+
prompt += `用户意图: ${context.intent}\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
prompt += `\n执行结果:\n${JSON.stringify(result, null, 2)}`;
|
|
32
|
+
|
|
33
|
+
return prompt;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Provider - Claude 模型接入
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const BaseModel = require('../base-model');
|
|
6
|
+
|
|
7
|
+
class AnthropicProvider extends BaseModel {
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
super('anthropic', config);
|
|
10
|
+
this.apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
11
|
+
this.model = config.model || 'claude-3-sonnet-20240229';
|
|
12
|
+
this.baseUrl = config.baseUrl || 'https://api.anthropic.com';
|
|
13
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
if (!this.apiKey) {
|
|
19
|
+
throw new Error('Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable or provide in config.');
|
|
20
|
+
}
|
|
21
|
+
this.initialized = true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validateConfig() {
|
|
25
|
+
return !!this.apiKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async chat(messages, options = {}) {
|
|
29
|
+
if (!this.initialized) {
|
|
30
|
+
await this.init();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 转换消息格式:提取 system 消息
|
|
34
|
+
let systemPrompt = '';
|
|
35
|
+
const userMessages = [];
|
|
36
|
+
|
|
37
|
+
for (const msg of messages) {
|
|
38
|
+
if (msg.role === 'system') {
|
|
39
|
+
systemPrompt = msg.content;
|
|
40
|
+
} else {
|
|
41
|
+
userMessages.push(msg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
'x-api-key': this.apiKey,
|
|
50
|
+
'anthropic-version': '2023-06-01'
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
model: options.model || this.model,
|
|
54
|
+
max_tokens: options.maxTokens ?? this.maxTokens,
|
|
55
|
+
system: systemPrompt || undefined,
|
|
56
|
+
messages: userMessages,
|
|
57
|
+
temperature: options.temperature ?? this.temperature
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const error = await response.text();
|
|
63
|
+
throw new Error(`Anthropic API error: ${response.status} - ${error}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
return data.content[0].text;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async complete(prompt, options = {}) {
|
|
71
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async isAvailable() {
|
|
75
|
+
try {
|
|
76
|
+
if (!this.apiKey) return false;
|
|
77
|
+
// Anthropic 没有 models 端点,直接尝试调用
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getInfo() {
|
|
85
|
+
return {
|
|
86
|
+
...super.getInfo(),
|
|
87
|
+
model: this.model,
|
|
88
|
+
baseUrl: this.baseUrl
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = AnthropicProvider;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Provider - DeepSeek 模型接入
|
|
3
|
+
* 支持 DeepSeek-V3、DeepSeek-R1 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class DeepSeekProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('deepseek', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.DEEPSEEK_API_KEY;
|
|
12
|
+
this.model = config.model || 'deepseek-chat';
|
|
13
|
+
this.baseUrl = config.baseUrl || 'https://api.deepseek.com/v1';
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (!this.apiKey) {
|
|
20
|
+
throw new Error('DeepSeek API key is required. Set DEEPSEEK_API_KEY environment variable or provide in config.');
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.apiKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model: options.model || this.model,
|
|
42
|
+
messages: messages,
|
|
43
|
+
temperature: options.temperature ?? this.temperature,
|
|
44
|
+
max_tokens: options.maxTokens ?? this.maxTokens
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`DeepSeek API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
...super.getInfo(),
|
|
73
|
+
model: this.model,
|
|
74
|
+
baseUrl: this.baseUrl,
|
|
75
|
+
provider: 'DeepSeek'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = DeepSeekProvider;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERNIE Provider - 百度文心一言模型接入
|
|
3
|
+
* 支持 ERNIE-4.0-Turbo-8K、ERNIE-3.5-8K 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class ERNIEProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('ernie', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.BAIDU_API_KEY;
|
|
12
|
+
this.secretKey = config.secretKey || process.env.BAIDU_SECRET_KEY;
|
|
13
|
+
this.model = config.model || 'ernie-4.0-turbo-8k';
|
|
14
|
+
this.baseUrl = config.baseUrl || 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop';
|
|
15
|
+
this.temperature = config.temperature ?? 0.3;
|
|
16
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
17
|
+
this.accessToken = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async init() {
|
|
21
|
+
if (!this.apiKey || !this.secretKey) {
|
|
22
|
+
throw new Error('Baidu API key and secret key are required. Set BAIDU_API_KEY and BAIDU_SECRET_KEY environment variables or provide in config.');
|
|
23
|
+
}
|
|
24
|
+
await this.refreshAccessToken();
|
|
25
|
+
this.initialized = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async refreshAccessToken() {
|
|
29
|
+
const response = await fetch(
|
|
30
|
+
`https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.apiKey}&client_secret=${this.secretKey}`,
|
|
31
|
+
{ method: 'POST' }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error('Failed to get Baidu access token');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
this.accessToken = data.access_token;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
validateConfig() {
|
|
43
|
+
return !!this.apiKey && !!this.secretKey;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async chat(messages, options = {}) {
|
|
47
|
+
if (!this.initialized) {
|
|
48
|
+
await this.init();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const model = options.model || this.model;
|
|
52
|
+
const endpoint = this.getEndpoint(model);
|
|
53
|
+
|
|
54
|
+
const response = await fetch(`${this.baseUrl}${endpoint}?access_token=${this.accessToken}`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json'
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
messages: messages,
|
|
61
|
+
temperature: options.temperature ?? this.temperature,
|
|
62
|
+
max_output_tokens: options.maxTokens ?? this.maxTokens
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const error = await response.text();
|
|
68
|
+
throw new Error(`ERNIE API error: ${response.status} - ${error}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
return data.result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getEndpoint(model) {
|
|
76
|
+
const endpoints = {
|
|
77
|
+
'ernie-4.0-turbo-8k': '/chat/ernie-4.0-turbo-8k',
|
|
78
|
+
'ernie-4.0-8k': '/chat/ernie-4.0-8k',
|
|
79
|
+
'ernie-3.5-8k': '/chat/ernie-3.5-8k',
|
|
80
|
+
'ernie-3.5-4k-0205': '/chat/ernie-3.5-4k-0205',
|
|
81
|
+
'ernie-speed-8k': '/chat/ernie-speed-8k',
|
|
82
|
+
'ernie-lite-8k': '/chat/ernie-lite-8k'
|
|
83
|
+
};
|
|
84
|
+
return endpoints[model] || `/chat/${model}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async complete(prompt, options = {}) {
|
|
88
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async isAvailable() {
|
|
92
|
+
try {
|
|
93
|
+
if (!this.apiKey || !this.secretKey) return false;
|
|
94
|
+
await this.refreshAccessToken();
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getInfo() {
|
|
102
|
+
return {
|
|
103
|
+
...super.getInfo(),
|
|
104
|
+
model: this.model,
|
|
105
|
+
baseUrl: this.baseUrl,
|
|
106
|
+
provider: 'Baidu'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = ERNIEProvider;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Provider - 智谱AI GLM 模型接入
|
|
3
|
+
* 支持 GLM-4、GLM-4-Flash、ChatGLM 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class GLMProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('glm', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.ZHIPU_API_KEY;
|
|
12
|
+
this.model = config.model || 'glm-4-flash';
|
|
13
|
+
this.baseUrl = config.baseUrl || 'https://open.bigmodel.cn/api/paas/v4';
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (!this.apiKey) {
|
|
20
|
+
throw new Error('Zhipu API key is required. Set ZHIPU_API_KEY environment variable or provide in config.');
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.apiKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model: options.model || this.model,
|
|
42
|
+
messages: messages,
|
|
43
|
+
temperature: options.temperature ?? this.temperature,
|
|
44
|
+
max_tokens: options.maxTokens ?? this.maxTokens
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`GLM API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
...super.getInfo(),
|
|
73
|
+
model: this.model,
|
|
74
|
+
baseUrl: this.baseUrl,
|
|
75
|
+
provider: 'Zhipu AI'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = GLMProvider;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MiMo Provider - 小米 MiMo 模型接入
|
|
3
|
+
* 支持 MiMo-7B 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class MiMoProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('mimo', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.MIMO_API_KEY;
|
|
12
|
+
this.model = config.model || 'mimo-7b';
|
|
13
|
+
this.baseUrl = config.baseUrl || 'https://api.mimo.ai/v1';
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (!this.apiKey) {
|
|
20
|
+
throw new Error('MiMo API key is required. Set MIMO_API_KEY environment variable or provide in config.');
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.apiKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model: options.model || this.model,
|
|
42
|
+
messages: messages,
|
|
43
|
+
temperature: options.temperature ?? this.temperature,
|
|
44
|
+
max_tokens: options.maxTokens ?? this.maxTokens
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`MiMo API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
...super.getInfo(),
|
|
73
|
+
model: this.model,
|
|
74
|
+
baseUrl: this.baseUrl,
|
|
75
|
+
provider: 'Xiaomi'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = MiMoProvider;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Provider - 本地模型接入
|
|
3
|
+
* 支持 Ollama 运行的本地模型(Llama2、Mistral、CodeLlama 等)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class OllamaProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('ollama', config);
|
|
11
|
+
this.baseUrl = config.baseUrl || 'http://localhost:11434';
|
|
12
|
+
this.model = config.model || 'llama2';
|
|
13
|
+
this.temperature = config.temperature ?? 0.3;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async init() {
|
|
17
|
+
// 检查 Ollama 服务是否可用
|
|
18
|
+
const available = await this.isAvailable();
|
|
19
|
+
if (!available) {
|
|
20
|
+
throw new Error(`Ollama service is not available at ${this.baseUrl}. Please start Ollama first.`);
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.baseUrl && !!this.model;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 转换消息格式为 Ollama 格式
|
|
35
|
+
const prompt = this._convertMessagesToPrompt(messages);
|
|
36
|
+
|
|
37
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
model: options.model || this.model,
|
|
44
|
+
prompt: prompt,
|
|
45
|
+
stream: false,
|
|
46
|
+
options: {
|
|
47
|
+
temperature: options.temperature ?? this.temperature,
|
|
48
|
+
num_predict: options.maxTokens ?? 2000
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const error = await response.text();
|
|
55
|
+
throw new Error(`Ollama API error: ${response.status} - ${error}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
return data.response;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async complete(prompt, options = {}) {
|
|
63
|
+
if (!this.initialized) {
|
|
64
|
+
await this.init();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json'
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
model: options.model || this.model,
|
|
74
|
+
prompt: prompt,
|
|
75
|
+
stream: false,
|
|
76
|
+
options: {
|
|
77
|
+
temperature: options.temperature ?? this.temperature,
|
|
78
|
+
num_predict: options.maxTokens ?? 2000
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const error = await response.text();
|
|
85
|
+
throw new Error(`Ollama API error: ${response.status} - ${error}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
return data.response;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async isAvailable() {
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(`${this.baseUrl}/api/tags`, {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
signal: AbortSignal.timeout(5000) // 5秒超时
|
|
97
|
+
});
|
|
98
|
+
return response.ok;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 获取可用模型列表
|
|
106
|
+
* @returns {Promise<Array>}
|
|
107
|
+
*/
|
|
108
|
+
async listModels() {
|
|
109
|
+
try {
|
|
110
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
111
|
+
if (!response.ok) return [];
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
return data.models || [];
|
|
114
|
+
} catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 将消息数组转换为单个提示词
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_convertMessagesToPrompt(messages) {
|
|
124
|
+
let prompt = '';
|
|
125
|
+
for (const msg of messages) {
|
|
126
|
+
if (msg.role === 'system') {
|
|
127
|
+
prompt += `[System]: ${msg.content}\n\n`;
|
|
128
|
+
} else if (msg.role === 'user') {
|
|
129
|
+
prompt += `[User]: ${msg.content}\n\n`;
|
|
130
|
+
} else if (msg.role === 'assistant') {
|
|
131
|
+
prompt += `[Assistant]: ${msg.content}\n\n`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
prompt += '[Assistant]: ';
|
|
135
|
+
return prompt;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getInfo() {
|
|
139
|
+
return {
|
|
140
|
+
...super.getInfo(),
|
|
141
|
+
model: this.model,
|
|
142
|
+
baseUrl: this.baseUrl
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = OllamaProvider;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Provider - OpenAI GPT 模型接入
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const BaseModel = require('../base-model');
|
|
6
|
+
|
|
7
|
+
class OpenAIProvider extends BaseModel {
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
super('openai', config);
|
|
10
|
+
this.apiKey = config.apiKey || process.env.OPENAI_API_KEY;
|
|
11
|
+
this.model = config.model || 'gpt-4';
|
|
12
|
+
this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
|
|
13
|
+
this.temperature = config.temperature ?? 0.3;
|
|
14
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
if (!this.apiKey) {
|
|
19
|
+
throw new Error('OpenAI API key is required. Set OPENAI_API_KEY environment variable or provide in config.');
|
|
20
|
+
}
|
|
21
|
+
this.initialized = true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validateConfig() {
|
|
25
|
+
return !!this.apiKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async chat(messages, options = {}) {
|
|
29
|
+
if (!this.initialized) {
|
|
30
|
+
await this.init();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
model: options.model || this.model,
|
|
41
|
+
messages: messages,
|
|
42
|
+
temperature: options.temperature ?? this.temperature,
|
|
43
|
+
max_tokens: options.maxTokens ?? this.maxTokens,
|
|
44
|
+
response_format: options.responseFormat || undefined
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
const response = await fetch(`${this.baseUrl}/models`, {
|
|
65
|
+
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
66
|
+
});
|
|
67
|
+
return response.ok;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getInfo() {
|
|
74
|
+
return {
|
|
75
|
+
...super.getInfo(),
|
|
76
|
+
model: this.model,
|
|
77
|
+
baseUrl: this.baseUrl
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = OpenAIProvider;
|