079project 3.0.0 → 5.0.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/forwarder.js CHANGED
@@ -285,6 +285,43 @@ app.get('/', (req, res) => {
285
285
  <button type="submit" class="btn btn-primary">应用参数</button>
286
286
  <button type="button" id="resetParams" class="btn btn-secondary">重置默认值</button>
287
287
  </div>
288
+ <hr class="my-4"/>
289
+ <h6 class="mb-3">激活与传递函数</h6>
290
+ <div class="row g-3">
291
+ <div class="col-md-6">
292
+ <label for="activationType" class="form-label">Activation Function</label>
293
+ <select id="activationType" class="form-select">
294
+ <option value="identity">identity</option>
295
+ <option value="relu" selected>relu</option>
296
+ <option value="leaky_relu">leaky_relu</option>
297
+ <option value="tanh">tanh</option>
298
+ <option value="sigmoid">sigmoid</option>
299
+ <option value="elu">elu</option>
300
+ <option value="softplus">softplus</option>
301
+ <option value="gelu">gelu</option>
302
+ <option value="custom">custom</option>
303
+ </select>
304
+ <small class="text-muted">Node 侧将对节点聚合与扩散值应用该激活</small>
305
+ <textarea id="activationCustom" class="form-control mt-2" rows="3" style="display:none;" placeholder="Custom activation JS. Example: return x > 0 ? x : 0;"></textarea>
306
+ </div>
307
+ <div class="col-md-6">
308
+ <label for="transferType" class="form-label">Transfer Function</label>
309
+ <select id="transferType" class="form-select">
310
+ <option value="linear" selected>linear</option>
311
+ <option value="exp">exp</option>
312
+ <option value="inverse">inverse</option>
313
+ <option value="capped">capped</option>
314
+ <option value="custom">custom</option>
315
+ </select>
316
+ <small class="text-muted">传递函数决定沿边传播的信号衰减方式</small>
317
+ <textarea id="transferCustom" class="form-control mt-2" rows="3" style="display:none;" placeholder="Custom transfer JS. Args: value, weight, decayK, ctx. Example: return value * Math.exp(-(decayK*weight));"></textarea>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="d-flex justify-content-between mt-4">
322
+ <button type="submit" class="btn btn-primary">应用参数</button>
323
+ <button type="button" id="resetParams" class="btn btn-secondary">重置默认值</button>
324
+ </div>
288
325
 
289
326
  </form>
290
327
  </div>
@@ -534,6 +571,7 @@ app.get('/', (req, res) => {
534
571
  document.getElementById('confirmDeleteSnapshot').addEventListener('click', function() {
535
572
  deleteSnapshot();
536
573
  });
574
+ bindActivationEditors(); // 新增:绑定选择器与自定义编辑显隐
537
575
  });
538
576
 
539
577
  // 发送消息
@@ -595,6 +633,8 @@ app.get('/', (req, res) => {
595
633
  }
596
634
 
597
635
  // 加载模型参数
636
+
637
+ // 加载模型参数(保持接口不变)
598
638
  function loadModelParams() {
599
639
  fetch('/api/model/params')
600
640
  .then(response => response.json())
@@ -672,6 +712,13 @@ function updateParamSliders(params) {
672
712
  document.getElementById('waitingTimeRange').value = params.waitingTime;
673
713
  document.getElementById('waitingTimeValue').textContent = params.waitingTime;
674
714
  }
715
+ if (params.activationType) document.getElementById('activationType').value = params.activationType;
716
+ if (params.transferType) document.getElementById('transferType').value = params.transferType;
717
+ if (typeof params.activationCustom === 'string') document.getElementById('activationCustom').value = params.activationCustom;
718
+ if (typeof params.transferCustom === 'string') document.getElementById('transferCustom').value = params.transferCustom;
719
+
720
+ // 触发一次显隐同步
721
+ if (typeof bindActivationEditors === 'function') bindActivationEditors();
675
722
  } // 添加这个花括号
676
723
 
677
724
  function bindSliderEvents() {
@@ -705,43 +752,47 @@ function bindSliderEvents() {
705
752
  });
706
753
  }
707
754
  // 更新模型参数
708
- function updateModelParams() {
709
- const params = {
710
- decayFactor: parseFloat(document.getElementById('decayFactorRange').value),
711
- maxMemeWords: parseInt(document.getElementById('maxMemeWordsRange').value),
712
- minOverlapThreshold: parseInt(document.getElementById('minOverlapThresholdRange').value),
713
- maliciousThreshold: parseFloat(document.getElementById('maliciousThresholdRange').value),
714
- learningIterations: parseInt(document.getElementById('learningIterationsRange').value),
715
- iteration: parseInt(document.getElementById('iterationRange').value),
716
- threshold: parseInt(document.getElementById('thresholdRange').value),
717
- decay: parseFloat(document.getElementById('decayRange').value),
718
- decayK: parseFloat(document.getElementById('decayKRange').value),
719
- maxLen: parseInt(document.getElementById('maxLenRange').value),
720
- edgeWeight: parseFloat(document.getElementById('edgeWeightRange').value),
721
- communicateCount: parseInt(document.getElementById('communicateCountRange').value),
722
- waitingTime: parseInt(document.getElementById('waitingTime').value) // 添加这个参数
723
- };
724
-
725
- fetch('/api/model/params', {
726
- method: 'POST',
727
- headers: {
728
- 'Content-Type': 'application/json',
729
- },
730
- body: JSON.stringify(params),
731
- })
732
- .then(response => response.json())
733
- .then(data => {
734
- if (data.success) {
735
- alert('参数更新成功!');
736
- } else {
737
- alert('参数更新失败: ' + (data.error || '未知错误'));
755
+ // 更新模型参数:增加激活/传递函数字段
756
+ function updateModelParams() {
757
+ const params = {
758
+ decayFactor: parseFloat(document.getElementById('decayFactorRange').value),
759
+ maxMemeWords: parseInt(document.getElementById('maxMemeWordsRange').value),
760
+ minOverlapThreshold: parseInt(document.getElementById('minOverlapThresholdRange').value),
761
+ maliciousThreshold: parseFloat(document.getElementById('maliciousThresholdRange').value),
762
+ learningIterations: parseInt(document.getElementById('learningIterationsRange').value),
763
+ iteration: parseInt(document.getElementById('iterationRange').value),
764
+ threshold: parseInt(document.getElementById('thresholdRange').value),
765
+ decay: parseFloat(document.getElementById('decayRange').value),
766
+ decayK: parseFloat(document.getElementById('decayKRange').value),
767
+ maxLen: parseInt(document.getElementById('maxLenRange').value),
768
+ edgeWeight: parseFloat(document.getElementById('edgeWeightRange').value),
769
+ communicateCount: parseInt(document.getElementById('communicateCountRange').value),
770
+ waitingTime: parseInt(document.getElementById('waitingTime').value),
771
+ // 新增
772
+ activationType: document.getElementById('activationType').value,
773
+ transferType: document.getElementById('transferType').value,
774
+ activationCustom: document.getElementById('activationCustom').value,
775
+ transferCustom: document.getElementById('transferCustom').value
776
+ };
777
+
778
+ fetch('/api/model/params', {
779
+ method: 'POST',
780
+ headers: { 'Content-Type': 'application/json' },
781
+ body: JSON.stringify(params),
782
+ })
783
+ .then(response => response.json())
784
+ .then(data => {
785
+ if (data.success) {
786
+ alert('参数更新成功!');
787
+ } else {
788
+ alert('参数更新失败: ' + (data.error || '未知错误'));
789
+ }
790
+ })
791
+ .catch(error => {
792
+ console.error('Error updating parameters:', error);
793
+ alert('更新参数时发生错误: ' + error.message);
794
+ });
738
795
  }
739
- })
740
- .catch(error => {
741
- console.error('Error updating parameters:', error);
742
- alert('更新参数时发生错误: ' + error.message);
743
- });
744
- }
745
796
 
746
797
  // 重置模型参数
747
798
  function resetModelParams() {
@@ -776,6 +827,19 @@ function updateModelParams() {
776
827
  .catch(error => console.error('Error loading system status:', error));
777
828
  }
778
829
 
830
+ function bindActivationEditors() {
831
+ const actSel = document.getElementById('activationType');
832
+ const actTxt = document.getElementById('activationCustom');
833
+ const trSel = document.getElementById('transferType');
834
+ const trTxt = document.getElementById('transferCustom');
835
+ const sync = () => {
836
+ actTxt.style.display = (actSel.value === 'custom') ? '' : 'none';
837
+ trTxt.style.display = (trSel.value === 'custom') ? '' : 'none';
838
+ };
839
+ actSel.addEventListener('change', sync);
840
+ trSel.addEventListener('change', sync);
841
+ sync();
842
+ }
779
843
  function updateParamSliders(params) {
780
844
  // 定义一个更新滑块的辅助函数
781
845
  function updateSlider(paramName, sliderId, valueId) {
@@ -1040,13 +1104,6 @@ function updateParamSliders(params) {
1040
1104
  `);
1041
1105
  });
1042
1106
 
1043
- // 目标端口列表
1044
- const AI_PORTS = [
1045
- process.env.AI_PORT_A || process.argv[3],
1046
- process.env.AI_PORT_B || process.argv[4],
1047
- process.env.AI_PORT_C || process.argv[5]
1048
- ];
1049
- const Study_Port=process.env.AI_STUDY_PORT || process.argv[6]; // 学习模块端口
1050
1107
 
1051
1108
  const systemStats = {
1052
1109
  requestsTotal: 0,
@@ -1057,7 +1114,167 @@ const systemStats = {
1057
1114
  aiResponseTimes: { [process.argv[3]]: [], [process.argv[4]]: [], [process.argv[5]]: [] },
1058
1115
  lastErrors: []
1059
1116
  };
1117
+ // ...existing code...
1118
+
1119
+ // ========== 分片/冷-热池调度(forwarder 层) ==========
1120
+ class ShardDescriptor {
1121
+ /**
1122
+ * @param {Object} opts
1123
+ * @param {string} opts.id 逻辑ID(如 "text_base", "law_1")
1124
+ * @param {number[]} opts.ports 该 shard 对应的后端端口列表(当前版本:本 forwarder 下的某个子集)
1125
+ * @param {number[]} [opts.embedding] 语义中心向量(可选,先空)
1126
+ * @param {string[]} [opts.tags] 领域标签,如 ['code','cn','law']
1127
+ */
1128
+ constructor(opts) {
1129
+ this.id = opts.id;
1130
+ this.ports = opts.ports || [];
1131
+ this.embedding = Array.isArray(opts.embedding) ? opts.embedding : null;
1132
+ this.tags = Array.isArray(opts.tags) ? opts.tags : [];
1133
+ this.lastUsedTs = 0;
1134
+ this.usageCount = 0;
1135
+ this.loaded = true; // 第1轮简化:都视为“热”,仅做路由选择
1136
+ }
1137
+
1138
+ touch() {
1139
+ this.lastUsedTs = Date.now();
1140
+ this.usageCount++;
1141
+ }
1142
+ }
1143
+
1144
+ // 非TF的极简 embedding:词袋hash到固定维度
1145
+ function hashStrSimple(str, seed = 1315423911) {
1146
+ let h = seed >>> 0;
1147
+ for (let i = 0; i < str.length; i++) {
1148
+ h ^= ((h << 5) + str.charCodeAt(i) + (h >>> 2)) >>> 0;
1149
+ }
1150
+ return h >>> 0;
1151
+ }
1152
+
1153
+ function textToMiniEmbedding(text, dim = 64) {
1154
+ const vec = new Float32Array(dim);
1155
+ const toks = basicClean(text).toLowerCase().split(/[^a-z0-9_\-\u4e00-\u9fa5]+/).filter(Boolean);
1156
+ if (!toks.length) return Array.from(vec);
1157
+ for (const t of toks) {
1158
+ const h = hashStrSimple(t);
1159
+ const idx = h % dim;
1160
+ vec[idx] += 1;
1161
+ }
1162
+ // L2 normalize
1163
+ let n2 = 0; for (let i = 0; i < dim; i++) n2 += vec[i] * vec[i];
1164
+ n2 = Math.sqrt(n2) || 1;
1165
+ for (let i = 0; i < dim; i++) vec[i] /= n2;
1166
+ return Array.from(vec);
1167
+ }
1060
1168
 
1169
+ function cosineSim(a, b) {
1170
+ if (!a || !b || a.length !== b.length) return 0;
1171
+ let dot = 0, na = 0, nb = 0;
1172
+ for (let i = 0; i < a.length; i++) {
1173
+ dot += a[i] * b[i];
1174
+ na += a[i] * a[i];
1175
+ nb += b[i] * b[i];
1176
+ }
1177
+ if (!na || !nb) return 0;
1178
+ return dot / Math.sqrt(na * nb);
1179
+ }
1180
+
1181
+ class ShardManager {
1182
+ constructor(allPorts) {
1183
+ this.shards = new Map(); // id -> ShardDescriptor
1184
+ this.portToShard = new Map(); // port -> shardId(方便反查)
1185
+ this.dim = 64;
1186
+
1187
+ // 初始策略:把当前 AI_PORTS 按顺序平均分成几组,构成 “模型组”
1188
+ // 例如 3 个端口 => 一个 shard;12 个端口 => 3~4 个 shard
1189
+ const ports = allPorts.filter(p => !!p).map(p => Number(p));
1190
+ const N = ports.length;
1191
+ if (!N) return;
1192
+ const targetShardCount = Math.min(4, Math.max(1, Math.floor(N / 3))) || 1;
1193
+ const shardSize = Math.max(1, Math.floor(N / targetShardCount));
1194
+ let idx = 0;
1195
+ for (let s = 0; s < targetShardCount; s++) {
1196
+ const slice = ports.slice(idx, idx + shardSize);
1197
+ idx += shardSize;
1198
+ if (!slice.length) break;
1199
+ const id = `shard_${s}`;
1200
+ const desc = new ShardDescriptor({
1201
+ id,
1202
+ ports: slice,
1203
+ embedding: null,
1204
+ tags: [] // 可以后续通过API补充
1205
+ });
1206
+ this.shards.set(id, desc);
1207
+ for (const p of slice) this.portToShard.set(p, id);
1208
+ }
1209
+ }
1210
+
1211
+ listShards() {
1212
+ return Array.from(this.shards.values()).map(s => ({
1213
+ id: s.id,
1214
+ ports: s.ports,
1215
+ tags: s.tags,
1216
+ loaded: s.loaded,
1217
+ lastUsedTs: s.lastUsedTs,
1218
+ usageCount: s.usageCount
1219
+ }));
1220
+ }
1221
+
1222
+ // 手工更新某个 shard 的语义中心 + 标签
1223
+ updateShardMeta(id, { embedding, tags } = {}) {
1224
+ const s = this.shards.get(id);
1225
+ if (!s) return false;
1226
+ if (Array.isArray(embedding)) s.embedding = embedding.slice();
1227
+ if (Array.isArray(tags)) s.tags = tags.slice();
1228
+ return true;
1229
+ }
1230
+
1231
+ /**
1232
+ * 根据当前对话 embedding + 可选 tags,选出本轮要用的 shard 列表
1233
+ * @param {number[]} queryEmb
1234
+ * @param {Object} opt
1235
+ * @param {number} opt.topK 选多少个 shard
1236
+ * @param {string[]} [opt.hints] 额外提示(如 'code','zh')
1237
+ */
1238
+ selectShards(queryEmb, { topK = 2, hints = [] } = {}) {
1239
+ const entries = Array.from(this.shards.values()).filter(s => s.loaded && s.ports.length);
1240
+ if (!entries.length) return [];
1241
+
1242
+ const scores = entries.map(s => {
1243
+ let sim = 0;
1244
+ if (s.embedding) sim = cosineSim(queryEmb, s.embedding);
1245
+ let tagBonus = 0;
1246
+ if (hints && hints.length && s.tags && s.tags.length) {
1247
+ const inter = s.tags.filter(t => hints.includes(t));
1248
+ tagBonus = inter.length ? 0.1 * inter.length : 0;
1249
+ }
1250
+ // 加一点近期使用度的温度
1251
+ const usageBoost = Math.log(1 + s.usageCount) * 0.01;
1252
+ return { shard: s, score: sim + tagBonus + usageBoost };
1253
+ });
1254
+ scores.sort((a, b) => b.score - a.score);
1255
+ const out = scores.slice(0, Math.max(1, topK)).map(x => x.shard);
1256
+ const now = Date.now();
1257
+ for (const s of out) { s.lastUsedTs = now; s.usageCount++; }
1258
+ return out;
1259
+ }
1260
+
1261
+ // 当前端口属于哪个 shard(用于监控)
1262
+ shardOfPort(port) {
1263
+ const id = this.portToShard.get(Number(port));
1264
+ return id || null;
1265
+ }
1266
+ }
1267
+
1268
+ // 初始化 ShardManager
1269
+ const AI_PORTS = [
1270
+ process.env.AI_PORT_A || process.argv[3],
1271
+ process.env.AI_PORT_B || process.argv[4],
1272
+ process.env.AI_PORT_C || process.argv[5]
1273
+ ];
1274
+ const Study_Port = process.env.AI_STUDY_PORT || process.argv[6];
1275
+
1276
+ const shardManager = new ShardManager(AI_PORTS);
1277
+ // ========== 冷/热池调度结束 ==========
1061
1278
  // 新增:统一从各后端响应中提取文本
1062
1279
  function extractText(resp) {
1063
1280
  if (!resp) return '';
@@ -1080,7 +1297,12 @@ const modelDefaults = {
1080
1297
  minOverlapThreshold: 2,
1081
1298
  maliciousThreshold: 0.7,
1082
1299
  learningIterations: 3,
1083
- communicateCount: 1 // 新增
1300
+ communicateCount: 1,
1301
+ // 新增:激活与传递函数选择
1302
+ activationType: 'relu',
1303
+ transferType: 'linear',
1304
+ activationCustom: '',
1305
+ transferCustom: ''
1084
1306
  };
1085
1307
 
1086
1308
  // 当前应用的模型参数
@@ -1100,28 +1322,30 @@ function perturb(arr) {
1100
1322
  */
1101
1323
  // 修改 requestAI,返回 null 表示离线
1102
1324
  // 修改 requestAI 函数,增加重试和超时处理
1103
- async function requestAI(port, message, retries = 3) {
1325
+ async function requestAI(port, message, retries = 3, shardId = null) {
1104
1326
  const url = `http://localhost:${port}/api/chat`;
1105
1327
  for (let attempt = 1; attempt <= retries; attempt++) {
1106
1328
  try {
1107
1329
  const start = Date.now();
1108
- const response = await axios.post(url, { message }, { timeout: 10000 }); // 10秒超时
1330
+ const response = await axios.post(url, { message }, { timeout: 10000 });
1109
1331
  const latency = Date.now() - start;
1110
1332
  systemStats.aiResponseTimes[port] = systemStats.aiResponseTimes[port] || [];
1111
1333
  systemStats.aiResponseTimes[port].push(latency);
1334
+ // 这里可以将 shardId 写入统计日志,但先不动 API
1112
1335
  return response.data;
1113
1336
  } catch (error) {
1114
- console.warn(`[WARN] 请求 AI 实例 ${port} 失败 (尝试 ${attempt}/${retries}): ${error.message}`);
1337
+ console.warn(`[WARN] 请求 AI 实例 ${port} (shard=${shardId || 'unknown'}) 失败 (尝试 ${attempt}/${retries}): ${error.message}`);
1115
1338
  if (attempt === retries) {
1116
1339
  systemStats.lastErrors.push({ port, error: error.message });
1117
- return null; // 返回 null 表示服务不可用
1340
+ return null;
1118
1341
  }
1119
- await new Promise(resolve => setTimeout(resolve, 1000)); // 重试前等待 1 秒
1342
+ await new Promise(resolve => setTimeout(resolve, 1000));
1120
1343
  }
1121
1344
  }
1122
1345
  }
1123
1346
 
1124
1347
 
1348
+ // ...existing code...
1125
1349
  app.post('/api/chat', async (req, res) => {
1126
1350
  const { message } = req.body;
1127
1351
  if (!message) return res.status(400).json({ error: 'No message' });
@@ -1136,27 +1360,49 @@ app.post('/api/chat', async (req, res) => {
1136
1360
  let results = [];
1137
1361
 
1138
1362
  try {
1139
- // 星火阵列多轮交互(每一轮并发请求3个AI)
1363
+ // === 核心:为当前请求生成embedding并选择合适 shard ===
1364
+ const queryEmb = textToMiniEmbedding(message, shardManager.dim);
1365
+ // 可加一点简单的“领域 hint”:例如包含 code / law / zh 等关键词
1366
+ const hints = [];
1367
+ const lower = message.toLowerCase();
1368
+ if (/[{};()=]/.test(message) || /code|function|class|import|def /.test(lower)) hints.push('code');
1369
+ if (/[,。?!]/.test(message) || /的|了|吗/.test(message)) hints.push('zh');
1370
+ // 暂时不细分,topK=2
1371
+ const selectedShards = shardManager.selectShards(queryEmb, { topK: 2, hints });
1372
+
1373
+ // 从被选中的 shard 中收集端口
1374
+ let candidatePorts = [];
1375
+ for (const s of selectedShards) candidatePorts.push(...s.ports);
1376
+ // 去重复
1377
+ candidatePorts = Array.from(new Set(candidatePorts));
1378
+
1379
+ // 如果还为空,兜底用全部 AI_PORTS
1380
+ if (!candidatePorts.length) {
1381
+ candidatePorts = AI_PORTS.filter(p => !!p);
1382
+ }
1383
+
1384
+ // 星火阵列多轮交互(每一轮并发请求若干 AI)
1140
1385
  for (let round = 0; round <= communicateCount; round++) {
1386
+ const msgArr = inputs.map(arr => (arr || words).join(' '));
1387
+ // 对不超过 candidatePorts.length 的前几个输入进行并发请求
1141
1388
  results = await Promise.all(
1142
- AI_PORTS.map((port, i) => requestAI(port, (inputs[i] || words).join(' ')))
1389
+ candidatePorts.map((port, idx) =>
1390
+ requestAI(port, msgArr[idx % msgArr.length], 3, shardManager.shardOfPort(port))
1391
+ )
1143
1392
  );
1144
1393
 
1145
- // 下一轮输入为本轮输出分词(先提取文本,再 split)
1146
1394
  if (round < communicateCount) {
1147
- console.log(results);
1148
1395
  inputs = results.map(r => extractText(r).toLowerCase().split(/\s+/).filter(Boolean));
1149
1396
  }
1150
1397
  }
1151
1398
 
1152
- // 只保留有效响应(提取为字符串)
1153
1399
  const texts = results.map(extractText).filter(t => typeof t === 'string' && t.length > 0);
1154
1400
  if (texts.length === 0) {
1155
1401
  systemStats.requestsFailed++;
1156
1402
  return res.status(502).json({ error: '所有AI实例均不可用或无响应' });
1157
1403
  }
1158
1404
 
1159
- // 统计词频
1405
+ // ...后面词频融合 + SERIALIZER_API 部分保持不变...
1160
1406
  const freq = {};
1161
1407
  texts.forEach(t => t.split(/\s+/).forEach(w => { if (w) freq[w] = (freq[w] || 0) + 1; }));
1162
1408
  const sorted = Object.entries(freq).sort((a, b) => b[1] - a[1]);
@@ -1178,7 +1424,18 @@ app.post('/api/chat', async (req, res) => {
1178
1424
  res.status(500).json({ error: err.message });
1179
1425
  }
1180
1426
  });
1427
+ app.get('/api/shards', (req, res) => {
1428
+ res.json({ ok: true, shards: shardManager.listShards() });
1429
+ });
1181
1430
 
1431
+ // 更新某个 shard 的标签/embedding(embedding 可手工填或从文件导入)
1432
+ app.post('/api/shards/:id/meta', (req, res) => {
1433
+ const { id } = req.params;
1434
+ const { embedding, tags } = req.body || {};
1435
+ const ok = shardManager.updateShardMeta(id, { embedding, tags });
1436
+ if (!ok) return res.status(404).json({ ok: false, error: 'shard not found' });
1437
+ res.json({ ok: true });
1438
+ });
1182
1439
  //==============================================================================
1183
1440
  // 新增功能:模型参数调节
1184
1441
  //==============================================================================