mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
feat(wiki): hot cache storage skeleton — table, entity, value object, read service
This commit is contained in:
parent
47127fe918
commit
a8f0998c24
@ -0,0 +1,87 @@
|
||||
package vip.mate.wiki.hotcache;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* In-memory value object for one hot cache snapshot — four sections plus
|
||||
* an "updated" timestamp that ride together into the agent system prompt.
|
||||
*
|
||||
* <p>Wire shape on disk is the rendered markdown produced by
|
||||
* {@link #toMarkdown()}; the structured form is preserved here so the
|
||||
* updater LLM can return JSON-shaped sections that are validated, scored,
|
||||
* and rendered uniformly across versions.
|
||||
*
|
||||
* <p>Example rendering:
|
||||
* <pre>
|
||||
* ---
|
||||
* type: meta
|
||||
* updated: 2026-05-02T08:30:00Z
|
||||
* ---
|
||||
*
|
||||
* ## Last Updated
|
||||
* 2026-05-02 — Ingested 3 papers on RedLock; flagged contradiction with paxos-comparison page
|
||||
*
|
||||
* ## Key Recent Facts
|
||||
* - RedLock has known safety issues under network partition (Kleppmann 2016)
|
||||
* - Internal Redis 7.4 release notes confirm scheduled deprecation in 8.0
|
||||
*
|
||||
* ## Recent Changes
|
||||
* - Created: [[redlock-safety-analysis]]
|
||||
* - Updated: [[distributed-locks]], [[paxos-comparison]]
|
||||
*
|
||||
* ## Active Threads
|
||||
* - Open question: should we recommend ZooKeeper for new services?
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class HotCacheContent {
|
||||
|
||||
private Instant updatedAt;
|
||||
|
||||
/** Single-paragraph "what happened most recently" headline. */
|
||||
private String lastUpdatedSummary;
|
||||
|
||||
@Builder.Default private List<String> keyRecentFacts = List.of();
|
||||
@Builder.Default private List<String> recentChanges = List.of();
|
||||
@Builder.Default private List<String> activeThreads = List.of();
|
||||
|
||||
/**
|
||||
* Renders the structured snapshot to its on-disk markdown form. Empty
|
||||
* sections render as {@code (none)} so the output remains readable when
|
||||
* a rebuild produces less than a full set of sections.
|
||||
*/
|
||||
public String toMarkdown() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("---\n");
|
||||
sb.append("type: meta\n");
|
||||
sb.append("updated: ").append(updatedAt != null ? updatedAt.toString() : "unknown").append("\n");
|
||||
sb.append("---\n\n");
|
||||
|
||||
sb.append("## Last Updated\n");
|
||||
sb.append(lastUpdatedSummary != null && !lastUpdatedSummary.isBlank()
|
||||
? lastUpdatedSummary : "(no recent activity)").append("\n\n");
|
||||
|
||||
appendBulletSection(sb, "Key Recent Facts", keyRecentFacts);
|
||||
appendBulletSection(sb, "Recent Changes", recentChanges);
|
||||
appendBulletSection(sb, "Active Threads", activeThreads);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void appendBulletSection(StringBuilder sb, String heading, List<String> bullets) {
|
||||
sb.append("## ").append(heading).append('\n');
|
||||
if (bullets == null || bullets.isEmpty()) {
|
||||
sb.append("(none)\n\n");
|
||||
return;
|
||||
}
|
||||
for (String bullet : bullets) {
|
||||
sb.append("- ").append(bullet).append('\n');
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package vip.mate.wiki.hotcache;
|
||||
|
||||
/**
|
||||
* Why a hot cache row was last rebuilt. Stored as
|
||||
* {@link Enum#name()} on {@code mate_wiki_hot_cache.update_reason}.
|
||||
*/
|
||||
public enum HotCacheUpdateReason {
|
||||
|
||||
/** A wiki compile job for some raw material in this KB just finished. */
|
||||
COMPILE_DONE,
|
||||
|
||||
/** A page in this KB was edited directly (not via compile). */
|
||||
PAGE_UPDATED,
|
||||
|
||||
/** A conversation that read pages from this KB ended (debounced). */
|
||||
CONVERSATION_END,
|
||||
|
||||
/** Operator hit the "rebuild now" button. */
|
||||
MANUAL,
|
||||
|
||||
/** Periodic refresh fired because no event had triggered a rebuild for a while. */
|
||||
STALE_CHECK,
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package vip.mate.wiki.hotcache;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import vip.mate.wiki.model.WikiHotCacheEntity;
|
||||
import vip.mate.wiki.repository.WikiHotCacheMapper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Read/write service for KB-level hot cache snapshots.
|
||||
*
|
||||
* <p>This PR provides read methods only — the LLM-driven rebuilder lands
|
||||
* in a follow-up PR (along with the event listener and debounce window).
|
||||
* Read methods are exposed now so the agent-prompt provider can wire up
|
||||
* against a stable API even before any cache rows exist.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WikiHotCacheService {
|
||||
|
||||
private final WikiHotCacheMapper mapper;
|
||||
|
||||
/**
|
||||
* Returns the active hot cache row for {@code kbId}, or empty when
|
||||
* none has ever been generated for this KB. The mapper logical-delete
|
||||
* filter excludes soft-deleted rows automatically.
|
||||
*/
|
||||
public Optional<WikiHotCacheEntity> findByKb(Long kbId) {
|
||||
if (kbId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(mapper.selectOne(
|
||||
new LambdaQueryWrapper<WikiHotCacheEntity>()
|
||||
.eq(WikiHotCacheEntity::getKbId, kbId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rendered markdown body for {@code kbId}, or {@code null}
|
||||
* when no cache row exists. Convenience for the agent-prompt provider
|
||||
* which only ever needs the body.
|
||||
*/
|
||||
public String getContentOrNull(Long kbId) {
|
||||
return findByKb(kbId).map(WikiHotCacheEntity::getContent).orElse(null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package vip.mate.wiki.model;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* One row per knowledge base — the rolling "what happened recently here"
|
||||
* snapshot rendered into the agent system prompt at conversation start.
|
||||
*
|
||||
* <p>The body is markdown produced from four sections (Last Updated, Key
|
||||
* Recent Facts, Recent Changes, Active Threads) by an LLM rebuild call.
|
||||
* This entity is the persistence shape; the value-object form lives in
|
||||
* {@link vip.mate.wiki.hotcache.HotCacheContent}.
|
||||
*/
|
||||
@Data
|
||||
@TableName("mate_wiki_hot_cache")
|
||||
public class WikiHotCacheEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long kbId;
|
||||
|
||||
/** Markdown body, target ≤ 4096 chars after rendering. */
|
||||
private String content;
|
||||
|
||||
/** SHA-256 of {@link #content}; lets the updater short-circuit identical rewrites. */
|
||||
private String contentHash;
|
||||
|
||||
private LocalDateTime lastUpdated;
|
||||
|
||||
/** {@link vip.mate.wiki.hotcache.HotCacheUpdateReason#name()} for the most recent rebuild. */
|
||||
private String updateReason;
|
||||
|
||||
private Long rebuildCount;
|
||||
|
||||
private LocalDateTime lastRebuildStartedAt;
|
||||
private Long lastRebuildDurationMs;
|
||||
private String lastRebuildError;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package vip.mate.wiki.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import vip.mate.wiki.model.WikiHotCacheEntity;
|
||||
|
||||
@Mapper
|
||||
public interface WikiHotCacheMapper extends BaseMapper<WikiHotCacheEntity> {
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
-- mate_wiki_hot_cache: KB-level rolling snapshot of "what happened recently"
|
||||
-- in this knowledge base, injected into the agent system prompt so the model
|
||||
-- doesn't have to wiki_search the obvious every turn.
|
||||
--
|
||||
-- One row per KB (uk_whc_kb). Body is markdown rendered from a four-section
|
||||
-- structure (Last Updated / Key Recent Facts / Recent Changes / Active Threads),
|
||||
-- regenerated by an LLM call after compile/page/conversation events; this
|
||||
-- migration only provisions storage. Consumers and updater land in later PRs.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mate_wiki_hot_cache (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
kb_id BIGINT NOT NULL UNIQUE,
|
||||
|
||||
-- Markdown body, target ≤ 4096 chars after rendering.
|
||||
content CLOB,
|
||||
|
||||
-- SHA-256 of `content`; lets the updater skip a write when LLM output
|
||||
-- ends up identical to the previous body.
|
||||
content_hash CHAR(64),
|
||||
|
||||
last_updated TIMESTAMP,
|
||||
|
||||
-- HotCacheUpdateReason enum name (COMPILE_DONE / PAGE_UPDATED /
|
||||
-- CONVERSATION_END / MANUAL / STALE_CHECK).
|
||||
update_reason VARCHAR(32),
|
||||
|
||||
rebuild_count BIGINT NOT NULL DEFAULT 0,
|
||||
last_rebuild_started_at TIMESTAMP,
|
||||
last_rebuild_duration_ms BIGINT,
|
||||
last_rebuild_error VARCHAR(512),
|
||||
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whc_kb ON mate_wiki_hot_cache (kb_id, deleted);
|
||||
@ -0,0 +1,31 @@
|
||||
-- mate_wiki_hot_cache: KB-level rolling snapshot of "what happened recently"
|
||||
-- in this knowledge base, injected into the agent system prompt so the model
|
||||
-- doesn't have to wiki_search the obvious every turn.
|
||||
--
|
||||
-- One row per KB (uk_whc_kb). Body is markdown rendered from a four-section
|
||||
-- structure (Last Updated / Key Recent Facts / Recent Changes / Active Threads),
|
||||
-- regenerated by an LLM call after compile/page/conversation events; this
|
||||
-- migration only provisions storage. Consumers and updater land in later PRs.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mate_wiki_hot_cache (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
kb_id BIGINT NOT NULL,
|
||||
|
||||
content TEXT,
|
||||
content_hash CHAR(64),
|
||||
|
||||
last_updated DATETIME(3),
|
||||
update_reason VARCHAR(32),
|
||||
|
||||
rebuild_count BIGINT NOT NULL DEFAULT 0,
|
||||
last_rebuild_started_at DATETIME(3),
|
||||
last_rebuild_duration_ms BIGINT,
|
||||
last_rebuild_error VARCHAR(512),
|
||||
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
|
||||
UNIQUE KEY uk_whc_kb (kb_id, deleted),
|
||||
KEY idx_whc_kb (kb_id, deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
Loading…
Reference in New Issue
Block a user