dsh-layered-memory 0.8.0 → 0.8.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.en.md +54 -11
- package/README.md +51 -9
- package/assets/readme/bench-dialog.svg +87 -0
- package/assets/readme/bench-workflow.svg +79 -0
- package/assets/readme/flow.svg +74 -74
- package/assets/readme/storage.svg +56 -56
- package/dist/client.js +369 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +10 -3
- package/dist/llm.d.ts +15 -0
- package/dist/llm.js +16 -0
- package/dist/pipeline/l1.js +3 -3
- package/dist/pipeline/l2.js +2 -2
- package/dist/pipeline/l3.js +2 -2
- package/dist/pipeline/rebuild.js +2 -2
- package/dist/pipeline/runner.d.ts +9 -3
- package/dist/pipeline/runner.js +37 -4
- package/dist/settings.d.ts +14 -0
- package/dist/settings.js +41 -2
- package/dist/stats.js +113 -1
- package/dist/store/download-queue.d.ts +35 -2
- package/dist/store/download-queue.js +102 -5
- package/dist/store/model-catalog.js +1 -1
- package/package.json +14 -12
package/dist/client.js
CHANGED
|
@@ -1463,6 +1463,363 @@ window.__ModuleLoader__.load({
|
|
|
1463
1463
|
);
|
|
1464
1464
|
}
|
|
1465
1465
|
|
|
1466
|
+
// ── 蒸馏模型选择器(供应商/模型两级下拉)──
|
|
1467
|
+
// 数据源:dsh-memory/llm-providers(供应商目录 + 当前覆盖 + 实际生效路由)与
|
|
1468
|
+
// dsh-memory/llm-models(所选供应商的模型列表)。写入走 settings-set 的
|
|
1469
|
+
// distillProvider/distillModel 两键(成对提交)。部署静态 pin(pinned)时禁用。
|
|
1470
|
+
function LlmModelRow(props) {
|
|
1471
|
+
var rpc = props.rpc;
|
|
1472
|
+
var disabled = !!props.disabled;
|
|
1473
|
+
|
|
1474
|
+
// 注意:全部 hook 必须在任何提前返回之前声明(Rules of Hooks)——
|
|
1475
|
+
// info 未加载的首渲染会 return null,晚于它的 useState 会让 hook 数随渲染漂移而崩溃
|
|
1476
|
+
var infoState = react.useState(null);
|
|
1477
|
+
var setInfo = infoState[1];
|
|
1478
|
+
var modelsState = react.useState(null);
|
|
1479
|
+
var setModels = modelsState[1];
|
|
1480
|
+
var draftState = react.useState("");
|
|
1481
|
+
var setDraft = draftState[1];
|
|
1482
|
+
var manualState = react.useState("");
|
|
1483
|
+
var manualModel = manualState[0];
|
|
1484
|
+
var setManualModel = manualState[1];
|
|
1485
|
+
|
|
1486
|
+
react.useEffect(function () {
|
|
1487
|
+
var load = function () {
|
|
1488
|
+
rpc("dsh-memory/llm-providers", {})
|
|
1489
|
+
.then(function (r) { if (r && r.ok) setInfo(r.value); })
|
|
1490
|
+
.catch(function () {});
|
|
1491
|
+
};
|
|
1492
|
+
load();
|
|
1493
|
+
var timer = setInterval(load, 5000);
|
|
1494
|
+
return function () { clearInterval(timer); };
|
|
1495
|
+
}, [rpc]);
|
|
1496
|
+
|
|
1497
|
+
var info = infoState[0];
|
|
1498
|
+
// 草稿供应商(已选待挑模型)优先于已存选择
|
|
1499
|
+
var draft = draftState[0];
|
|
1500
|
+
var curProvider = draft || (info && info.current ? info.current.provider : "");
|
|
1501
|
+
var curModel = info && info.current ? info.current.model : "";
|
|
1502
|
+
|
|
1503
|
+
react.useEffect(function () {
|
|
1504
|
+
if (!curProvider) { setModels(null); return undefined; }
|
|
1505
|
+
var alive = true;
|
|
1506
|
+
setModels(null);
|
|
1507
|
+
rpc("dsh-memory/llm-models", { provider: curProvider })
|
|
1508
|
+
.then(function (r) { if (alive && r && r.ok) setModels(r.value.models || []); })
|
|
1509
|
+
.catch(function () { if (alive) setModels([]); });
|
|
1510
|
+
return function () { alive = false; };
|
|
1511
|
+
}, [rpc, curProvider]);
|
|
1512
|
+
|
|
1513
|
+
var writeLlm = function (patch) {
|
|
1514
|
+
if (!info) return;
|
|
1515
|
+
// 乐观更新:立即写视图字段,失败回滚
|
|
1516
|
+
var prev = info;
|
|
1517
|
+
setInfo(Object.assign({}, prev, {
|
|
1518
|
+
current: Object.assign({}, prev.current, patch),
|
|
1519
|
+
}));
|
|
1520
|
+
setDraft("");
|
|
1521
|
+
rpc("dsh-memory/settings-set", patch)
|
|
1522
|
+
.then(function (r) {
|
|
1523
|
+
if (!r || !r.ok) {
|
|
1524
|
+
setInfo(prev);
|
|
1525
|
+
setDraft(draft);
|
|
1526
|
+
}
|
|
1527
|
+
})
|
|
1528
|
+
.catch(function () {
|
|
1529
|
+
setInfo(prev);
|
|
1530
|
+
setDraft(draft);
|
|
1531
|
+
});
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
if (!info) return null;
|
|
1535
|
+
if (info.pinned) {
|
|
1536
|
+
// 部署静态 pin:显示固定路由,选择器不出场
|
|
1537
|
+
return react.createElement(
|
|
1538
|
+
"div", { style: S.switchRow },
|
|
1539
|
+
react.createElement("div", null,
|
|
1540
|
+
react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
|
|
1541
|
+
react.createElement(
|
|
1542
|
+
"div", { style: S.switchDesc },
|
|
1543
|
+
"已由部署配置固定:" + info.effective.provider + " / " + info.effective.model,
|
|
1544
|
+
)),
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
var providers = info.providers || [];
|
|
1549
|
+
var models = modelsState[0];
|
|
1550
|
+
var providerKnown = curProvider === "" || providers.some(function (p) { return p.id === curProvider; });
|
|
1551
|
+
var modelKnown = models && models.some(function (m) { return m.id === curModel; });
|
|
1552
|
+
// 供应商列表为空数组 = 适配器不提供模型目录:降级为手动输入模型 id
|
|
1553
|
+
var manualInput = models !== null && models.length === 0;
|
|
1554
|
+
|
|
1555
|
+
var defaultLabel = info.default
|
|
1556
|
+
? "跟随默认(" + info.default.provider + " / " + info.default.model + ")"
|
|
1557
|
+
: "跟随默认";
|
|
1558
|
+
|
|
1559
|
+
var providerOptions = [{ id: "", label: defaultLabel }].concat(
|
|
1560
|
+
providers.map(function (p) { return { id: p.id, label: p.name + "(" + p.id + ")" }; }),
|
|
1561
|
+
);
|
|
1562
|
+
if (curProvider && !providerKnown) {
|
|
1563
|
+
providerOptions.push({ id: curProvider, label: curProvider + "(已不在列表)" });
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
var modelOptions = [{ id: "", label: "跟随默认" }].concat(
|
|
1567
|
+
(models || []).map(function (m) { return { id: m.id, label: m.name !== m.id ? m.name + "(" + m.id + ")" : m.id }; }),
|
|
1568
|
+
);
|
|
1569
|
+
if (curModel && !modelKnown) {
|
|
1570
|
+
modelOptions.push({ id: curModel, label: curModel + "(已不在列表)" });
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
return react.createElement(
|
|
1574
|
+
"div", { style: Object.assign({}, S.switchRow, { flexWrap: "wrap" }) },
|
|
1575
|
+
react.createElement(
|
|
1576
|
+
"select",
|
|
1577
|
+
{
|
|
1578
|
+
className: "dsh-mem-select",
|
|
1579
|
+
style: { flexShrink: 0, maxWidth: 220 },
|
|
1580
|
+
value: curProvider,
|
|
1581
|
+
disabled: disabled,
|
|
1582
|
+
onChange: function (e) {
|
|
1583
|
+
var v = e.target.value;
|
|
1584
|
+
if (v === "") {
|
|
1585
|
+
writeLlm({ distillProvider: "", distillModel: "" });
|
|
1586
|
+
} else {
|
|
1587
|
+
setDraft(v);
|
|
1588
|
+
}
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
providerOptions.map(function (o) {
|
|
1592
|
+
return react.createElement("option", { key: o.id, value: o.id }, o.label);
|
|
1593
|
+
}),
|
|
1594
|
+
),
|
|
1595
|
+
!curProvider
|
|
1596
|
+
? null
|
|
1597
|
+
: manualInput
|
|
1598
|
+
? react.createElement(NInput, {
|
|
1599
|
+
style: { flex: 1, minWidth: 160 },
|
|
1600
|
+
placeholder: "模型 id(该供应商未提供列表,手动输入后回车)…",
|
|
1601
|
+
value: manualModel,
|
|
1602
|
+
onChange: function (e) { setManualModel(e.target.value); },
|
|
1603
|
+
onKeyDown: function (e) {
|
|
1604
|
+
if (e.key === "Enter" && manualModel.trim()) {
|
|
1605
|
+
writeLlm({ distillProvider: curProvider, distillModel: manualModel.trim() });
|
|
1606
|
+
setManualModel("");
|
|
1607
|
+
}
|
|
1608
|
+
},
|
|
1609
|
+
})
|
|
1610
|
+
: react.createElement(
|
|
1611
|
+
"select",
|
|
1612
|
+
{
|
|
1613
|
+
className: "dsh-mem-select",
|
|
1614
|
+
style: { flex: 1, minWidth: 160 },
|
|
1615
|
+
value: curModel,
|
|
1616
|
+
disabled: disabled || models === null,
|
|
1617
|
+
onChange: function (e) {
|
|
1618
|
+
writeLlm({ distillProvider: curProvider, distillModel: e.target.value });
|
|
1619
|
+
},
|
|
1620
|
+
},
|
|
1621
|
+
modelOptions.map(function (o) {
|
|
1622
|
+
return react.createElement("option", { key: o.id, value: o.id }, o.label);
|
|
1623
|
+
}),
|
|
1624
|
+
),
|
|
1625
|
+
react.createElement("div", null,
|
|
1626
|
+
react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
|
|
1627
|
+
react.createElement(
|
|
1628
|
+
"div", { style: S.switchDesc },
|
|
1629
|
+
info.effective
|
|
1630
|
+
? "当前生效 " + info.effective.provider + " / " + info.effective.model +
|
|
1631
|
+
(curProvider ? "" : "(跟随默认选择)")
|
|
1632
|
+
: "暂无可用路由(未选择且无默认)",
|
|
1633
|
+
),
|
|
1634
|
+
!providerKnown || (curModel && models !== null && !modelKnown)
|
|
1635
|
+
? react.createElement(
|
|
1636
|
+
"div", { style: { fontSize: 12, color: "var(--dsh-mem-danger)" } },
|
|
1637
|
+
"所选供应商或模型已不在已配置列表中,蒸馏调用可能失败,建议重新选择。",
|
|
1638
|
+
)
|
|
1639
|
+
: null),
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// ── 蒸馏预算(分层输出 token 上限 + 输入字符上限)──
|
|
1644
|
+
// 数据源:settings-get 的 budgets(current 运行时覆盖 0=跟随默认 / defaults
|
|
1645
|
+
// 内置默认 / effective 实际生效)与 inputBudget(current 0=跟随配置 /
|
|
1646
|
+
// fallback 静态配置值 / effective)。输出四键经 settings-set 的
|
|
1647
|
+
// distillBudgets 成对提交;输入走 distillMaxInputChars 单键提交。
|
|
1648
|
+
// 思考档 high/max 的 ×4 放大只作用于输出预算(提示文案注明)。
|
|
1649
|
+
function BudgetInputs(props) {
|
|
1650
|
+
var rpc = props.rpc;
|
|
1651
|
+
var disabled = !!props.disabled;
|
|
1652
|
+
var data = props.data;
|
|
1653
|
+
var setData = props.setData;
|
|
1654
|
+
var onError = props.onError;
|
|
1655
|
+
var layers = [
|
|
1656
|
+
["extract", "抽取"],
|
|
1657
|
+
["dedup", "去重"],
|
|
1658
|
+
["l2", "L2 场景"],
|
|
1659
|
+
["l3", "L3 画像"],
|
|
1660
|
+
];
|
|
1661
|
+
// 输入草稿(string|null):击键只改本地,blur/回车才提交(input 键 = 输入预算)
|
|
1662
|
+
var draftState = react.useState(null);
|
|
1663
|
+
var draft = draftState[0];
|
|
1664
|
+
var setDraft = draftState[1];
|
|
1665
|
+
|
|
1666
|
+
if (!data || !data.budgets) return null;
|
|
1667
|
+
var cur = data.budgets.current || {};
|
|
1668
|
+
var def = data.budgets.defaults || {};
|
|
1669
|
+
var eff = data.budgets.effective || {};
|
|
1670
|
+
var ib = data.inputBudget || {};
|
|
1671
|
+
var curIn = ib.current || 0;
|
|
1672
|
+
var effIn = ib.effective || ib.fallback || 0;
|
|
1673
|
+
|
|
1674
|
+
var shown = function (key) {
|
|
1675
|
+
if (draft && draft[key] !== undefined) return draft[key];
|
|
1676
|
+
var c = key === "input" ? curIn : cur[key];
|
|
1677
|
+
return c > 0 ? String(c) : "";
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
/** 通用提交:patchKey 为 settings-set 载荷键名;pick 从草稿取值并校验。 */
|
|
1681
|
+
var commitPart = function (keys, buildPayload, applyView) {
|
|
1682
|
+
if (!draft) return;
|
|
1683
|
+
var values = {};
|
|
1684
|
+
for (var i = 0; i < keys.length; i++) {
|
|
1685
|
+
var key = keys[i];
|
|
1686
|
+
var raw = (draft[key] !== undefined ? draft[key] : shown(key)).trim();
|
|
1687
|
+
var n = raw === "" ? 0 : Number(raw);
|
|
1688
|
+
var max = key === "input" ? 1000000 : 1000000;
|
|
1689
|
+
var min = key === "input" ? (raw === "" ? 0 : 1000) : 0;
|
|
1690
|
+
if (!Number.isInteger(n) || n < min || n > max) {
|
|
1691
|
+
onError(key === "input"
|
|
1692
|
+
? "输入预算须为 0 或 1000~1000000 的整数(留空或 0 = 跟随配置)"
|
|
1693
|
+
: "输出预算须为 0~1000000 的整数(留空或 0 = 跟随默认)");
|
|
1694
|
+
setDraft(null);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
values[key] = n;
|
|
1698
|
+
}
|
|
1699
|
+
setDraft(null);
|
|
1700
|
+
var prev = data;
|
|
1701
|
+
setData(applyView(prev, values));
|
|
1702
|
+
rpc("dsh-memory/settings-set", buildPayload(values))
|
|
1703
|
+
.then(function (r) {
|
|
1704
|
+
if (!r || !r.ok) {
|
|
1705
|
+
setData(prev);
|
|
1706
|
+
onError(r && r.error ? "预算写入失败:" + r.error.message : "预算写入失败");
|
|
1707
|
+
} else {
|
|
1708
|
+
onError(null);
|
|
1709
|
+
}
|
|
1710
|
+
})
|
|
1711
|
+
.catch(function (e) {
|
|
1712
|
+
setData(prev);
|
|
1713
|
+
onError("预算写入失败:" + String((e && e.message) || e));
|
|
1714
|
+
});
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1717
|
+
/** 输出四键成对提交(distillBudgets)。 */
|
|
1718
|
+
var commitOutputs = function () {
|
|
1719
|
+
commitPart(
|
|
1720
|
+
["extract", "dedup", "l2", "l3"],
|
|
1721
|
+
function (v) { return { distillBudgets: v }; },
|
|
1722
|
+
function (prev, v) {
|
|
1723
|
+
var effNext = {};
|
|
1724
|
+
for (var j = 0; j < layers.length; j++) {
|
|
1725
|
+
var k = layers[j][0];
|
|
1726
|
+
effNext[k] = v[k] > 0 ? v[k] : (def[k] || 0);
|
|
1727
|
+
}
|
|
1728
|
+
return Object.assign({}, prev, {
|
|
1729
|
+
settings: Object.assign({}, prev.settings, { distillBudgets: v }),
|
|
1730
|
+
budgets: Object.assign({}, prev.budgets, { current: v, effective: effNext }),
|
|
1731
|
+
});
|
|
1732
|
+
},
|
|
1733
|
+
);
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
/** 输入预算单键提交(distillMaxInputChars)。 */
|
|
1737
|
+
var commitInput = function () {
|
|
1738
|
+
commitPart(
|
|
1739
|
+
["input"],
|
|
1740
|
+
function (v) { return { distillMaxInputChars: v.input }; },
|
|
1741
|
+
function (prev, v) {
|
|
1742
|
+
return Object.assign({}, prev, {
|
|
1743
|
+
settings: Object.assign({}, prev.settings, { distillMaxInputChars: v.input }),
|
|
1744
|
+
inputBudget: Object.assign({}, prev.inputBudget || {}, {
|
|
1745
|
+
current: v.input,
|
|
1746
|
+
effective: v.input > 0 ? v.input : (ib.fallback || 0),
|
|
1747
|
+
}),
|
|
1748
|
+
});
|
|
1749
|
+
},
|
|
1750
|
+
);
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
/** 某组键是否有未提交改动(触发 blur/Enter 提交)。 */
|
|
1754
|
+
var dirty = function (keys) {
|
|
1755
|
+
if (!draft) return false;
|
|
1756
|
+
for (var i = 0; i < keys.length; i++) {
|
|
1757
|
+
var key = keys[i];
|
|
1758
|
+
var want = (draft[key] !== undefined ? draft[key] : "").trim();
|
|
1759
|
+
var was = key === "input"
|
|
1760
|
+
? (curIn > 0 ? String(curIn) : "")
|
|
1761
|
+
: (cur[key] > 0 ? String(cur[key]) : "");
|
|
1762
|
+
if (want !== was) return true;
|
|
1763
|
+
}
|
|
1764
|
+
return false;
|
|
1765
|
+
};
|
|
1766
|
+
var outKeys = ["extract", "dedup", "l2", "l3"];
|
|
1767
|
+
|
|
1768
|
+
var inputBox = function (key, label, title, width, placeholder, onCommit) {
|
|
1769
|
+
return react.createElement(NInput, {
|
|
1770
|
+
key: key,
|
|
1771
|
+
type: "number",
|
|
1772
|
+
min: 0,
|
|
1773
|
+
max: 1000000,
|
|
1774
|
+
style: { width: width },
|
|
1775
|
+
title: title,
|
|
1776
|
+
placeholder: String(placeholder || ""),
|
|
1777
|
+
value: shown(key),
|
|
1778
|
+
disabled: disabled,
|
|
1779
|
+
onChange: function (e) {
|
|
1780
|
+
var v = e.target.value;
|
|
1781
|
+
var d = Object.assign({}, draft || {});
|
|
1782
|
+
d[key] = v;
|
|
1783
|
+
setDraft(d);
|
|
1784
|
+
},
|
|
1785
|
+
onBlur: function () { if (dirty([key])) onCommit(); },
|
|
1786
|
+
onKeyDown: function (e) { if (e.key === "Enter" && dirty([key])) onCommit(); },
|
|
1787
|
+
});
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
return react.createElement(
|
|
1791
|
+
"div", null,
|
|
1792
|
+
react.createElement(
|
|
1793
|
+
"div", { style: S.switchRow },
|
|
1794
|
+
react.createElement(
|
|
1795
|
+
"div", { style: Object.assign({}, S.flexRow, { gap: 6 }) },
|
|
1796
|
+
layers.map(function (l) {
|
|
1797
|
+
return inputBox(l[0], l[1], l[1] + " 输出预算(token,留空 = 默认 " + (def[l[0]] || "?") + ")", 92, def[l[0]], commitOutputs);
|
|
1798
|
+
}),
|
|
1799
|
+
),
|
|
1800
|
+
react.createElement("div", null,
|
|
1801
|
+
react.createElement("div", { style: S.switchLabel }, "输出预算"),
|
|
1802
|
+
react.createElement(
|
|
1803
|
+
"div", { style: S.switchDesc },
|
|
1804
|
+
"各蒸馏层单次输出的 token 上限(抽取/去重/L2/L3);留空或 0 = 跟随默认(当前生效 " +
|
|
1805
|
+
layers.map(function (l) { return eff[l[0]] || "?"; }).join(" / ") +
|
|
1806
|
+
");思考档 high/max 时实际限额自动 ×4",
|
|
1807
|
+
)),
|
|
1808
|
+
),
|
|
1809
|
+
react.createElement(
|
|
1810
|
+
"div", { style: S.switchRow },
|
|
1811
|
+
inputBox("input", "输入", "单次蒸馏输入字符上限(≈token,中文 1 字≈1 token;留空 = 跟随配置 " + (ib.fallback || "?") + ")", 120, ib.fallback, commitInput),
|
|
1812
|
+
react.createElement("div", null,
|
|
1813
|
+
react.createElement("div", { style: S.switchLabel }, "输入预算"),
|
|
1814
|
+
react.createElement(
|
|
1815
|
+
"div", { style: S.switchDesc },
|
|
1816
|
+
"单次蒸馏调用的输入字符上限(≈token):L1 抽取按此分块、L2/L3 超限截断;" +
|
|
1817
|
+
"留空或 0 = 跟随配置(当前生效 " + (effIn || "?") + ",来自 llm.maxInputChars)",
|
|
1818
|
+
)),
|
|
1819
|
+
),
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1466
1823
|
// ── Tab:概览(开关面板 + 计数) ──
|
|
1467
1824
|
function OverviewTab(props) {
|
|
1468
1825
|
var rpc = props.rpc;
|
|
@@ -1567,6 +1924,8 @@ window.__ModuleLoader__.load({
|
|
|
1567
1924
|
: settingsData
|
|
1568
1925
|
? react.createElement(
|
|
1569
1926
|
"div", { style: S.switchPanel },
|
|
1927
|
+
// 分组:记忆模式(总闸与三个分项开关)
|
|
1928
|
+
react.createElement("div", { style: S.panelLabel }, "记忆模式"),
|
|
1570
1929
|
react.createElement(SwitchRow, {
|
|
1571
1930
|
label: "记忆模式",
|
|
1572
1931
|
desc: master ? "已开启:捕获对话并蒸馏记忆" : "已关闭:不捕获、不蒸馏、不注入(数据保留)",
|
|
@@ -1594,6 +1953,8 @@ window.__ModuleLoader__.load({
|
|
|
1594
1953
|
disabled: !master,
|
|
1595
1954
|
onChange: function (v) { toggle("recall", v); },
|
|
1596
1955
|
}),
|
|
1956
|
+
// 分组:蒸馏参数(模型路由 / 思考档位 / 输出预算)
|
|
1957
|
+
react.createElement("div", { style: S.panelLabel }, "蒸馏参数"),
|
|
1597
1958
|
settingsData.effort
|
|
1598
1959
|
? react.createElement(
|
|
1599
1960
|
"div",
|
|
@@ -1619,6 +1980,14 @@ window.__ModuleLoader__.load({
|
|
|
1619
1980
|
)),
|
|
1620
1981
|
)
|
|
1621
1982
|
: null,
|
|
1983
|
+
react.createElement(LlmModelRow, { rpc: rpc, disabled: !master }),
|
|
1984
|
+
react.createElement(BudgetInputs, {
|
|
1985
|
+
rpc: rpc,
|
|
1986
|
+
disabled: !master,
|
|
1987
|
+
data: settingsData,
|
|
1988
|
+
setData: setSettingsData,
|
|
1989
|
+
onError: setError,
|
|
1990
|
+
}),
|
|
1622
1991
|
ceilingNote
|
|
1623
1992
|
? react.createElement("p", { style: S.hint }, ceilingNote)
|
|
1624
1993
|
: null,
|
package/dist/config.d.ts
CHANGED
|
@@ -77,6 +77,9 @@ export interface MemoryConfig {
|
|
|
77
77
|
allowLocalModels: boolean;
|
|
78
78
|
/** 模型下载镜像根地址(默认国内可达的 hf-mirror.com,可改回官方)。 */
|
|
79
79
|
mirror: string;
|
|
80
|
+
/** 模型下载代理三态:''(默认)= 探测代理环境变量(HTTPS_PROXY/ALL_PROXY 等);
|
|
81
|
+
* 'none' = 禁用强制直连;其他值 = 代理 URL(如 http://127.0.0.1:7890)。 */
|
|
82
|
+
proxy: string;
|
|
80
83
|
};
|
|
81
84
|
llm: {
|
|
82
85
|
/** 蒸馏用的 provider 路由;留空用当前默认选择。 */
|
|
@@ -92,6 +95,14 @@ export interface MemoryConfig {
|
|
|
92
95
|
maxInputChars: number;
|
|
93
96
|
/** 单次蒸馏调用超时(ms)。 */
|
|
94
97
|
timeoutMs: number;
|
|
98
|
+
/** 分层输出预算运行时覆盖(设置页 distillBudgets 经 effectiveCfg 注入;
|
|
99
|
+
* 0/缺省 = 用内置默认。不属于静态 schema——预算无部署上限语义,只有运行时偏好)。 */
|
|
100
|
+
budgets?: Partial<{
|
|
101
|
+
extract: number;
|
|
102
|
+
dedup: number;
|
|
103
|
+
l2: number;
|
|
104
|
+
l3: number;
|
|
105
|
+
}>;
|
|
95
106
|
};
|
|
96
107
|
/** 是否注册模型可调用的记忆工具。 */
|
|
97
108
|
tools: boolean;
|
|
@@ -170,6 +181,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
170
181
|
timeoutMs: Schema<number, number>;
|
|
171
182
|
allowLocalModels: Schema<boolean, boolean>;
|
|
172
183
|
mirror: Schema<string, string>;
|
|
184
|
+
proxy: Schema<string, string>;
|
|
173
185
|
}>, Schemastery.ObjectT<{
|
|
174
186
|
enabled: Schema<boolean, boolean>;
|
|
175
187
|
baseUrl: Schema<string, string>;
|
|
@@ -180,6 +192,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
180
192
|
timeoutMs: Schema<number, number>;
|
|
181
193
|
allowLocalModels: Schema<boolean, boolean>;
|
|
182
194
|
mirror: Schema<string, string>;
|
|
195
|
+
proxy: Schema<string, string>;
|
|
183
196
|
}>>;
|
|
184
197
|
llm: Schema<Schemastery.ObjectS<{
|
|
185
198
|
provider: Schema<string, string>;
|
|
@@ -273,6 +286,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
273
286
|
timeoutMs: Schema<number, number>;
|
|
274
287
|
allowLocalModels: Schema<boolean, boolean>;
|
|
275
288
|
mirror: Schema<string, string>;
|
|
289
|
+
proxy: Schema<string, string>;
|
|
276
290
|
}>, Schemastery.ObjectT<{
|
|
277
291
|
enabled: Schema<boolean, boolean>;
|
|
278
292
|
baseUrl: Schema<string, string>;
|
|
@@ -283,6 +297,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
283
297
|
timeoutMs: Schema<number, number>;
|
|
284
298
|
allowLocalModels: Schema<boolean, boolean>;
|
|
285
299
|
mirror: Schema<string, string>;
|
|
300
|
+
proxy: Schema<string, string>;
|
|
286
301
|
}>>;
|
|
287
302
|
llm: Schema<Schemastery.ObjectS<{
|
|
288
303
|
provider: Schema<string, string>;
|
package/dist/config.js
CHANGED
|
@@ -53,6 +53,7 @@ export const memorySchema = Schema.object({
|
|
|
53
53
|
timeoutMs: Schema.number().min(1000).max(300_000).default(10_000),
|
|
54
54
|
allowLocalModels: Schema.boolean().default(true),
|
|
55
55
|
mirror: Schema.string().default('https://hf-mirror.com'),
|
|
56
|
+
proxy: Schema.string().default(''),
|
|
56
57
|
}),
|
|
57
58
|
llm: Schema.object({
|
|
58
59
|
provider: Schema.string().default(''),
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
82
82
|
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
83
83
|
allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
84
84
|
mirror: import("@deepseek-ai/schemastery").default<string, string>;
|
|
85
|
+
proxy: import("@deepseek-ai/schemastery").default<string, string>;
|
|
85
86
|
}>, Schemastery.ObjectT<{
|
|
86
87
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
87
88
|
baseUrl: import("@deepseek-ai/schemastery").default<string, string>;
|
|
@@ -92,6 +93,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
92
93
|
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
93
94
|
allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
94
95
|
mirror: import("@deepseek-ai/schemastery").default<string, string>;
|
|
96
|
+
proxy: import("@deepseek-ai/schemastery").default<string, string>;
|
|
95
97
|
}>>;
|
|
96
98
|
llm: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
97
99
|
provider: import("@deepseek-ai/schemastery").default<string, string>;
|
|
@@ -185,6 +187,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
185
187
|
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
186
188
|
allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
187
189
|
mirror: import("@deepseek-ai/schemastery").default<string, string>;
|
|
190
|
+
proxy: import("@deepseek-ai/schemastery").default<string, string>;
|
|
188
191
|
}>, Schemastery.ObjectT<{
|
|
189
192
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
190
193
|
baseUrl: import("@deepseek-ai/schemastery").default<string, string>;
|
|
@@ -195,6 +198,7 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
195
198
|
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
196
199
|
allowLocalModels: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
197
200
|
mirror: import("@deepseek-ai/schemastery").default<string, string>;
|
|
201
|
+
proxy: import("@deepseek-ai/schemastery").default<string, string>;
|
|
198
202
|
}>>;
|
|
199
203
|
llm: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
200
204
|
provider: import("@deepseek-ai/schemastery").default<string, string>;
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import { StateStore } from './store/state.js';
|
|
|
39
39
|
import { registerMemoryTools } from './tools/index.js';
|
|
40
40
|
import { errDetail, withFileLog } from './util/filelog.js';
|
|
41
41
|
import { resolveModelRoute } from './llm.js';
|
|
42
|
+
import { effectiveCfg } from './pipeline/runner.js';
|
|
42
43
|
export const name = 'dsh-memory-plugin';
|
|
43
44
|
/** 硬依赖:蒸馏要用 llm,工具注册要用 tools,召回注入要用 systemPrompt。 */
|
|
44
45
|
export const inject = ['llm', 'tools', 'systemPrompt'];
|
|
@@ -86,7 +87,11 @@ export async function apply(ctx, config) {
|
|
|
86
87
|
// ── 嵌入源三态(D4:远程/本地/关闭)——状态文件优先于静态配置 ──
|
|
87
88
|
const sourceStore = new EmbeddingSourceStore(dataDir, logger);
|
|
88
89
|
const installer = new RuntimeInstaller(dataDir, PINNED_TRANSFORMERS_VERSION, { logger });
|
|
89
|
-
const downloader = new ModelDownloadQueue(dataDir, {
|
|
90
|
+
const downloader = new ModelDownloadQueue(dataDir, {
|
|
91
|
+
mirror: config.embedding.mirror,
|
|
92
|
+
logger,
|
|
93
|
+
proxy: config.embedding.proxy,
|
|
94
|
+
});
|
|
90
95
|
const makeLocalService = makeLocalServiceFactory(installer, downloader, logger);
|
|
91
96
|
let initial = { svc: new NoopEmbeddingService(), dims: 0 };
|
|
92
97
|
/** 管理器引用:启动重嵌链/backfill 闭包在运行期解引用(声明早于创建避免 TDZ)。 */
|
|
@@ -157,9 +162,10 @@ export async function apply(ctx, config) {
|
|
|
157
162
|
logger.warn(`[memory] 检索策略 ${config.recall.strategy} 需要向量能力(embedding 未启用/不可用),本次运行按 keyword(FTS5 BM25)检索`);
|
|
158
163
|
}
|
|
159
164
|
logger.info(`[memory] 数据目录: ${dataDir}(v${PLUGIN_VERSION},默认档=${config.family},检索: FTS=${caps.ftsSearch} 向量=${caps.vectorSearch}${storageOk ? '' : ',存储不可用已停用捕获/蒸馏'})`);
|
|
160
|
-
//
|
|
165
|
+
// 蒸馏模型路由:启动期解析一次并记录(路由错误是最难事后排查的问题之一);
|
|
166
|
+
// 用运行时调参视图解析——用户已用 UI 覆盖蒸馏模型时,日志反映实际路由
|
|
161
167
|
try {
|
|
162
|
-
const route = await resolveModelRoute(ctx, config);
|
|
168
|
+
const route = await resolveModelRoute(ctx, effectiveCfg(config, live));
|
|
163
169
|
logger.info(`[memory] 蒸馏模型路由: ${route.provider}/${route.model}`);
|
|
164
170
|
}
|
|
165
171
|
catch (err) {
|
|
@@ -298,6 +304,7 @@ export async function apply(ctx, config) {
|
|
|
298
304
|
disposed = true;
|
|
299
305
|
runner.stop();
|
|
300
306
|
embedManager?.dispose();
|
|
307
|
+
downloader.dispose();
|
|
301
308
|
return (async () => {
|
|
302
309
|
await flushL0?.();
|
|
303
310
|
db.close();
|
package/dist/llm.d.ts
CHANGED
|
@@ -21,6 +21,21 @@ export declare const LAYER_MAX_TOKENS_DEDUP = 8000;
|
|
|
21
21
|
export declare const LAYER_MAX_TOKENS_L2 = 32000;
|
|
22
22
|
/** L3 画像(完整 persona 文档)。 */
|
|
23
23
|
export declare const LAYER_MAX_TOKENS_L3 = 16000;
|
|
24
|
+
/** 分层输出预算键(运行时设置 / UI 与内置默认共用同一套键名)。 */
|
|
25
|
+
export type DistillBudgetLayer = 'extract' | 'dedup' | 'l2' | 'l3';
|
|
26
|
+
/** 各层内置默认预算(设置页"0 = 跟随默认"的默认值来源)。 */
|
|
27
|
+
export declare const LAYER_DEFAULT_BUDGETS: Record<DistillBudgetLayer, number>;
|
|
28
|
+
/**
|
|
29
|
+
* 解析某蒸馏层的生效输出预算:运行时覆盖(cfg.llm.budgets,由 effectiveCfg 从
|
|
30
|
+
* 设置页 distillBudgets 注入,0/缺省 = 跟随)→ 内置默认 → 思考档放大
|
|
31
|
+
* (high/max ×4,reasoning 计入输出预算的历史事故防线)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveLayerTokens(cfg: {
|
|
34
|
+
llm: {
|
|
35
|
+
reasoningEffort: string;
|
|
36
|
+
budgets?: Partial<Record<DistillBudgetLayer, number>>;
|
|
37
|
+
};
|
|
38
|
+
}, layer: DistillBudgetLayer): number;
|
|
24
39
|
/**
|
|
25
40
|
* 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
|
|
26
41
|
* 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
|
package/dist/llm.js
CHANGED
|
@@ -10,6 +10,22 @@ export const LAYER_MAX_TOKENS_DEDUP = 8_000;
|
|
|
10
10
|
export const LAYER_MAX_TOKENS_L2 = 32_000;
|
|
11
11
|
/** L3 画像(完整 persona 文档)。 */
|
|
12
12
|
export const LAYER_MAX_TOKENS_L3 = 16_000;
|
|
13
|
+
/** 各层内置默认预算(设置页"0 = 跟随默认"的默认值来源)。 */
|
|
14
|
+
export const LAYER_DEFAULT_BUDGETS = {
|
|
15
|
+
extract: LAYER_MAX_TOKENS_EXTRACT,
|
|
16
|
+
dedup: LAYER_MAX_TOKENS_DEDUP,
|
|
17
|
+
l2: LAYER_MAX_TOKENS_L2,
|
|
18
|
+
l3: LAYER_MAX_TOKENS_L3,
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 解析某蒸馏层的生效输出预算:运行时覆盖(cfg.llm.budgets,由 effectiveCfg 从
|
|
22
|
+
* 设置页 distillBudgets 注入,0/缺省 = 跟随)→ 内置默认 → 思考档放大
|
|
23
|
+
* (high/max ×4,reasoning 计入输出预算的历史事故防线)。
|
|
24
|
+
*/
|
|
25
|
+
export function resolveLayerTokens(cfg, layer) {
|
|
26
|
+
const override = cfg.llm.budgets?.[layer];
|
|
27
|
+
return layerMaxTokens(override && override > 0 ? override : LAYER_DEFAULT_BUDGETS[layer], cfg.llm.reasoningEffort);
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
|
|
15
31
|
* 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
|
package/dist/pipeline/l1.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 移植 MemoryCore 的 l1 runner 语义(抽取 prompt → batch 冲突检测 → apply)。
|
|
4
4
|
*/
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
|
-
import { callLLM,
|
|
6
|
+
import { callLLM, parseJsonLogged, resolveLayerTokens } from '../llm.js';
|
|
7
7
|
import { formatExtractionPrompt, getExtractMemoriesSystemPrompt } from '../prompts/l1-extraction.js';
|
|
8
8
|
import { formatBatchConflictPrompt, getConflictDetectionSystemPrompt } from '../prompts/l1-dedup.js';
|
|
9
9
|
import { familyForType } from '../types.js';
|
|
@@ -63,7 +63,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
|
|
|
63
63
|
const raw = await callLLM(ctx, cfg, {
|
|
64
64
|
system: getExtractMemoriesSystemPrompt(mode),
|
|
65
65
|
user: userPrompt,
|
|
66
|
-
maxTokens:
|
|
66
|
+
maxTokens: resolveLayerTokens(cfg, 'extract'),
|
|
67
67
|
logger,
|
|
68
68
|
});
|
|
69
69
|
const scenes = parseJsonLogged(raw, 'L1 抽取', logger);
|
|
@@ -101,7 +101,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
|
|
|
101
101
|
const dedupRaw = await callLLM(ctx, cfg, {
|
|
102
102
|
system: getConflictDetectionSystemPrompt(mode),
|
|
103
103
|
user: dedupPrompt,
|
|
104
|
-
maxTokens:
|
|
104
|
+
maxTokens: resolveLayerTokens(cfg, 'dedup'),
|
|
105
105
|
logger,
|
|
106
106
|
});
|
|
107
107
|
const decisions = parseJsonLogged(dedupRaw, 'L1 去重判定', logger);
|
package/dist/pipeline/l2.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callLLM,
|
|
1
|
+
import { callLLM, parseJsonLogged, resolveLayerTokens } from '../llm.js';
|
|
2
2
|
import { buildScenePrompt, formatSceneSummaries } from '../prompts/scene.js';
|
|
3
3
|
import { Bm25Index } from '../store/bm25.js';
|
|
4
4
|
const REQUEST_RE = /\[PERSONA_UPDATE_REQUEST\]\s*reason:\s*([\s\S]*?)\[\/PERSONA_UPDATE_REQUEST\]/;
|
|
@@ -30,7 +30,7 @@ export async function runSceneConsolidation(ctx, cfg, scenes, newMemories, logge
|
|
|
30
30
|
const raw = await callLLM(ctx, cfg, {
|
|
31
31
|
system: systemPrompt,
|
|
32
32
|
user: userPrompt,
|
|
33
|
-
maxTokens:
|
|
33
|
+
maxTokens: resolveLayerTokens(cfg, 'l2'),
|
|
34
34
|
logger,
|
|
35
35
|
});
|
|
36
36
|
// ── 解析:先取 PERSONA_UPDATE_REQUEST 标记,再解析操作数组 ──
|
package/dist/pipeline/l3.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callLLM,
|
|
1
|
+
import { callLLM, resolveLayerTokens } from '../llm.js';
|
|
2
2
|
import { buildPersonaPrompt } from '../prompts/persona.js';
|
|
3
3
|
export async function runPersona(ctx, cfg, scenes, persona, state, logger, family) {
|
|
4
4
|
if (!cfg.l3.enabled)
|
|
@@ -51,7 +51,7 @@ export async function runPersona(ctx, cfg, scenes, persona, state, logger, famil
|
|
|
51
51
|
const raw = await callLLM(ctx, cfg, {
|
|
52
52
|
system: systemPrompt,
|
|
53
53
|
user: userPrompt,
|
|
54
|
-
maxTokens:
|
|
54
|
+
maxTokens: resolveLayerTokens(cfg, 'l3'),
|
|
55
55
|
logger,
|
|
56
56
|
});
|
|
57
57
|
const body = unwrapFence(raw);
|
package/dist/pipeline/rebuild.js
CHANGED
|
@@ -99,7 +99,7 @@ export class RebuildController {
|
|
|
99
99
|
...this.status,
|
|
100
100
|
sessionCount: est.sessions,
|
|
101
101
|
messageCount: est.messages,
|
|
102
|
-
estCalls: estimateCalls(est.sessions, est.messages, est.chars, this.cfg.llm.maxInputChars),
|
|
102
|
+
estCalls: estimateCalls(est.sessions, est.messages, est.chars, effectiveCfg(this.cfg, this.live).llm.maxInputChars),
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
return { ...this.status };
|
|
@@ -123,7 +123,7 @@ export class RebuildController {
|
|
|
123
123
|
phase: 'preparing',
|
|
124
124
|
sessionCount: est.sessions,
|
|
125
125
|
messageCount: est.messages,
|
|
126
|
-
estCalls: estimateCalls(est.sessions, est.messages, est.chars, this.cfg.llm.maxInputChars),
|
|
126
|
+
estCalls: estimateCalls(est.sessions, est.messages, est.chars, effectiveCfg(this.cfg, this.live).llm.maxInputChars),
|
|
127
127
|
startedAt: Date.now(),
|
|
128
128
|
};
|
|
129
129
|
this.runner.enqueueRebuildTask(() => this.prepare());
|