@tencentcloud/sdk-mcp 1.6.0-beta.2 → 1.6.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.
package/README.md CHANGED
@@ -41,7 +41,7 @@ npx -y @tencentcloud/sdk-mcp
41
41
  "mcpServers": {
42
42
  "tencentcloud-sdk-mcp": {
43
43
  "command": "npx",
44
- "args": ["-y", "@tencentcloud/sdk-mcp"],
44
+ "args": ["-y", "@tencentcloud/sdk-mcp@latest"],
45
45
  "env": {
46
46
  "SDKAPPID": "YOUR_SDKAPPID",
47
47
  "SECRETKEY": "YOUR_SECRET_KEY"
Binary file
@@ -2,9 +2,10 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { chunkMarkdown, toManifestChunk } from './chunker.js';
5
+ import { buildDocSourceUrlMap } from './doc-url-resolver.js';
5
6
  import { inferPathMeta } from './path-meta.js';
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const RESOURCE_ROOT = path.resolve(__dirname, '../../resource');
8
+ const RESOURCE_ROOT = path.resolve(__dirname, '../resource');
8
9
  function walkMarkdownFiles(dir, baseRel) {
9
10
  if (!fs.existsSync(dir))
10
11
  return [];
@@ -57,6 +58,9 @@ export function buildKnowledgeIndex() {
57
58
  const componentSlugs = collectComponentSlugs(chunks);
58
59
  fs.writeFileSync(path.join(indexDir, 'manifest.json'), JSON.stringify(manifest));
59
60
  fs.writeFileSync(path.join(indexDir, 'component-slugs.json'), JSON.stringify(componentSlugs));
61
+ const syncManifestPath = path.resolve(__dirname, '../../sync/sync-manifest.json');
62
+ const docSourceUrls = buildDocSourceUrlMap(syncManifestPath);
63
+ fs.writeFileSync(path.join(indexDir, 'doc-source-urls.json'), JSON.stringify(docSourceUrls));
60
64
  const staleSearchIndex = path.join(indexDir, 'search-index.json');
61
65
  if (fs.existsSync(staleSearchIndex)) {
62
66
  fs.unlinkSync(staleSearchIndex);
@@ -4,8 +4,8 @@ import { fileURLToPath } from 'url';
4
4
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
5
  const fileCache = new Map();
6
6
  function resolveResourceRoot() {
7
- // dist/knowledge/ → 上溯两级到包根,resource/ 与 package.json 平级
8
- return path.resolve(__dirname, '../../resource');
7
+ // npm: knowledge/ → ../resource/ 到包根;dev: dist/knowledge/ → ../resource/ 到 dist/resource/
8
+ return path.resolve(__dirname, '../resource');
9
9
  }
10
10
  function readSourceFile(source) {
11
11
  const cached = fileCache.get(source);
@@ -16,6 +16,9 @@ function readSourceFile(source) {
16
16
  fileCache.set(source, text);
17
17
  return text;
18
18
  }
19
+ export function getSourceFileContent(source) {
20
+ return readSourceFile(source);
21
+ }
19
22
  /** 按 build 时写入的 char 偏移从 md 源文件切片(方案 B:manifest 不存 content) */
20
23
  export function getChunkContent(chunk) {
21
24
  const full = readSourceFile(chunk.source);
@@ -0,0 +1,118 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ /** 控制台/购买页等不适合作为「参考文档」的链接 */
6
+ const NOISE_URL_RE = /console\.cloud\.tencent|buy\.cloud\.tencent|account\.renewal|edu\/learning/i;
7
+ const PREFERRED_DOC_HOST_RE = /cloud\.tencent\.com|web\.sdk\.qcloud\.com|github\.com\/Tencent(?:Cloud|RTC)/i;
8
+ let cachedSourceUrlMap = null;
9
+ function loadSourceUrlMap() {
10
+ if (cachedSourceUrlMap)
11
+ return cachedSourceUrlMap;
12
+ const mapPath = path.resolve(__dirname, '../resource/index/doc-source-urls.json');
13
+ if (!fs.existsSync(mapPath)) {
14
+ cachedSourceUrlMap = {};
15
+ return cachedSourceUrlMap;
16
+ }
17
+ cachedSourceUrlMap = JSON.parse(fs.readFileSync(mapPath, 'utf-8'));
18
+ return cachedSourceUrlMap;
19
+ }
20
+ function normalizeUrl(raw) {
21
+ return raw.replace(/[.,;)\]]+$/, '').trim();
22
+ }
23
+ function isUsefulDocUrl(url) {
24
+ if (!/^https?:\/\//i.test(url))
25
+ return false;
26
+ if (NOISE_URL_RE.test(url))
27
+ return false;
28
+ return true;
29
+ }
30
+ /** 从 markdown 正文中提取可参考的官网链接(优先 cloud.tencent / web.sdk.qcloud) */
31
+ export function extractUrlsFromContent(content, max = 3) {
32
+ const found = [];
33
+ const preferred = [];
34
+ for (const match of content.matchAll(/https?:\/\/[^\s`|)\]"'>]+/g)) {
35
+ const url = normalizeUrl(match[0]);
36
+ if (!isUsefulDocUrl(url))
37
+ continue;
38
+ if (PREFERRED_DOC_HOST_RE.test(url)) {
39
+ if (!preferred.includes(url))
40
+ preferred.push(url);
41
+ }
42
+ else if (!found.includes(url)) {
43
+ found.push(url);
44
+ }
45
+ }
46
+ const merged = [...preferred, ...found];
47
+ return merged.slice(0, max);
48
+ }
49
+ /** L1 URL 映射 chunk:表格行中的目标文档 URL */
50
+ export function extractUrlMappingPrimaryUrl(content) {
51
+ const row = content.match(/\|\s*[^|]+\s*\|\s*`?(https?:\/\/[^|`\s]+)`?\s*\|/);
52
+ return row?.[1] ? normalizeUrl(row[1]) : null;
53
+ }
54
+ function isUrlMappingSource(source) {
55
+ return /knowledge\/chat\/urls\/.+\.md$/i.test(source.replace(/\\/g, '/'));
56
+ }
57
+ /**
58
+ * 为检索 fragment 解析可参考的官网 URL(去重、按优先级)。
59
+ * 1. sync-manifest 落盘的 localPath → 官网 URL(rtcengine 等)
60
+ * 2. L1 URL 映射行内目标 URL
61
+ * 3. 本地文档正文中的官网链接
62
+ */
63
+ export function resolveFragmentDocUrls(source, content, fullSourceContent) {
64
+ const normalized = source.replace(/\\/g, '/');
65
+ const urls = [];
66
+ const canonical = loadSourceUrlMap()[normalized];
67
+ if (canonical)
68
+ urls.push(canonical);
69
+ if (isUrlMappingSource(normalized)) {
70
+ const primary = extractUrlMappingPrimaryUrl(content);
71
+ if (primary && !urls.includes(primary))
72
+ urls.push(primary);
73
+ return urls;
74
+ }
75
+ const collect = (text) => {
76
+ for (const url of extractUrlsFromContent(text)) {
77
+ if (!urls.includes(url))
78
+ urls.push(url);
79
+ }
80
+ };
81
+ collect(content);
82
+ if (urls.length === 0 && fullSourceContent && fullSourceContent !== content) {
83
+ collect(fullSourceContent);
84
+ }
85
+ return urls.slice(0, 3);
86
+ }
87
+ /** 从 fragments 汇总去重后的参考链接列表(按 fragment 相关度排序) */
88
+ export function buildReferences(fragments) {
89
+ const refs = [];
90
+ const seen = new Set();
91
+ for (const fragment of fragments) {
92
+ for (const url of fragment.doc_urls) {
93
+ if (seen.has(url))
94
+ continue;
95
+ seen.add(url);
96
+ refs.push({
97
+ rank: refs.length + 1,
98
+ title: fragment.title,
99
+ url,
100
+ source: fragment.source,
101
+ });
102
+ }
103
+ }
104
+ return refs;
105
+ }
106
+ /** build 时从 sync-manifest 生成 localPath → 官网 URL 映射 */
107
+ export function buildDocSourceUrlMap(syncManifestPath) {
108
+ if (!fs.existsSync(syncManifestPath))
109
+ return {};
110
+ const manifest = JSON.parse(fs.readFileSync(syncManifestPath, 'utf-8'));
111
+ const map = {};
112
+ for (const entry of Object.values(manifest.entries ?? {})) {
113
+ if (entry.nodeId?.startsWith('http') && entry.localPath) {
114
+ map[entry.localPath.replace(/\\/g, '/')] = entry.nodeId;
115
+ }
116
+ }
117
+ return map;
118
+ }
@@ -148,8 +148,10 @@ export function inferPathMeta(relativeSource) {
148
148
  types.push('integration');
149
149
  }
150
150
  else if (category === 'config' && parts[3]) {
151
- // config/{platform}/{vendor}.md:platform 决定 framework,vendor 靠关键词
152
- framework = [parts[3]];
151
+ // config/{platform}/{vendor}.md(Android 厂商)或 config/{platform}.md(非 Android 平台配置)
152
+ // platform 决定 framework,vendor 靠 BM25 命中
153
+ const platform = parts[3].replace(/\.mdx?$/, '');
154
+ framework = [platform];
153
155
  types.push('integration');
154
156
  }
155
157
  else {
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
- const SCHEMA_ROOT = path.resolve(__dirname, '../../../resource/_schema');
5
+ const SCHEMA_ROOT = path.resolve(__dirname, '../../resource/_schema');
6
6
  export const DOC_PATH_MAP = {
7
7
  android: {
8
8
  core_api: 'TRTCCloud',
@@ -146,7 +146,7 @@ export function runExactLookup(prompt, pool, norm) {
146
146
  }
147
147
  // E3: component slugs
148
148
  const componentSlugs = loadJson('../index/component-slugs.json', {});
149
- const indexPath = path.resolve(__dirname, '../../../resource/index/component-slugs.json');
149
+ const indexPath = path.resolve(__dirname, '../../resource/index/component-slugs.json');
150
150
  const slugs = fs.existsSync(indexPath)
151
151
  ? JSON.parse(fs.readFileSync(indexPath, 'utf-8'))
152
152
  : componentSlugs;
@@ -248,7 +248,7 @@ export function runExactLookup(prompt, pool, norm) {
248
248
  }
249
249
  }
250
250
  }
251
- const errorCodeIntent = /错误码/i.test(prompt) || errorMatches.length > 0 || norm.types?.includes('error_code');
251
+ const errorCodeIntent = /错误码/i.test(prompt) || errorMatches.length > 0 || norm.searchIntent?.includes('error_code');
252
252
  if (errorCodeIntent) {
253
253
  for (const chunk of pool) {
254
254
  if (chunk.source.includes('knowledge/chat/urls/error-code.md')) {
@@ -12,15 +12,14 @@ function expandFrameworks(framework) {
12
12
  return ['react', 'vue', 'web'];
13
13
  return FRAMEWORK_SEARCH_ALIASES[framework] ?? [framework];
14
14
  }
15
- /** Chat 无独立 faq.md;types 含 faq 时改为检索 knowledge/chat 全语料(feature/component/urls 等) */
15
+ /** Chat 无独立 faq.md;types 含 faq 时退化到全语料检索(feature/component/urls 等均命中) */
16
16
  function resolveTypes(products, types) {
17
17
  if (!types?.length)
18
18
  return undefined;
19
- let resolved = [...types];
20
- if (products?.includes('chat') && resolved.includes('faq')) {
21
- resolved = resolved.filter((t) => t !== 'faq');
19
+ if (products?.includes('chat') && types.includes('faq')) {
20
+ return undefined;
22
21
  }
23
- return resolved.length ? resolved : undefined;
22
+ return types;
24
23
  }
25
24
  /** 单平台产品的隐式 framework 推断:避免对显然唯一的平台再发起询问 */
26
25
  function inferImplicitFramework(products, framework) {
@@ -40,6 +39,7 @@ export function normalizeParams(params) {
40
39
  product: params.product,
41
40
  framework: effectiveFramework,
42
41
  frameworks,
42
+ searchIntent: params.types,
43
43
  types,
44
44
  scenario: params.scenario,
45
45
  limit,
@@ -31,7 +31,7 @@ function isChatErrorCodeAmbiguous(norm) {
31
31
  return false;
32
32
  if (norm.framework)
33
33
  return false;
34
- if (norm.types?.includes('error_code'))
34
+ if (norm.searchIntent?.includes('error_code'))
35
35
  return true;
36
36
  return /错误码|error\s*code|ERR_/i.test(norm.prompt);
37
37
  }
@@ -44,7 +44,7 @@ export function requiresFramework(norm) {
44
44
  return true;
45
45
  // push:判定逻辑(自上而下)
46
46
  if (hasProduct(norm, 'push')) {
47
- if (norm.types?.length && norm.types.every((t) => CROSS_PLATFORM_TYPES.has(t))) {
47
+ if (norm.searchIntent?.length && norm.searchIntent.every((t) => CROSS_PLATFORM_TYPES.has(t))) {
48
48
  return false;
49
49
  }
50
50
  if (PUSH_PLATFORM_SPECIFIC_PROMPT.test(norm.prompt))
@@ -58,13 +58,13 @@ export function requiresFramework(norm) {
58
58
  return true;
59
59
  if (CROSS_PLATFORM_PROMPT.test(norm.prompt))
60
60
  return false;
61
- if (norm.types?.length) {
62
- if (norm.types.some((t) => PLATFORM_TYPES.has(t)))
61
+ if (norm.searchIntent?.length) {
62
+ if (norm.searchIntent.some((t) => PLATFORM_TYPES.has(t)))
63
63
  return true;
64
- if (norm.types.every((t) => CROSS_PLATFORM_TYPES.has(t)))
64
+ if (norm.searchIntent.every((t) => CROSS_PLATFORM_TYPES.has(t)))
65
65
  return false;
66
66
  // faq / error_code 等默认可跨平台检索
67
- if (norm.types.every((t) => t === 'faq' || t === 'error_code' || CROSS_PLATFORM_TYPES.has(t))) {
67
+ if (norm.searchIntent.every((t) => t === 'faq' || t === 'error_code' || CROSS_PLATFORM_TYPES.has(t))) {
68
68
  return false;
69
69
  }
70
70
  }
@@ -1,7 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { attachPoolContent } from '../content-loader.js';
4
+ import { attachPoolContent, getSourceFileContent } from '../content-loader.js';
5
+ import { buildReferences, resolveFragmentDocUrls } from '../doc-url-resolver.js';
5
6
  import { rankSubsetWithIndex } from '../bm25.js';
6
7
  import { getProductsBM25Index } from '../bm25-cache.js';
7
8
  import { filterPool, normalizeParams } from './filter-pool.js';
@@ -10,15 +11,7 @@ import { requiresFramework } from './requires-framework.js';
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  let cachedManifest = null;
12
13
  function resolveResourcePath(...segments) {
13
- const candidates = [
14
- path.resolve(__dirname, '../../../resource', ...segments),
15
- path.resolve(__dirname, '../../resource', ...segments),
16
- ];
17
- for (const candidate of candidates) {
18
- if (fs.existsSync(candidate))
19
- return candidate;
20
- }
21
- return candidates[0];
14
+ return path.resolve(__dirname, '../../resource', ...segments);
22
15
  }
23
16
  function loadManifest() {
24
17
  if (cachedManifest)
@@ -87,6 +80,7 @@ function postProcess(ranked, chunksById, limit) {
87
80
  title: chunk.title,
88
81
  content: chunk.content,
89
82
  source: chunk.source,
83
+ doc_urls: resolveFragmentDocUrls(chunk.source, chunk.content, getSourceFileContent(chunk.source)),
90
84
  product: chunk.product,
91
85
  framework: chunk.framework,
92
86
  types: chunk.types,
@@ -143,8 +137,11 @@ function assessConfidence(fragments, exactHits) {
143
137
  }
144
138
  return { confidence: 'low', status: 'low_confidence' };
145
139
  }
146
- function buildAgentMessage(status, fragments, confidence) {
147
- const tail = '合成答案后请调用 `record_answer`;用户反馈请调用 `record_feedback`。';
140
+ function buildAgentMessage(status, fragments, confidence, references) {
141
+ const citeTail = references?.length
142
+ ? '作答时 MUST 在答案末尾附「**参考文档**」小节,逐条列出 `meta.references`(或各 fragment 的 `doc_urls`)中的 URL,便于用户核对;禁止编造未出现的链接。'
143
+ : '若 fragments 含 `doc_urls`,作答时须在末尾附「**参考文档**」列出对应 URL;禁止编造链接。';
144
+ const tail = `${citeTail} 合成答案后请调用 \`record_answer\`;用户反馈请调用 \`record_feedback\`。`;
148
145
  if (status === 'needs_framework') {
149
146
  return '【硬规则】问题依赖具体平台实现,但未指定 framework,全平台混检信息密度低且被禁止。MUST 先调用 `present_framework_choice`(传入 product),等用户选定 android/ios/flutter/react/vue/uni-app 等后再调用本工具,并 MUST 带上 **product + framework**。禁止传空 framework 硬搜。';
150
147
  }
@@ -179,7 +176,7 @@ export function searchKnowledge(params) {
179
176
  return {
180
177
  status: 'needs_framework',
181
178
  confidence: 'none',
182
- message: buildAgentMessage('needs_framework', [], 'none'),
179
+ message: buildAgentMessage('needs_framework', [], 'none', undefined),
183
180
  query: params,
184
181
  meta: {
185
182
  pool_size: manifest.chunks.length,
@@ -203,6 +200,7 @@ export function searchKnowledge(params) {
203
200
  const ranked = rankSubsetWithIndex(norm.prompt, pool.map((chunk) => chunk.id), productIndex, boosts);
204
201
  const fragments = postProcess(ranked, chunksById, norm.limit);
205
202
  const urlMappingSources = [...new Set(fragments.map((f) => f.source).filter((source) => isUrlMappingSource(source)))];
203
+ const references = buildReferences(fragments);
206
204
  // P0-3:相对阈值评估置信度(替代原硬编码 0.5 死阈值)
207
205
  const { confidence, status } = assessConfidence(fragments, exactHits);
208
206
  const topScore = fragments[0]?.score;
@@ -210,13 +208,14 @@ export function searchKnowledge(params) {
210
208
  return {
211
209
  status,
212
210
  confidence,
213
- message: buildAgentMessage(status, fragments, confidence),
211
+ message: buildAgentMessage(status, fragments, confidence, references),
214
212
  query: params,
215
213
  meta: {
216
214
  pool_size: allChunks.length,
217
215
  filtered_size: pool.length,
218
216
  exact_hits: exactHits,
219
217
  ...(urlMappingSources.length ? { url_mapping_sources: urlMappingSources } : {}),
218
+ ...(references.length ? { references } : {}),
220
219
  ...(topScore !== undefined ? { top_score: Number(topScore.toFixed(2)) } : {}),
221
220
  ...(secondScore !== undefined ? { second_score: Number(secondScore.toFixed(2)) } : {}),
222
221
  },
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencentcloud/sdk-mcp",
3
- "version": "1.6.0-beta.2",
3
+ "version": "1.6.0",
4
4
  "description": "Tencent Cloud MCP Server for SDK",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1 @@
1
+ {"knowledge/rtcengine/web/how_to_run_web_sdk_demo.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-10-basic-get-started-with-demo.md","knowledge/rtcengine/web/plugin/20-advanced-customized-capture-rendering.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-20-advanced-customized-capture-rendering.md","knowledge/rtcengine/web/plugin/21-advanced-auto-play-policy.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-21-advanced-auto-play-policy.md","knowledge/rtcengine/web/plugin/22-advanced-audio-mixer.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-22-advanced-audio-mixer.md","knowledge/rtcengine/web/plugin/23-advanced-support-detection.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-23-advanced-support-detection.md","knowledge/rtcengine/web/plugin/24-advanced-network-quality.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-24-advanced-network-quality.md","knowledge/rtcengine/web/plugin/25-advanced-device-change.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-25-advanced-device-change.md","knowledge/rtcengine/web/plugin/26-advanced-publish-cdn-stream.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-26-advanced-publish-cdn-stream.md","knowledge/rtcengine/web/plugin/27-advanced-small-stream.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-27-advanced-small-stream.md","knowledge/rtcengine/web/plugin/28-advanced-beauty.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-28-advanced-beauty.md","knowledge/rtcengine/web/plugin/29-advanced-water-mark.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-29-advanced-water-mark.md","knowledge/rtcengine/web/plugin/30-advanced-cross-room-link.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-30-advanced-cross-room-link.md","knowledge/rtcengine/web/plugin/31-advanced-plugins-overview.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-31-advanced-plugins-overview.md","knowledge/rtcengine/web/plugin/34-advanced-proxy.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-34-advanced-proxy.md","knowledge/rtcengine/web/plugin/35-advanced-ai-denoiser.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-35-advanced-ai-denoiser.md","knowledge/rtcengine/web/plugin/36-advanced-virtual-background.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-36-advanced-virtual-background.md","knowledge/rtcengine/web/plugin/37-advanced-voice-changer.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-37-advanced-voice-changer.md","knowledge/rtcengine/web/plugin/38-advanced-basic-beauty.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-38-advanced-basic-beauty.md","knowledge/rtcengine/web/plugin/39-advanced-video-decoder.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-39-advanced-video-decoder.md","knowledge/rtcengine/web/plugin/40-advanced-video-mixer.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-40-advanced-video-mixer.md","knowledge/rtcengine/web/plugin/41-advanced-small-stream-auto-switcher.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-41-advanced-small-stream-auto-switcher.md","knowledge/rtcengine/web/plugin/42-advanced-realtime-transcriber.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-42-advanced-realtime-transcriber.md","knowledge/rtcengine/web/plugin/43-advanced-face-detection.md":"https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-43-advanced-face-detection.md"}