diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java
index 9aa9a51d..7b23dd04 100644
--- a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java
@@ -15,8 +15,30 @@ public class WikiProperties {
/** 是否启用 Wiki 知识库功能 */
private boolean enabled = true;
- /** LLM 单次处理最大字符数(超过则分块) */
- private int maxChunkSize = 30000;
+ /**
+ * LLM 单次处理最大字符数(超过则分块)。
+ *
+ * RFC-012:默认从 30000 下调到 15000 —— 单 chunk 输出 tokens 砍半、并行饱和度更高、
+ * 质量也更稳定。中端模型(qwen-plus/claude-sonnet)建议 12000-20000,
+ * 旗舰模型(qwen-max/claude-opus)可调大到 20000-30000。
+ */
+ private int maxChunkSize = 15000;
+
+ /**
+ * 同一次 processAllPending 下同时处理的原始材料数上限。
+ *
+ * RFC-012 Change 1:保护共享的 @Async 线程池(max=16)不被 wiki 长时间占满,
+ * 同时给材料级并发定一个可控的上限,避免 LLM 提供方触发限流。
+ */
+ private int maxParallelRawMaterials = 3;
+
+ /**
+ * 单个材料内 chunk 的并行处理数上限。
+ *
+ * RFC-012 Change 1:从硬编码 3 提到 5,并暴露为配置项。默认总并发为
+ * maxParallelRawMaterials × maxParallelChunks = 15,仍在常见 60 RPM 限额下。
+ */
+ private int maxParallelChunks = 5;
/** 注入 agent prompt 的最大字符数 */
private int maxContextChars = 10000;
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java
index 8e3bb318..547d2fa9 100644
--- a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java
@@ -240,15 +240,20 @@ public class WikiController {
}
@RequireWorkspaceRole("member")
- @Operation(summary = "重新处理原始材料")
+ @Operation(summary = "重新处理原始材料(force=true 时绕过 content_hash 短路)")
@PostMapping("/knowledge-bases/{kbId}/raw/{rawId}/reprocess")
public R reprocessRaw(@PathVariable Long kbId, @PathVariable Long rawId,
+ @RequestParam(value = "force", defaultValue = "false") boolean force,
@RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId) {
verifyKBWorkspace(kbId, workspaceId);
WikiRawMaterialEntity raw = rawService.getById(rawId);
if (raw == null || !kbId.equals(raw.getKbId())) {
return R.fail("Raw material not found in this knowledge base");
}
+ // RFC-012 Change 5:force=true 时清空 last_processed_hash,让下一次处理必然执行完整管线
+ if (force) {
+ rawService.setLastProcessedHash(rawId, null);
+ }
rawService.reprocess(rawId);
return R.ok();
}
@@ -320,16 +325,27 @@ public class WikiController {
// ==================== Processing ====================
@RequireWorkspaceRole("member")
- @Operation(summary = "触发知识库处理(异步)")
+ @Operation(summary = "触发知识库处理(异步);force=true 时清空所有 last_processed_hash 并重新入队全部材料")
@PostMapping("/knowledge-bases/{kbId}/process")
public R