@tencentcloud/sdk-mcp 1.6.8 → 1.7.0

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +54 -35
  3. package/knowledge/build-index.js +28 -4
  4. package/knowledge/chunker.js +82 -14
  5. package/knowledge/chunking-policy.js +100 -0
  6. package/knowledge/frontmatter-meta.js +167 -0
  7. package/knowledge/path-meta.js +28 -6
  8. package/knowledge/search/exact-lookup.js +26 -123
  9. package/knowledge/search/filter-pool.js +12 -29
  10. package/knowledge/search/framework-fallback.js +22 -0
  11. package/knowledge/search/intent-router.js +13 -0
  12. package/knowledge/search/query-normalizer.js +56 -3
  13. package/knowledge/search/search-core.js +391 -0
  14. package/knowledge/search/search-knowledge.js +290 -361
  15. package/knowledge/search/search-message.js +87 -0
  16. package/knowledge/search/search-pass.js +428 -0
  17. package/knowledge/search/search-types.js +1 -0
  18. package/knowledge/search/url-row-scorer.js +258 -0
  19. package/package.json +1 -1
  20. package/resource/index/manifest.json +1 -1
  21. package/resource/integration/callkit/preset/ios/integration.md +1 -0
  22. package/resource/integration/chat/preset/flutter/integration.md +1 -0
  23. package/resource/integration/livekit/preset/live/vue/live-player-desk-integration.md +11 -9
  24. package/resource/integration/livekit/preset/live/vue/live-pusher-integration.md +11 -11
  25. package/resource/knowledge/callkit/best-practice/callkit-miniprogram/uniapp-shrink.md +64 -0
  26. package/resource/knowledge/callkit/best-practice/common/call-autoplay-blocked.md +81 -0
  27. package/resource/knowledge/callkit/best-practice/common/call-message-in-chat.md +84 -0
  28. package/resource/knowledge/callkit/best-practice/common/call-vs-calls.md +114 -0
  29. package/resource/knowledge/callkit/best-practice/common/callengine-call-flow.md +128 -0
  30. package/resource/knowledge/callkit/best-practice/common/callengine-log-level.md +72 -0
  31. package/resource/knowledge/callkit/best-practice/common/callkit-get-engine-instance.md +87 -0
  32. package/resource/knowledge/callkit/best-practice/common/callkit-get-roomid.md +83 -0
  33. package/resource/knowledge/callkit/best-practice/common/callkit-limitations.md +37 -0
  34. package/resource/knowledge/callkit/best-practice/common/callkit-user-register-and-kicked.md +103 -0
  35. package/resource/knowledge/callkit/preset/react/api.md +1 -150
  36. package/resource/knowledge/callkit/preset/vue/api.md +1 -38
  37. package/resource/knowledge/chat/best-practice/common/avchatroom-message-loss-diagnosis.md +71 -0
  38. package/resource/knowledge/chat/best-practice/common/group-conversation-cleanup-on-system-notice.md +67 -0
  39. package/resource/knowledge/chat/best-practice/common/image-message-url-lifecycle.md +63 -0
  40. package/resource/knowledge/chat/best-practice/common/login-repeat-2025-diagnosis.md +65 -0
  41. package/resource/knowledge/chat/best-practice/common/message-read-report-and-receipt.md +72 -0
  42. package/resource/knowledge/chat/best-practice/common/message-receive-event.md +70 -0
  43. package/resource/knowledge/chat/best-practice/common/message-risk-error-and-render.md +69 -0
  44. package/resource/knowledge/chat/best-practice/common/message-unread-count.md +71 -0
  45. package/resource/knowledge/chat/best-practice/miniprogram/audio-message-send.md +118 -0
  46. package/resource/knowledge/chat/best-practice/miniprogram/login-timeout-2801-diagnosis.md +67 -0
  47. package/resource/knowledge/chat/best-practice/miniprogram/media-upload-fail-diagnosis.md +68 -0
  48. package/resource/knowledge/chat/best-practice/web/audio-message-send.md +123 -0
  49. package/resource/knowledge/chat/preset/android/features/conversation-list.md +1 -0
  50. package/resource/knowledge/chat/preset/android/features/language.md +1 -0
  51. package/resource/knowledge/chat/preset/android/features/message-input.md +1 -0
  52. package/resource/knowledge/chat/preset/android/features/message-list.md +1 -0
  53. package/resource/knowledge/chat/preset/flutter/features/conversation-list.md +1 -0
  54. package/resource/knowledge/chat/preset/flutter/features/message-input.md +1 -0
  55. package/resource/knowledge/chat/preset/flutter/features/message-list.md +1 -0
  56. package/resource/knowledge/chat/preset/ios/features/conversation-list.md +1 -0
  57. package/resource/knowledge/chat/preset/ios/features/message-input.md +1 -0
  58. package/resource/knowledge/chat/preset/ios/features/message-list.md +1 -0
  59. package/resource/knowledge/chat/preset/react/features/conversation-preview.md +1 -0
  60. package/resource/knowledge/chat/urls/sdk/web.md +1 -1
  61. package/resource/knowledge/livekit/best-practice/android/voice-room-anchor-start.md +124 -0
  62. package/resource/knowledge/livekit/best-practice/common/admin-take-seat.md +73 -0
  63. package/resource/knowledge/livekit/best-practice/common/audience-leave-live.md +81 -0
  64. package/resource/knowledge/livekit/best-practice/common/voice-room-no-audio-troubleshoot.md +100 -0
  65. package/resource/knowledge/livekit/best-practice/common/voice-room-seat-list-change.md +65 -0
  66. package/resource/knowledge/livekit/best-practice/flutter/voice-room-anchor-start.md +126 -0
  67. package/resource/knowledge/livekit/best-practice/ios/voice-room-anchor-start.md +130 -0
  68. package/resource/knowledge/push/config/react-native.md +1 -0
  69. package/resource/knowledge/push/config/unity.md +1 -0
  70. package/resource/knowledge/push/config/unreal-engine.md +1 -0
  71. package/resource/knowledge/push/integration/android.md +16 -16
  72. package/resource/knowledge/push/integration/flutter.md +3 -3
  73. package/resource/knowledge/push/integration/ios.md +3 -3
  74. package/resource/knowledge/push/integration/react-native.md +1 -1
  75. package/resource/knowledge/roomkit/best-practice/common/room-ghost-member.md +75 -0
  76. package/resource/knowledge/roomkit/best-practice/common/room-limit-and-multi.md +88 -0
  77. package/resource/knowledge/roomkit/best-practice/common/room-screen-share-preview.md +87 -0
  78. package/resource/knowledge/roomkit/best-practice/common/room-status-after-all-leave.md +73 -0
  79. package/resource/knowledge/roomkit/best-practice/vue/room-uikit-vs-atomicx.md +115 -0
  80. package/resource/knowledge/rtcengine/web/plugin/30-advanced-cross-room-link.md +2 -0
  81. package/resource/knowledge/rtcengine/web/plugin/39-advanced-video-decoder.md +2 -0
  82. package/server.js +8 -3
  83. package/tools/common/finalize_answer.js +9 -1
  84. package/tools/common/search_trtc_knowledge.js +1 -1
  85. package/tools/definitions/finalize_answer_def.js +2 -2
  86. package/tools/definitions/integration-boundary.js +2 -1
  87. package/tools/definitions/search_trtc_knowledge_def.js +1 -1
  88. package/utils/report-cls-client.js +1 -1
  89. package/utils/report-es-client.js +1 -1
  90. package/cursor-setting.png +0 -0
  91. package/mcp-server-prompt.png +0 -0
  92. package/resource/knowledge/callkit/best-practice/web/callengine-invitation.md +0 -80
  93. package/resource/knowledge/callkit/best-practice/web/callengine-log-control.md +0 -20
  94. package/resource/knowledge/callkit/best-practice/web/callkit-call-vs-calls.md +0 -116
  95. package/resource/knowledge/callkit/preset/react/uikit-api-reference.md +0 -469
  96. package/resource/knowledge/callkit/preset/vue/uikit-api-reference.md +0 -581
  97. package/resource/knowledge/callkit/preset/web/api.md +0 -79
  98. package/resource/knowledge/chat/best-practice/web/image-message-url-handling.md +0 -16
