memory_recall was returning almost the entire store instead of a small
set of relevant matches. Two compounding causes, both fixed here:
1. Default limit was 10000 (commit d03a77a "effectively unlimited").
recall_memories/MemoryRecall and the memory_recall MCP tool now
default to 30 (ceiling stays 10000 for callers that opt in).
2. The OR-broadening fallback (fires when the precise AND-match is
sparse) ordered by the importance hybrid and padded up to `limit`,
so with limit=10000 it flooded results with high-importance but
irrelevant memories. It now orders OR-matches by ts_rank(relevance)
DESC and applies a minimum-rank floor (OR_BROADEN_MIN_RANK=0.01) to
drop rows that merely contain a query word incidentally.
Tests: add test_recall_default_limit_is_capped (asserts 30 passed to
fetch) and test_recall_or_broadening_is_relevance_bounded (asserts the
OR query is relevance-ordered + rank-floored). Full suite 176 green,
ruff + mypy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
1.7 KiB
Python
62 lines
1.7 KiB
Python
from typing import Any, Literal, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class MemoryStore(BaseModel):
|
|
content: str
|
|
category: str = "facts"
|
|
tags: str = Field(default="", max_length=500)
|
|
expanded_keywords: str = Field(default="", max_length=500)
|
|
importance: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
force_sensitive: bool = False
|
|
|
|
|
|
class MemoryRecall(BaseModel):
|
|
context: str
|
|
expanded_query: str = ""
|
|
category: Optional[str] = None
|
|
sort_by: Literal["importance", "relevance", "recency"] = "importance"
|
|
# Default to a small top-N so recall returns the most relevant matches, not
|
|
# the whole store. Ceiling stays high for callers that explicitly want more.
|
|
limit: int = Field(default=30, ge=1, le=10000)
|
|
|
|
|
|
class MemoryResponse(BaseModel):
|
|
id: int
|
|
category: str
|
|
importance: float
|
|
|
|
|
|
class SecretResponse(BaseModel):
|
|
id: int
|
|
content: str
|
|
source: str # "vault", "encrypted", "plaintext"
|
|
|
|
|
|
class SyncResponse(BaseModel):
|
|
memories: list[dict[str, Any]]
|
|
server_time: str
|
|
|
|
|
|
class ShareMemory(BaseModel):
|
|
shared_with: str = Field(..., min_length=1, max_length=100)
|
|
permission: Literal["read", "write"] = "read"
|
|
|
|
|
|
class ShareTag(BaseModel):
|
|
tag: str = Field(..., min_length=1, max_length=100)
|
|
shared_with: str = Field(..., min_length=1, max_length=100)
|
|
permission: Literal["read", "write"] = "read"
|
|
|
|
|
|
class UnshareTag(BaseModel):
|
|
tag: str = Field(..., min_length=1, max_length=100)
|
|
shared_with: str = Field(..., min_length=1, max_length=100)
|
|
|
|
|
|
class MemoryUpdate(BaseModel):
|
|
content: Optional[str] = None
|
|
tags: Optional[str] = None
|
|
importance: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
expanded_keywords: Optional[str] = None
|