mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +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;
|
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
|
@Async
|
||||||
@Scheduled(cron = "${mate.wiki.chunk-token-backfill-cron:0 */30 * * * ?}")
|
@Scheduled(cron = "${mate.wiki.chunk-token-backfill-cron:0 */30 * * * ?}")
|
||||||
public void runOnce() {
|
public void runOnce() {
|
||||||
|
|||||||
@ -58,8 +58,22 @@ public class WikiCompileService {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private WikiLogService logService;
|
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,
|
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.
|
* 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).
|
// 1. Retrieve evidence chunks via semantic search (hybrid retriever).
|
||||||
List<HybridRetriever.ChunkHit> hits = hybridRetriever.searchChunks(kbId, topic, cap);
|
List<HybridRetriever.ChunkHit> hits = hybridRetriever.searchChunks(kbId, topic, cap);
|
||||||
if (hits.isEmpty()) {
|
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());
|
List<Long> evidenceChunkIds = new ArrayList<>(hits.size());
|
||||||
|
|||||||
@ -390,8 +390,21 @@ public class WikiTool {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
WikiCompileService.CompileResult res = compileService.compilePage(kbId, topic, slug, maxEvidenceChunks);
|
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()
|
return JSONUtil.createObj()
|
||||||
.set("ok", true)
|
.set("ok", true)
|
||||||
|
.set("compiled", true)
|
||||||
.set("slug", res.slug())
|
.set("slug", res.slug())
|
||||||
.set("title", res.title())
|
.set("title", res.title())
|
||||||
.set("evidenceChunks", res.evidenceChunkCount())
|
.set("evidenceChunks", res.evidenceChunkCount())
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user