Skip to content

Commit 81674d5

Browse files
committed
fix: FTS5 dots, semantic search, vector embeddings
- FTS5: wrap queries in quotes to handle dots/special chars - vec_raw_observations: enable by default (skip_vector=False) - Project: pass project from sessions to vector embeddings - source=semantic tag for vector search results in CLI - docs: sync version to 2.0.22
1 parent 2b230e6 commit 81674d5

4 files changed

Lines changed: 27 additions & 9 deletions

File tree

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ <h1>🧠 kimi-mneme</h1>
5151
<p class="subtitle">Persistent Memory Plugin for Kimi Code CLI</p>
5252

5353
<div>
54-
<span class="badge badge-pypi">PyPI v<!-- VERSION -->2.0.17<!-- /VERSION --></span>
54+
<span class="badge badge-pypi">PyPI v<!-- VERSION -->2.0.22<!-- /VERSION --></span>
5555
<span class="badge badge-python">Python 3.10+</span>
5656
<span class="badge badge-license">AGPL-3.0</span>
5757
<span class="badge badge-kimi">Kimi CLI Plugin</span>

mneme/db/store.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def end_session(self, session_id: str, reason: str) -> None:
110110
# Observations
111111
# -----------------------------------------------------------------------
112112

113-
def add_observation(self, observation: Observation, skip_vector: bool = True) -> int:
113+
def add_observation(self, observation: Observation, skip_vector: bool = False) -> int:
114114
"""Add an observation and return its ID."""
115115
content = self._observation_to_text(observation)
116116
content_hash = self._hash_content(content) if content else None
@@ -168,12 +168,25 @@ def add_observation(self, observation: Observation, skip_vector: bool = True) ->
168168
if obs_id and not skip_vector:
169169
# Add to sqlite-vec for semantic search
170170
if content:
171+
# Get project from session
172+
project = ""
173+
try:
174+
with self._get_conn() as conn:
175+
row = conn.execute(
176+
"SELECT project FROM sessions WHERE id = ?",
177+
(observation.session_id,),
178+
).fetchone()
179+
if row:
180+
project = row["project"] or ""
181+
except Exception:
182+
pass
171183
self.sqlite_vec.add_raw_observation(
172184
observation_id=obs_id,
173185
session_id=observation.session_id,
174186
content=content,
175187
event_type=observation.event_type,
176188
tool_name=observation.tool_name or "",
189+
project=project,
177190
)
178191
logger.debug(f"Observation added: {obs_id}")
179192
else:
@@ -222,12 +235,15 @@ def search(
222235
) -> list[dict[str, Any]]:
223236
"""Hybrid search: FTS + fallback LIKE + vector similarity."""
224237
# Build FTS query with OR between words for better recall
238+
# Sanitize query for FTS5: wrap in quotes to handle special chars like dots
239+
# FTS5 treats unquoted dots as column filters (column_name.token), which fails
240+
# Quoted strings are treated as literal phrases
225241
words = [w for w in query.strip().split() if len(w) > 2]
226242
if words:
227-
# Try exact phrase first, then individual words with OR
228-
fts_query = f'"{query}" OR ' + " OR ".join(words) if len(words) > 1 else query
243+
# Try exact phrase first (quoted), then individual words with OR
244+
fts_query = f'"{query}" OR ' + " OR ".join(words) if len(words) > 1 else f'"{query}"'
229245
else:
230-
fts_query = query
246+
fts_query = f'"{query}"'
231247

232248
fts_results: list[dict[str, Any]] = []
233249

@@ -341,7 +357,8 @@ def search(
341357
"tool_name": vr.get("tool_name"),
342358
"file_path": None,
343359
"created_at": None,
344-
"snippet": "",
360+
"snippet": f"[semantic match, distance={vr.get('distance', 0):.3f}]",
361+
"source": "semantic",
345362
"vector_distance": vr.get("distance"),
346363
}
347364
)

mneme/db/structured_store.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,11 @@ def get_for_injection(
267267
def search_fts(self, query: str, limit: int = 10) -> list[dict[str, Any]]:
268268
"""Full-text search over structured observations."""
269269
words = [w for w in query.strip().split() if len(w) > 2]
270+
# Sanitize query for FTS5: wrap in quotes to handle special chars like dots
270271
if words:
271-
fts_query = f'"{query}" OR ' + " OR ".join(words) if len(words) > 1 else query
272+
fts_query = f'"{query}" OR ' + " OR ".join(words) if len(words) > 1 else f'"{query}"'
272273
else:
273-
fts_query = query
274+
fts_query = f'"{query}"'
274275

275276
with self._get_conn() as conn:
276277
try:

mneme/db/wire_store.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def add_observation_from_wire(
352352
prompt=prompt,
353353
created_at=created_at,
354354
)
355-
obs_id = store.add_observation(obs, skip_vector=True)
355+
obs_id = store.add_observation(obs, skip_vector=False)
356356

357357
# Queue for background structuring
358358
if obs_id:

0 commit comments

Comments
 (0)