From 93807d85ef01e91dac7df8e4edab5e84e39cce0b Mon Sep 17 00:00:00 2001 From: matevip Date: Sat, 2 May 2026 21:45:49 +0800 Subject: [PATCH] feat(wiki): inject KB hot cache into agent system prompt via MemoryProvider SPI --- .../wiki/hotcache/WikiHotCacheProvider.java | 85 +++++++++++++++++++ .../src/views/Settings/FeatureFlags/index.vue | 1 + 2 files changed, 86 insertions(+) create mode 100644 mateclaw-server/src/main/java/vip/mate/wiki/hotcache/WikiHotCacheProvider.java diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/hotcache/WikiHotCacheProvider.java b/mateclaw-server/src/main/java/vip/mate/wiki/hotcache/WikiHotCacheProvider.java new file mode 100644 index 00000000..10d291f4 --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/wiki/hotcache/WikiHotCacheProvider.java @@ -0,0 +1,85 @@ +package vip.mate.wiki.hotcache; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import vip.mate.memory.spi.MemoryProvider; +import vip.mate.system.featureflag.FeatureFlagService; +import vip.mate.wiki.model.WikiKnowledgeBaseEntity; +import vip.mate.wiki.service.WikiKnowledgeBaseService; + +import java.util.List; + +/** + * Memory-SPI provider that injects each KB's hot cache snapshot into the + * agent's system prompt at build time. + * + *

Resolves the agent's accessible KBs via the same path the chat + * pipeline already uses ({@link WikiKnowledgeBaseService#listByAgentId}). + * If the agent reaches more than {@link #MAX_KBS_PER_AGENT} KBs, only the + * top KBs by recent-update order contribute (the list is already sorted + * by {@code update_time DESC}); this is a prompt-budget guard, not a + * permissions decision. + * + *

Per-turn paths are intentionally untouched — wiki relevance is + * already handled by {@code WikiContextService.buildRelevantContext} on + * each user message; this provider's job is the steady "what's been + * happening here" header that the model sees once at session start. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class WikiHotCacheProvider implements MemoryProvider { + + private static final String FLAG = "wiki.hot_cache.enabled"; + private static final int MAX_KBS_PER_AGENT = 2; + + private final WikiHotCacheService cacheService; + private final WikiKnowledgeBaseService kbService; + private final FeatureFlagService featureFlagService; + + @Override + public String id() { + return "wiki_hot_cache"; + } + + @Override + public int order() { + // After builtin (0), structured (10), session-search (20). + // Hot cache is freshest, but slots after the more general blocks + // so the model frames "recent activity" as a sub-context, not a + // top-level identity hint. + return 30; + } + + @Override + public String systemPromptBlock(Long agentId) { + if (agentId == null) return ""; + try { + if (!featureFlagService.isEnabled(FLAG)) return ""; + + List kbs = kbService.listByAgentId(agentId); + if (kbs.isEmpty()) return ""; + + StringBuilder sb = new StringBuilder(); + int taken = 0; + for (WikiKnowledgeBaseEntity kb : kbs) { + if (taken >= MAX_KBS_PER_AGENT) break; + String content = cacheService.getContentOrNull(kb.getId()); + if (content == null || content.isBlank()) continue; + if (taken == 0) { + sb.append("# Recent Wiki Activity\n\n"); + } else { + sb.append("\n\n## ").append(kb.getName()).append("\n\n"); + } + sb.append(content); + taken++; + } + return sb.toString(); + } catch (Exception e) { + log.warn("[WikiHotCache] systemPromptBlock failed for agent={}: {}", + agentId, e.getMessage()); + return ""; + } + } +} diff --git a/mateclaw-ui/src/views/Settings/FeatureFlags/index.vue b/mateclaw-ui/src/views/Settings/FeatureFlags/index.vue index 88115634..6d999ec4 100644 --- a/mateclaw-ui/src/views/Settings/FeatureFlags/index.vue +++ b/mateclaw-ui/src/views/Settings/FeatureFlags/index.vue @@ -85,6 +85,7 @@ const { t, getLocaleMessage, locale } = useI18n() // grep -rn 'featureFlagService\.isEnabled' mateclaw-server/src/main/java const WIRED_FLAGS = new Set([ 'wiki.ocr.enabled', + 'wiki.hot_cache.enabled', ]) function isWired(flag: FeatureFlag): boolean {