@tencent-rtc/mcp 1.7.2 → 1.7.3
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 +13 -0
- package/README.md +13 -5
- package/knowledge/bm25.js +20 -11
- package/knowledge/search/generic-rerank.js +123 -0
- package/knowledge/search/search-pass.js +8 -1
- package/package.json +1 -1
- package/resource/index/manifest.json +1 -1
- package/resource/knowledge/chat/best-practice/common/web-sdk-log-level.md +76 -0
- package/resource/knowledge/chat/best-practice/miniprogram/desktop-wechat-miniprogram-error.md +79 -0
- package/resource/knowledge/roomkit/best-practice/vue/screen-share-auto-stop-on-leave.md +62 -0
- package/resource/knowledge/roomkit/core-sdk/react/network-proxy.md +83 -0
- package/resource/knowledge/roomkit/core-sdk/vue/network-proxy.md +83 -0
- package/resource/knowledge/roomkit/core-sdk/vue/recording.md +245 -0
- package/resource/knowledge/roomkit/core-sdk/vue/whiteboard-annotation.md +196 -0
- package/resource/knowledge/roomkit/faq/error-code.md +103 -0
- package/resource/knowledge/roomkit/preset/harmonyos/integration.md +461 -0
- package/resource/knowledge/roomkit/preset/react/features/network-proxy.md +79 -0
- package/resource/knowledge/roomkit/preset/uniapp-app/integration.md +580 -0
- package/resource/knowledge/roomkit/preset/vue/features/ai-caption-translation.md +69 -0
- package/resource/knowledge/roomkit/preset/vue/features/ai-denoiser.md +67 -0
- package/resource/knowledge/roomkit/preset/vue/features/network-proxy.md +79 -0
- package/resource/knowledge/roomkit/preset/vue/features/recording.md +85 -0
- package/resource/knowledge/roomkit/preset/vue/features/whiteboard-annotation.md +86 -0
- package/server.js +1 -1
- package/utils/preflight-route.js +18 -6
- package/utils/report-cls-client.js +1 -1
- package/utils/report-es-client.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Version 1.7.3 @2026.08.17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added knowledge base resources for RoomKit multi-platform integration, recording, whiteboard, network proxy, AI noise reduction, and subtitle translation, expanding coverage of the Room product domain.
|
|
7
|
+
- Added best-practice docs covering error codes, log levels, automatic screen-share stop, and WeChat Mini Program error troubleshooting.
|
|
8
|
+
- Added a generic additive rerank module with anchor selection and phrase extraction, integrated into the search reranking logic to improve recall ranking accuracy.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Optimized the BM25 index to use prototype-less objects, improving the isolation and stability of the index structure.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Fixed BM25 scoring anomalies caused by prototype-key pollution by adding prototype-key guards, preventing score pollution and NaN issues.
|
|
15
|
+
|
|
3
16
|
## Version 1.7.2 @2026.08.13
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -57,6 +57,19 @@ npx -y @tencent-rtc/mcp
|
|
|
57
57
|
|
|
58
58
|
> Only recent updates are listed here. For full history, see `CHANGELOG.md`.
|
|
59
59
|
|
|
60
|
+
## Version 1.7.3 @2026.08.17
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Added knowledge base resources for RoomKit multi-platform integration, recording, whiteboard, network proxy, AI noise reduction, and subtitle translation, expanding coverage of the Room product domain.
|
|
64
|
+
- Added best-practice docs covering error codes, log levels, automatic screen-share stop, and WeChat Mini Program error troubleshooting.
|
|
65
|
+
- Added a generic additive rerank module with anchor selection and phrase extraction, integrated into the search reranking logic to improve recall ranking accuracy.
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- Optimized the BM25 index to use prototype-less objects, improving the isolation and stability of the index structure.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- Fixed BM25 scoring anomalies caused by prototype-key pollution by adding prototype-key guards, preventing score pollution and NaN issues.
|
|
72
|
+
|
|
60
73
|
## Version 1.7.1 @2026.08.11
|
|
61
74
|
|
|
62
75
|
### Added
|
|
@@ -80,8 +93,3 @@ npx -y @tencent-rtc/mcp
|
|
|
80
93
|
|
|
81
94
|
#### Fixed
|
|
82
95
|
- Fixed off-topic retrieval in some complex queries, reducing high-score false-positive hits.
|
|
83
|
-
|
|
84
|
-
### Version 1.6.6 @2026.07.18
|
|
85
|
-
|
|
86
|
-
#### Added
|
|
87
|
-
- Added support for `get_native_live_uikit_integration`, enabling LiveKit(Android/iOS) UI integration guidance.
|
package/knowledge/bm25.js
CHANGED
|
@@ -61,28 +61,29 @@ export function tokenize(text) {
|
|
|
61
61
|
return tokens;
|
|
62
62
|
}
|
|
63
63
|
export function buildBM25Index(documents) {
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
64
|
+
// 用无原型对象,杜绝 term/id 为 'constructor'/'__proto__' 等原型键时的污染。
|
|
65
|
+
const postings = Object.create(null);
|
|
66
|
+
const docLengths = Object.create(null);
|
|
67
|
+
const docFreq = Object.create(null);
|
|
67
68
|
let totalLength = 0;
|
|
68
69
|
for (const doc of documents) {
|
|
69
70
|
const tokens = tokenize(doc.text);
|
|
70
71
|
docLengths[doc.id] = tokens.length;
|
|
71
72
|
totalLength += tokens.length;
|
|
72
|
-
const termFreq =
|
|
73
|
+
const termFreq = Object.create(null);
|
|
73
74
|
for (const token of tokens) {
|
|
74
75
|
termFreq[token] = (termFreq[token] ?? 0) + 1;
|
|
75
76
|
}
|
|
76
77
|
for (const [term, freq] of Object.entries(termFreq)) {
|
|
77
78
|
if (!postings[term])
|
|
78
|
-
postings[term] =
|
|
79
|
+
postings[term] = Object.create(null);
|
|
79
80
|
postings[term][doc.id] = freq;
|
|
80
81
|
docFreq[term] = (docFreq[term] ?? 0) + 1;
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
const n = documents.length;
|
|
84
85
|
const avgDocLength = n > 0 ? totalLength / n : 0;
|
|
85
|
-
const idf =
|
|
86
|
+
const idf = Object.create(null);
|
|
86
87
|
for (const [term, df] of Object.entries(docFreq)) {
|
|
87
88
|
idf[term] = Math.log(1 + (n - df + 0.5) / (df + 0.5));
|
|
88
89
|
}
|
|
@@ -96,15 +97,23 @@ export function buildBM25Index(documents) {
|
|
|
96
97
|
}
|
|
97
98
|
export function scoreBM25(query, chunkId, index) {
|
|
98
99
|
const tokens = tokenize(query);
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
// Object.prototype 污染防护:postings/idf/docLengths 来自 JSON.parse 的普通对象,
|
|
101
|
+
// term 可能是 'constructor'/'toString'/'__proto__' 等原型键,直接 [] 取值会命中
|
|
102
|
+
// 原型链上的函数(如 Object.prototype.constructor),污染打分为 NaN。用 hasOwn 守卫。
|
|
103
|
+
const docLength = Object.hasOwn(index.docLengths, chunkId) ? index.docLengths[chunkId] : 0;
|
|
104
|
+
if (!docLength || Number.isNaN(docLength))
|
|
101
105
|
return 0;
|
|
102
106
|
let score = 0;
|
|
103
107
|
for (const term of tokens) {
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
if (!Object.hasOwn(index.postings, term) || !Object.hasOwn(index.idf, term))
|
|
109
|
+
continue;
|
|
110
|
+
const posting = index.postings[term];
|
|
111
|
+
const tf = Object.hasOwn(posting, chunkId) ? posting[chunkId] : 0;
|
|
112
|
+
if (!tf || Number.isNaN(tf))
|
|
113
|
+
continue;
|
|
114
|
+
const idf = index.idf[term];
|
|
115
|
+
if (Number.isNaN(idf))
|
|
106
116
|
continue;
|
|
107
|
-
const idf = index.idf[term] ?? 0;
|
|
108
117
|
const numerator = tf * (K1 + 1);
|
|
109
118
|
const denominator = tf + K1 * (1 - B + B * (docLength / (index.avgDocLength || 1)));
|
|
110
119
|
score += idf * (numerator / denominator);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用加性 rerank:数据驱动的高 IDF 锚点命中 + 连续短语命中。
|
|
3
|
+
*
|
|
4
|
+
* 设计约束(零衰减 + 通用):
|
|
5
|
+
* - 纯加性:只往 boosts 加正数,无 penalty;无信号(无锚点且无短语命中)时返回空 map,
|
|
6
|
+
* 排序数学上完全等于现状 → 已有功能零衰减。
|
|
7
|
+
* - 量纲受控:单 chunk 上限 = W_ANCHOR + W_PHRASE,远低于 exact-lookup 的 +200~+300,
|
|
8
|
+
* 只在 BM25 平局区间起微调,不翻转既有精确命中。
|
|
9
|
+
* - 零硬编码:锚点门槛来自运行时 index.idf 分位、短语来自 query 结构,
|
|
10
|
+
* 不含任何具体词 / product / 文件名 → 对任意产品成立。
|
|
11
|
+
*
|
|
12
|
+
* 治两个诊断确认的真病根:
|
|
13
|
+
* - A(锚点命中):对抗「CJK unigram 被拆多份 × 高 tf 堆分」——稀有锚点命中给加权,
|
|
14
|
+
* 把靠通用词堆分的长文档区分开。
|
|
15
|
+
* - B(连续短语):用原始字符串连续匹配抓「组合稀有」(如 WebSocket is not a constructor),
|
|
16
|
+
* 同时天然排除 tokenize 产生的跨词垃圾 bigram(如「错怎」——它在原 query 里虽连续,
|
|
17
|
+
* 但作为连续串在无关文档正文中不命中,不会被误加分)。
|
|
18
|
+
*/
|
|
19
|
+
// ── 超参(唯一可调项,均可回归验证)──────────────────────────────
|
|
20
|
+
/** 锚点门槛:取当前检索子库 IDF 分布的分位阈值,IDF ≥ 此值的 query term 视为锚点 */
|
|
21
|
+
const IDF_PERCENTILE = 0.75;
|
|
22
|
+
/** 锚点数上限,防止长 query 稀释权重 */
|
|
23
|
+
const MAX_ANCHORS = 6;
|
|
24
|
+
/** 锚点命中权重(与 BM25 top 分 20~50 同量级,低于 exact-lookup boost 量级) */
|
|
25
|
+
const W_ANCHOR = 40;
|
|
26
|
+
/** 连续短语命中权重 */
|
|
27
|
+
const W_PHRASE = 30;
|
|
28
|
+
/**
|
|
29
|
+
* 从 query term 中选出锚点:IDF ≥ 子库分位阈值的 term,按 IDF 降序取 top-K。
|
|
30
|
+
* 门槛完全由 index.idf 的运行时分布决定(数据驱动),不写死任何词。
|
|
31
|
+
*/
|
|
32
|
+
function selectAnchors(queryTerms, index) {
|
|
33
|
+
const allIdf = Object.values(index.idf);
|
|
34
|
+
if (allIdf.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
const sorted = [...allIdf].sort((a, b) => a - b);
|
|
37
|
+
const threshold = sorted[Math.floor(sorted.length * IDF_PERCENTILE)] ?? 0;
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const anchors = [];
|
|
40
|
+
for (const term of queryTerms) {
|
|
41
|
+
if (seen.has(term))
|
|
42
|
+
continue;
|
|
43
|
+
seen.add(term);
|
|
44
|
+
// Object.prototype 污染防护:term 可能是 'constructor' 等原型键。
|
|
45
|
+
if (!Object.hasOwn(index.idf, term))
|
|
46
|
+
continue;
|
|
47
|
+
const idf = index.idf[term];
|
|
48
|
+
if (typeof idf !== 'number' || Number.isNaN(idf) || idf < threshold)
|
|
49
|
+
continue;
|
|
50
|
+
anchors.push({ term, idf });
|
|
51
|
+
}
|
|
52
|
+
anchors.sort((a, b) => b.idf - a.idf);
|
|
53
|
+
return anchors.slice(0, MAX_ANCHORS);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 从原始 query 抽取连续短语(不经 tokenize,避免分词假象):
|
|
57
|
+
* - 英文:相邻词对(连续两个 ≥2 长度的英文/数字 token)
|
|
58
|
+
* - 中文:连续 ≥2 字的中文片段(整段,不切分)
|
|
59
|
+
*/
|
|
60
|
+
function extractPhrases(prompt) {
|
|
61
|
+
const phrases = [];
|
|
62
|
+
const enWords = prompt.toLowerCase().match(/[a-z0-9_]{2,}/g) ?? [];
|
|
63
|
+
for (let i = 0; i < enWords.length - 1; i += 1) {
|
|
64
|
+
phrases.push(`${enWords[i]} ${enWords[i + 1]}`);
|
|
65
|
+
}
|
|
66
|
+
for (const seg of prompt.match(/[\u4e00-\u9fff]{2,}/g) ?? []) {
|
|
67
|
+
phrases.push(seg);
|
|
68
|
+
}
|
|
69
|
+
return [...new Set(phrases)].filter((p) => p.replace(/\s/g, '').length >= 2);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 计算通用加性 boost。返回 chunkId -> 正分。
|
|
73
|
+
* 无锚点且无短语时返回空 map(排序退化为原始,零衰减)。
|
|
74
|
+
*/
|
|
75
|
+
export function computeGenericAnchorBoosts(prompt, pool, index, tokenizeFn) {
|
|
76
|
+
const result = {};
|
|
77
|
+
const anchors = selectAnchors(tokenizeFn(prompt), index);
|
|
78
|
+
const phrases = extractPhrases(prompt);
|
|
79
|
+
// 门槛:既无锚点也无短语 → 直接返回空,排序完全等于原来。
|
|
80
|
+
if (anchors.length === 0 && phrases.length === 0)
|
|
81
|
+
return result;
|
|
82
|
+
const maxIdf = Math.max(1, ...anchors.map((a) => a.idf));
|
|
83
|
+
for (const chunk of pool) {
|
|
84
|
+
// ── 信号 A:命中锚点,按 IDF 归一化累加命中强度 ──
|
|
85
|
+
let anchorStrength = 0;
|
|
86
|
+
let anchorHits = 0;
|
|
87
|
+
for (const { term, idf } of anchors) {
|
|
88
|
+
// Object.prototype 污染防护:term 可能是 'constructor' 等原型键。
|
|
89
|
+
if (!Object.hasOwn(index.postings, term))
|
|
90
|
+
continue;
|
|
91
|
+
const posting = index.postings[term];
|
|
92
|
+
if ((Object.hasOwn(posting, chunk.id) ? posting[chunk.id] : 0) > 0) {
|
|
93
|
+
anchorStrength += idf / maxIdf;
|
|
94
|
+
anchorHits += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// ── 信号 B:连续短语在 title / key_phrases / 正文原始串命中 ──
|
|
98
|
+
const haystack = [
|
|
99
|
+
chunk.title,
|
|
100
|
+
...(chunk.key_phrases ?? []),
|
|
101
|
+
chunk.content,
|
|
102
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
103
|
+
let phraseHits = 0;
|
|
104
|
+
for (const phrase of phrases) {
|
|
105
|
+
if (haystack.includes(phrase.toLowerCase())) {
|
|
106
|
+
phraseHits += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 该 chunk 无任何信号 → 不加分(保持原分)。
|
|
110
|
+
if (anchorHits === 0 && phraseHits === 0)
|
|
111
|
+
continue;
|
|
112
|
+
const boostA = anchors.length > 0
|
|
113
|
+
? W_ANCHOR * (anchorStrength / anchors.length) // 归一化到 [0, W_ANCHOR]
|
|
114
|
+
: 0;
|
|
115
|
+
const boostB = phrases.length > 0
|
|
116
|
+
? W_PHRASE * Math.min(1, phraseHits / phrases.length) // [0, W_PHRASE]
|
|
117
|
+
: 0;
|
|
118
|
+
const total = boostA + boostB;
|
|
119
|
+
if (total > 0)
|
|
120
|
+
result[chunk.id] = total;
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { attachPoolContent } from '../content-loader.js';
|
|
2
2
|
import { buildReferences } from '../doc-url-resolver.js';
|
|
3
|
-
import { rankSubsetWithIndex } from '../bm25.js';
|
|
3
|
+
import { rankSubsetWithIndex, tokenize } from '../bm25.js';
|
|
4
4
|
import { getProductsBM25Index } from '../bm25-cache.js';
|
|
5
5
|
import { filterPool } from './filter-pool.js';
|
|
6
6
|
import { runExactLookup } from './exact-lookup.js';
|
|
7
|
+
import { computeGenericAnchorBoosts } from './generic-rerank.js';
|
|
7
8
|
import { applyRankMultipliers, computeApiNameCoverage, computeMissingApiTokens, isUrlMappingSource, postProcess, } from './search-core.js';
|
|
8
9
|
function isBestPracticeSource(source) {
|
|
9
10
|
return /\/best-practice\//i.test(source);
|
|
@@ -379,6 +380,12 @@ export function runSearchPass(params) {
|
|
|
379
380
|
const productIndex = getProductsBM25Index(allChunks, passNorm.product);
|
|
380
381
|
const poolWithContent = attachPoolContent(pool);
|
|
381
382
|
const chunksById = new Map(poolWithContent.map((chunk) => [chunk.id, chunk]));
|
|
383
|
+
// 通用加性 rerank:数据驱动的高 IDF 锚点命中 + 连续短语命中。
|
|
384
|
+
// 纯加性、只加不减、无信号时返回空 map → 排序退化为原始,已有功能零衰减。
|
|
385
|
+
const genericBoosts = computeGenericAnchorBoosts(effectivePassNorm.prompt, poolWithContent, productIndex, tokenize);
|
|
386
|
+
for (const [id, amount] of Object.entries(genericBoosts)) {
|
|
387
|
+
boosts[id] = (boosts[id] ?? 0) + amount;
|
|
388
|
+
}
|
|
382
389
|
const ranked = rankSubsetWithIndex(effectivePassNorm.prompt, pool.map((chunk) => chunk.id), productIndex, boosts);
|
|
383
390
|
const rankedWithMultipliers = applyRankMultipliers(ranked, chunksById, {
|
|
384
391
|
product: passNorm.product,
|