mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +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