flowmind 1.2.3 → 1.4.0
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/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/bin/flowmind.js +69 -16
- package/core/ai/base-model.js +47 -28
- package/core/ai/model-manager.js +100 -90
- package/core/ai/providers/anthropic.js +21 -14
- package/core/ai/providers/deepseek.js +12 -2
- package/core/ai/providers/ernie.js +2 -2
- package/core/ai/providers/glm.js +12 -2
- package/core/ai/providers/mimo.js +12 -2
- package/core/ai/providers/ollama.js +5 -4
- package/core/ai/providers/openai.js +8 -12
- package/core/ai/providers/qwen.js +12 -2
- package/core/config-manager.js +1 -0
- package/core/index.js +183 -6
- package/core/learning-engine.js +122 -30
- package/mcp/server.js +2 -1
- package/package.json +1 -1
- package/skills/api-sync/index.js +130 -0
- package/skills/archive-change/index.js +104 -0
- package/skills/auto-flow/index.js +124 -0
- package/skills/code-review/index.js +79 -0
- package/skills/code-review-audit/index.js +77 -0
- package/skills/data-logic-validation/index.js +108 -0
- package/skills/data-validation/index.js +72 -0
- package/skills/git-review/index.js +73 -0
- package/skills/learning-engine/index.js +50 -0
- package/skills/learning-feedback/index.js +83 -0
- package/skills/log-audit/index.js +88 -0
- package/skills/project-review/index.js +105 -0
- package/skills/requirement-analyst/index.js +88 -0
- package/skills/resource-bind/index.js +60 -0
- package/skills/sls-log-audit/index.js +120 -0
- package/skills/yapi-sync-interface/index.js +101 -0
- package/skills/yuque-sync-design/index.js +133 -0
- package/tui/app.jsx +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.3.0] - 2026-06-26
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Missing `os` import in config-manager.js
|
|
7
|
+
- MCP Server version hardcoded instead of reading from package.json
|
|
8
|
+
- `config --set` and `skill --set` CLI commands broken (Commander arg parsing)
|
|
9
|
+
- `learn --reset` and `learn --delete` were unimplemented stubs
|
|
10
|
+
- Learning engine race condition on concurrent file writes (added WriteQueue)
|
|
11
|
+
- TUI/Dashboard crash on non-TTY stdin (Ink raw mode error)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- All 17 skills now have working `execute()` implementations
|
|
15
|
+
- Global uncaughtException/unhandledRejection handlers
|
|
16
|
+
- Graceful exit handling in TUI via onExit callback
|
|
17
|
+
|
|
18
|
+
## [1.2.2] - 2026-06-26
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- ChatPanel React import issue with ink-text-input and ink-spinner ESM/CJS interop
|
|
22
|
+
|
|
23
|
+
## [1.2.0] - 2026-06-26
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- TUI with split panels, skill browser, dragon display
|
|
27
|
+
- Dashboard with real-time activity feed and stats
|
|
28
|
+
- Event system for cross-component communication
|
|
29
|
+
- Update command for auto-updating flowmind
|
|
30
|
+
|
|
31
|
+
## [1.1.0] - 2026-06-25
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Mainstream AI model providers (GLM, MiMo, Qwen, ERNIE, DeepSeek)
|
|
35
|
+
- AI model integration with rule-based fallback
|
|
36
|
+
- MCP Server for Claude/Codex integration
|
|
37
|
+
|
|
38
|
+
## [1.0.1] - 2026-06-24
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- Initial release
|
|
42
|
+
- Skill loading and execution engine
|
|
43
|
+
- Learning engine for user corrections and preferences
|
|
44
|
+
- Honor engine with dragon totem gamification
|
|
45
|
+
- Scene matcher for workflow automation
|
|
46
|
+
- Configuration manager
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](CONTRIBUTING.md)
|
|
11
|
-
[](CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
[中文](README_CN.md) | [Quick Start](#-quick-start) | [How It Works](#-how-it-works) | [Use Cases](#-use-cases) | [Architecture](#-architecture)
|
|
14
14
|
|
package/bin/flowmind.js
CHANGED
|
@@ -536,11 +536,15 @@ program
|
|
|
536
536
|
await fm.importLearnings(data);
|
|
537
537
|
console.log(chalk.green('✓ Learnings imported successfully'));
|
|
538
538
|
} else if (options.reset) {
|
|
539
|
-
|
|
540
|
-
|
|
539
|
+
const count = await fm.learning.resetSkill(options.reset);
|
|
540
|
+
console.log(chalk.green(`✓ Reset ${count} learning(s) for skill: ${options.reset}`));
|
|
541
541
|
} else if (options.delete) {
|
|
542
|
-
|
|
543
|
-
|
|
542
|
+
const deleted = await fm.learning.deleteRecord(options.delete);
|
|
543
|
+
if (deleted) {
|
|
544
|
+
console.log(chalk.green(`✓ Deleted learning record: ${options.delete}`));
|
|
545
|
+
} else {
|
|
546
|
+
console.log(chalk.yellow(`Record not found: ${options.delete}`));
|
|
547
|
+
}
|
|
544
548
|
} else {
|
|
545
549
|
// Default to list
|
|
546
550
|
const stats = await fm.getStats();
|
|
@@ -620,7 +624,7 @@ program
|
|
|
620
624
|
.description('View or modify skill configuration')
|
|
621
625
|
.option('-i, --info', 'Show skill info (default)')
|
|
622
626
|
.option('-c, --config', 'Show/edit skill configuration')
|
|
623
|
-
.option('-s, --set <key>
|
|
627
|
+
.option('-s, --set <key>', 'Set config value (value as next argument)')
|
|
624
628
|
.option('-r, --read', 'Read SKILL.md content')
|
|
625
629
|
.option('-e, --edit', 'Open SKILL.md in editor')
|
|
626
630
|
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
@@ -650,13 +654,10 @@ program
|
|
|
650
654
|
} else if (options.config) {
|
|
651
655
|
await showSkillConfig(skill, fm, options.json);
|
|
652
656
|
} else if (options.set) {
|
|
653
|
-
// options.set is the key, need value from next arg
|
|
654
|
-
const value = options.set;
|
|
655
|
-
const key = options.set;
|
|
656
|
-
// Get key and value from command line
|
|
657
657
|
const args = process.argv.slice(3);
|
|
658
|
-
|
|
659
|
-
|
|
658
|
+
const value = args.find(a => !a.startsWith('-'));
|
|
659
|
+
if (value) {
|
|
660
|
+
await setSkillConfig(skill, fm, options.set, value);
|
|
660
661
|
} else {
|
|
661
662
|
console.error(chalk.red('Usage: flowmind skill <name> --set <key> <value>'));
|
|
662
663
|
}
|
|
@@ -775,7 +776,7 @@ program
|
|
|
775
776
|
.command('config')
|
|
776
777
|
.description('Manage configuration')
|
|
777
778
|
.option('-l, --list', 'List configuration')
|
|
778
|
-
.option('-s, --set <key>
|
|
779
|
+
.option('-s, --set <key>', 'Set configuration value (value as next argument)')
|
|
779
780
|
.option('-g, --get <key>', 'Get configuration value')
|
|
780
781
|
.action(async (options) => {
|
|
781
782
|
try {
|
|
@@ -785,10 +786,16 @@ program
|
|
|
785
786
|
const config = fm.config.getAll();
|
|
786
787
|
console.log(chalk.cyan('\nFlowMind Configuration:'));
|
|
787
788
|
console.log(JSON.stringify(config, null, 2));
|
|
788
|
-
} else if (options.set
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
789
|
+
} else if (options.set) {
|
|
790
|
+
const args = process.argv.slice(3);
|
|
791
|
+
const value = args.find(a => !a.startsWith('-'));
|
|
792
|
+
if (value) {
|
|
793
|
+
fm.config.set(options.set, value);
|
|
794
|
+
await fm.config.save();
|
|
795
|
+
console.log(chalk.green('✓ Configuration updated'));
|
|
796
|
+
} else {
|
|
797
|
+
console.error(chalk.red('Usage: flowmind config --set <key> <value>'));
|
|
798
|
+
}
|
|
792
799
|
} else if (options.get) {
|
|
793
800
|
const value = fm.config.get(options.get);
|
|
794
801
|
console.log(value);
|
|
@@ -1453,6 +1460,52 @@ program
|
|
|
1453
1460
|
}
|
|
1454
1461
|
});
|
|
1455
1462
|
|
|
1463
|
+
// Doctor command - System health check
|
|
1464
|
+
program
|
|
1465
|
+
.command('doctor')
|
|
1466
|
+
.description('Run system health checks and diagnostics')
|
|
1467
|
+
.option('-j, --json', 'Output as JSON')
|
|
1468
|
+
.action(async (options) => {
|
|
1469
|
+
try {
|
|
1470
|
+
const fm = await initFlowMind();
|
|
1471
|
+
const result = await fm.doctor();
|
|
1472
|
+
|
|
1473
|
+
if (options.json) {
|
|
1474
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
console.log(chalk.cyan('\nFlowMind Health Check\n'));
|
|
1479
|
+
console.log('─'.repeat(60));
|
|
1480
|
+
|
|
1481
|
+
for (const check of result.checks) {
|
|
1482
|
+
const icon = check.status === 'ok' ? chalk.green('✓')
|
|
1483
|
+
: check.status === 'warning' ? chalk.yellow('⚠')
|
|
1484
|
+
: chalk.red('✗');
|
|
1485
|
+
console.log(` ${icon} ${check.name.padEnd(20)} ${check.message}`);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
console.log('─'.repeat(60));
|
|
1489
|
+
const { ok, warnings, errors } = result.summary;
|
|
1490
|
+
const summaryLine = [
|
|
1491
|
+
chalk.green(`${ok} ok`),
|
|
1492
|
+
warnings > 0 ? chalk.yellow(`${warnings} warning(s)`) : null,
|
|
1493
|
+
errors > 0 ? chalk.red(`${errors} error(s)`) : null
|
|
1494
|
+
].filter(Boolean).join(', ');
|
|
1495
|
+
console.log(`\n Result: ${summaryLine}`);
|
|
1496
|
+
|
|
1497
|
+
if (errors > 0) {
|
|
1498
|
+
console.log(chalk.red('\n Some checks failed. Please fix the errors above.'));
|
|
1499
|
+
} else if (warnings > 0) {
|
|
1500
|
+
console.log(chalk.yellow('\n Some warnings detected. Review for potential issues.'));
|
|
1501
|
+
} else {
|
|
1502
|
+
console.log(chalk.green('\n All checks passed!'));
|
|
1503
|
+
}
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
console.error(chalk.red('Doctor Error:'), error.message);
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1456
1509
|
// Update command - Auto-update flowmind
|
|
1457
1510
|
program
|
|
1458
1511
|
.command('update')
|
package/core/ai/base-model.js
CHANGED
|
@@ -8,48 +8,27 @@ class BaseModel {
|
|
|
8
8
|
this.name = name;
|
|
9
9
|
this.config = config;
|
|
10
10
|
this.initialized = false;
|
|
11
|
+
this.requestTimeout = config.requestTimeout || 30000;
|
|
12
|
+
this.maxRetries = config.maxRetries || 3;
|
|
13
|
+
this.retryDelay = config.retryDelay || 1000;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
/**
|
|
14
|
-
* 初始化模型
|
|
15
|
-
* @returns {Promise<void>}
|
|
16
|
-
*/
|
|
17
16
|
async init() {
|
|
18
17
|
throw new Error('init() must be implemented by subclass');
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
/**
|
|
22
|
-
* 发送聊天请求
|
|
23
|
-
* @param {Array} messages - 消息数组 [{role, content}]
|
|
24
|
-
* @param {Object} options - 请求选项
|
|
25
|
-
* @returns {Promise<string>} 模型响应
|
|
26
|
-
*/
|
|
27
20
|
async chat(messages, options = {}) {
|
|
28
21
|
throw new Error('chat() must be implemented by subclass');
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
/**
|
|
32
|
-
* 发送补全请求
|
|
33
|
-
* @param {string} prompt - 提示词
|
|
34
|
-
* @param {Object} options - 请求选项
|
|
35
|
-
* @returns {Promise<string>} 模型响应
|
|
36
|
-
*/
|
|
37
24
|
async complete(prompt, options = {}) {
|
|
38
25
|
throw new Error('complete() must be implemented by subclass');
|
|
39
26
|
}
|
|
40
27
|
|
|
41
|
-
/**
|
|
42
|
-
* 检查模型是否可用
|
|
43
|
-
* @returns {Promise<boolean>}
|
|
44
|
-
*/
|
|
45
28
|
async isAvailable() {
|
|
46
29
|
throw new Error('isAvailable() must be implemented by subclass');
|
|
47
30
|
}
|
|
48
31
|
|
|
49
|
-
/**
|
|
50
|
-
* 获取模型信息
|
|
51
|
-
* @returns {Object}
|
|
52
|
-
*/
|
|
53
32
|
getInfo() {
|
|
54
33
|
return {
|
|
55
34
|
name: this.name,
|
|
@@ -58,13 +37,53 @@ class BaseModel {
|
|
|
58
37
|
};
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
/**
|
|
62
|
-
* 验证配置
|
|
63
|
-
* @returns {boolean}
|
|
64
|
-
*/
|
|
65
40
|
validateConfig() {
|
|
66
41
|
return true;
|
|
67
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Fetch with retry, timeout, and exponential backoff
|
|
46
|
+
*/
|
|
47
|
+
async fetchWithRetry(url, options = {}) {
|
|
48
|
+
const timeout = options.timeout || this.requestTimeout;
|
|
49
|
+
const maxRetries = options.retries || this.maxRetries;
|
|
50
|
+
|
|
51
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
52
|
+
try {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
55
|
+
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
...options,
|
|
58
|
+
signal: controller.signal
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
|
|
63
|
+
// Retry on rate limit (429) and server errors (5xx)
|
|
64
|
+
if (response.status === 429 || response.status >= 500) {
|
|
65
|
+
if (attempt < maxRetries) {
|
|
66
|
+
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
67
|
+
await new Promise(r => setTimeout(r, delay));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return response;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.name === 'AbortError') {
|
|
75
|
+
if (attempt < maxRetries) continue;
|
|
76
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
77
|
+
}
|
|
78
|
+
if (attempt < maxRetries) {
|
|
79
|
+
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
80
|
+
await new Promise(r => setTimeout(r, delay));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
module.exports = BaseModel;
|
package/core/ai/model-manager.js
CHANGED
|
@@ -28,6 +28,9 @@ class ModelManager {
|
|
|
28
28
|
};
|
|
29
29
|
this.fallbackToRules = config.fallbackToRules !== false;
|
|
30
30
|
this.initialized = false;
|
|
31
|
+
this.cache = new Map();
|
|
32
|
+
this.cacheMaxAge = config.cacheMaxAge || 300000; // 5 minutes
|
|
33
|
+
this.cacheMaxSize = config.cacheMaxSize || 100;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -119,30 +122,16 @@ class ModelManager {
|
|
|
119
122
|
* @returns {Promise<Object>} 意图分析结果
|
|
120
123
|
*/
|
|
121
124
|
async understandIntent(input, context = {}) {
|
|
122
|
-
if (!this.features.intentUnderstanding)
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const provider = this.getDefaultProvider();
|
|
127
|
-
if (!provider) {
|
|
128
|
-
return this._fallback('intentUnderstanding', input, context);
|
|
129
|
-
}
|
|
125
|
+
if (!this.features.intentUnderstanding) return null;
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
return this._callWithFailover(async (provider) => {
|
|
132
128
|
const messages = [
|
|
133
129
|
{ role: 'system', content: prompts.intent.system },
|
|
134
130
|
{ role: 'user', content: prompts.intent.user(input, context) }
|
|
135
131
|
];
|
|
136
|
-
|
|
137
|
-
const response = await provider.chat(messages, {
|
|
138
|
-
responseFormat: { type: 'json_object' }
|
|
139
|
-
});
|
|
140
|
-
|
|
132
|
+
const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
|
|
141
133
|
return JSON.parse(response);
|
|
142
|
-
}
|
|
143
|
-
console.warn(`AI intent understanding failed: ${error.message}`);
|
|
144
|
-
return this._fallback('intentUnderstanding', input, context);
|
|
145
|
-
}
|
|
134
|
+
}, 'intentUnderstanding');
|
|
146
135
|
}
|
|
147
136
|
|
|
148
137
|
/**
|
|
@@ -153,30 +142,16 @@ class ModelManager {
|
|
|
153
142
|
* @returns {Promise<Object>} 提取的参数
|
|
154
143
|
*/
|
|
155
144
|
async extractParameters(input, skillName, skillSchema = {}) {
|
|
156
|
-
if (!this.features.parameterExtraction) {
|
|
157
|
-
return {};
|
|
158
|
-
}
|
|
145
|
+
if (!this.features.parameterExtraction) return {};
|
|
159
146
|
|
|
160
|
-
|
|
161
|
-
if (!provider) {
|
|
162
|
-
return this._fallback('parameterExtraction', input, { skillName });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
try {
|
|
147
|
+
return this._callWithFailover(async (provider) => {
|
|
166
148
|
const messages = [
|
|
167
149
|
{ role: 'system', content: prompts.extraction.system },
|
|
168
150
|
{ role: 'user', content: prompts.extraction.user(input, skillName, skillSchema) }
|
|
169
151
|
];
|
|
170
|
-
|
|
171
|
-
const response = await provider.chat(messages, {
|
|
172
|
-
responseFormat: { type: 'json_object' }
|
|
173
|
-
});
|
|
174
|
-
|
|
152
|
+
const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
|
|
175
153
|
return JSON.parse(response);
|
|
176
|
-
}
|
|
177
|
-
console.warn(`AI parameter extraction failed: ${error.message}`);
|
|
178
|
-
return this._fallback('parameterExtraction', input, { skillName });
|
|
179
|
-
}
|
|
154
|
+
}, 'parameterExtraction');
|
|
180
155
|
}
|
|
181
156
|
|
|
182
157
|
/**
|
|
@@ -186,30 +161,16 @@ class ModelManager {
|
|
|
186
161
|
* @returns {Promise<Object>} 选择的技能
|
|
187
162
|
*/
|
|
188
163
|
async selectSkill(input, candidates) {
|
|
189
|
-
if (!this.features.skillSelection)
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
164
|
+
if (!this.features.skillSelection) return null;
|
|
192
165
|
|
|
193
|
-
|
|
194
|
-
if (!provider) {
|
|
195
|
-
return this._fallback('skillSelection', input, { candidates });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
166
|
+
return this._callWithFailover(async (provider) => {
|
|
199
167
|
const messages = [
|
|
200
168
|
{ role: 'system', content: prompts.selection.system },
|
|
201
169
|
{ role: 'user', content: prompts.selection.user(input, candidates) }
|
|
202
170
|
];
|
|
203
|
-
|
|
204
|
-
const response = await provider.chat(messages, {
|
|
205
|
-
responseFormat: { type: 'json_object' }
|
|
206
|
-
});
|
|
207
|
-
|
|
171
|
+
const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
|
|
208
172
|
return JSON.parse(response);
|
|
209
|
-
}
|
|
210
|
-
console.warn(`AI skill selection failed: ${error.message}`);
|
|
211
|
-
return this._fallback('skillSelection', input, { candidates });
|
|
212
|
-
}
|
|
173
|
+
}, 'skillSelection');
|
|
213
174
|
}
|
|
214
175
|
|
|
215
176
|
/**
|
|
@@ -219,26 +180,15 @@ class ModelManager {
|
|
|
219
180
|
* @returns {Promise<string>} 摘要文本
|
|
220
181
|
*/
|
|
221
182
|
async summarizeResult(result, context = {}) {
|
|
222
|
-
if (!this.features.resultSummary)
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
183
|
+
if (!this.features.resultSummary) return null;
|
|
225
184
|
|
|
226
|
-
|
|
227
|
-
if (!provider) {
|
|
228
|
-
return this._fallback('resultSummary', result, context);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
try {
|
|
185
|
+
return this._callWithFailover(async (provider) => {
|
|
232
186
|
const messages = [
|
|
233
187
|
{ role: 'system', content: prompts.summary.system },
|
|
234
188
|
{ role: 'user', content: prompts.summary.user(result, context) }
|
|
235
189
|
];
|
|
236
|
-
|
|
237
190
|
return await provider.chat(messages);
|
|
238
|
-
}
|
|
239
|
-
console.warn(`AI result summary failed: ${error.message}`);
|
|
240
|
-
return this._fallback('resultSummary', result, context);
|
|
241
|
-
}
|
|
191
|
+
}, 'resultSummary');
|
|
242
192
|
}
|
|
243
193
|
|
|
244
194
|
/**
|
|
@@ -248,42 +198,102 @@ class ModelManager {
|
|
|
248
198
|
* @returns {Promise<Object>} 学习分析结果
|
|
249
199
|
*/
|
|
250
200
|
async analyzeLearningFeedback(input, context = {}) {
|
|
251
|
-
if (!this.features.learningFeedback)
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const provider = this.getDefaultProvider();
|
|
256
|
-
if (!provider) {
|
|
257
|
-
return this._fallback('learningFeedback', input, context);
|
|
258
|
-
}
|
|
201
|
+
if (!this.features.learningFeedback) return null;
|
|
259
202
|
|
|
260
|
-
|
|
203
|
+
return this._callWithFailover(async (provider) => {
|
|
261
204
|
const messages = [
|
|
262
205
|
{ role: 'system', content: prompts.learning.system },
|
|
263
206
|
{ role: 'user', content: prompts.learning.user(input, context) }
|
|
264
207
|
];
|
|
208
|
+
const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
|
|
209
|
+
return JSON.parse(response);
|
|
210
|
+
}, 'learningFeedback');
|
|
211
|
+
}
|
|
265
212
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Get cached result if available and not expired
|
|
215
|
+
* @private
|
|
216
|
+
*/
|
|
217
|
+
_getCached(key) {
|
|
218
|
+
const entry = this.cache.get(key);
|
|
219
|
+
if (!entry) return null;
|
|
220
|
+
if (Date.now() - entry.timestamp > this.cacheMaxAge) {
|
|
221
|
+
this.cache.delete(key);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
return entry.value;
|
|
225
|
+
}
|
|
269
226
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Set cache entry, evicting oldest if at capacity
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
_setCache(key, value) {
|
|
232
|
+
if (this.cache.size >= this.cacheMaxSize) {
|
|
233
|
+
const oldest = this.cache.keys().next().value;
|
|
234
|
+
this.cache.delete(oldest);
|
|
235
|
+
}
|
|
236
|
+
this.cache.set(key, { value, timestamp: Date.now() });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Clear the AI response cache
|
|
241
|
+
*/
|
|
242
|
+
clearCache() {
|
|
243
|
+
this.cache.clear();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Call AI with automatic failover to other providers
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
async _callWithFailover(fn, feature) {
|
|
251
|
+
// Try default provider first
|
|
252
|
+
const defaultProvider = this.getDefaultProvider();
|
|
253
|
+
if (defaultProvider) {
|
|
254
|
+
try {
|
|
255
|
+
return await fn(defaultProvider);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.warn(`Default provider (${this.defaultProvider}) failed for ${feature}: ${error.message}`);
|
|
258
|
+
}
|
|
274
259
|
}
|
|
260
|
+
|
|
261
|
+
// Try other providers
|
|
262
|
+
for (const [name, provider] of this.providers) {
|
|
263
|
+
if (name === this.defaultProvider || !provider.initialized) continue;
|
|
264
|
+
try {
|
|
265
|
+
console.warn(`Failing over to provider: ${name} for ${feature}`);
|
|
266
|
+
return await fn(provider);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.warn(`Failover provider ${name} failed for ${feature}: ${error.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// All providers failed, use rule-based fallback
|
|
273
|
+
return this._fallback(feature);
|
|
275
274
|
}
|
|
276
275
|
|
|
277
276
|
/**
|
|
278
|
-
*
|
|
277
|
+
* Rule-based fallback when AI is unavailable
|
|
279
278
|
* @private
|
|
280
279
|
*/
|
|
281
|
-
_fallback(feature
|
|
282
|
-
if (!this.fallbackToRules)
|
|
283
|
-
|
|
280
|
+
_fallback(feature) {
|
|
281
|
+
if (!this.fallbackToRules) return null;
|
|
282
|
+
|
|
283
|
+
switch (feature) {
|
|
284
|
+
case 'intentUnderstanding':
|
|
285
|
+
return { intent: 'unknown', confidence: 0, source: 'rules' };
|
|
286
|
+
case 'parameterExtraction':
|
|
287
|
+
return {};
|
|
288
|
+
case 'skillSelection':
|
|
289
|
+
return { selectedSkill: null, confidence: 0, source: 'rules' };
|
|
290
|
+
case 'resultSummary':
|
|
291
|
+
return null;
|
|
292
|
+
case 'learningFeedback':
|
|
293
|
+
return { isLearning: false, source: 'rules' };
|
|
294
|
+
default:
|
|
295
|
+
return null;
|
|
284
296
|
}
|
|
285
|
-
// 返回 null,让调用方使用规则引擎
|
|
286
|
-
return null;
|
|
287
297
|
}
|
|
288
298
|
|
|
289
299
|
/**
|
|
@@ -8,7 +8,7 @@ class AnthropicProvider extends BaseModel {
|
|
|
8
8
|
constructor(config = {}) {
|
|
9
9
|
super('anthropic', config);
|
|
10
10
|
this.apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
11
|
-
this.model = config.model || 'claude-
|
|
11
|
+
this.model = config.model || 'claude-sonnet-4-20250514';
|
|
12
12
|
this.baseUrl = config.baseUrl || 'https://api.anthropic.com';
|
|
13
13
|
this.maxTokens = config.maxTokens ?? 2000;
|
|
14
14
|
this.temperature = config.temperature ?? 0.3;
|
|
@@ -26,14 +26,10 @@ class AnthropicProvider extends BaseModel {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async chat(messages, options = {}) {
|
|
29
|
-
if (!this.initialized)
|
|
30
|
-
await this.init();
|
|
31
|
-
}
|
|
29
|
+
if (!this.initialized) await this.init();
|
|
32
30
|
|
|
33
|
-
// 转换消息格式:提取 system 消息
|
|
34
31
|
let systemPrompt = '';
|
|
35
32
|
const userMessages = [];
|
|
36
|
-
|
|
37
33
|
for (const msg of messages) {
|
|
38
34
|
if (msg.role === 'system') {
|
|
39
35
|
systemPrompt = msg.content;
|
|
@@ -42,7 +38,7 @@ class AnthropicProvider extends BaseModel {
|
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
const response = await
|
|
41
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/v1/messages`, {
|
|
46
42
|
method: 'POST',
|
|
47
43
|
headers: {
|
|
48
44
|
'Content-Type': 'application/json',
|
|
@@ -74,19 +70,30 @@ class AnthropicProvider extends BaseModel {
|
|
|
74
70
|
async isAvailable() {
|
|
75
71
|
try {
|
|
76
72
|
if (!this.apiKey) return false;
|
|
77
|
-
// Anthropic
|
|
78
|
-
|
|
73
|
+
// Anthropic has no /models endpoint; make a lightweight messages call to verify
|
|
74
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/v1/messages`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'x-api-key': this.apiKey,
|
|
79
|
+
'anthropic-version': '2023-06-01'
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
model: this.model,
|
|
83
|
+
max_tokens: 1,
|
|
84
|
+
messages: [{ role: 'user', content: 'hi' }]
|
|
85
|
+
}),
|
|
86
|
+
retries: 1,
|
|
87
|
+
timeout: 10000
|
|
88
|
+
});
|
|
89
|
+
return response.ok || response.status === 400; // 400 = bad request but API is reachable
|
|
79
90
|
} catch {
|
|
80
91
|
return false;
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
getInfo() {
|
|
85
|
-
return {
|
|
86
|
-
...super.getInfo(),
|
|
87
|
-
model: this.model,
|
|
88
|
-
baseUrl: this.baseUrl
|
|
89
|
-
};
|
|
96
|
+
return { ...super.getInfo(), model: this.model, baseUrl: this.baseUrl };
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -31,7 +31,7 @@ class DeepSeekProvider extends BaseModel {
|
|
|
31
31
|
await this.init();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const response = await
|
|
34
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
|
|
35
35
|
method: 'POST',
|
|
36
36
|
headers: {
|
|
37
37
|
'Content-Type': 'application/json',
|
|
@@ -61,7 +61,17 @@ class DeepSeekProvider extends BaseModel {
|
|
|
61
61
|
async isAvailable() {
|
|
62
62
|
try {
|
|
63
63
|
if (!this.apiKey) return false;
|
|
64
|
-
|
|
64
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({ model: this.model, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
|
|
71
|
+
retries: 1,
|
|
72
|
+
timeout: 10000
|
|
73
|
+
});
|
|
74
|
+
return response.ok || response.status === 400;
|
|
65
75
|
} catch {
|
|
66
76
|
return false;
|
|
67
77
|
}
|
|
@@ -26,7 +26,7 @@ class ERNIEProvider extends BaseModel {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async refreshAccessToken() {
|
|
29
|
-
const response = await
|
|
29
|
+
const response = await this.fetchWithRetry(
|
|
30
30
|
`https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.apiKey}&client_secret=${this.secretKey}`,
|
|
31
31
|
{ method: 'POST' }
|
|
32
32
|
);
|
|
@@ -51,7 +51,7 @@ class ERNIEProvider extends BaseModel {
|
|
|
51
51
|
const model = options.model || this.model;
|
|
52
52
|
const endpoint = this.getEndpoint(model);
|
|
53
53
|
|
|
54
|
-
const response = await
|
|
54
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}${endpoint}?access_token=${this.accessToken}`, {
|
|
55
55
|
method: 'POST',
|
|
56
56
|
headers: {
|
|
57
57
|
'Content-Type': 'application/json'
|