From 5206d65be75f3af7e1419bf59ac83a2a135142d4 Mon Sep 17 00:00:00 2001 From: matevip Date: Sat, 25 Apr 2026 19:02:33 +0800 Subject: [PATCH] fix(wiki): eager 0-pages = partial when chunks indexed; per-KB structured route; archived filter completion --- .../java/vip/mate/wiki/job/WikiKbConfig.java | 11 ++++ .../mate/wiki/service/WikiPageService.java | 12 ++++- .../wiki/service/WikiProcessingService.java | 51 ++++++++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/job/WikiKbConfig.java b/mateclaw-server/src/main/java/vip/mate/wiki/job/WikiKbConfig.java index f7dce512..29fece9a 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/job/WikiKbConfig.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/job/WikiKbConfig.java @@ -36,4 +36,15 @@ public class WikiKbConfig { /** Global fallback model chain for all steps in this KB */ private List fallbackModelIds; + + /** + * RFC-051 PR-6b follow-up: per-KB opt-in for structured route output. + *

+ * Different KBs run different chat models — DashScope and Anthropic + * follow the format hint reliably; weaker locally-served Ollama models + * may not. Keeping the flag per-KB lets users flip it where it pays + * off. {@code null} (the common case) falls back to + * {@link vip.mate.wiki.WikiProperties#isUseStructuredRoute()}. + */ + private Boolean useStructuredRoute; } diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java index 4c7f788d..ba9d8b82 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java @@ -68,24 +68,28 @@ public class WikiPageService { } /** - * 列出知识库的所有页面(不含 content) + * 列出知识库的所有页面(不含 content)。 + * RFC-051 PR-7: archived 页面默认不返回。 */ public List listByKbId(Long kbId) { List pages = pageMapper.selectList( new LambdaQueryWrapper() .eq(WikiPageEntity::getKbId, kbId) + .ne(WikiPageEntity::getArchived, 1) .orderByAsc(WikiPageEntity::getTitle)); pages.forEach(p -> p.setContent(null)); return pages; } /** - * 列出知识库所有页面(含 content,用于全文搜索) + * 列出知识库所有页面(含 content,用于全文搜索)。 + * RFC-051 PR-7: archived 页面不参与 enrich / 全文搜索遍历。 */ public List listByKbIdWithContent(Long kbId) { return pageMapper.selectList( new LambdaQueryWrapper() .eq(WikiPageEntity::getKbId, kbId) + .ne(WikiPageEntity::getArchived, 1) .orderByAsc(WikiPageEntity::getTitle)); } @@ -226,6 +230,10 @@ public class WikiPageService { List pages = pageMapper.selectList( new LambdaQueryWrapper() .eq(WikiPageEntity::getKbId, kbId) + // RFC-051 PR-7: a raw's archived pages stop showing up in the + // sidebar's "filter by raw" listing. Lineage is still queryable + // by hitting the page directly via slug. + .ne(WikiPageEntity::getArchived, 1) .like(WikiPageEntity::getSourceRawIds, rawId.toString()) .orderByAsc(WikiPageEntity::getTitle)); pages.forEach(p -> p.setContent(null)); diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java index 40c0ee07..1a3c9b76 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java @@ -284,9 +284,25 @@ public class WikiProcessingService { String finalStatus; String finalDetail = null; if (totalPages == 0) { - rawService.updateProcessingStatus(rawId, "failed", "No pages generated from LLM response"); - finalStatus = "failed"; - finalDetail = "No pages generated from LLM response"; + // RFC-051 follow-up: previously this was an unconditional "failed". + // But chunks were already persisted (and the materials are searchable + // via wiki_semantic_search) — the only thing that actually went wrong + // was the LLM not synthesizing pages. Treat that as partial when chunks + // landed: search works, the agent can still wiki_compile_page on demand, + // and the row is rerun-able. Reserve "failed" for the case where nothing + // got indexed at all. + if (totalChunks > 0) { + finalDetail = "Indexed " + totalChunks + + " chunk(s) but no pages were generated. Search and wiki_compile_page still work; reprocess to retry page generation."; + rawService.updateProcessingStatus(rawId, "partial", finalDetail); + finalStatus = "partial"; + log.info("[Wiki] Eager produced 0 pages but {} chunks indexed; marking partial for raw={}", + totalChunks, rawId); + } else { + rawService.updateProcessingStatus(rawId, "failed", "No pages generated from LLM response"); + finalStatus = "failed"; + finalDetail = "No pages generated from LLM response"; + } } else if (failedChunks > 0 || failedPages > 0) { // 部分成功:chunk 整体失败 或 chunk 内有 page 失败 // (M2 v2 follow-up:page 级失败原本被计入 completed,现在正确归 partial) @@ -362,7 +378,10 @@ public class WikiProcessingService { // 如果未来加了事务包裹 processRawMaterial,这里的异步任务需要改用 // TransactionSynchronizationManager.registerSynchronization(afterCommit) // 否则新线程会查不到 chunk(事务未提交)导致 embedding 静默跳过。 - if (totalPages > 0) { + // RFC-051 follow-up: trigger embedding whenever chunks landed, not only when + // pages were produced. Otherwise the partial-with-no-pages case above ends up + // with chunks in DB but never embedded, so semantic search silently misses them. + if (totalChunks > 0) { final Long fKbId = kb.getId(); WIKI_EXECUTOR.submit(() -> { try { @@ -667,9 +686,11 @@ public class WikiProcessingService { .replace("{raw_content}", textContent); // RFC-051 PR-6b: optionally inject Spring AI's structured-output hint so the - // LLM produces strict RouteResult JSON. Default off; flip via mate.wiki.use-structured-route. + // LLM produces strict RouteResult JSON. KB config wins; falls back to global + // mate.wiki.use-structured-route default when the KB hasn't expressed a preference. + boolean useStructured = resolveStructuredRouteFlag(kb); org.springframework.ai.converter.BeanOutputConverter routeConverter = - properties.isUseStructuredRoute() + useStructured ? new org.springframework.ai.converter.BeanOutputConverter<>(vip.mate.wiki.dto.RouteResult.class) : null; if (routeConverter != null) { @@ -1790,6 +1811,24 @@ public class WikiProcessingService { } } + /** + * RFC-051 PR-6b follow-up: KB-level override for structured route output, + * falling back to the global property when the KB hasn't set a preference. + * Parse failures fall back to global too — never block ingest on bad config. + */ + private boolean resolveStructuredRouteFlag(WikiKnowledgeBaseEntity kb) { + boolean fallback = properties.isUseStructuredRoute(); + if (kb == null || kb.getConfigContent() == null) return fallback; + try { + WikiKbConfig config = objectMapper.readValue(kb.getConfigContent(), WikiKbConfig.class); + return config.getUseStructuredRoute() != null + ? config.getUseStructuredRoute() + : fallback; + } catch (Exception e) { + return fallback; + } + } + /** * RFC-051 PR-1b: read {@code ingestMode} from KB config JSON. Returns null * on any parse error or missing field so the caller falls through to eager.