mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): eager 0-pages = partial when chunks indexed; per-KB structured route; archived filter completion
This commit is contained in:
parent
3f25064ef9
commit
5206d65be7
@ -36,4 +36,15 @@ public class WikiKbConfig {
|
||||
|
||||
/** Global fallback model chain for all steps in this KB */
|
||||
private List<Long> fallbackModelIds;
|
||||
|
||||
/**
|
||||
* RFC-051 PR-6b follow-up: per-KB opt-in for structured route output.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
@ -68,24 +68,28 @@ public class WikiPageService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出知识库的所有页面(不含 content)
|
||||
* 列出知识库的所有页面(不含 content)。
|
||||
* RFC-051 PR-7: archived 页面默认不返回。
|
||||
*/
|
||||
public List<WikiPageEntity> listByKbId(Long kbId) {
|
||||
List<WikiPageEntity> pages = pageMapper.selectList(
|
||||
new LambdaQueryWrapper<WikiPageEntity>()
|
||||
.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<WikiPageEntity> listByKbIdWithContent(Long kbId) {
|
||||
return pageMapper.selectList(
|
||||
new LambdaQueryWrapper<WikiPageEntity>()
|
||||
.eq(WikiPageEntity::getKbId, kbId)
|
||||
.ne(WikiPageEntity::getArchived, 1)
|
||||
.orderByAsc(WikiPageEntity::getTitle));
|
||||
}
|
||||
|
||||
@ -226,6 +230,10 @@ public class WikiPageService {
|
||||
List<WikiPageEntity> pages = pageMapper.selectList(
|
||||
new LambdaQueryWrapper<WikiPageEntity>()
|
||||
.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));
|
||||
|
||||
@ -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<vip.mate.wiki.dto.RouteResult> 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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user