stage-10: memory search with Ollama embeddings + SQLite
Semantic search over memory/*.md files using all-minilm embeddings. Adds /search Discord command and `echo memory search/reindex` CLI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,7 @@ def create_bot(config: Config) -> discord.Client:
|
||||
"`/logs [n]` — Show last N log lines (default 10)",
|
||||
"`/restart` — Restart the bot process (owner only)",
|
||||
"`/heartbeat` — Run heartbeat health checks",
|
||||
"`/search <query>` — Search Echo's memory",
|
||||
"",
|
||||
"**Cron Jobs**",
|
||||
"`/cron list` — List all scheduled jobs",
|
||||
@@ -413,6 +414,41 @@ def create_bot(config: Config) -> discord.Client:
|
||||
f"Heartbeat error: {e}", ephemeral=True
|
||||
)
|
||||
|
||||
@tree.command(name="search", description="Search Echo's memory")
|
||||
@app_commands.describe(query="What to search for")
|
||||
async def search_cmd(
|
||||
interaction: discord.Interaction, query: str
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
try:
|
||||
from src.memory_search import search
|
||||
|
||||
results = await asyncio.to_thread(search, query)
|
||||
if not results:
|
||||
await interaction.followup.send(
|
||||
"No results found (index may be empty — run `echo memory reindex`)."
|
||||
)
|
||||
return
|
||||
|
||||
lines = [f"**Search results for:** {query}\n"]
|
||||
for i, r in enumerate(results, 1):
|
||||
score = r["score"]
|
||||
preview = r["chunk"][:150]
|
||||
if len(r["chunk"]) > 150:
|
||||
preview += "..."
|
||||
lines.append(
|
||||
f"**{i}.** `{r['file']}` (score: {score:.3f})\n{preview}\n"
|
||||
)
|
||||
text = "\n".join(lines)
|
||||
if len(text) > 1900:
|
||||
text = text[:1900] + "\n..."
|
||||
await interaction.followup.send(text)
|
||||
except ConnectionError as e:
|
||||
await interaction.followup.send(f"Search error: {e}")
|
||||
except Exception as e:
|
||||
logger.exception("Search command failed")
|
||||
await interaction.followup.send(f"Search error: {e}")
|
||||
|
||||
@tree.command(name="channels", description="List registered channels")
|
||||
async def channels(interaction: discord.Interaction) -> None:
|
||||
ch_map = config.get("channels", {})
|
||||
|
||||
Reference in New Issue
Block a user