@@ -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
- framework = [fwSlug];
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
- framework = [normalizeFrameworkDir(parts[3])];
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,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { normalizeUrlRowQuery, scoreUrlRow } from './url-row-scorer.js';
4
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
6
  const SCHEMA_ROOT = path.resolve(__dirname, '../../resource/_schema');
6
7
  export const DOC_PATH_MAP = {
@@ -94,19 +95,6 @@ function loadFeatureAliases() {
94
95
  }
95
96
  return result;
96
97
  }
97
- /**
98
- * 将 prompt 按分隔符拆成子句列表,用于 url 行匹配。
99
- * 分隔符:、 , , ; ; 空格
100
- * 保留每个子句的原文(去首尾空白),不引入新词。
101
- */
102
- function splitPromptIntoClauses(prompt) {
103
- const raw = prompt
104
- .replace(/rest\s*api|服务端|server\s*api|api|怎么|如何|什么|用哪个|查询/g, '')
105
- .replace(/\s+/g, ' ')
106
- .trim();
107
- const parts = raw.split(/[、,,;;、\s]+/).filter((p) => p.length >= 2);
108
- return parts.length > 0 ? parts : [raw];
109
- }
110
98
  const ROOM_CORE_SIGNAL = /atomicx\s*core|atomicxcore|atomicx|core\s*sdk|无\s*ui|自定义\s*ui|自实现|roomstore|store|useRoomState|useDeviceState|useLoginState/i;
