@tencent-rtc/mcp 1.6.8 → 1.7.1
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 +36 -0
- package/README.md +58 -39
- package/knowledge/build-index.js +30 -6
- package/knowledge/chunker.js +82 -14
- package/knowledge/chunking-policy.js +100 -0
- package/knowledge/content-loader.js +3 -3
- package/knowledge/doc-url-resolver.js +43 -16
- package/knowledge/frontmatter-meta.js +167 -0
- package/knowledge/path-meta.js +28 -6
- package/knowledge/search/exact-lookup.js +120 -239
- package/knowledge/search/filter-pool.js +12 -29
- package/knowledge/search/framework-fallback.js +22 -0
- package/knowledge/search/intent-router.js +29 -0
- package/knowledge/search/l1-enrich.js +159 -0
- package/knowledge/search/query-normalizer.js +63 -3
- package/knowledge/search/search-core.js +410 -0
- package/knowledge/search/search-knowledge.js +421 -379
- package/knowledge/search/search-message.js +88 -0
- package/knowledge/search/search-pass.js +428 -0
- package/knowledge/search/search-types.js +1 -0
- package/knowledge/search/url-row-idf.js +120 -0
- package/knowledge/search/url-row-scorer.js +337 -0
- package/package.json +3 -1
- package/resource/_schema/feature-aliases.yaml +1 -1
- package/resource/index/manifest.json +1 -1
- package/resource/knowledge/callkit/best-practice/callkit-miniprogram/uniapp-shrink.md +60 -0
- package/resource/knowledge/callkit/best-practice/common/call-autoplay-blocked.md +77 -0
- package/resource/knowledge/callkit/best-practice/common/call-message-in-chat.md +78 -0
- package/resource/knowledge/callkit/best-practice/common/call-vs-calls.md +110 -0
- package/resource/knowledge/callkit/best-practice/common/callengine-call-flow.md +123 -0
- package/resource/knowledge/callkit/best-practice/common/callengine-log-level.md +67 -0
- package/resource/knowledge/callkit/best-practice/common/callkit-get-engine-instance.md +81 -0
- package/resource/knowledge/callkit/best-practice/common/callkit-get-roomid.md +78 -0
- package/resource/knowledge/callkit/best-practice/common/callkit-limitations.md +37 -0
- package/resource/knowledge/callkit/best-practice/common/callkit-user-register-and-kicked.md +98 -0
- package/resource/knowledge/chat/best-practice/common/avchatroom-message-loss-diagnosis.md +65 -0
- package/resource/knowledge/chat/best-practice/common/group-conversation-cleanup-on-system-notice.md +62 -0
- package/resource/knowledge/chat/best-practice/common/image-message-url-lifecycle.md +59 -0
- package/resource/knowledge/chat/best-practice/common/login-repeat-2025-diagnosis.md +61 -0
- package/resource/knowledge/chat/best-practice/common/message-read-report-and-receipt.md +64 -0
- package/resource/knowledge/chat/best-practice/common/message-receive-event.md +65 -0
- package/resource/knowledge/chat/best-practice/common/message-risk-error-and-render.md +65 -0
- package/resource/knowledge/chat/best-practice/common/message-unread-count.md +71 -0
- package/resource/knowledge/chat/best-practice/miniprogram/audio-message-send.md +113 -0
- package/resource/knowledge/chat/best-practice/miniprogram/login-timeout-2801-diagnosis.md +63 -0
- package/resource/knowledge/chat/best-practice/miniprogram/media-upload-fail-diagnosis.md +64 -0
- package/resource/knowledge/chat/best-practice/web/audio-message-send.md +118 -0
- package/resource/knowledge/chat/urls/product.md +17 -16
- package/resource/knowledge/chat/urls/restapi.md +3 -2
- package/resource/knowledge/chat/urls/sdk/android.md +6 -5
- package/resource/knowledge/chat/urls/sdk/flutter.md +6 -5
- package/resource/knowledge/chat/urls/sdk/ios.md +4 -3
- package/resource/knowledge/chat/urls/sdk/web.md +7 -6
- package/resource/knowledge/chat/urls/webhook.md +0 -5
- package/resource/knowledge/livekit/best-practice/android/voice-room-anchor-start.md +120 -0
- package/resource/knowledge/livekit/best-practice/common/admin-take-seat.md +73 -0
- package/resource/knowledge/livekit/best-practice/common/audience-leave-live.md +81 -0
- package/resource/knowledge/livekit/best-practice/common/voice-room-no-audio-troubleshoot.md +100 -0
- package/resource/knowledge/livekit/best-practice/common/voice-room-seat-list-change.md +65 -0
- package/resource/knowledge/livekit/best-practice/flutter/voice-room-anchor-start.md +122 -0
- package/resource/knowledge/livekit/best-practice/ios/voice-room-anchor-start.md +126 -0
- package/resource/knowledge/roomkit/best-practice/common/room-ghost-member.md +70 -0
- package/resource/knowledge/roomkit/best-practice/common/room-limit-and-multi.md +83 -0
- package/resource/knowledge/roomkit/best-practice/common/room-screen-share-preview.md +83 -0
- package/resource/knowledge/roomkit/best-practice/common/room-status-after-all-leave.md +69 -0
- package/resource/knowledge/roomkit/best-practice/vue/room-uikit-vs-atomicx.md +111 -0
- package/resource/knowledge/rtcengine/web/plugin/30-advanced-cross-room-link.md +2 -0
- package/resource/knowledge/rtcengine/web/plugin/39-advanced-video-decoder.md +2 -0
- package/server.js +28 -23
- package/streamable-http.js +19 -54
- package/tools/common/search_trtc_knowledge.js +24 -12
- package/tools/definitions/finalize_answer_def.js +2 -2
- package/tools/definitions/integration-boundary.js +2 -1
- package/tools/definitions/search_trtc_knowledge_def.js +5 -3
- package/utils/fetch-doc-content.js +198 -0
- package/utils/preflight-route.js +197 -0
- package/utils/report-cls-client.js +1 -1
- package/utils/report-es-client.js +1 -1
- package/cursor-setting.png +0 -0
- package/mcp-server-prompt.png +0 -0
- package/resource/knowledge/callkit/best-practice/web/callengine-invitation.md +0 -80
- package/resource/knowledge/callkit/best-practice/web/callengine-log-control.md +0 -24
- package/resource/knowledge/callkit/best-practice/web/callkit-call-vs-calls.md +0 -116
- package/resource/knowledge/callkit/preset/react/uikit-api-reference.md +0 -444
- package/resource/knowledge/callkit/preset/vue/uikit-api-reference.md +0 -464
- package/resource/knowledge/chat/best-practice/web/image-message-url-handling.md +0 -16
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as yaml from 'js-yaml';
|
|
2
|
+
const FRONTMATTER_RE = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
3
|
+
function uniq(arr) {
|
|
4
|
+
return [...new Set(arr)];
|
|
5
|
+
}
|
|
6
|
+
function normalizeString(value) {
|
|
7
|
+
if (typeof value !== 'string')
|
|
8
|
+
return undefined;
|
|
9
|
+
const normalized = value.trim();
|
|
10
|
+
return normalized ? normalized : undefined;
|
|
11
|
+
}
|
|
12
|
+
function normalizeStringArray(value) {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
const normalized = value
|
|
15
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
return normalized.length ? uniq(normalized) : undefined;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
const normalized = value
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((item) => item.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
return normalized.length ? uniq(normalized) : undefined;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function normalizeScenario(value) {
|
|
29
|
+
if (value === 'live' || value === 'voice')
|
|
30
|
+
return value;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
function normalizeVariant(value) {
|
|
34
|
+
if (value === 'core-sdk' || value === 'preset')
|
|
35
|
+
return value;
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
function normalizeDocKind(value) {
|
|
39
|
+
if (value === 'capability_matrix' || value === 'unsupported_summary' || value === 'negative_faq') {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function normalizeCapabilityStatus(value) {
|
|
45
|
+
if (value === 'supported' || value === 'unsupported' || value === 'partial' || value === 'version_gated') {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
function normalizeRetrievableScope(value) {
|
|
51
|
+
if (value === 'always')
|
|
52
|
+
return 'always';
|
|
53
|
+
return normalizeStringArray(value);
|
|
54
|
+
}
|
|
55
|
+
function parseFrontmatterObject(input) {
|
|
56
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
export function resolveFrontmatterMeta(rawMarkdown) {
|
|
62
|
+
const match = rawMarkdown.match(FRONTMATTER_RE);
|
|
63
|
+
if (!match) {
|
|
64
|
+
return {
|
|
65
|
+
content: rawMarkdown,
|
|
66
|
+
contentOffset: 0,
|
|
67
|
+
hasFrontmatter: false,
|
|
68
|
+
frontmatterMeta: {},
|
|
69
|
+
overrideFields: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const rawBlock = match[1] ?? '';
|
|
73
|
+
const contentOffset = match[0].length;
|
|
74
|
+
const content = rawMarkdown.slice(contentOffset);
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = yaml.load(rawBlock);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const parseError = error instanceof Error ? error.message : String(error);
|
|
81
|
+
return {
|
|
82
|
+
content,
|
|
83
|
+
contentOffset,
|
|
84
|
+
hasFrontmatter: true,
|
|
85
|
+
parseError,
|
|
86
|
+
frontmatterMeta: {},
|
|
87
|
+
overrideFields: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const fm = parseFrontmatterObject(parsed);
|
|
91
|
+
const frontmatterMeta = {};
|
|
92
|
+
const overrideFields = [];
|
|
93
|
+
const product = normalizeString(fm.product);
|
|
94
|
+
if (product) {
|
|
95
|
+
frontmatterMeta.product = product;
|
|
96
|
+
overrideFields.push('product');
|
|
97
|
+
}
|
|
98
|
+
const framework = normalizeStringArray(fm.frameworks) ?? normalizeStringArray(fm.framework);
|
|
99
|
+
if (framework?.length) {
|
|
100
|
+
frontmatterMeta.framework = framework;
|
|
101
|
+
overrideFields.push('framework');
|
|
102
|
+
}
|
|
103
|
+
const types = normalizeStringArray(fm.types);
|
|
104
|
+
if (types?.length) {
|
|
105
|
+
frontmatterMeta.types = types;
|
|
106
|
+
overrideFields.push('types');
|
|
107
|
+
}
|
|
108
|
+
const scenario = normalizeScenario(fm.scenario);
|
|
109
|
+
if (scenario !== undefined) {
|
|
110
|
+
frontmatterMeta.scenario = scenario;
|
|
111
|
+
overrideFields.push('scenario');
|
|
112
|
+
}
|
|
113
|
+
const variant = normalizeVariant(fm.variant);
|
|
114
|
+
if (variant) {
|
|
115
|
+
frontmatterMeta.variant = variant;
|
|
116
|
+
overrideFields.push('variant');
|
|
117
|
+
}
|
|
118
|
+
const scope = normalizeString(fm.scope);
|
|
119
|
+
if (scope) {
|
|
120
|
+
frontmatterMeta.scope = scope;
|
|
121
|
+
overrideFields.push('scope');
|
|
122
|
+
}
|
|
123
|
+
const docKind = normalizeDocKind(fm.doc_kind);
|
|
124
|
+
if (docKind) {
|
|
125
|
+
frontmatterMeta.doc_kind = docKind;
|
|
126
|
+
overrideFields.push('doc_kind');
|
|
127
|
+
}
|
|
128
|
+
const capabilityStatus = normalizeCapabilityStatus(fm.capability_status);
|
|
129
|
+
if (capabilityStatus) {
|
|
130
|
+
frontmatterMeta.capability_status = capabilityStatus;
|
|
131
|
+
overrideFields.push('capability_status');
|
|
132
|
+
}
|
|
133
|
+
const retrievableScope = normalizeRetrievableScope(fm.retrievable_scope);
|
|
134
|
+
if (retrievableScope) {
|
|
135
|
+
frontmatterMeta.retrievable_scope = retrievableScope;
|
|
136
|
+
overrideFields.push('retrievable_scope');
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content,
|
|
140
|
+
contentOffset,
|
|
141
|
+
hasFrontmatter: true,
|
|
142
|
+
frontmatterMeta,
|
|
143
|
+
overrideFields: uniq(overrideFields),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function mergePathMetaWithFrontmatter(pathMeta, frontmatterMeta, overrideFields) {
|
|
147
|
+
if (!overrideFields.length) {
|
|
148
|
+
return {
|
|
149
|
+
...pathMeta,
|
|
150
|
+
meta_source: 'path',
|
|
151
|
+
meta_override_fields: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const merged = {
|
|
155
|
+
...pathMeta,
|
|
156
|
+
meta_source: 'merged',
|
|
157
|
+
meta_override_fields: uniq(overrideFields),
|
|
158
|
+
};
|
|
159
|
+
for (const field of overrideFields) {
|
|
160
|
+
const value = frontmatterMeta[field];
|
|
161
|
+
if (value === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
// PathMeta 字段由 frontmatter 显式声明时优先;未声明字段保留路径推断兜底。
|
|
164
|
+
merged[field] = value;
|
|
165
|
+
}
|
|
166
|
+
return merged;
|
|
167
|
+
}
|
package/knowledge/path-meta.js
CHANGED
|
@@ -110,7 +110,9 @@ export function inferPathMeta(relativeSource) {
|
|
|
110
110
|
// 两者都统一标记为 types=['faq'],便于通过 types=faq 命中“经验类问答”。
|
|
111
111
|
if (parts[1] === 'chat' && parts[2] === 'best-practice' && parts.length >= 5) {
|
|
112
112
|
const fwSlug = normalizeFrameworkDir(parts[3]);
|
|
113
|
-
|
|
113
|
+
// chat best-practice 的 common 目录表示平台通用(Web + 小程序),
|
|
114
|
+
// 不能按 framework=common 打标,否则会被 framework 硬过滤误排除。
|
|
115
|
+
framework = fwSlug === 'common' ? ['web', 'miniprogram'] : [fwSlug];
|
|
114
116
|
types.push('faq');
|
|
115
117
|
}
|
|
116
118
|
if (parts[1] === 'chat' && parts[2] === 'faq' && parts.length >= 4) {
|
|
@@ -137,13 +139,13 @@ export function inferPathMeta(relativeSource) {
|
|
|
137
139
|
}
|
|
138
140
|
// callkit 最佳实践目录:knowledge/callkit/best-practice/{platform}/{topic}.md
|
|
139
141
|
// 统一按 FAQ/经验类知识处理,支持 types=faq + frameworks={platform} 的精确检索。
|
|
142
|
+
// 与 chat best-practice 对齐:common 目录表示多平台通用,按最全集覆盖。
|
|
140
143
|
if (parts[1] === 'callkit' && parts[2] === 'best-practice' && parts.length >= 5) {
|
|
141
|
-
|
|
144
|
+
const fwSlug = normalizeFrameworkDir(parts[3]);
|
|
145
|
+
framework = fwSlug === 'common'
|
|
146
|
+
? ['react', 'vue', 'miniprogram', 'web', 'android', 'ios', 'flutter', 'uni-app']
|
|
147
|
+
: [fwSlug];
|
|
142
148
|
types.push('faq');
|
|
143
|
-
// calls 相关最佳实践既是 FAQ 也是 API 语义,避免 types=api 时被误过滤。
|
|
144
|
-
if (/(callengine-invitation|callkit-call-vs-calls)\.md$/i.test(fileName)) {
|
|
145
|
-
types.push('api');
|
|
146
|
-
}
|
|
147
149
|
}
|
|
148
150
|
if (parts.includes('faq'))
|
|
149
151
|
types.push('faq', 'error_code');
|
|
@@ -226,6 +228,15 @@ export function inferPathMeta(relativeSource) {
|
|
|
226
228
|
types.push('component');
|
|
227
229
|
if (fileName === 'faq.md')
|
|
228
230
|
types.push('faq');
|
|
231
|
+
// livekit 最佳实践目录:knowledge/livekit/best-practice/{platform}/{topic}.md
|
|
232
|
+
// 与 chat/callkit 对齐:common 目录表示多平台通用,按最全集覆盖。
|
|
233
|
+
if (parts[1] === 'livekit' && parts[2] === 'best-practice' && parts.length >= 5) {
|
|
234
|
+
const fwSlug = normalizeFrameworkDir(parts[3]);
|
|
235
|
+
framework = fwSlug === 'common'
|
|
236
|
+
? ['react', 'vue', 'web', 'android', 'ios', 'flutter']
|
|
237
|
+
: [fwSlug];
|
|
238
|
+
types.push('faq');
|
|
239
|
+
}
|
|
229
240
|
// 与 roomkit 对齐:core-sdk 下 capability-matrix.md / unsupported-features.md
|
|
230
241
|
// 视为能力边界文档(doc_kind + capability_status),方便检索层按 doc_kind 过滤
|
|
231
242
|
const docMeta = inferDocMeta(fileName);
|
|
@@ -255,6 +266,15 @@ export function inferPathMeta(relativeSource) {
|
|
|
255
266
|
types.push('feature');
|
|
256
267
|
if (parts.includes('faq'))
|
|
257
268
|
types.push('faq', 'error_code');
|
|
269
|
+
// roomkit 最佳实践目录:knowledge/roomkit/best-practice/{platform}/{topic}.md
|
|
270
|
+
// 与 chat/callkit 对齐:common 目录表示多平台通用,按最全集覆盖。
|
|
271
|
+
if (parts[1] === 'roomkit' && parts[2] === 'best-practice' && parts.length >= 5) {
|
|
272
|
+
const fwSlug = normalizeFrameworkDir(parts[3]);
|
|
273
|
+
framework = fwSlug === 'common'
|
|
274
|
+
? ['react', 'vue', 'web', 'android', 'ios', 'flutter']
|
|
275
|
+
: [fwSlug];
|
|
276
|
+
types.push('faq');
|
|
277
|
+
}
|
|
258
278
|
const docMeta = inferDocMeta(fileName);
|
|
259
279
|
doc_kind = docMeta.doc_kind;
|
|
260
280
|
capability_status = docMeta.capability_status ?? capability_status;
|
|
@@ -333,5 +353,7 @@ export function inferPathMeta(relativeSource) {
|
|
|
333
353
|
doc_kind,
|
|
334
354
|
capability_status,
|
|
335
355
|
retrievable_scope,
|
|
356
|
+
meta_source: 'path',
|
|
357
|
+
meta_override_fields: [],
|
|
336
358
|
};
|
|
337
359
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { getSourceFileContent } from '../content-loader.js';
|
|
5
|
+
import { computeCommonSectionTokens, isUrlMappingSource, normalizeUrlRowQuery, queryMatchesSection, scoreUrlRow } from './url-row-scorer.js';
|
|
4
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
7
|
const SCHEMA_ROOT = path.resolve(__dirname, '../../resource/_schema');
|
|
8
|
+
/**
|
|
9
|
+
* 分组语义命中的基础分:当 scoreUrlRow=0 但该 url 行「分组被 query 命中 + types 匹配」时给予。
|
|
10
|
+
* 取值需满足 E7 strongCandidates 门槛(typeMatch ×1.2 后 ≥500 → base ≥417)。
|
|
11
|
+
* 只在「分组命中」时给(区别于旧的「所有 types 匹配行给 500」),避免无关行涌入。
|
|
12
|
+
*/
|
|
13
|
+
const SECTION_MATCH_BASE_SCORE = 450;
|
|
6
14
|
export const DOC_PATH_MAP = {
|
|
7
15
|
android: {
|
|
8
16
|
core_api: 'TRTCCloud',
|
|
@@ -69,184 +77,6 @@ function loadJson(fileName, fallback) {
|
|
|
69
77
|
return fallback;
|
|
70
78
|
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
71
79
|
}
|
|
72
|
-
const E7_EN_STOPWORDS = new Set([
|
|
73
|
-
'the', 'and', 'for', 'with', 'from', 'into', 'about', 'using', 'via',
|
|
74
|
-
'how', 'what', 'where', 'when', 'which', 'please', 'docs', 'documentation',
|
|
75
|
-
'tencent', 'im', 'rest', 'server', 'webhook', 'webhooks',
|
|
76
|
-
]);
|
|
77
|
-
/** Generic terms — excluded from API-token hits to avoid false positives */
|
|
78
|
-
const E7_GENERIC_TERMS = new Set([
|
|
79
|
-
'message', 'messages', 'group', 'groups', 'user', 'users', 'account', 'accounts',
|
|
80
|
-
'send', 'get', 'set', 'delete', 'create', 'update', 'query', 'before', 'after',
|
|
81
|
-
'webhook', 'friend', 'single', 'multiple', 'history', 'chat', 'sdk', 'api',
|
|
82
|
-
'profile', 'conversation', 'import', 'recall', 'modify', 'pull', 'mark', 'data',
|
|
83
|
-
'list', 'online', 'status', 'custom', 'attributes', 'counter', 'member', 'members',
|
|
84
|
-
'management', 'overview', 'integration', 'initialization', 'billing', 'topic',
|
|
85
|
-
'topics', 'community', 'robot', 'chatbot', 'streaming', 'broadcast', 'official',
|
|
86
|
-
'extension', 'receipt', 'receipts', 'global', 'mute', 'ban', 'unban', 'forbid',
|
|
87
|
-
'ordinary', 'specified', 'historical', 'unread', 'read', 'configure', 'configuration', 'invalidate',
|
|
88
|
-
'invalidating', 'disband', 'change', 'search', 'cloud', 'local', 'robot', 'bots',
|
|
89
|
-
]);
|
|
90
|
-
const E7_QUERY_NOISE = /\b(rest\s*ful?\s*api|rest\s*api|server\s*api|webhook|webhooks|how\s+to|how\s+do\s+i|what\s+is|where\s+is|please|documentation|docs?)\b/gi;
|
|
91
|
-
const E7_QUERY_NOISE_CN = /怎么|如何|什么|用哪个|查询|服务端/g;
|
|
92
|
-
const E7_PHRASES = [
|
|
93
|
-
'before send one-to-one message',
|
|
94
|
-
'after send one-to-one message',
|
|
95
|
-
'one-to-one message',
|
|
96
|
-
'group message',
|
|
97
|
-
'recall group message',
|
|
98
|
-
'recall one-to-one message',
|
|
99
|
-
'send one-to-one message',
|
|
100
|
-
'send single chat message',
|
|
101
|
-
'getmessagelist',
|
|
102
|
-
'message history',
|
|
103
|
-
'add friend',
|
|
104
|
-
'import single account',
|
|
105
|
-
'import a single account',
|
|
106
|
-
'multi-device login',
|
|
107
|
-
'multi device login',
|
|
108
|
-
'login policy',
|
|
109
|
-
'login strategy',
|
|
110
|
-
'message storage duration',
|
|
111
|
-
'roaming message storage',
|
|
112
|
-
];
|
|
113
|
-
function extractRestEndpoints(text) {
|
|
114
|
-
return [...text.matchAll(/v4\/[\w/]+/gi)].map((m) => m[0].toLowerCase());
|
|
115
|
-
}
|
|
116
|
-
function extractE7ApiTokens(text) {
|
|
117
|
-
const tokens = new Set();
|
|
118
|
-
for (const match of text.matchAll(/\b([a-z][a-z0-9_]{2,})\b/gi)) {
|
|
119
|
-
const token = match[1].toLowerCase();
|
|
120
|
-
if (!E7_GENERIC_TERMS.has(token) && !E7_EN_STOPWORDS.has(token))
|
|
121
|
-
tokens.add(token);
|
|
122
|
-
}
|
|
123
|
-
for (const match of text.matchAll(/\b([A-Z][A-Za-z0-9]*(?:\.[A-Za-z]\w*)?)\b/g)) {
|
|
124
|
-
const token = match[1].toLowerCase();
|
|
125
|
-
if (!E7_GENERIC_TERMS.has(token))
|
|
126
|
-
tokens.add(token);
|
|
127
|
-
}
|
|
128
|
-
for (const match of text.matchAll(/([a-z]+[A-Z][A-Za-z0-9]*)/g)) {
|
|
129
|
-
tokens.add(match[1].toLowerCase());
|
|
130
|
-
}
|
|
131
|
-
return [...tokens];
|
|
132
|
-
}
|
|
133
|
-
function normalizeE7Text(text) {
|
|
134
|
-
return text
|
|
135
|
-
.toLowerCase()
|
|
136
|
-
.replace(E7_QUERY_NOISE, ' ')
|
|
137
|
-
.replace(E7_QUERY_NOISE_CN, '')
|
|
138
|
-
.replace(/[^\w\u4e00-\u9fff/]+/g, ' ')
|
|
139
|
-
.replace(/\s+/g, ' ')
|
|
140
|
-
.trim();
|
|
141
|
-
}
|
|
142
|
-
function phraseBonus(query, title) {
|
|
143
|
-
const q = query.toLowerCase().replace(/\s+/g, ' ');
|
|
144
|
-
const t = title.toLowerCase();
|
|
145
|
-
let bonus = 0;
|
|
146
|
-
for (const phrase of E7_PHRASES) {
|
|
147
|
-
if (q.includes(phrase) && t.includes(phrase))
|
|
148
|
-
bonus += 180;
|
|
149
|
-
}
|
|
150
|
-
if (/send single chat message/.test(q) && /send one-to-one message/.test(t))
|
|
151
|
-
bonus += 200;
|
|
152
|
-
if (/recall group message/.test(q) && /recall group message/.test(t))
|
|
153
|
-
bonus += 200;
|
|
154
|
-
if (/before send one-to-one message/.test(q) && /before send one-to-one message/.test(t))
|
|
155
|
-
bonus += 220;
|
|
156
|
-
if (/\bbefore\b/.test(q) && t.startsWith('before '))
|
|
157
|
-
bonus += 120;
|
|
158
|
-
if (/\bafter\b/.test(q) && t.startsWith('after '))
|
|
159
|
-
bonus += 120;
|
|
160
|
-
if (/多端登录/.test(query) && /多端登录|multi[\s-]?device/i.test(title))
|
|
161
|
-
bonus += 220;
|
|
162
|
-
if (/登录策略|login\s*(policy|strategy)/i.test(query) && /登录策略|login\s*(policy|strategy)/i.test(title))
|
|
163
|
-
bonus += 200;
|
|
164
|
-
return bonus;
|
|
165
|
-
}
|
|
166
|
-
function scoreE7UrlRow(query, title) {
|
|
167
|
-
const queryEndpoints = extractRestEndpoints(query);
|
|
168
|
-
const titleEndpoints = extractRestEndpoints(title);
|
|
169
|
-
for (const qe of queryEndpoints) {
|
|
170
|
-
if (titleEndpoints.some((te) => te === qe || te.endsWith(qe) || qe.endsWith(te))) {
|
|
171
|
-
return 1000;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
let score = phraseBonus(query, title);
|
|
175
|
-
const titleLower = title.toLowerCase();
|
|
176
|
-
const queryApis = extractE7ApiTokens(query);
|
|
177
|
-
let apiHits = 0;
|
|
178
|
-
let longApiHit = false;
|
|
179
|
-
for (const api of queryApis) {
|
|
180
|
-
if (!titleLower.includes(api))
|
|
181
|
-
continue;
|
|
182
|
-
apiHits += 1;
|
|
183
|
-
if (api.length >= 8)
|
|
184
|
-
longApiHit = true;
|
|
185
|
-
}
|
|
186
|
-
if (longApiHit)
|
|
187
|
-
score = Math.max(score, 940);
|
|
188
|
-
else if (apiHits >= 2)
|
|
189
|
-
score = Math.max(score, 900);
|
|
190
|
-
else if (apiHits === 1)
|
|
191
|
-
score = Math.max(score, 860);
|
|
192
|
-
const qNorm = normalizeE7Text(query);
|
|
193
|
-
const tNorm = normalizeE7Text(title);
|
|
194
|
-
const qTokens = qNorm.split(' ').filter((t) => t.length >= 3 && !E7_GENERIC_TERMS.has(t));
|
|
195
|
-
const tTokens = tNorm.split(' ').filter((t) => t.length >= 3 && !E7_GENERIC_TERMS.has(t));
|
|
196
|
-
let overlap = 0;
|
|
197
|
-
for (const qt of qTokens) {
|
|
198
|
-
if (tTokens.some((tt) => tt.includes(qt) || qt.includes(tt)))
|
|
199
|
-
overlap += 1;
|
|
200
|
-
}
|
|
201
|
-
if (overlap >= 3)
|
|
202
|
-
score = Math.max(score, 720 + overlap * 15);
|
|
203
|
-
else if (overlap >= 2)
|
|
204
|
-
score = Math.max(score, 650);
|
|
205
|
-
else if (overlap >= 1)
|
|
206
|
-
score = Math.max(score, 520);
|
|
207
|
-
const qCompact = qNorm.replace(/\s+/g, '');
|
|
208
|
-
const tCompact = tNorm.replace(/\s+/g, '');
|
|
209
|
-
if (qCompact.length >= 5 && tCompact.includes(qCompact)) {
|
|
210
|
-
score = Math.max(score, 560 - Math.min(tCompact.length - qCompact.length, 80));
|
|
211
|
-
}
|
|
212
|
-
else if (tCompact.length >= 5 && qCompact.includes(tCompact)) {
|
|
213
|
-
score = Math.max(score, 500 + Math.min(tCompact.length, 30));
|
|
214
|
-
}
|
|
215
|
-
const qCjk = query.replace(/[^\u4e00-\u9fff]/g, '');
|
|
216
|
-
const tCjk = title.replace(/[^\u4e00-\u9fff]/g, '');
|
|
217
|
-
if (qCjk.length >= 2 && tCjk.includes(qCjk)) {
|
|
218
|
-
let cjkScore = 500;
|
|
219
|
-
if (/批量|群组|群流|广播|导入|公众号|已读|回执/.test(tCjk) && !/批量|群组|群|已读|回执/.test(qCjk)) {
|
|
220
|
-
cjkScore -= 250;
|
|
221
|
-
}
|
|
222
|
-
if (/^单发/.test(tCjk) && /发送|单聊/.test(qCjk))
|
|
223
|
-
cjkScore += 120;
|
|
224
|
-
score = Math.max(score, cjkScore);
|
|
225
|
-
}
|
|
226
|
-
if (score <= 0)
|
|
227
|
-
return 0;
|
|
228
|
-
const queryL = query.toLowerCase();
|
|
229
|
-
const titleL = title.toLowerCase();
|
|
230
|
-
if (/group/.test(queryL) && /one-to-one|c2c/.test(titleL) && !/group/.test(titleL))
|
|
231
|
-
score -= 400;
|
|
232
|
-
if (/one-to-one|c2c|single chat/.test(queryL) && /import.*account|add friend/.test(titleL))
|
|
233
|
-
score -= 400;
|
|
234
|
-
if (/send.*message|one-to-one message/.test(queryL) && /add friend|import.*account/.test(titleL))
|
|
235
|
-
score -= 400;
|
|
236
|
-
if (/getmessagelist|message history/.test(qCompact) && /integration|initialization/.test(titleL) && !/getmessagelist|history/.test(tCompact))
|
|
237
|
-
score -= 400;
|
|
238
|
-
if (/extension/.test(titleL) && !/extension/.test(queryL))
|
|
239
|
-
score -= 350;
|
|
240
|
-
if (/\bbefore\b/.test(queryL) && !/^before\b/.test(titleL.trim()))
|
|
241
|
-
score -= 250;
|
|
242
|
-
if (/\bafter\b/.test(queryL) && !/^after\b/.test(titleL.trim()))
|
|
243
|
-
score -= 250;
|
|
244
|
-
if (/batch|multiple/.test(titleL) && !/batch|multiple/.test(queryL))
|
|
245
|
-
score -= 200;
|
|
246
|
-
if (/group/.test(titleL) && !/group|channel/.test(queryL) && /one-to-one|c2c|single chat/.test(queryL))
|
|
247
|
-
score -= 200;
|
|
248
|
-
return Math.max(score, 0);
|
|
249
|
-
}
|
|
250
80
|
function loadFeatureAliases() {
|
|
251
81
|
const filePath = path.join(SCHEMA_ROOT, 'feature-aliases.yaml');
|
|
252
82
|
if (!fs.existsSync(filePath))
|
|
@@ -314,8 +144,6 @@ const ROOM_FEATURE_SPECS = [
|
|
|
314
144
|
function detectRoomFeatureHits(prompt) {
|
|
315
145
|
return ROOM_FEATURE_SPECS.filter((spec) => spec.promptPattern.test(prompt));
|
|
316
146
|
}
|
|
317
|
-
const LIVE_LIFECYCLE_PROMPT = /create\s*live|end\s*live|join\s*live|leave\s*live|start\s*live|stop\s*live|watch\s*live|host_streaming|audience_streaming|开播|创建直播|结束直播|停播|观看直播|退出直播|进房|离房|createLive|endLive|joinLive|leaveLive/i;
|
|
318
|
-
const LIVE_LIFECYCLE_APIS = ['createLive', 'endLive', 'joinLive', 'leaveLive'];
|
|
319
147
|
export function runExactLookup(prompt, pool, norm) {
|
|
320
148
|
const boosts = {};
|
|
321
149
|
const exactHits = [];
|
|
@@ -468,70 +296,145 @@ export function runExactLookup(prompt, pool, norm) {
|
|
|
468
296
|
}
|
|
469
297
|
}
|
|
470
298
|
}
|
|
471
|
-
// E6:
|
|
472
|
-
|
|
299
|
+
// E6: URL 映射文件 boost(L1;产品无关:任意 product 的 urls 均适用)
|
|
300
|
+
const hasUrlChunks = pool.some((c) => isUrlMappingSource(c.source));
|
|
301
|
+
if (hasUrlChunks) {
|
|
473
302
|
if (/payload|usersig|authentication|auth|login|signature|登录|鉴权/i.test(prompt)) {
|
|
474
303
|
for (const chunk of pool) {
|
|
475
|
-
if (chunk.source
|
|
476
|
-
addBoost(chunk, 100, 'E6:
|
|
304
|
+
if (isUrlMappingSource(chunk.source)) {
|
|
305
|
+
addBoost(chunk, 100, 'E6:urls');
|
|
477
306
|
}
|
|
478
307
|
}
|
|
479
308
|
}
|
|
480
309
|
const errorCodeIntent = /错误码|\berror\s*code\b/i.test(prompt) || errorMatches.length > 0 || norm.searchIntent?.includes('error_code');
|
|
481
310
|
if (errorCodeIntent) {
|
|
482
311
|
for (const chunk of pool) {
|
|
483
|
-
if (
|
|
484
|
-
addBoost(chunk, 180, 'E6:
|
|
312
|
+
if (/\/error-code\.md$/i.test(chunk.source)) {
|
|
313
|
+
addBoost(chunk, 180, 'E6:error-code-url');
|
|
485
314
|
}
|
|
486
315
|
}
|
|
487
316
|
}
|
|
488
|
-
// E7: URL
|
|
489
|
-
|
|
317
|
+
// E7: URL 映射表行级匹配(chunk.title = 子问题列)
|
|
318
|
+
// 共享 scorer 按「REST 端点 > API token > 语义词对齐(IDF 加权)」打分。
|
|
319
|
+
// 支持 types 领域限定:上游推断出 types 时,优先 types 匹配的子文件行,
|
|
320
|
+
// 不匹配的行降权(避免跨域误命中,如 SDK 问题命中 restapi)。
|
|
321
|
+
if (normalizeUrlRowQuery(prompt).replace(/\s+/g, '').length >= 2) {
|
|
490
322
|
const candidates = [];
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
323
|
+
const hasTypeFilter = Boolean(norm.searchIntent?.length);
|
|
324
|
+
// 预收集 urls 行的分组结构:按 source 读整个 md 源文件,解析 `##` 分组与每行归属。
|
|
325
|
+
// 分组粗筛对所有 urls 资源生效(产品无关),不针对 webhook.md 打补丁。
|
|
326
|
+
const urlChunks = pool.filter((c) => isUrlMappingSource(c.source) && c.title.trim());
|
|
327
|
+
const sectionByChunk = new Map(); // chunk.id -> 分组标题
|
|
328
|
+
const sectionsBySource = new Map(); // source -> 分组标题集合
|
|
329
|
+
const mdRowsBySource = new Map(); // source -> {行title -> 分组标题}
|
|
330
|
+
for (const source of new Set(urlChunks.map((c) => c.source))) {
|
|
331
|
+
let full = '';
|
|
332
|
+
try {
|
|
333
|
+
full = getSourceFileContent(source);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
full = '';
|
|
337
|
+
}
|
|
338
|
+
let cur = '';
|
|
339
|
+
const rows = new Map();
|
|
340
|
+
const sections = new Set();
|
|
341
|
+
for (const line of full.split('\n')) {
|
|
342
|
+
const h = line.match(/^#{2,3}\s+(.+)$/);
|
|
343
|
+
if (h) {
|
|
344
|
+
cur = h[1].trim();
|
|
345
|
+
sections.add(cur);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const row = line.match(/^\|\s*([^|]+?)\s*\|\s*`(https?:[^`]+)`/);
|
|
349
|
+
if (row)
|
|
350
|
+
rows.set(row[1].trim(), cur);
|
|
351
|
+
}
|
|
352
|
+
mdRowsBySource.set(source, rows);
|
|
353
|
+
sectionsBySource.set(source, sections);
|
|
354
|
+
}
|
|
355
|
+
for (const chunk of urlChunks) {
|
|
356
|
+
const section = mdRowsBySource.get(chunk.source)?.get(chunk.title);
|
|
357
|
+
if (section)
|
|
358
|
+
sectionByChunk.set(chunk.id, section);
|
|
359
|
+
}
|
|
360
|
+
// 计算每个 source 的共有词(该文件所有分组都出现的词)
|
|
361
|
+
const commonTokensBySource = new Map();
|
|
362
|
+
for (const [source, sections] of sectionsBySource) {
|
|
363
|
+
commonTokensBySource.set(source, computeCommonSectionTokens([...sections]));
|
|
364
|
+
}
|
|
365
|
+
for (const chunk of urlChunks) {
|
|
366
|
+
const scoreResult = scoreUrlRow(prompt, chunk.title);
|
|
367
|
+
// types 领域限定:query types 与 chunk types 是否有交集
|
|
368
|
+
const typeMatch = hasTypeFilter
|
|
369
|
+
? (norm.searchIntent ?? []).some((t) => chunk.types.includes(t))
|
|
370
|
+
: true;
|
|
371
|
+
// 分组粗筛:query 是否命中该 chunk 所在的分组。
|
|
372
|
+
// 命中分组 → 正常;未命中分组 → 降权(不排除,避免 query 与分组名匹配不准时误杀)。
|
|
373
|
+
const section = sectionByChunk.get(chunk.id);
|
|
374
|
+
const commonTokens = commonTokensBySource.get(chunk.source);
|
|
375
|
+
const sectionMatch = section && commonTokens
|
|
376
|
+
? queryMatchesSection(prompt, section, commonTokens)
|
|
377
|
+
: true; // 无分组信息时不做粗筛(保持原行为)
|
|
378
|
+
// 高 scoreUrlRow 兜底:scoreUrlRow ≥ 700 是极强语义信号(query 2-gram 全覆盖),
|
|
379
|
+
// 此时不应被 sectionMatch 软降权压制。常见场景:短 query 如「群消息发送异常」
|
|
380
|
+
// 跟 section 标题「群组系统回调」共享字符少(仅「群」),sectionMatch 失败,
|
|
381
|
+
// 但 row title 本身完美匹配 user 意图,强行降权会让单聊 row 顶上。
|
|
382
|
+
const highConfidence = scoreResult.score >= 700;
|
|
383
|
+
// 分组语义命中基础分:scoreUrlRow 可能因「同义词/长句」对精确行打 0 分
|
|
384
|
+
// (如「群消息发送前回调」vs「群内发言之前回调」),但分组已语义命中,
|
|
385
|
+
// 给予基础分进入候选,避免该领域行被 score<=0 排除。
|
|
386
|
+
// 区别于旧的「所有 types 匹配行给 500」——只在分组命中时才给,避免无关行涌入。
|
|
387
|
+
if (scoreResult.score <= 0) {
|
|
388
|
+
if (sectionMatch && typeMatch) {
|
|
389
|
+
candidates.push({ chunk, score: SECTION_MATCH_BASE_SCORE, typeMatch, sectionMatch });
|
|
390
|
+
}
|
|
495
391
|
continue;
|
|
496
|
-
const score = scoreE7UrlRow(prompt, chunk.title);
|
|
497
|
-
if (score > 0) {
|
|
498
|
-
candidates.push({ chunk, score });
|
|
499
392
|
}
|
|
393
|
+
// finalScore 排序用:sectionMatch 失败且非高置信时,×0.7 软降权。
|
|
394
|
+
// 高置信(scoreUrlRow ≥ 700)跳过降权,避免短 query + 长 section 标题时的误杀。
|
|
395
|
+
const finalScore = (sectionMatch || highConfidence) ? scoreResult.score : scoreResult.score * 0.7;
|
|
396
|
+
candidates.push({ chunk, score: finalScore, typeMatch, sectionMatch });
|
|
500
397
|
}
|
|
501
398
|
candidates.sort((a, b) => b.score - a.score);
|
|
502
|
-
|
|
503
|
-
|
|
399
|
+
// types 匹配的行加分(更优先);不匹配的行轻度降权但仍可兜底召回。
|
|
400
|
+
// 避免上游 types 推断不准确时误杀正确文档(如「多端登录配置」被推断为 feature 而非 product)。
|
|
401
|
+
const typeScored = candidates
|
|
402
|
+
.map((c) => (c.typeMatch ? { ...c, score: c.score * 1.2 } : { ...c, score: c.score * 0.6 }))
|
|
403
|
+
.filter(({ score }) => score >= 500);
|
|
404
|
+
// 同分 row(典型:query「群消息撤回回调」时「撤回群消息」跟「单聊消息撤回」都 scoreUrlRow=660),
|
|
405
|
+
// 按「query 字符集被 row title 覆盖比例」再排,覆盖更高的 row 排前。
|
|
406
|
+
// 修 E7 boost idx=0 多 40 分的副作用:让字符集更精准的 row 拿 idx=0 → 300 分而不是 260 分。
|
|
407
|
+
const queryChars = new Set(prompt.match(/[\u4e00-\u9fff]/g) ?? []);
|
|
408
|
+
const charCoverage = (title) => {
|
|
409
|
+
if (queryChars.size === 0)
|
|
410
|
+
return 0;
|
|
411
|
+
const titleChars = new Set(title.match(/[\u4e00-\u9fff]/g) ?? []);
|
|
412
|
+
let hit = 0;
|
|
413
|
+
for (const ch of queryChars)
|
|
414
|
+
if (titleChars.has(ch))
|
|
415
|
+
hit += 1;
|
|
416
|
+
return hit / queryChars.size;
|
|
417
|
+
};
|
|
418
|
+
const strongCandidates = typeScored
|
|
419
|
+
.sort((a, b) => {
|
|
420
|
+
// 优先按 typeScored score 降序;同分时按字符集覆盖率降序
|
|
421
|
+
if (b.score !== a.score)
|
|
422
|
+
return b.score - a.score;
|
|
423
|
+
return charCoverage(b.chunk.title) - charCoverage(a.chunk.title);
|
|
424
|
+
})
|
|
504
425
|
.slice(0, 12);
|
|
505
426
|
strongCandidates.forEach(({ chunk, score }, idx) => {
|
|
427
|
+
// 递减 boost:前两条更高,其余保持可召回但不过度压制 BM25
|
|
506
428
|
const amount = idx === 0
|
|
507
429
|
? 200 + Math.min(score, 100)
|
|
508
430
|
: idx === 1
|
|
509
431
|
? 170 + Math.min(score, 90)
|
|
510
|
-
: Math.max(score >=
|
|
432
|
+
: Math.max(score >= 600 ? 150 : 90, 140 - idx * 8);
|
|
511
433
|
addBoost(chunk, amount, `E7:url-row:${chunk.title}`);
|
|
512
434
|
urlRowHitChunkIds.add(chunk.id);
|
|
513
435
|
});
|
|
514
436
|
}
|
|
515
437
|
}
|
|
516
|
-
// E8: 集成查阅意图 boost——prompt 含集成/接入/快速开始/接入文档等意图词时,
|
|
517
|
-
// 把 {integration|knowledge}/{product}/preset/{fw}/integration.md 系列文件顶到前列。
|
|
518
|
-
// 解决问题:集成文档内容长、词密度低,BM25 原始分常被短小的 features/ 文档压过。
|
|
519
|
-
// 注:android-view / ios-uikit 已迁到 knowledge corpus,但同样是集成指引,需要一并 boost。
|
|
520
|
-
const isIntegrationIntent = /集成|接入|快速开始|快速接入|集成文档|接入文档|集成指南|接入指南|接入流程|集成步骤|如何接入|如何集成|怎么接入|怎么集成/i.test(prompt)
|
|
521
|
-
|| /\b(integrat(e|ion|ing)|getting\s+started|quick\s+start|setup\s+guide|how\s+to\s+(integrate|set\s+up)|install(?:ation)?\s+guide)\b/i.test(prompt);
|
|
522
|
-
if (isIntegrationIntent && norm.product?.length && norm.frameworks?.length) {
|
|
523
|
-
for (const chunk of pool) {
|
|
524
|
-
if (!norm.product.includes(chunk.product))
|
|
525
|
-
continue;
|
|
526
|
-
const fwMatch = norm.frameworks.some((fw) => chunk.framework.includes(fw));
|
|
527
|
-
if (!fwMatch)
|
|
528
|
-
continue;
|
|
529
|
-
// 命中 preset/{fw}/integration.md 系列(含 integration/ 和 knowledge/ 两种 corpus)
|
|
530
|
-
if (!chunk.source.includes('/preset/') || (!chunk.source.endsWith('integration.md') && !chunk.source.endsWith('-integration.md')))
|
|
531
|
-
continue;
|
|
532
|
-
addBoost(chunk, 120, `E8:integration-doc:${chunk.source}`);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
438
|
// E9: chat faq — types=faq 时优先 knowledge/ 语料,避免 integration 文档内 FAQ 章节抢首位
|
|
536
439
|
if (norm.searchIntent?.includes('faq') && norm.product?.includes('chat')) {
|
|
537
440
|
const authPrompt = /auth|login|signature|usersig|鉴权|登录/i.test(prompt);
|
|
@@ -545,28 +448,6 @@ export function runExactLookup(prompt, pool, norm) {
|
|
|
545
448
|
}
|
|
546
449
|
}
|
|
547
450
|
}
|
|
548
|
-
// E11: LiveKit lifecycle strong recall
|
|
549
|
-
if (norm.product?.includes('live') && LIVE_LIFECYCLE_PROMPT.test(prompt)) {
|
|
550
|
-
const queryApiHits = LIVE_LIFECYCLE_APIS.filter((api) => new RegExp(api, 'i').test(prompt));
|
|
551
|
-
for (const chunk of pool) {
|
|
552
|
-
const isLiveCoreIntegration = chunk.source.startsWith('knowledge/livekit/core-sdk/live/')
|
|
553
|
-
&& chunk.source.endsWith('/integration.md');
|
|
554
|
-
const isVoiceCoreIntegration = chunk.source.startsWith('knowledge/livekit/core-sdk/voice/')
|
|
555
|
-
&& chunk.source.endsWith('/integration.md');
|
|
556
|
-
if (!isLiveCoreIntegration && !isVoiceCoreIntegration)
|
|
557
|
-
continue;
|
|
558
|
-
if (norm.scenario === 'live' && !isLiveCoreIntegration)
|
|
559
|
-
continue;
|
|
560
|
-
if (norm.scenario === 'voice' && !isVoiceCoreIntegration)
|
|
561
|
-
continue;
|
|
562
|
-
addBoost(chunk, 140, 'E11:live-lifecycle-core-integration');
|
|
563
|
-
for (const api of queryApiHits) {
|
|
564
|
-
if (chunk.api_names?.some((name) => name.toLowerCase() === api.toLowerCase())) {
|
|
565
|
-
addBoost(chunk, 160, `E11:live-lifecycle-api:${api}`);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
451
|
// E10: RoomKit Core/Preset disambiguation + unsupported feature routing
|
|
571
452
|
if (norm.product?.includes('room') && norm.frameworks?.length) {
|
|
572
453
|
const platform = norm.frameworks.find((fw) => ['android', 'ios', 'flutter', 'vue'].includes(fw));
|