mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(memory): prefer recalled personal memory over knowledge base for user/project questions
This commit is contained in:
parent
d4ea75a806
commit
7e9f2ee54c
@ -1376,6 +1376,18 @@ public class AgentGraphBuilder {
|
||||
Use workspace memory tools (MEMORY.md, daily notes) for long-form narrative notes.
|
||||
Use structured memory tools for key-value facts the system can query efficiently.
|
||||
|
||||
## Memory vs Knowledge Base Precedence
|
||||
When a question is about the user themselves — who they are, their current
|
||||
project, its name/codename, tech stack, goals, metrics, budget, team, or what
|
||||
they are working on — your recalled memory (the <memory-context> block plus
|
||||
structured/workspace memory) is the authoritative source. Knowledge-base / wiki
|
||||
pages are reference material that may describe unrelated, example, or upstream
|
||||
projects; do NOT treat a KB page's subject as the user's own project. Only read
|
||||
the knowledge base for explicit reference lookups, never to decide what the
|
||||
user's project is. If memory and a KB page disagree about the user's project,
|
||||
trust memory. If memory has no answer, say you do not have it rather than
|
||||
adopting a KB article as the user's project.
|
||||
|
||||
## Session Search
|
||||
- `session_search(agentId, currentConversationId, mode, query, limit)` — search conversation history
|
||||
- mode="recent": list recent conversations (titles, times, message counts)
|
||||
|
||||
@ -971,7 +971,17 @@ public class ReasoningNode implements NodeAction {
|
||||
List<Message> prefix = new ArrayList<>();
|
||||
prefix.add(new SystemMessage(systemPrompt));
|
||||
prefix.add(new UserMessage(RuntimeContextInjector.buildContextMessage(workspaceBasePath, null, chatOrigin)));
|
||||
if (wikiContextService != null && agentIdStr != null && !agentIdStr.isEmpty()) {
|
||||
// When this turn already recalled the user's own current project from
|
||||
// structured memory, skip auto-injecting knowledge-base reference context.
|
||||
// Otherwise the KB pages (reference material, possibly about unrelated
|
||||
// projects) compete with — and tend to override — the user's actual
|
||||
// project identity. The agent can still query the wiki on demand.
|
||||
boolean projectRecalled = userMsg != null
|
||||
&& userMsg.contains(vip.mate.memory.service.StructuredMemoryService.PROJECT_RECALLED_MARKER);
|
||||
if (projectRecalled) {
|
||||
log.debug("[ReasoningNode] Skipping wiki-relevant injection: user's project was recalled from memory this turn");
|
||||
}
|
||||
if (!projectRecalled && wikiContextService != null && agentIdStr != null && !agentIdStr.isEmpty()) {
|
||||
try {
|
||||
Long parsedAgentId = Long.parseLong(agentIdStr);
|
||||
String wikiRelevant = wikiContextService.buildRelevantContext(parsedAgentId, userMsg);
|
||||
|
||||
@ -54,6 +54,15 @@ public class StructuredMemoryService {
|
||||
/** Maximum number of entries injected by a single query-conditioned prefetch. */
|
||||
private static final int MAX_PREFETCH_ENTRIES = 6;
|
||||
|
||||
/**
|
||||
* Appended to the prefetch block header when a {@code project}-type entry is
|
||||
* included, i.e. the user's own current project was recalled for this turn.
|
||||
* Downstream prompt assembly detects this marker to avoid also injecting
|
||||
* knowledge-base reference context that would compete for "what project is
|
||||
* this" — personal project memory is authoritative over reference articles.
|
||||
*/
|
||||
public static final String PROJECT_RECALLED_MARKER = "includes the user's current project";
|
||||
|
||||
/** Latin word tokens of length >= 2 used for relevance shingling. */
|
||||
private static final Pattern WORD_RE = Pattern.compile("[a-z0-9]{2,}");
|
||||
|
||||
@ -249,7 +258,12 @@ public class StructuredMemoryService {
|
||||
List<ScoredEntry> scored = recallRelevant(agentId, userQuery, PREFETCH_TYPES, MAX_PREFETCH_ENTRIES);
|
||||
if (scored.isEmpty()) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder("## Relevant Structured Memory\n");
|
||||
boolean hasProject = scored.stream().anyMatch(e -> "project".equals(e.type()));
|
||||
StringBuilder sb = new StringBuilder("## Relevant Structured Memory");
|
||||
if (hasProject) {
|
||||
sb.append(" (").append(PROJECT_RECALLED_MARKER).append(")");
|
||||
}
|
||||
sb.append("\n");
|
||||
for (ScoredEntry e : scored) {
|
||||
sb.append("- **").append(e.key()).append("**: ")
|
||||
.append(extractContentOnly(e.body()));
|
||||
|
||||
@ -85,8 +85,12 @@ public class WikiContextService {
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("<wiki-relevant>\n");
|
||||
sb.append("[Relevant wiki pages for this query. Use wiki_read_page(slug) for full content. " +
|
||||
"When using information from these pages in your answer, always cite the source page title, " +
|
||||
sb.append("[Relevant pages from the shared knowledge base for this query. These are " +
|
||||
"reference articles and may cover topics unrelated to this user — do NOT assume " +
|
||||
"they describe the user's own project, identity, or current work. For who the user " +
|
||||
"is and what they are working on, rely on <memory-context> instead; it takes " +
|
||||
"precedence over these pages. Use wiki_read_page(slug) for full content. When using " +
|
||||
"information from these pages in your answer, always cite the source page title, " +
|
||||
"e.g. 「来源:[[页面标题]]」or「(来源:页面标题)」.]\n\n");
|
||||
int totalChars = 0;
|
||||
int maxChars = properties.getMaxContextChars();
|
||||
|
||||
@ -99,6 +99,36 @@ class StructuredMemoryPrefetchTest {
|
||||
assertTrue(block.contains("updated 2026-05-29"), "recency hint should be present");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("prefetch marks the block when the user's own project is recalled (project type)")
|
||||
void prefetchMarksProjectRecall() {
|
||||
StructuredMemoryService svc = newService(
|
||||
"## project_codename\n用户的项目代号叫\"天枢\"。\n> Source: agent | Updated: 2026-05-29",
|
||||
null);
|
||||
|
||||
String block = svc.buildPrefetchBlock(AGENT_ID, "我的项目代号是什么?");
|
||||
|
||||
assertTrue(block.contains(StructuredMemoryService.PROJECT_RECALLED_MARKER),
|
||||
"a project-type recall should carry the marker so wiki injection can be suppressed");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("prefetch does NOT mark the block for reference-only recall")
|
||||
void prefetchNoMarkerForReferenceOnly() {
|
||||
// Only a reference-type file is present; the project file is absent.
|
||||
WorkspaceFileService files = mock(WorkspaceFileService.class);
|
||||
when(files.getFile(eq(AGENT_ID), anyString())).thenReturn(null);
|
||||
WorkspaceFileEntity ref = new WorkspaceFileEntity();
|
||||
ref.setContent("## api_endpoint\n参考:订单查询接口 /api/orders。\n> Source: agent | Updated: 2026-05-29");
|
||||
when(files.getFile(AGENT_ID, "structured/reference.md")).thenReturn(ref);
|
||||
StructuredMemoryService svc = new StructuredMemoryService(files, mock(ApplicationEventPublisher.class));
|
||||
|
||||
String block = svc.buildPrefetchBlock(AGENT_ID, "订单查询接口参考是什么?");
|
||||
|
||||
assertFalse(block.contains(StructuredMemoryService.PROJECT_RECALLED_MARKER),
|
||||
"reference-only recall must not claim a project so wiki context stays available");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("prefetch returns empty for an unrelated question")
|
||||
void prefetchEmptyForUnrelatedQuery() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user