mslxdff 0.1.147 → 0.1.149
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/package.json
CHANGED
|
@@ -34,20 +34,39 @@ function fmtTps(v) {
|
|
|
34
34
|
return v == null || !Number.isFinite(v) ? "—" : `${v} tok/s`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// 中文字符按 2
|
|
38
|
-
function width(
|
|
37
|
+
// 中文字符按 2 列宽算,否则表格错位。
|
|
38
|
+
function width(value) {
|
|
39
39
|
let w = 0;
|
|
40
|
-
for (const ch of String(
|
|
40
|
+
for (const ch of String(value ?? "")) w += /[\u1100-\u115F\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/.test(ch) ? 2 : 1;
|
|
41
41
|
return w;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
function padCell(value, w, align = "left") {
|
|
45
|
+
const text = String(value ?? "");
|
|
46
|
+
const padding = " ".repeat(Math.max(0, w - width(text)));
|
|
47
|
+
return align === "right" ? padding + text : text + padding;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function
|
|
50
|
-
|
|
50
|
+
function renderTable(headers, rows, aligns = []) {
|
|
51
|
+
const matrix = [headers, ...rows];
|
|
52
|
+
const widths = headers.map((_, col) => Math.max(...matrix.map((row) => width(row[col]))));
|
|
53
|
+
const rule = (left, middle, right) => left + widths.map((w) => "─".repeat(w + 2)).join(middle) + right;
|
|
54
|
+
const line = (row) => `│${row.map((cell, col) => ` ${padCell(cell, widths[col], aligns[col])} `).join("│")}│`;
|
|
55
|
+
return [
|
|
56
|
+
rule("┌", "┬", "┐"),
|
|
57
|
+
line(headers),
|
|
58
|
+
rule("├", "┼", "┤"),
|
|
59
|
+
...rows.map(line),
|
|
60
|
+
rule("└", "┴", "┘"),
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tokenRow(id, r) {
|
|
65
|
+
return [id, r.requests, fmtTok(r.promptTokens), fmtTok(r.completionTokens), fmtTok(r.reasoningTokens), fmtTok(r.totalTokens)];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function performanceRow(id, r) {
|
|
69
|
+
return [id, fmtMs(r.avgTtfbMs), fmtMs(r.avgTotalMs), fmtTps(r.avgTps)];
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
export function renderStats(report, { hours = 24, model = null } = {}) {
|
|
@@ -59,15 +78,30 @@ export function renderStats(report, { hours = 24, model = null } = {}) {
|
|
|
59
78
|
lines.push("说明:-chat 直连 mimo/big-pickle 不经网关,不计入本表");
|
|
60
79
|
return lines.join("\n");
|
|
61
80
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
lines.push(
|
|
67
|
-
|
|
81
|
+
|
|
82
|
+
const right = "right";
|
|
83
|
+
const tokenRows = [...models.map((m) => tokenRow(m.id, m)), tokenRow("合计", totals)];
|
|
84
|
+
const performanceRows = [...models.map((m) => performanceRow(m.id, m)), performanceRow("合计", totals)];
|
|
85
|
+
lines.push(`模型用量报告(近 ${hours}h${model ? ` · 筛选 ${model}` : ""})`);
|
|
86
|
+
lines.push(`成功请求:${totals.requests} 次 · 模型:${models.length} 个`);
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push("Token 用量");
|
|
89
|
+
lines.push(renderTable(["模型", "请求", "输入", "输出", "思考", "合计"], tokenRows, ["left", right, right, right, right, right]));
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push("响应性能");
|
|
92
|
+
lines.push(renderTable(["模型", "首字", "总耗时", "速度"], performanceRows, ["left", right, right, right]));
|
|
68
93
|
lines.push("");
|
|
69
94
|
lines.push("说明:速度 = 输出 tokens ÷ 生成耗时(总耗时−首字),按窗口加权;只统计成功请求。");
|
|
70
|
-
lines.push(
|
|
95
|
+
lines.push("范围:只含经 8989 网关的成功请求;不含失败请求和 -chat 直连。");
|
|
96
|
+
const keepDays = usageKeepDays();
|
|
97
|
+
if (hours > keepDays * 24) {
|
|
98
|
+
lines.push(`警告:查询 ${hours}h 超过数据保留 ${keepDays} 天,历史可能不完整。`);
|
|
99
|
+
} else {
|
|
100
|
+
lines.push(`数据保留 ${keepDays} 天;更早的历史已删除。`);
|
|
101
|
+
}
|
|
102
|
+
lines.push("明细口径:本表按模型聚合,不展开 via、interrupted 和单次 tps。");
|
|
103
|
+
lines.push("数值使用 k/M 缩写;需要精确值或机器处理请加 --json。");
|
|
104
|
+
lines.push("可调:mslxdff -stats --hours 1 | --model <id> | --json");
|
|
71
105
|
return lines.join("\n");
|
|
72
106
|
}
|
|
73
107
|
|
|
@@ -85,4 +119,4 @@ export async function handleStats(args) {
|
|
|
85
119
|
}
|
|
86
120
|
console.log(renderStats(report, { hours, model }));
|
|
87
121
|
return true;
|
|
88
|
-
}
|
|
122
|
+
}
|
|
@@ -54,6 +54,9 @@ export function responsesToChatBody(req = {}) {
|
|
|
54
54
|
// 客户端重试/重放历史时会把同一 item 存两份),故按 id 保首个、丢后续——重复项无合法语义,删了不吃亏。
|
|
55
55
|
let pendingReasoning = [];
|
|
56
56
|
const seenReasoningIds = new Set();
|
|
57
|
+
// 同 call_id 的 function_call_output 重复出现 → 上游 400 "Duplicate function_call_output"
|
|
58
|
+
// (2026-09-23 实测,客户端重试/重放历史时会把同一工具结果存两份),故同样按 call_id 保首个、丢后续。
|
|
59
|
+
const seenToolResultIds = new Set();
|
|
57
60
|
for (const it of items) {
|
|
58
61
|
if (!it || typeof it !== "object") continue;
|
|
59
62
|
if (it.type === "reasoning") {
|
|
@@ -81,6 +84,8 @@ export function responsesToChatBody(req = {}) {
|
|
|
81
84
|
if (pendingReasoning.length) { msg.reasoning_items = pendingReasoning; pendingReasoning = []; }
|
|
82
85
|
messages.push(msg);
|
|
83
86
|
} else if (it.type === "function_call_output") {
|
|
87
|
+
const _tkey = String(it.call_id || "");
|
|
88
|
+
if (_tkey) { if (seenToolResultIds.has(_tkey)) continue; seenToolResultIds.add(_tkey); }
|
|
84
89
|
messages.push({ role: "tool", tool_call_id: it.call_id || "", content: typeof it.output === "string" ? it.output : JSON.stringify(it.output ?? "") });
|
|
85
90
|
}
|
|
86
91
|
}
|
|
@@ -62,6 +62,9 @@ export function toModelPrompt(messages, { dropEncrypted = false } = {}) {
|
|
|
62
62
|
// (上游报错原文即要求 Remove duplicate items)。这里按 itemId 保首个、丢后续,
|
|
63
63
|
// 防客户端重放/其他生产者把同一加密 item 组装两次。
|
|
64
64
|
const seenReasoningIds = new Set();
|
|
65
|
+
// 同 toolCallId 的 tool 结果重复出现 → 上游 400 "Duplicate function_call_output"
|
|
66
|
+
// (2026-09-23 实测:responses 入站虽已去重,此处仍加防线,防其他生产者直接组装 chat messages 重放)。
|
|
67
|
+
const seenToolResultIds = new Set();
|
|
65
68
|
for (const m of Array.isArray(messages) ? messages : []) {
|
|
66
69
|
if (!m || typeof m !== "object") continue;
|
|
67
70
|
const role = String(m.role || "");
|
|
@@ -112,6 +115,8 @@ export function toModelPrompt(messages, { dropEncrypted = false } = {}) {
|
|
|
112
115
|
}
|
|
113
116
|
out.push({ role: "assistant", content: parts });
|
|
114
117
|
} else if (role === "tool") {
|
|
118
|
+
const _tkey = String(m.tool_call_id || "");
|
|
119
|
+
if (_tkey) { if (seenToolResultIds.has(_tkey)) continue; seenToolResultIds.add(_tkey); }
|
|
115
120
|
out.push({
|
|
116
121
|
role: "tool",
|
|
117
122
|
content: [{
|
|
@@ -24,6 +24,7 @@ export function diagnoseToolSequence(messages) {
|
|
|
24
24
|
const list = Array.isArray(messages) ? messages : [];
|
|
25
25
|
const issues = [];
|
|
26
26
|
const open = new Map();
|
|
27
|
+
const seenResults = new Set();
|
|
27
28
|
let calls = 0;
|
|
28
29
|
let results = 0;
|
|
29
30
|
const first = list[0];
|
|
@@ -50,8 +51,8 @@ export function diagnoseToolSequence(messages) {
|
|
|
50
51
|
results++;
|
|
51
52
|
const id = String(m.tool_call_id || "");
|
|
52
53
|
if (!id) issues.push(`#${i} tool 结果空 id`);
|
|
53
|
-
else if (
|
|
54
|
-
else open.delete(id);
|
|
54
|
+
else if (seenResults.has(id)) issues.push(`#${i} 结果重复:${id}(上游按 call_id 查重会 400 Duplicate function_call_output,去重后只应出现一次)`);
|
|
55
|
+
else { seenResults.add(id); if (!open.has(id)) issues.push(`#${i} 孤立结果:${id}`); else open.delete(id); }
|
|
55
56
|
} else if (open.size) {
|
|
56
57
|
issues.push(`#${i} ${role} 打断{${[...open.keys()].join(",")}}`);
|
|
57
58
|
}
|