mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +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 */
|
/** Global fallback model chain for all steps in this KB */
|
||||||
private List<Long> fallbackModelIds;
|
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) {
|
public List<WikiPageEntity> listByKbId(Long kbId) {
|
||||||
List<WikiPageEntity> pages = pageMapper.selectList(
|
List<WikiPageEntity> pages = pageMapper.selectList(
|
||||||
new LambdaQueryWrapper<WikiPageEntity>()
|
new LambdaQueryWrapper<WikiPageEntity>()
|
||||||
.eq(WikiPageEntity::getKbId, kbId)
|
.eq(WikiPageEntity::getKbId, kbId)
|
||||||
|
.ne(WikiPageEntity::getArchived, 1)
|
||||||
.orderByAsc(WikiPageEntity::getTitle));
|
.orderByAsc(WikiPageEntity::getTitle));
|
||||||
pages.forEach(p -> p.setContent(null));
|
pages.forEach(p -> p.setContent(null));
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列出知识库所有页面(含 content,用于全文搜索)
|
* 列出知识库所有页面(含 content,用于全文搜索)。
|
||||||
|
* RFC-051 PR-7: archived 页面不参与 enrich / 全文搜索遍历。
|
||||||
*/
|
*/
|
||||||
public List<WikiPageEntity> listByKbIdWithContent(Long kbId) {
|
public List<WikiPageEntity> listByKbIdWithContent(Long kbId) {
|
||||||
return pageMapper.selectList(
|
return pageMapper.selectList(
|
||||||
new LambdaQueryWrapper<WikiPageEntity>()
|
new LambdaQueryWrapper<WikiPageEntity>()
|
||||||
.eq(WikiPageEntity::getKbId, kbId)
|
.eq(WikiPageEntity::getKbId, kbId)
|
||||||
|
.ne(WikiPageEntity::getArchived, 1)
|
||||||
.orderByAsc(WikiPageEntity::getTitle));
|
.orderByAsc(WikiPageEntity::getTitle));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +230,10 @@ public class WikiPageService {
|
|||||||
List<WikiPageEntity> pages = pageMapper.selectList(
|
List<WikiPageEntity> pages = pageMapper.selectList(
|
||||||
new LambdaQueryWrapper<WikiPageEntity>()
|
new LambdaQueryWrapper<WikiPageEntity>()
|
||||||
.eq(WikiPageEntity::getKbId, kbId)
|
.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())
|
.like(WikiPageEntity::getSourceRawIds, rawId.toString())
|
||||||
.orderByAsc(WikiPageEntity::getTitle));
|
.orderByAsc(WikiPageEntity::getTitle));
|
||||||
pages.forEach(p -> p.setContent(null));
|
pages.forEach(p -> p.setContent(null));
|
||||||
|
|||||||
@ -284,9 +284,25 @@ public class WikiProcessingService {
|
|||||||
String finalStatus;
|
String finalStatus;
|
||||||
String finalDetail = null;
|
String finalDetail = null;
|
||||||
if (totalPages == 0) {
|
if (totalPages == 0) {
|
||||||
rawService.updateProcessingStatus(rawId, "failed", "No pages generated from LLM response");
|
// RFC-051 follow-up: previously this was an unconditional "failed".
|
||||||
finalStatus = "failed";
|
// But chunks were already persisted (and the materials are searchable
|
||||||
finalDetail = "No pages generated from LLM response";
|
// 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) {
|
} else if (failedChunks > 0 || failedPages > 0) {
|
||||||
// 部分成功:chunk 整体失败 或 chunk 内有 page 失败
|
// 部分成功:chunk 整体失败 或 chunk 内有 page 失败
|
||||||
// (M2 v2 follow-up:page 级失败原本被计入 completed,现在正确归 partial)
|
// (M2 v2 follow-up:page 级失败原本被计入 completed,现在正确归 partial)
|
||||||
@ -362,7 +378,10 @@ public class WikiProcessingService {
|
|||||||
// 如果未来加了事务包裹 processRawMaterial,这里的异步任务需要改用
|
// 如果未来加了事务包裹 processRawMaterial,这里的异步任务需要改用
|
||||||
// TransactionSynchronizationManager.registerSynchronization(afterCommit)
|
// TransactionSynchronizationManager.registerSynchronization(afterCommit)
|
||||||
// 否则新线程会查不到 chunk(事务未提交)导致 embedding 静默跳过。
|
// 否则新线程会查不到 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();
|
final Long fKbId = kb.getId();
|
||||||
WIKI_EXECUTOR.submit(() -> {
|
WIKI_EXECUTOR.submit(() -> {
|
||||||
try {
|
try {
|
||||||
@ -667,9 +686,11 @@ public class WikiProcessingService {
|
|||||||
.replace("{raw_content}", textContent);
|
.replace("{raw_content}", textContent);
|
||||||
|
|
||||||
// RFC-051 PR-6b: optionally inject Spring AI's structured-output hint so the
|
// 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 =
|
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)
|
? new org.springframework.ai.converter.BeanOutputConverter<>(vip.mate.wiki.dto.RouteResult.class)
|
||||||
: null;
|
: null;
|
||||||
if (routeConverter != 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
|
* 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.
|
* on any parse error or missing field so the caller falls through to eager.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user