@tencent-rtc/mcp 1.7.2 → 1.7.4
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 +26 -0
- package/README.md +25 -17
- package/knowledge/bm25.js +20 -11
- package/knowledge/path-meta.js +23 -1
- 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/callkit/best-practice/android/video-chat-dating.md +497 -0
- package/resource/knowledge/callkit/best-practice/flutter/android-keep-call-alive-background.md +79 -0
- package/resource/knowledge/callkit/best-practice/flutter/background-incoming-call-no-notification.md +99 -0
- package/resource/knowledge/callkit/best-practice/flutter/ios-keep-call-alive-background.md +93 -0
- package/resource/knowledge/callkit/best-practice/flutter/ios-startup-crash-version-voip.md +68 -0
- package/resource/knowledge/callkit/core-sdk/miniprogram/call-engine.md +386 -0
- package/resource/knowledge/callkit/core-sdk/miniprogram/call-event.md +315 -0
- package/resource/knowledge/callkit/faq/uniapp-miniprogram.md +204 -0
- package/resource/knowledge/callkit/preset/harmonyos/api.md +362 -0
- package/resource/knowledge/callkit/preset/harmonyos/integration.md +318 -0
- package/resource/knowledge/callkit/preset/miniprogram/api-uniapp.md +430 -0
- package/resource/knowledge/callkit/preset/miniprogram/api.md +293 -0
- package/resource/knowledge/callkit/preset/miniprogram/integration.md +294 -0
- package/resource/knowledge/callkit/preset/miniprogram/uniapp-integration.md +224 -0
- package/resource/knowledge/callkit/preset/uni-app/api.md +339 -0
- package/resource/knowledge/callkit/preset/uni-app/integration.md +243 -0
- 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/chat/preset/react/h5/integration.md +964 -0
- package/resource/knowledge/chat/preset/vue/h5/integration.md +717 -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/resource/knowledge/rtcengine/web/plugin/23-advanced-support-detection.md +6 -6
- package/resource/knowledge/rtcengine/web/plugin/31-advanced-plugins-overview.md +8 -6
- package/resource/knowledge/rtcengine/web/plugin/42-advanced-realtime-transcriber.md +30 -10
- 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,31 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Version 1.7.4 @2026.08.31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added 4 CallKit Flutter best-practice docs: Android background call keep-alive, iOS background audio keep-alive, iOS startup crash troubleshooting, background incoming call notification troubleshooting.
|
|
7
|
+
- Added CallKit Android video chat dating scenario (VideoChat Demo) best-practice doc, covering Demo setup and component quick integration.
|
|
8
|
+
- Added CallKit HarmonyOS platform support: UIKit integration guide and API reference.
|
|
9
|
+
- Added CallKit Mini Program & uni-app docs: UIKit integration guide, API reference, uni-app packaging guide.
|
|
10
|
+
- Added CallKit uni-app (Android/iOS) standalone integration docs and API reference.
|
|
11
|
+
- Added Chat Vue3/React H5 mobile integration docs.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Optimized Chat UIKit search tags: vue/react framework changed from single tag to dual tags (`['vue','web']` / `['react','web']`), allowing `frameworks=['web']` to match Chat Web docs.
|
|
15
|
+
|
|
16
|
+
## Version 1.7.3 @2026.08.17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- 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.
|
|
20
|
+
- Added best-practice docs covering error codes, log levels, automatic screen-share stop, and WeChat Mini Program error troubleshooting.
|
|
21
|
+
- Added a generic additive rerank module with anchor selection and phrase extraction, integrated into the search reranking logic to improve recall ranking accuracy.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Optimized the BM25 index to use prototype-less objects, improving the isolation and stability of the index structure.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Fixed BM25 scoring anomalies caused by prototype-key pollution by adding prototype-key guards, preventing score pollution and NaN issues.
|
|
28
|
+
|
|
3
29
|
## Version 1.7.2 @2026.08.13
|
|
4
30
|
|
|
5
31
|
### Changed
|
package/README.md
CHANGED
|
@@ -57,31 +57,39 @@ 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.
|
|
60
|
+
## Version 1.7.4 @2026.08.31
|
|
61
61
|
|
|
62
62
|
### Added
|
|
63
|
-
- Added
|
|
63
|
+
- Added 4 CallKit Flutter best-practice docs: Android background call keep-alive, iOS background audio keep-alive, iOS startup crash troubleshooting, background incoming call notification troubleshooting.
|
|
64
|
+
- Added CallKit Android video chat dating scenario (VideoChat Demo) best-practice doc, covering Demo setup and component quick integration.
|
|
65
|
+
- Added CallKit HarmonyOS platform support: UIKit integration guide and API reference.
|
|
66
|
+
- Added CallKit Mini Program & uni-app docs: UIKit integration guide, API reference, uni-app packaging guide.
|
|
67
|
+
- Added CallKit uni-app (Android/iOS) standalone integration docs and API reference.
|
|
68
|
+
- Added Chat Vue3/React H5 mobile integration docs.
|
|
64
69
|
|
|
65
70
|
### Changed
|
|
66
|
-
- Optimized
|
|
71
|
+
- Optimized Chat UIKit search tags: vue/react framework changed from single tag to dual tags (`['vue','web']` / `['react','web']`), allowing `frameworks=['web']` to match Chat Web docs.
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
## Version 1.7.3 @2026.08.17
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- 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.
|
|
77
|
+
- Added best-practice docs covering error codes, log levels, automatic screen-share stop, and WeChat Mini Program error troubleshooting.
|
|
78
|
+
- Added a generic additive rerank module with anchor selection and phrase extraction, integrated into the search reranking logic to improve recall ranking accuracy.
|
|
70
79
|
|
|
71
|
-
###
|
|
80
|
+
### Changed
|
|
81
|
+
- Optimized the BM25 index to use prototype-less objects, improving the isolation and stability of the index structure.
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
-
|
|
83
|
+
### Fixed
|
|
84
|
+
- Fixed BM25 scoring anomalies caused by prototype-key pollution by adding prototype-key guards, preventing score pollution and NaN issues.
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
- Continued refactoring of the bilingual retrieval pipeline, including query normalization, intent routing, candidate filtering, and exact-match strategies.
|
|
78
|
-
- Improved recall and ranking for best-practice and URL-based documents to increase answer usability and stability.
|
|
79
|
-
- Improved `search_trtc_knowledge` definitions and answer constraints for better readability and executability.
|
|
86
|
+
## Version 1.7.1 @2026.08.11
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
-
|
|
88
|
+
### Added
|
|
89
|
+
- Added pre-search normalization: normalizes `product` / `frameworks` based on prompt truth, fixing retrieval bias caused by wrong agent inference.
|
|
83
90
|
|
|
84
|
-
###
|
|
91
|
+
### Changed
|
|
92
|
+
- Optimized the `search_trtc_knowledge` output contract: removed brevity wording such as "1-2 sentence per subsection", replaced with complete synthesized answers.
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
-
|
|
94
|
+
### Fixed
|
|
95
|
+
- Fixed the case where mis-passed dual product / dual frameworks (e.g., "安卓 Web TRTC") were not corrected according to the prompt truth.
|
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);
|
package/knowledge/path-meta.js
CHANGED
|
@@ -8,6 +8,9 @@ function inferTypesFromFilename(fileName, baseTypes) {
|
|
|
8
8
|
types.push('error_code');
|
|
9
9
|
if (lower === 'client-api.md' || lower === 'api.md' || lower === 'index.md')
|
|
10
10
|
types.push('api');
|
|
11
|
+
// api-<变体>.md(如 api-uniapp.md:微信小程序 uni-app 打包 API)同样是 API 文档
|
|
12
|
+
if (/^api-[\w-]+\.mdx?$/.test(lower))
|
|
13
|
+
types.push('api');
|
|
11
14
|
if (lower === 'faq.md')
|
|
12
15
|
types.push('faq');
|
|
13
16
|
// 文件名 = integration.md 不再默认 push integration(由上游 framework 分支显式声明 type)
|
|
@@ -75,7 +78,14 @@ export function inferPathMeta(relativeSource) {
|
|
|
75
78
|
else if (normalized.startsWith('knowledge/chat/') || normalized.startsWith('integration/chat/')) {
|
|
76
79
|
product = 'chat';
|
|
77
80
|
if (parts.includes('preset') && parts.length >= 4) {
|
|
78
|
-
|
|
81
|
+
const fw = normalizeFrameworkDir(parts[parts.indexOf('preset') + 1]);
|
|
82
|
+
// vue/react 运行在 Web/H5 端,追加 web 标签,使 frameworks=['web'] 也能命中
|
|
83
|
+
framework = (fw === 'vue' || fw === 'react') ? [fw, 'web'] : [fw];
|
|
84
|
+
variant = 'preset';
|
|
85
|
+
// preset 下的 integration.md 显式标记 integration type
|
|
86
|
+
if (fileName.toLowerCase() === 'integration.md') {
|
|
87
|
+
types.push('integration');
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
if (parts.includes('features')) {
|
|
81
91
|
types.push('feature');
|
|
@@ -137,6 +147,13 @@ export function inferPathMeta(relativeSource) {
|
|
|
137
147
|
framework = [normalizeFrameworkDir(parts[coreIdx + 1])];
|
|
138
148
|
variant = 'core-sdk';
|
|
139
149
|
}
|
|
150
|
+
// 微信小程序 uni-app 打包版:目录归入 miniprogram(运行平台=微信小程序),
|
|
151
|
+
// 但技术栈是 uni-app,通过文件名 *-uniapp.md / uniapp-*.md 触发双标签,
|
|
152
|
+
// 使「微信小程序」与「uni-app」两种 framework 检索都能召回同一篇文档。
|
|
153
|
+
// 原生小程序(api.md/integration.md 等无 uniapp 标记)保持单 miniprogram 标签。
|
|
154
|
+
if (framework[0] === 'miniprogram' && /(^uniapp[-.]|-uniapp\.mdx?$)/.test(fileName.toLowerCase())) {
|
|
155
|
+
framework = ['miniprogram', 'uni-app'];
|
|
156
|
+
}
|
|
140
157
|
// callkit 最佳实践目录:knowledge/callkit/best-practice/{platform}/{topic}.md
|
|
141
158
|
// 统一按 FAQ/经验类知识处理,支持 types=faq + frameworks={platform} 的精确检索。
|
|
142
159
|
// 与 chat best-practice 对齐:common 目录表示多平台通用,按最全集覆盖。
|
|
@@ -147,6 +164,11 @@ export function inferPathMeta(relativeSource) {
|
|
|
147
164
|
: [fwSlug];
|
|
148
165
|
types.push('faq');
|
|
149
166
|
}
|
|
167
|
+
// preset 下的 integration.md(纯文件名,无连字符前缀)需显式标记 integration type,
|
|
168
|
+
// 否则 inferTypesFromFilename 只匹配 *-integration.md,导致兜底为 faq。
|
|
169
|
+
if (variant === 'preset' && fileName.toLowerCase() === 'integration.md') {
|
|
170
|
+
types.push('integration');
|
|
171
|
+
}
|
|
150
172
|
if (parts.includes('faq'))
|
|
151
173
|
types.push('faq', 'error_code');
|
|
152
174
|
if (fileName === 'api.md')
|
|
@@ -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,
|