111
99
  const ROOM_PRESET_SIGNAL = /预制|preset|roommainview|roommainwidget|默认\s*ui|主页面|房间主页面/i;
112
100
  const ROOM_FEATURE_SPECS = [
@@ -149,8 +137,6 @@ const ROOM_FEATURE_SPECS = [
149
137
  function detectRoomFeatureHits(prompt) {
150
138
  return ROOM_FEATURE_SPECS.filter((spec) => spec.promptPattern.test(prompt));
151
139
  }
152
- const LIVE_LIFECYCLE_PROMPT = /开播|创建直播|结束直播|停播|观看直播|退出直播|进房|离房|createLive|endLive|joinLive|leaveLive|host_streaming|audience_streaming/i;
153
- const LIVE_LIFECYCLE_APIS = ['createLive', 'endLive', 'joinLive', 'leaveLive'];
154
140
  export function runExactLookup(prompt, pool, norm) {
155
141
  const boosts = {};
156
142
  const exactHits = [];
@@ -318,95 +304,34 @@ export function runExactLookup(prompt, pool, norm) {
318
304
  }
319
305
  }
320
306
  // E7: URL 映射表行级匹配(chunk.title = 子问题列)
321
- // 策略:保留整句匹配,同时补充子句拆分 + 逐行匹配,同一 chunk 取最高分。
322
- // 子句拆分使 "创建群组、加入群组、发送群组自定义消息" 能命中多行。
323
- const clauses = splitPromptIntoClauses(prompt.toLowerCase());
324
- const candidates = [];
325
- for (const chunk of pool) {
326
- if (!chunk.source.includes('knowledge/chat/urls/'))
327
- continue;
328
- const titleNorm = chunk.title.toLowerCase().replace(/\s+/g, '');
329
- if (!titleNorm)
330
- continue;
331
- // 对每个子句和整句分别打分,取最高分
332
- let bestScore = 0;
333
- const allParts = [...clauses, prompt.toLowerCase().replace(/\s+/g, '')];
334
- // 也按常见分隔符拆分 title 得到 tokens,用于不连续匹配
335
- const titleTokens = titleNorm.split(/[\/(((、,,\s]+/).filter(Boolean);
336
- const seen = new Set();
337
- for (const part of allParts) {
338
- const pn = part.replace(/\s+/g, '');
339
- if (pn.length < 2 || seen.has(pn))
307
+ // 共享 scorer 并行计算 EN token/phrase 与 ZH 子句匹配,取最高分。
308
+ if (normalizeUrlRowQuery(prompt).replace(/\s+/g, '').length >= 2) {
309
+ const candidates = [];
310
+ for (const chunk of pool) {
311
+ if (!chunk.source.includes('knowledge/chat/urls/'))
340
312
  continue;
341
- seen.add(pn);
342
- let s = 0;
343
- if (pn === titleNorm) {
344
- s = 1000;
345
- }
346
- else if (titleNorm.includes(pn)) {
347
- const trailing = titleNorm.slice(titleNorm.indexOf(pn) + pn.length);
348
- s = 800 - trailing.length * 80;
349
- }
350
- else if (pn.includes(titleNorm)) {
351
- s = 700 + titleNorm.length;
352
- }
353
- else if (titleTokens.some((token) => pn.includes(token) || token.includes(pn))) {
354
- // 子句匹配 title 的 token(如 "创建群组" 匹配 "创建/解散群组(普通群管理)" 中的 "创建" token)
355
- s = 600;
356
- }
357
- if (s > bestScore)
358
- bestScore = s;
359
- }
360
- if (bestScore <= 0) {
361
- // 退化:子句无匹配时,试 loose(去噪声字 + 单字容错)
362
- const qn = clauses[0].replace(/发送/g, '发');
363
- if (qn.length >= 3 && titleNorm.includes(qn)) {
364
- bestScore = 500;
365
- if (/批量|群组|群流|广播|导入|公众号|已读|回执/.test(titleNorm)
366
- && !/批量|群组|群|已读|回执/.test(clauses[0])) {
367
- bestScore -= 250;
368
- }
369
- if (/^单发/.test(titleNorm) && /发送|单聊/.test(clauses[0])) {
370
- bestScore += 120;
371
- }
313
+ if (!chunk.title.trim())
314
+ continue;
315
+ const scoreResult = scoreUrlRow(prompt, chunk.title);
316
+ if (scoreResult.score > 0) {
317
+ candidates.push({ chunk, score: scoreResult.score });
372
318
  }
373
319
  }
374
- if (bestScore > 0) {
375
- candidates.push({ chunk, score: bestScore });
376
- }
377
- }
378
- candidates.sort((a, b) => b.score - a.score);
379
- const strongCandidates = candidates
380
- .filter(({ score }) => score >= 500)
381
- .slice(0, 12);
382
- strongCandidates.forEach(({ chunk, score }, idx) => {
383
- // 递减 boost:前两条更高,其余保持可召回但不过度压制 BM25
384
- // token 匹配(600 分)至少给 150,确保子句拆分的行不被 BM25 原始分压掉
385
- const amount = idx === 0
386
- ? 200 + Math.min(score, 100)
387
- : idx === 1
388
- ? 170 + Math.min(score, 90)
389
- : Math.max(score >= 600 ? 150 : 90, 140 - idx * 8);
390
- addBoost(chunk, amount, `E7:url-row:${chunk.title}`);
391
- urlRowHitChunkIds.add(chunk.id);
392
- });
393
- }
394
- // E8: 集成查阅意图 boost——prompt 含集成/接入/快速开始/接入文档等意图词时,
395
- // 把 {integration|knowledge}/{product}/preset/{fw}/integration.md 系列文件顶到前列。
396
- // 解决问题:集成文档内容长、词密度低,BM25 原始分常被短小的 features/ 文档压过。
397
- // 注:android-view / ios-uikit 已迁到 knowledge corpus,但同样是集成指引,需要一并 boost。
398
- const isIntegrationIntent = /集成|接入|快速开始|快速接入|集成文档|接入文档|集成指南|接入指南|接入流程|集成步骤|如何接入|如何集成|怎么接入|怎么集成/i.test(prompt);
399
- if (isIntegrationIntent && norm.product?.length && norm.frameworks?.length) {
400
- for (const chunk of pool) {
401
- if (!norm.product.includes(chunk.product))
402
- continue;
403
- const fwMatch = norm.frameworks.some((fw) => chunk.framework.includes(fw));
404
- if (!fwMatch)
405
- continue;
406
- // 命中 preset/{fw}/integration.md 系列(含 integration/ 和 knowledge/ 两种 corpus)
407
- if (!chunk.source.includes('/preset/') || (!chunk.source.endsWith('integration.md') && !chunk.source.endsWith('-integration.md')))
408
- continue;
409
- addBoost(chunk, 120, `E8:integration-doc:${chunk.source}`);
320
+ candidates.sort((a, b) => b.score - a.score);
321
+ const strongCandidates = candidates
322
+ .filter(({ score }) => score >= 500)
323
+ .slice(0, 12);
324
+ strongCandidates.forEach(({ chunk, score }, idx) => {
325
+ // 递减 boost:前两条更高,其余保持可召回但不过度压制 BM25
326
+ // token 匹配(600 分)至少给 150,确保子句拆分的行不被 BM25 原始分压掉
327
+ const amount = idx === 0
328
+ ? 200 + Math.min(score, 100)
329
+ : idx === 1
330
+ ? 170 + Math.min(score, 90)
331
+ : Math.max(score >= 600 ? 150 : 90, 140 - idx * 8);
332
+ addBoost(chunk, amount, `E7:url-row:${chunk.title}`);
333
+ urlRowHitChunkIds.add(chunk.id);
334
+ });
410
335
  }
411
336
  }
412
337
  // E9: chat faq——types=faq 时优先 knowledge 语料,避免 integration 文档内 FAQ 章节抢首位
@@ -422,28 +347,6 @@ export function runExactLookup(prompt, pool, norm) {
422
347
  }
423
348
  }
424
349
  }
425
- // E11: LiveKit 生命周期意图强召回(开播/停播/观看/退出)
426
- if (norm.product?.includes('live') && LIVE_LIFECYCLE_PROMPT.test(prompt)) {
427
- const queryApiHits = LIVE_LIFECYCLE_APIS.filter((api) => new RegExp(api, 'i').test(prompt));
428
- for (const chunk of pool) {
429
- const isLiveCoreIntegration = chunk.source.startsWith('knowledge/livekit/core-sdk/live/')
430
- && chunk.source.endsWith('/integration.md');
431
- const isVoiceCoreIntegration = chunk.source.startsWith('knowledge/livekit/core-sdk/voice/')
432
- && chunk.source.endsWith('/integration.md');
433
- if (!isLiveCoreIntegration && !isVoiceCoreIntegration)
434
- continue;
435
- if (norm.scenario === 'live' && !isLiveCoreIntegration)
436
- continue;
437
- if (norm.scenario === 'voice' && !isVoiceCoreIntegration)
438
- continue;
439
- addBoost(chunk, 140, 'E11:live-lifecycle-core-integration');
440
- for (const api of queryApiHits) {
441
- if (chunk.api_names?.some((name) => name.toLowerCase() === api.toLowerCase())) {
442
- addBoost(chunk, 160, `E11:live-lifecycle-api:${api}`);
443
- }
444
- }
445
- }
446
- }
447
350
  // E10: roomkit Core/Preset 消歧 + unsupported feature 强召回
448
351
  if (norm.product?.includes('room') && norm.frameworks?.length) {
449
352
  const platform = norm.frameworks.find((fw) => ['android', 'ios', 'flutter', 'vue'].includes(fw));
@@ -1,5 +1,6 @@
1
1
  import { downgradeToProductType } from './product-scope-intent.js';
2
2
  import { normalizeQueryPrompt } from './query-normalizer.js';
3
+ import { hasExplicitFrameworkMatch, hasFrameworkFallbackMatch } from './framework-fallback.js';
3
4
  /**
4
5
  * MCP framework enum → index 中对应的 framework 值列表。
5
6
  * path-meta.ts 已通过 normalizeFrameworkDir 把目录名归一到标准 framework,
@@ -42,14 +43,6 @@ function resolveTypes(products, types, prompt, frameworks) {
42
43
  return undefined;
43
44
  const downgraded = downgradeToProductType(products, types, prompt, frameworks);
44
45
  const expanded = expandTypeAliases(downgraded);
45
- // Live 生命周期兼容:用户常传 types=['api'],但核心生命周期方法来自 core-sdk/live|voice/*/integration.md。
46
- // 这里补充 core/integration,避免被类型硬过滤提前排除。
47
- if (products?.includes('live') && expanded.includes('api') && LIVE_LIFECYCLE_PROMPT.test(prompt)) {
48
- if (!expanded.includes('core'))
49
- expanded.push('core');
50
- if (!expanded.includes('integration'))
51
- expanded.push('integration');
52
- }
53
46
  return expanded;
54
47
  }
55
48
  /** 单平台产品的隐式 framework 推断:避免对显然唯一的平台再发起询问 */
@@ -133,33 +126,23 @@ export function filterPool(chunks, norm) {
133
126
  // 收紧:framework=['*'] 仅在该 chunk 明确是跨平台 type(product/server_api/webhook)时放行;
134
127
  // 否则要求 chunk.framework 与请求 framework 有交集。
135
128
  const wildcardAllowed = isWildcardChunk && chunkIsCrossPlatform(chunk.types);
136
- const explicitMatch = norm.frameworks.some((fw) => chunk.framework.includes(fw));
137
- // CallKit 特例:core-sdk 仍维持 web 单套(纯 JS),允许 react/vue 查询命中 core-sdk/web。
138
- const callCoreWebFallback = Boolean(norm.product?.includes('call')
139
- && (norm.framework === 'react' || norm.framework === 'vue')
140
- && chunk.variant === 'core-sdk'
141
- && chunk.framework.includes('web'));
142
- // Live 特例:写写侧把 'Web' 平台归到 livekit/core-sdk/api-reference/{vue,web} 目录(写写节点名是 'Web'),
143
- // 本地目录重命名为 'vue' 后 framework 标签变为 'vue'。'web' 查询仍应能命中这些 chunk。
144
- const liveWebVueFallback = Boolean(norm.product?.includes('live')
145
- && norm.framework === 'web'
146
- && chunk.variant === 'core-sdk'
147
- && chunk.framework.includes('vue')
148
- && chunk.source.includes('/api-reference/'));
149
- // Room 特例:Conference Web 实际语料在 roomkit/core-sdk/vue,web 查询应可命中。
150
- const roomWebVueFallback = Boolean(norm.product?.includes('room')
151
- && norm.framework === 'web'
152
- && chunk.variant === 'core-sdk'
153
- && chunk.framework.includes('vue')
154
- && chunk.source.includes('knowledge/roomkit/core-sdk/vue/'));
155
- if (!explicitMatch && !wildcardAllowed && !callCoreWebFallback && !liveWebVueFallback && !roomWebVueFallback)
129
+ const explicitMatch = hasExplicitFrameworkMatch(chunk.framework, norm.frameworks);
130
+ const fallbackMatch = hasFrameworkFallbackMatch(chunk, {
131
+ product: norm.product,
132
+ framework: norm.framework,
133
+ frameworks: norm.frameworks,
134
+ });
135
+ if (!explicitMatch && !wildcardAllowed && !fallbackMatch)
156
136
  return false;
157
137
  }
158
138
  if (norm.scenario && chunk.scenario && chunk.scenario !== norm.scenario) {
159
139
  return false;
160
140
  }
141
+ // best-practice 文档对 types 过滤免疫:内容不依赖 types 分类,应始终进 pool 靠 BM25 自然排名。
142
+ // 典型场景:Agent 传 types=['api'] 时,types=['faq'] 的 best-practice 不应被过滤掉。
161
143
  if (norm.types?.length && !norm.types.some((t) => chunk.types.includes(t))) {
162
- return false;
144
+ if (!chunk.source.includes('/best-practice/'))
145
+ return false;
163
146
  }
164
147
  return true;
165
148
  });
@@ -0,0 +1,22 @@
1
+ export function hasExplicitFrameworkMatch(chunkFrameworks, requestFrameworks) {
2
+ if (!requestFrameworks?.length)
3
+ return false;
4
+ return requestFrameworks.some((fw) => chunkFrameworks.includes(fw));
5
+ }
6
+ export function hasFrameworkFallbackMatch(chunk, opts) {
7
+ const callCoreWebFallback = Boolean(opts.product?.includes('call')
8
+ && (opts.framework === 'react' || opts.framework === 'vue')
9
+ && chunk.variant === 'core-sdk'
10
+ && chunk.framework.includes('web'));
11
+ const liveWebVueFallback = Boolean(opts.product?.includes('live')
12
+ && opts.framework === 'web'
13
+ && chunk.variant === 'core-sdk'
14
+ && chunk.framework.includes('vue')
15
+ && chunk.source.includes('/api-reference/'));
16
+ const roomWebVueFallback = Boolean(opts.product?.includes('room')
17
+ && opts.framework === 'web'
18
+ && chunk.variant === 'core-sdk'
19
+ && chunk.framework.includes('vue')
20
+ && chunk.source.includes('knowledge/roomkit/core-sdk/vue/'));
21
+ return callCoreWebFallback || liveWebVueFallback || roomWebVueFallback;
22
+ }
@@ -0,0 +1,13 @@
1
+ /** 枚举/目录浏览意图 */
2
+ const DISCOVERY_INTENT_RE = /有哪些|支持哪些|列一下|清单|目录|可用|全部|全量|对比|怎么选|选哪个|which\s+one|compare|comparison|what\s+(is|are)\s+available|what\s+.*\b(components?|features?|apis?)\b|available\s+(components?|features?|apis?)|supported\s+(components?|features?|apis?)|list\s+(all|available)|catalog/i;
3
+ /** 显式完整文档意图(用户明确说"完整/从零/step by step") */
4
+ const NARRATIVE_INTENT_RE = /从头到尾|一步一步|完整指南|完整教程|完整流程|完整接入|完整集成|完整的\s*(接入|集成|教程|指南|流程)|从零|全流程|接入指南|集成指南|step\s*by(?:\s*step)?|end[-\s]?to[-\s]?end|full\s+(guide|tutorial|flow|integration)|complete\s+(guide|tutorial|integration)|quick\s*start|getting\s*started/i;
5
+ /** 强 point 信号:提到具体 API/错误码/参数 → 即使同时命中 narrative 关键词也优先走 point */
6
+ const POINT_STRONG_SIGNAL_RE = /\b\d{5,7}\b|\bERR_[A-Z0-9_]+\b|\buse[A-Z]\w*\b|\b[A-Z][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*\b|\b(TRTC|TX)[A-Za-z0-9_]+\b|\bprops?\b|\b参数\b|\b事件\b|\b回调\b|\berror\s*code\b|错误码|怎么用|如何用|参数是什么/i;
7
+ export function classifyRetrievalMode(prompt) {
8
+ if (NARRATIVE_INTENT_RE.test(prompt) && !POINT_STRONG_SIGNAL_RE.test(prompt))
9
+ return 'narrative';
10
+ if (DISCOVERY_INTENT_RE.test(prompt))
11
+ return 'discovery';
12
+ return 'point';
13
+ }
@@ -5,6 +5,46 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
5
  const SCHEMA_ROOT = path.resolve(__dirname, '../../resource/_schema');
6
6
  const QUERY_NORMALIZATION_FILE = path.join(SCHEMA_ROOT, 'query-normalization.yaml');
7
7
  const MAX_APPEND_TOKENS = 6;
8
+ const BASE_QUERY_NORMALIZATION = {
9
+ literal: {
10
+ 登录: ['login', 'signin', 'authentication', 'auth'],
11
+ 鉴权: ['authentication', 'auth', 'usersig'],
12
+ 错误码: ['error', 'error code', 'code'],
13
+ 重复登录: ['repeat login', 'multi-device login', 'login policy'],
14
+ 单聊: ['one-to-one', 'c2c', 'single chat'],
15
+ 群聊: ['group chat', 'group message'],
16
+ 直播群: ['avchatroom'],
17
+ 直播间: ['avchatroom', 'live room'],
18
+ 丢消息: ['message loss', 'reliability', 'priority'],
19
+ 消息丢失: ['message loss', 'reliability', 'priority'],
20
+ 重要消息: ['priority', 'high priority', 'critical message'],
21
+ 用户: ['user', 'account'],
22
+ 消息: ['message', 'messages'],
23
+ userSig: ['usersig', 'auth', 'authentication'],
24
+ AVChatRoom: ['直播群'],
25
+ 'error code': ['错误码', '错误', 'code'],
26
+ 'repeat login': ['重复登录', '多端登录', 'login policy'],
27
+ 'one-to-one': ['单聊', 'c2c', 'single chat'],
28
+ },
29
+ word: {
30
+ login: ['登录', '鉴权', 'authentication', 'auth'],
31
+ auth: ['鉴权', '登录', 'authentication'],
32
+ authentication: ['鉴权', '登录', 'auth'],
33
+ usersig: ['userSig', '鉴权', '登录'],
34
+ error: ['错误', '错误码', 'error code'],
35
+ code: ['错误码', 'error code'],
36
+ message: ['消息', 'chat', 'conversation'],
37
+ messages: ['消息', 'chat', 'conversation'],
38
+ group: ['群组', '群聊', 'group chat'],
39
+ c2c: ['单聊', 'one-to-one', 'single chat'],
40
+ webhook: ['回调', 'webhook', 'callbacks'],
41
+ avchatroom: ['直播群'],
42
+ priority: ['重要消息', '优先级'],
43
+ reliability: ['消息可靠性'],
44
+ 'message loss': ['丢消息', '消息丢失'],
45
+ 'rate limit': ['限频', '刷屏'],
46
+ },
47
+ };
8
48
  let cachedConfig = null;
9
49
  function unquote(input) {
10
50
  return input.trim().replace(/^['"]|['"]$/g, '');
@@ -12,7 +52,10 @@ function unquote(input) {
12
52
  function loadQueryNormalizationConfig() {
13
53
  if (cachedConfig)
14
54
  return cachedConfig;
15
- const config = { literal: {}, word: {} };
55
+ const config = {
56
+ literal: { ...BASE_QUERY_NORMALIZATION.literal },
57
+ word: { ...BASE_QUERY_NORMALIZATION.word },
58
+ };
16
59
  if (!fs.existsSync(QUERY_NORMALIZATION_FILE)) {
17
60
  cachedConfig = config;
18
61
  return config;
@@ -46,6 +89,17 @@ function loadQueryNormalizationConfig() {
46
89
  function escapeRegExp(value) {
47
90
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
48
91
  }
92
+ function hasCjk(text) {
93
+ return /[\u4e00-\u9fff]/.test(text);
94
+ }
95
+ function hasWordMatch(prompt, promptLower, needle) {
96
+ const normalizedNeedle = needle.toLowerCase();
97
+ if (hasCjk(normalizedNeedle)) {
98
+ return prompt.includes(needle);
99
+ }
100
+ const pattern = new RegExp(`\\b${escapeRegExp(normalizedNeedle)}\\b`, 'i');
101
+ return pattern.test(promptLower);
102
+ }
49
103
  export function normalizeQueryPrompt(prompt) {
50
104
  const config = loadQueryNormalizationConfig();
51
105
  const terms = new Set(prompt.split(/\s+/).filter(Boolean));
@@ -67,8 +121,7 @@ export function normalizeQueryPrompt(prompt) {
67
121
  tryAppend(expansion);
68
122
  }
69
123
  for (const [needle, expansion] of Object.entries(config.word)) {
70
- const pattern = new RegExp(`\\b${escapeRegExp(needle.toLowerCase())}\\b`, 'i');
71
- if (!pattern.test(promptLower))
124
+ if (!hasWordMatch(prompt, promptLower, needle))
72
125
  continue;
73
126
  tryAppend(expansion);
74
127
  }