siluzan-cso-cli 1.1.15 → 1.1.16

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/dist/index.js CHANGED
@@ -2454,6 +2454,7 @@ var MIN_SEGMENT_BYTES = 256 * 1024;
2454
2454
  var MAX_SEGMENT_BYTES = 50 * 1024 * 1024;
2455
2455
  var MAX_RETENTION_DAYS = 366 * 5;
2456
2456
  var PRUNE_THROTTLE_MS = 60 * 60 * 1e3;
2457
+ var MAX_SNAPSHOT_FILE_BYTES = 2 * 1024 * 1024;
2457
2458
 
2458
2459
  // src/commands/login.ts
2459
2460
  async function runLogin(opts = {}) {
@@ -6227,6 +6228,38 @@ function splitCsv(s) {
6227
6228
  if (!s) return [];
6228
6229
  return s.split(",").map((x) => x.trim()).filter(Boolean);
6229
6230
  }
6231
+ function splitQueryWhitespaceToKeywords(query) {
6232
+ return String(query ?? "").trim().split(/\s+/).filter(Boolean);
6233
+ }
6234
+ function ragItemDedupeKey(item) {
6235
+ const id = String(item.id ?? "").trim();
6236
+ if (id) return `id:${id}`;
6237
+ const name = String(item.fields?.name ?? "").trim();
6238
+ const content = String(item.fields?.content ?? "").slice(0, 160);
6239
+ return `fb:${name}\0${content}`;
6240
+ }
6241
+ function mergeRagResultsByBestScore(batches) {
6242
+ const best = /* @__PURE__ */ new Map();
6243
+ for (const batch of batches) {
6244
+ for (const item of batch) {
6245
+ const key = ragItemDedupeKey(item);
6246
+ const prev = best.get(key);
6247
+ const s = item.score ?? 0;
6248
+ if (!prev || s > (prev.score ?? 0)) {
6249
+ best.set(key, item);
6250
+ }
6251
+ }
6252
+ }
6253
+ return [...best.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
6254
+ }
6255
+ function normalizeRagScores(rawOutput) {
6256
+ return rawOutput.map((item) => {
6257
+ const r = item.score;
6258
+ if (typeof r !== "number" || Number.isNaN(r)) return item;
6259
+ const s = Math.min(1, Math.max(0, 1 - r));
6260
+ return { ...item, score: s };
6261
+ });
6262
+ }
6230
6263
  function resolveTags(tagsOption) {
6231
6264
  return splitCsv(tagsOption);
6232
6265
  }
@@ -6270,12 +6303,27 @@ function flatCategoryArray(category) {
6270
6303
  if (!Array.isArray(category) || category.length === 0) return [];
6271
6304
  return category.flat().filter((c) => typeof c === "string" && c.trim().length > 0);
6272
6305
  }
6273
- function formatRagResultsMarkdown(query, items) {
6306
+ function formatRagResultsMarkdown(query, items, subQueries) {
6274
6307
  if (!items.length) {
6275
6308
  return "\u672A\u627E\u5230\u76F8\u5173\u7ED3\u679C\u3002\n";
6276
6309
  }
6277
- const parts = [`## \u67E5\u8BE2\uFF1A${query}
6278
- `];
6310
+ const parts = [];
6311
+ if (subQueries && subQueries.length > 1) {
6312
+ parts.push(`## \u67E5\u8BE2\uFF1A${query}
6313
+
6314
+ `);
6315
+ parts.push(
6316
+ `\u5DF2\u6309\u7A7A\u767D\u62C6\u5206\u4E3A **${subQueries.length}** \u4E2A\u5173\u952E\u8BCD\u5206\u522B\u68C0\u7D22\uFF0C\u7ED3\u679C\u6309\u7247\u6BB5\u53BB\u91CD\uFF08\u4FDD\u7559\u6700\u9AD8\u76F8\u4F3C\u5EA6\uFF09\u540E\u6392\u5E8F\u3002
6317
+
6318
+ `
6319
+ );
6320
+ parts.push(`${subQueries.map((q) => `- \`${q}\``).join("\n")}
6321
+
6322
+ `);
6323
+ } else {
6324
+ parts.push(`## \u67E5\u8BE2\uFF1A${query}
6325
+ `);
6326
+ }
6279
6327
  items.forEach((item, itemIndex) => {
6280
6328
  const fields = item.fields;
6281
6329
  if (!fields) return;
@@ -6367,6 +6415,9 @@ async function runRagQuery(options) {
6367
6415
  console.error("\n\u274C --query \u4E0D\u80FD\u4E3A\u7A7A\u3002");
6368
6416
  process.exit(1);
6369
6417
  }
6418
+ const subQueries = splitQueryWhitespaceToKeywords(queryTxt);
6419
+ const multiKeyword = subQueries.length > 1;
6420
+ const mergedCap = Math.min(30, clamped * subQueries.length);
6370
6421
  const config = loadConfig(options.token);
6371
6422
  let belongToId = String(options.belongToId ?? "").trim();
6372
6423
  if (!belongToId) {
@@ -6381,8 +6432,7 @@ async function runRagQuery(options) {
6381
6432
  }
6382
6433
  const tagList = resolveTags(options.tags);
6383
6434
  const categories = splitCsv(options.category);
6384
- const qs = buildQueryKnowledgesSearchParams({
6385
- queryTxt,
6435
+ const baseSearch = {
6386
6436
  belongTo,
6387
6437
  belongToId,
6388
6438
  topK: clamped,
@@ -6390,44 +6440,54 @@ async function runRagQuery(options) {
6390
6440
  folderIds,
6391
6441
  tags: tagList,
6392
6442
  categories
6393
- });
6394
- const url = `${config.csoBaseUrl}/cutapi/v1/material/queryknowledges?${qs}`;
6395
- let raw;
6396
- try {
6397
- raw = await apiFetch2(
6398
- url,
6399
- config,
6400
- {},
6401
- Boolean(options.verbose)
6402
- );
6403
- } catch (e) {
6404
- const msg = e.message;
6405
- console.error(`
6406
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${msg}`);
6407
- if (!options.verbose) console.error(" \u52A0 --verbose \u53C2\u6570\u53EF\u67E5\u770B\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F");
6408
- process.exit(1);
6409
- }
6410
- if (raw && typeof raw === "object" && "code" in raw) {
6411
- const w = raw;
6412
- if (w.code !== 1) {
6443
+ };
6444
+ const verbose = Boolean(options.verbose);
6445
+ async function fetchKnowledgesOne(queryPart) {
6446
+ const qs = buildQueryKnowledgesSearchParams({
6447
+ ...baseSearch,
6448
+ queryTxt: queryPart
6449
+ });
6450
+ const url = `${config.csoBaseUrl}/cutapi/v1/material/queryknowledges?${qs}`;
6451
+ let raw;
6452
+ try {
6453
+ raw = await apiFetch2(
6454
+ url,
6455
+ config,
6456
+ {},
6457
+ verbose
6458
+ );
6459
+ } catch (e) {
6460
+ const msg = e.message;
6413
6461
  console.error(`
6414
- \u274C \u77E5\u8BC6\u5E93\u68C0\u7D22\u5931\u8D25\uFF1A${w.message || `\u4E1A\u52A1\u7801 ${w.code}`}`);
6462
+ \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${msg}`);
6463
+ if (!verbose) console.error(" \u52A0 --verbose \u53C2\u6570\u53EF\u67E5\u770B\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F");
6415
6464
  process.exit(1);
6416
6465
  }
6466
+ if (raw && typeof raw === "object" && "code" in raw) {
6467
+ const w = raw;
6468
+ if (w.code !== 1) {
6469
+ console.error(`
6470
+ \u274C \u77E5\u8BC6\u5E93\u68C0\u7D22\u5931\u8D25\uFF1A${w.message || `\u4E1A\u52A1\u7801 ${w.code}`}`);
6471
+ process.exit(1);
6472
+ }
6473
+ }
6474
+ const payload = unwrapQueryKnowledges(raw);
6475
+ const rawOutput = Array.isArray(payload.output) ? payload.output : [];
6476
+ return normalizeRagScores(rawOutput);
6477
+ }
6478
+ let output;
6479
+ if (multiKeyword) {
6480
+ const batches = await Promise.all(subQueries.map((q) => fetchKnowledgesOne(q)));
6481
+ output = mergeRagResultsByBestScore(batches).slice(0, mergedCap);
6482
+ } else {
6483
+ output = await fetchKnowledgesOne(subQueries[0]);
6417
6484
  }
6418
- const payload = unwrapQueryKnowledges(raw);
6419
- const rawOutput = Array.isArray(payload.output) ? payload.output : [];
6420
- const output = rawOutput.map((item) => {
6421
- const r = item.score;
6422
- if (typeof r !== "number" || Number.isNaN(r)) return item;
6423
- const s = Math.min(1, Math.max(0, 1 - r));
6424
- return { ...item, score: s };
6425
- });
6426
6485
  if (options.json) {
6427
6486
  console.log(
6428
6487
  JSON.stringify(
6429
6488
  {
6430
6489
  query: queryTxt,
6490
+ ...multiKeyword ? { subQueries, mergedCap } : {},
6431
6491
  belongToId,
6432
6492
  tags: tagList,
6433
6493
  topK: clamped,
@@ -6443,10 +6503,11 @@ async function runRagQuery(options) {
6443
6503
  console.log("\n\u672A\u68C0\u7D22\u5230\u5339\u914D\u7247\u6BB5\uFF08\u53EF\u5C1D\u8BD5\u653E\u5BBD --top-k \u6216\u8C03\u6574\u5173\u952E\u8BCD/\u8303\u56F4\uFF09\u3002\n");
6444
6504
  return;
6445
6505
  }
6446
- const header = `> \u77E5\u8BC6\u5E93\u68C0\u7D22 \xB7 topK=${clamped} \xB7 \u4EC5\u7528\u4E8E\u6587\u6848\u53C2\u8003
6506
+ const modeHint = multiKeyword ? ` \xB7 \u5206\u8BCD\u5408\u5E76\u2264${mergedCap}\u6761` : "";
6507
+ const header = `> \u77E5\u8BC6\u5E93\u68C0\u7D22 \xB7 topK=${clamped}${modeHint} \xB7 \u4EC5\u7528\u4E8E\u6587\u6848\u53C2\u8003
6447
6508
  >
6448
6509
  `;
6449
- const body = formatRagResultsMarkdown(queryTxt, output);
6510
+ const body = formatRagResultsMarkdown(queryTxt, output, multiKeyword ? subQueries : void 0);
6450
6511
  console.log(`
6451
6512
  ${header}${body}`);
6452
6513
  }
@@ -62,7 +62,7 @@ compatibility: Requires siluzan-cso-cli installed and authenticated via `siluzan
62
62
  | `siluzan-cso list-accounts` | 列出媒体账号,获取账号 ID / 数据总览 | `references/list-accounts.md` |
63
63
  | `siluzan-cso persona list` | 拉取 CSO 人设列表(含 styleGuide Markdown) | `references/persona.md` |
64
64
  | `siluzan-cso rag list` | 列出知识库文件夹;`--rag-only` 仅已建索引;`--folder-id` 查指定文件夹下的子库 | `references/rag.md` |
65
- | `siluzan-cso rag query` | RAG 知识库检索;默认全量(不过滤标签);`--folder-id` 限定范围;`--tags` 按标签精确筛选 | `references/rag.md` |
65
+ | `siluzan-cso rag query` | RAG 知识库检索;`-q` 含空白时按多词分检后自动合并排序;默认全量;`--folder-id` / `--tags` `references/rag.md` | `references/rag.md` |
66
66
  | `siluzan-cso account-group list/create/add-accounts/remove-accounts/update/delete` | 账号分组管理 | `references/account-group.md` |
67
67
  | `siluzan-cso upload -f <file>` | 上传视频 / 图片到素材库 | `references/upload.md` |
68
68
  | `siluzan-cso extract-cover -f <video> -p <平台>` | 从视频截取封面帧 | `references/extract-cover.md` |
@@ -90,7 +90,7 @@ compatibility: Requires siluzan-cso-cli installed and authenticated via `siluzan
90
90
  | AI 内容规划 | `references/planning.md` |
91
91
  | 需要给用户提供后台页面链接 | `references/web-pages.md` |
92
92
  | 拉取人设 / styleGuide(写稿前) | `references/persona.md` |
93
- | 写稿时检索素材库 RAG 片段(三库拆素材等) | `references/rag.md`(先 `rag list` 选库,再多次 `rag query` 拆词检索) |
93
+ | 写稿时检索素材库 RAG 片段(三库拆素材等) | `references/rag.md`(先 `rag list` 选库;`rag query -q "词甲 词乙"` 可一次空格分检合并,或按标签/库多轮调用) |
94
94
  | 选题 / 三库拆解 / 口播文案/其他文案 / 人设卡 / 代表作品反推人设 | `three-lib-content-workflow/content-writer.workflow.md` |
95
95
 
96
96
  ---
@@ -138,22 +138,25 @@ siluzan-cso rag list --folder-id <父文件夹id> --rag-only --json
138
138
  - 多品牌 → `--folder-id id1,id2`(逗号分隔)
139
139
  - 无明确品牌 → 不传 `--folder-id`(全库检索)
140
140
 
141
- **Step 3 — 拆词多轮检索**(2–5 个子查询,每个聚焦一个维度)
141
+ **Step 3 — 拆词检索**(2–5 个短关键词,各聚焦一个维度)
142
142
 
143
143
  ```bash
144
144
  # 默认不传 --tags = 全量检索(适用于绝大多数场景)
145
- siluzan-cso rag query -q "产品核心卖点" --folder-id <id> --top-k 10
146
- siluzan-cso rag query -q "用户使用场景" --folder-id <id>
147
- siluzan-cso rag query -q "品牌差异优势" --folder-id <id>
145
+ # 推荐:同一库、同一标签策略下,用空格一次传多词,CLI 会分检合并排序
146
+ siluzan-cso rag query -q "产品核心卖点 用户使用场景 品牌差异优势" --folder-id <id> --top-k 10
148
147
 
149
- # 仅当知识库已按标签打标,且需要精确筛选时才传 --tags
150
- siluzan-cso rag query -q "抖音爆款钩子" --tags "流量因子库"
151
- siluzan-cso rag query -q "产品卖点故事" --tags "产品资产库"
148
+ # 仍可用多轮独立 -q(例如需要分步查看或参数不同)
149
+ # siluzan-cso rag query -q "产品核心卖点" --folder-id <id> --top-k 10
150
+ # siluzan-cso rag query -q "用户使用场景" --folder-id <id>
151
+
152
+ # 仅当知识库已按标签打标,且需要精确筛选时才传 --tags(不同标签需多条命令)
153
+ siluzan-cso rag query -q "抖音 爆款 钩子" --tags "流量因子库"
154
+ siluzan-cso rag query -q "产品 卖点 故事" --tags "产品资产库"
152
155
  ```
153
156
 
154
157
  **Step 4 — 合成使用**
155
158
 
156
- 将多轮结果去重 → 按相关度(score 越小越相关)排序 作为写稿/回答的事实依据,重新组织表达(不直接粘贴原文)。
159
+ 合并后的结果中 **`score` 越大越相关**(CLI 已做 0–1 归一化)。将片段作为写稿/回答的事实依据,重新组织表达(不直接粘贴原文);若执行了多条 `rag query`,再在对话侧对重复片段去重。
157
160
 
158
161
  ---
159
162
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "slug": "siluzan-cso",
3
- "version": "1.1.15",
4
- "publishedAt": 1777536583822,
3
+ "version": "1.1.16",
4
+ "publishedAt": 1777543619814,
5
5
  "homepage": "https://www.siluzan.com",
6
6
  "source": "https://dev.azure.com/jack4it/Sammamish/_git/siluzan-skill",
7
7
  "requiredBinaries": [
@@ -1,7 +1,21 @@
1
1
  # rag:RAG 知识库检索
2
+ 知识库管理页面在https://www.siluzan.com/knowledge-base/
2
3
 
3
- Siluzan-RAG-sys-ui `MultiFileChat.vue` / `queryKnowledges` 一致,用于写稿、产品知识问答、三库素材提取等场景。
4
- 其他任意时刻,你觉得需要查询知识库的场景
4
+ 用于写稿、产品知识问答、三库素材提取等场景;凡回答依赖「本企业已入库素材」且不应凭模型记忆编造时,优先走 RAG
5
+
6
+ ## 业界检索共识与本 CLI 的对应关系(为何要靠「提示词」补效果)
7
+
8
+ 生产级 RAG 常见组合是:**合理分块** + **混合检索(关键词 BM25 + 向量)** + **重排序**,并对用户问句做 **查询改写 / 多查询扩展 / 拆解**。本 CLI 调用的是平台侧 **向量相似度检索**(`queryknowledges`),AI 无法改分块、无法开关 BM25 或重排。要在当前架构下提高 **召回(命中率)** 与 **精确(噪声少)**,主要靠:
9
+
10
+ | 业界手段 | 在本项目中的落地方式 |
11
+ | ------------------ | -------------------- |
12
+ | 混合检索补「专名」 | 在 `-q` 中显式加入 **素材里更可能出现的词**:品牌全称、产品型号、**官网/画册里的英文类目**、行业术语,减少「口语 ↔ 正文」词面不一致 |
13
+ | 查询改写 | 先去掉寒暄与指代,把用户话改成 **2–8 字的检索型短语**;口语转书面、别名转正名 |
14
+ | 多查询 / 拆解 | 与 `-q` 内 **空格分检** 对齐:多角度短词并行检索再合并;避免一句长问塞满空格导致「词太碎」 |
15
+ | 元数据过滤 | 用 **`--folder-id` 锁定品牌库**、必要时 `--tags` 缩小类目标签,降低跨库噪声(全库时易混入其他客户素材) |
16
+ | 提高候选再筛 | 适当 **`--top-k` 10–15**,在对话里按 `score` 与正文相关性丢弃明显跑题的片段 |
17
+
18
+ 以下「AI 检索策略」与「提示词规范」均按上表设计:**先锁范围、再改写、再拆词、最后控噪**。
5
19
 
6
20
  ## 命令速览
7
21
 
@@ -18,10 +32,10 @@ siluzan-cso rag query -q "<关键词>" # 全库 + 三
18
32
 
19
33
  | 选项 | 默认 | 说明 |
20
34
  | ------------- | ------ | ---------------------------------------------------------------- |
21
- | `-q, --query` | 必填 | 检索关键词或短句(越具体越好,避免长段落) |
35
+ | `-q, --query` | 必填 | 检索词或短句。**若含空白**(如 `卖点 场景 优势`),CLI 会按空白拆成多个词**分别请求**,再按片段 id 去重、取最高相似度、排序后合并返回(`--json` 中含 `subQueries`);无空白时仍为单次检索 |
22
36
  | `--folder-id` | 全库 | 指定一个或多个文件夹 ID(逗号分隔),锁定检索范围 |
23
37
  | `--tags` | 不过滤 | 不传 = 全量检索(无标签限制);传具体标签 = 只返回含该标签的片段 |
24
- | `--top-k` | 7 | 每次返回条数,3–30;复杂问题建议调高到 10–15 |
38
+ | `--top-k` | 7 | 每个分检请求返回条数(3–30);多词合并后总条数上限为 `min(30, topK × 词数)` |
25
39
  | `--json` | false | 输出完整 JSON,适合程序处理 |
26
40
 
27
41
  ---
@@ -30,6 +44,50 @@ siluzan-cso rag query -q "<关键词>" # 全库 + 三
30
44
 
31
45
  > 以下规则指导 AI 如何将用户意图转化为高质量的 RAG 检索。
32
46
 
47
+ ### 0. 提升命中率与准确率:给模型用的「检索提示词」规范(执行前自检)
48
+
49
+ 在调用 `rag query` 前,先在对话内完成下面 **5 步**,再把产物填入 `-q` / `--folder-id` / `--tags` / `--top-k`。这是本仓库在「仅向量检索、无 BM25/重排」约束下的最佳补偿流程。
50
+
51
+ **(1)意图归一(查询改写,Rewrite)**
52
+
53
+ - 去掉寒暄、指代与任务句(「帮我」「查一下」「有哪些」),保留 **实体 + 属性**。
54
+ - **品牌 / 公司**:用 `rag list` 里与知识库 **名称最一致** 的写法(如知识库名为「广东三合…」则不要用未入库的「广州三合」作为主检索词,除非用户坚持且已确认无对应库)。
55
+ - **一词多义**:若用户词太泛(「包装」「胶水」),补上 **行业或场景**(「食品软包装」「工业胶粘剂」),否则易全库跑偏。
56
+
57
+ **(2)锁范围再查(元数据优先,控精确)**
58
+
59
+ - 已识别品牌 → **必须** `--folder-id`;多品牌对比 → `id1,id2`。
60
+ - 仅当知识库已打标且用户要某一类素材时传 `--tags`;不确定标签是否存在时 **宁可不传**,避免 0 结果。
61
+
62
+ **(3)构造「检索型」`-q`(对齐索引词面,补混合检索之缺)**
63
+
64
+ - 优先 **名词与专名**,少用「如何」「为什么」等泛词单独检索。
65
+ - 素材常为 **官网 HTML、多语言导航、OCR/图片描述**:同一概念用 **中 + 英** 各检一轮或写在同一 `-q` 的空格分检里,例如:`垃圾袋 garbage-bag`、`SUN-HO OEM`。
66
+ - **必须整句不被拆分**时(如固定英文短语):不要在该短语内插空格;或拆成两次独立命令各 `-q` 一整串无空格短语。
67
+
68
+ **(4)拆解与多角检索(Query Decomposition / Multi-query)**
69
+
70
+ - 与 CLI 行为对齐:**`-q "甲 乙 丙"` = 三次向量检索 + 去重合并**,适合「多角度各 2–6 字」;**不要**把一句话切成十几个单字。
71
+ - 复杂问题模板(思路,非固定文案):
72
+ - 事实类:`「品牌 + 产品名」` + `「型号 规格」` + `「认证 标准」`(各为短词组,空格连接)。
73
+ - 写稿类:`「核心卖点」` + `「使用场景」` + `「对比 差异」` + `「证言 案例」`(视库内是否有案例再打「案例」)。
74
+ - **数值 / 条款 / 精确数据**:业界上 HyDE/随意扩写易引入幻觉;此处只做 **改写与专名对齐**,不在检索词里编造数字。
75
+
76
+ **(5)控噪与复盘(Precision)**
77
+
78
+ - 首次结果混入明显无关库(如标签带其他项目名)→ **收紧 `--folder-id`**,或略增 `--top-k` 后在对话里只采纳高 `score` 且正文与问题一致的片段。
79
+ - 空结果:**同义替换 → 扩大实体(上级类目)→ 去掉过严的 `--tags` → 最后才全库**。
80
+
81
+ **可复制给「执行检索的子任务」的系统提示片段(中文):**
82
+
83
+ ```text
84
+ 你是 RAG 检索规划器。目标:在 siluzan-cso rag query 约束下最大化命中与准确。
85
+ 约束:仅向量检索;`-q` 中含空格会拆成多次检索再合并;folder-id 不传则全库。
86
+ 要求:1) 输出 JSON 计划:folderIds(数组)、queries(字符串数组,每个元素将单独作为一次 -q,或合并为一个用空格连接的 -q)、tags(可选)、topK(默认 10)、reason(简短中文)。
87
+ 2) 每个 query 为检索型短语文本:2–12 个汉字为宜,或短英文专名;避免空洞词与过长整句。
88
+ 3) 先锁品牌文件夹;禁止在 query 中编造具体数字、证书号、未确认的产品型号。
89
+ ```
90
+
33
91
  ### 1. 判断是否需要 RAG
34
92
 
35
93
  **需要 RAG 的场景:**
@@ -82,7 +140,14 @@ siluzan-cso rag query -q "海科佳产品特点"
82
140
 
83
141
  ### 4. 查询拆解(Query Decomposition)
84
142
 
85
- 单次检索语义受限,**同一个问题应拆分为 2–5 个独立关键词并行检索**,每个关键词聚焦一个子维度:
143
+ 单次检索语义受限,**同一检索意图应拆成 2–5 个短关键词**,每个词聚焦一个子维度。
144
+
145
+ **传给 CLI 的两种方式(等价于「多词检索」时):**
146
+
147
+ 1. **一条命令、空格分隔**(推荐,省调用次数):`-q "词甲 词乙 词丙"` — CLI 内部分别请求再合并排序。
148
+ 2. **多条命令**:每个 `-q` 一条,适合 **不同 `--folder-id` / `--tags`** 等参数组合**无法合并**时仍须多轮。
149
+
150
+ > 注意:`-q` 里一旦出现空白就会触发拆分;**必须整段当一句查**时不要插入空格(例如英文长句若不想被拆,需写成无内部空格的表述或拆成单次短句检索)。
86
151
 
87
152
  **示例 1:写产品口播文案**
88
153
 
@@ -101,12 +166,13 @@ siluzan-cso rag query -q "海科佳产品特点"
101
166
  ```
102
167
  用户意图 → "给我找抖音爆款选题角度"
103
168
 
104
- 拆解检索(知识库已打三库标签,按标签精确分库):
169
+ 拆解检索(知识库已打三库标签,按标签精确分库;每个标签各一条命令,-q 内可用空格触发 CLI 分检合并):
105
170
  siluzan-cso rag query -q "抖音 爆款 钩子" --tags "流量因子库"
106
171
  siluzan-cso rag query -q "产品 卖点 故事" --tags "产品资产库"
107
172
  siluzan-cso rag query -q "内容结构 情绪" --tags "烹调方法库"
108
173
 
109
- 若知识库未打标签,直接不传 --tags,一次全量检索即可。
174
+ 若知识库未打标签,直接不传 --tags;多个词可写在一行,例如:
175
+ siluzan-cso rag query -q "抖音 钩子 情绪 结构" --folder-id <id>
110
176
  ```
111
177
 
112
178
  **示例 3:回答品牌知识问题**
@@ -122,7 +188,7 @@ siluzan-cso rag query -q "海科佳产品特点"
122
188
 
123
189
  **拆词规则:**
124
190
 
125
- - 每个子查询 **2–6 个字**,过长的句子效果反而差
191
+ - 每个子查询 **2–6 个汉字** 为宜(英文专名可略长);过长的整句效果差;执行顺序与控噪见 **「### 0」**。
126
192
  - 避免重复语义("卖点"和"优势"只选一个)
127
193
  - 优先具体名词,避免虚词(不要查"的"、"是"、"如何")
128
194
  - 需要回答某个具体问题时,先查核心实体,再查修饰属性
@@ -131,11 +197,11 @@ siluzan-cso rag query -q "海科佳产品特点"
131
197
 
132
198
  ### 5. 结果合成与使用
133
199
 
134
- 检索完成后,按以下方式处理多次检索的结果:
200
+ 检索完成后,按以下方式处理结果:
135
201
 
136
- 1. **去重**:相似度分数相同且内容高度重合的片段只保留一条
202
+ 1. **去重**:同一 `-q` 空格分检时,CLI 已按片段 id 合并并保留**最高** `score`。若你仍执行了多条 `rag query`,再在对话侧对重复片段做一次合并。
137
203
  2. **分类归档**:按检索关键词分组(例如:卖点类、场景类、情绪类)
138
- 3. **优先级**:`rag query` 的 `--json` 与 Markdown 中,`score` 已为 **1 − 接口原始分** 并限制在 0–1,**越大越相关**;合并结果时优先采用 `score` 更高的片段
204
+ 3. **优先级**:`rag query` 的 `--json` 与 Markdown 中,`score` 已为 **1 − 接口原始分** 并限制在 0–1,**越大越相关**。
139
205
  4. **引用而非复制**:将片段作为信息来源重新组织表达,不直接粘贴原文
140
206
  5. **结果为空时**:尝试更换关键词(同义替换、扩大范围),或告知用户知识库中暂无相关内容
141
207
 
@@ -149,10 +215,13 @@ siluzan-cso rag query -q "海科佳产品特点"
149
215
  # Step 1: 找知识库
150
216
  siluzan-cso rag list --rag-only --json
151
217
 
152
- # Step 2: 假设找到 id=affe64c5...,多轮拆词检索(不传 --tags,全量)
153
- siluzan-cso rag query -q "产品核心卖点" --folder-id affe64c5-d3d4-4cec-ab33-196035916894 --top-k 10
154
- siluzan-cso rag query -q "用户痛点解决方案" --folder-id affe64c5-d3d4-4cec-ab33-196035916894
155
- siluzan-cso rag query -q "产品使用场景" --folder-id affe64c5-d3d4-4cec-ab33-196035916894
218
+ # Step 2: 假设找到 id=affe64c5...(不传 --tags,全量)
219
+ # 方式 A:一条命令,空格分检合并(与方式 B 二选一即可)
220
+ siluzan-cso rag query -q "产品核心卖点 用户痛点 使用场景 品牌差异" --folder-id affe64c5-d3d4-4cec-ab33-196035916894 --top-k 10
221
+ # 方式 B:仍可多轮独立 -q(适合想分步看结果时)
222
+ # siluzan-cso rag query -q "产品核心卖点" --folder-id affe64c5-... --top-k 10
223
+ # siluzan-cso rag query -q "用户痛点解决方案" --folder-id affe64c5-...
224
+ # siluzan-cso rag query -q "产品使用场景" --folder-id affe64c5-...
156
225
 
157
226
  # Step 3: 汇总结果,结合写稿流程生成口播稿
158
227
  # (参考 three-lib-content-workflow/content-writer.workflow.md)
@@ -35,7 +35,7 @@
35
35
 
36
36
  > 如用户有联网搜索的SKILL此时可以调用,搜索你需要的内容
37
37
 
38
- 3. **RAG**(可选):如需从平台知识库检索内部素材,调用 `siluzan-cso rag query`,结果归入「拆素材」环节;不用于与文案无关的场景。
38
+ 3. **RAG**(可选):如需从平台知识库检索内部素材,调用 `siluzan-cso rag query`(`-q` 内多个短词用**空格**分隔时,CLI 会分检再合并排序;详见 `references/rag.md`),结果归入「拆素材」环节;不用于与文案无关的场景。
39
39
 
40
40
  ## 阅读顺序
41
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-cso-cli",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Siluzan platform AI Skill CLI — multi-platform content publishing (video/image-text) for Cursor, Claude Code, and OpenClaw.",
5
5
  "keywords": [
6
6
  "ai-skill",