feat(wiki): instrument compile / relation / retrieval with WikiMetrics

This commit is contained in:
matevip 2026-05-02 19:04:19 +08:00
parent 4f96a46900
commit 6f0ead162e
3 changed files with 39 additions and 3 deletions

View File

@ -8,11 +8,13 @@ import vip.mate.wiki.WikiProperties;
import vip.mate.wiki.dto.PageSearchResult;
import vip.mate.wiki.dto.RelatedPageResult;
import vip.mate.wiki.dto.WikiPageLite;
import vip.mate.wiki.metrics.WikiMetrics;
import vip.mate.wiki.model.WikiChunkEntity;
import vip.mate.wiki.model.WikiPageEntity;
import vip.mate.wiki.repository.WikiPageMapper;
import vip.mate.wiki.retrieval.SnippetExtractor;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@ -32,6 +34,7 @@ public class HybridRetriever {
private final WikiEmbeddingService embeddingService;
private final WikiProperties properties;
private final WikiPageMapper pageMapper;
private final WikiMetrics metrics;
@Autowired(required = false)
private WikiRelationService relationService;
@ -42,12 +45,14 @@ public class HybridRetriever {
WikiChunkService chunkService,
WikiEmbeddingService embeddingService,
WikiProperties properties,
WikiPageMapper pageMapper) {
WikiPageMapper pageMapper,
WikiMetrics metrics) {
this.pageService = pageService;
this.chunkService = chunkService;
this.embeddingService = embeddingService;
this.properties = properties;
this.pageMapper = pageMapper;
this.metrics = metrics;
}
public enum Mode { KEYWORD, SEMANTIC, HYBRID }
@ -79,6 +84,8 @@ public class HybridRetriever {
public List<PageSearchResult> search(Long kbId, String query, String modeStr, int topK) {
Mode mode = parseMode(modeStr);
long startNanos = System.nanoTime();
List<RankedItem> semantic = List.of();
List<RankedItem> keyword = List.of();
@ -107,7 +114,11 @@ public class HybridRetriever {
// Batch-fetch page info (N+1 fix)
List<Long> topIds = fused.stream().limit(topK).map(ri -> ri.pageId).toList();
if (topIds.isEmpty()) return List.of();
if (topIds.isEmpty()) {
metrics.recordRetrieval(mode.name().toLowerCase(),
Duration.ofNanos(System.nanoTime() - startNanos), 0);
return List.of();
}
Map<Long, WikiPageLite> liteMap = pageMapper.selectBatchLite(topIds)
.stream().collect(Collectors.toMap(WikiPageLite::id, p -> p));
@ -142,6 +153,8 @@ public class HybridRetriever {
snippet != null ? snippet : lite.summary(),
ri.matchedBy, reason, ri.score));
}
metrics.recordRetrieval(mode.name().toLowerCase(),
Duration.ofNanos(System.nanoTime() - startNanos), results.size());
return results;
}

View File

@ -14,10 +14,12 @@ import org.springframework.stereotype.Service;
import vip.mate.agent.prompt.PromptLoader;
import vip.mate.wiki.job.WikiJobStep;
import vip.mate.wiki.job.WikiModelRoutingService;
import vip.mate.wiki.metrics.WikiMetrics;
import vip.mate.wiki.model.WikiChunkEntity;
import vip.mate.wiki.model.WikiPageEntity;
import vip.mate.wiki.repository.WikiChunkMapper;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@ -43,6 +45,7 @@ public class WikiCompileService {
private final WikiPageService pageService;
private final WikiCitationService citationService;
private final ObjectMapper objectMapper;
private final WikiMetrics metrics;
/**
* Optional. When wired we use the routing chain (stepModels[CREATE_PAGE]
@ -91,12 +94,16 @@ public class WikiCompileService {
int cap = (maxEvidenceChunks == null || maxEvidenceChunks <= 0)
? 8 : Math.min(20, maxEvidenceChunks);
long startNanos = System.nanoTime();
// 1. Retrieve evidence chunks via semantic search (hybrid retriever).
List<HybridRetriever.ChunkHit> hits = hybridRetriever.searchChunks(kbId, topic, cap);
if (hits.isEmpty()) {
// 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);
metrics.recordCompileStage("no_evidence", kbId,
Duration.ofNanos(System.nanoTime() - startNanos));
return CompileResult.noEvidence();
}
@ -186,6 +193,8 @@ public class WikiCompileService {
}
if (overviewService != null) overviewService.rebuild(kbId);
metrics.recordCompileStage("compile_page", kbId,
Duration.ofNanos(System.nanoTime() - startNanos));
return new CompileResult(persisted.getId(), resolvedSlug, title, evidenceChunkIds.size(), created);
}

View File

@ -3,11 +3,13 @@ package vip.mate.wiki.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import vip.mate.wiki.dto.*;
import vip.mate.wiki.metrics.WikiMetrics;
import vip.mate.wiki.model.WikiPageEntity;
import vip.mate.wiki.relation.RelationSignalStrategy;
import vip.mate.wiki.repository.WikiPageCitationMapper;
import vip.mate.wiki.repository.WikiPageMapper;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@ -23,15 +25,18 @@ public class WikiRelationService {
private final WikiPageMapper pageMapper;
private final WikiPageService pageService;
private final WikiPageCitationMapper citationMapper;
private final WikiMetrics metrics;
public WikiRelationService(List<RelationSignalStrategy> signals,
WikiPageMapper pageMapper,
WikiPageService pageService,
WikiPageCitationMapper citationMapper) {
WikiPageCitationMapper citationMapper,
WikiMetrics metrics) {
this.signals = signals;
this.pageMapper = pageMapper;
this.pageService = pageService;
this.citationMapper = citationMapper;
this.metrics = metrics;
}
/**
@ -41,6 +46,8 @@ public class WikiRelationService {
WikiPageEntity seed = pageService.getBySlug(kbId, seedSlug);
if (seed == null) return List.of();
long startNanos = System.nanoTime();
Map<Long, Double> totalScores = new HashMap<>();
Map<Long, List<String>> signalHits = new HashMap<>();
@ -62,6 +69,13 @@ public class WikiRelationService {
.map(Map.Entry::getKey)
.toList();
// Reports both compute duration and the candidate-set size.
// Cache hit/miss is reported once a relation cache layer is wired up;
// until then every call is a cold compute by definition.
metrics.recordRelationCompute(kbId, totalScores.size(),
Duration.ofNanos(System.nanoTime() - startNanos));
metrics.recordRelationCacheHit(false);
if (topIds.isEmpty()) return List.of();
Map<Long, WikiPageLite> liteMap = pageMapper.selectBatchLite(topIds)
.stream().collect(Collectors.toMap(WikiPageLite::id, l -> l));