mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(wiki): structured no-evidence compile + ops admin endpoints
This commit is contained in:
parent
5206d65be7
commit
58b49f6e20
@ -0,0 +1,81 @@
|
||||
package vip.mate.wiki.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import vip.mate.wiki.job.WikiChunkTokenBackfillJob;
|
||||
import vip.mate.wiki.service.WikiOverviewService;
|
||||
import vip.mate.wiki.service.WikiScaffoldService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* RFC-051 follow-up: small set of operator-facing endpoints for things the
|
||||
* scheduled jobs / event hooks normally handle automatically. Useful when the
|
||||
* cron hasn't fired yet (fresh upgrade), the auto-rebuild was skipped, or you
|
||||
* just want to force-refresh during debugging.
|
||||
*
|
||||
* <p>All endpoints are idempotent and synchronous.
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/wiki/admin")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Wiki Admin", description = "Operator endpoints for system pages and backfill jobs")
|
||||
public class WikiAdminController {
|
||||
|
||||
private final WikiScaffoldService scaffoldService;
|
||||
|
||||
/** Optional so the controller can boot in environments where the rebuilder isn't wired (e.g. minimal tests). */
|
||||
@Autowired(required = false)
|
||||
private WikiOverviewService overviewService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private WikiChunkTokenBackfillJob backfillJob;
|
||||
|
||||
@Operation(summary = "Ensure overview/log scaffold + rebuild overview stats now",
|
||||
description = "Idempotent. Use after manual data imports or when stats look stale.")
|
||||
@PostMapping("/kb/{kbId}/rebuild-overview")
|
||||
public ResponseEntity<Map<String, Object>> rebuildOverview(@PathVariable Long kbId) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
scaffoldService.ensureScaffold(kbId);
|
||||
if (overviewService != null) {
|
||||
overviewService.rebuild(kbId);
|
||||
body.put("rebuilt", true);
|
||||
} else {
|
||||
body.put("rebuilt", false);
|
||||
body.put("note", "Overview service not wired; only scaffold ensured");
|
||||
}
|
||||
body.put("kbId", kbId);
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
|
||||
@Operation(summary = "Force-run the token-count backfill batch now",
|
||||
description = "Picks up to BATCH_SIZE chunks with token_count IS NULL and fills them. "
|
||||
+ "Returns the pending count after the batch so callers can poll.")
|
||||
@PostMapping("/backfill-tokens")
|
||||
public ResponseEntity<Map<String, Object>> backfillTokens() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
if (backfillJob == null) {
|
||||
body.put("ok", false);
|
||||
body.put("note", "Backfill job not wired");
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
long beforePending = backfillJob.pendingCount();
|
||||
backfillJob.runOnce();
|
||||
long afterPending = backfillJob.pendingCount();
|
||||
body.put("ok", true);
|
||||
body.put("pendingBefore", beforePending);
|
||||
body.put("pendingAfter", afterPending);
|
||||
body.put("filledThisBatch", Math.max(0, beforePending - afterPending));
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,16 @@ public class WikiChunkTokenBackfillJob {
|
||||
|
||||
private final WikiChunkMapper chunkMapper;
|
||||
|
||||
/**
|
||||
* RFC-051 follow-up: count chunks still missing a token estimate.
|
||||
* Used by the admin endpoint to decide whether a manual rerun is worthwhile.
|
||||
*/
|
||||
public long pendingCount() {
|
||||
return chunkMapper.selectCount(
|
||||
new LambdaQueryWrapper<WikiChunkEntity>()
|
||||
.isNull(WikiChunkEntity::getTokenCount));
|
||||
}
|
||||
|
||||
@Async
|
||||
@Scheduled(cron = "${mate.wiki.chunk-token-backfill-cron:0 */30 * * * ?}")
|
||||
public void runOnce() {
|
||||
|
||||
@ -58,8 +58,22 @@ public class WikiCompileService {
|
||||
@Autowired(required = false)
|
||||
private WikiLogService logService;
|
||||
|
||||
/**
|
||||
* Compile outcome.
|
||||
* <ul>
|
||||
* <li>{@code pageId}, {@code slug}, {@code title} non-null → page produced.</li>
|
||||
* <li>{@code evidenceChunkCount == 0} → no chunks matched the topic;
|
||||
* {@code pageId/slug/title} all null. Caller (agent) should fall
|
||||
* back to {@code wiki_search_pages} or report no source material.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public record CompileResult(Long pageId, String slug, String title, int evidenceChunkCount,
|
||||
boolean created) {}
|
||||
boolean created) {
|
||||
|
||||
public static CompileResult noEvidence() {
|
||||
return new CompileResult(null, null, null, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile or update a single page on the topic.
|
||||
@ -79,7 +93,10 @@ public class WikiCompileService {
|
||||
// 1. Retrieve evidence chunks via semantic search (hybrid retriever).
|
||||
List<HybridRetriever.ChunkHit> hits = hybridRetriever.searchChunks(kbId, topic, cap);
|
||||
if (hits.isEmpty()) {
|
||||
throw new IllegalStateException("No evidence chunks found for topic: " + topic);
|
||||
// Structured "nothing matched" result rather than throw — lets the
|
||||
// tool surface respond with a clean message instead of a stack trace.
|
||||
log.info("[WikiCompile] No evidence chunks for topic='{}' kbId={}", topic, kbId);
|
||||
return CompileResult.noEvidence();
|
||||
}
|
||||
|
||||
List<Long> evidenceChunkIds = new ArrayList<>(hits.size());
|
||||
|
||||
@ -390,8 +390,21 @@ public class WikiTool {
|
||||
|
||||
try {
|
||||
WikiCompileService.CompileResult res = compileService.compilePage(kbId, topic, slug, maxEvidenceChunks);
|
||||
// RFC-051 follow-up: distinguish "no source material" from a hard error
|
||||
// so the agent can decide whether to retry, fall back to search, or tell
|
||||
// the user there's nothing on this topic.
|
||||
if (res.evidenceChunkCount() == 0) {
|
||||
return JSONUtil.createObj()
|
||||
.set("ok", true)
|
||||
.set("compiled", false)
|
||||
.set("reason", "no_evidence")
|
||||
.set("message", "No chunks matched the topic. Try wiki_search_pages, or upload source material first.")
|
||||
.set("evidenceChunks", 0)
|
||||
.toString();
|
||||
}
|
||||
return JSONUtil.createObj()
|
||||
.set("ok", true)
|
||||
.set("compiled", true)
|
||||
.set("slug", res.slug())
|
||||
.set("title", res.title())
|
||||
.set("evidenceChunks", res.evidenceChunkCount())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user