From a18925674b7a1c841084c6c516ade3694214d365 Mon Sep 17 00:00:00 2001 From: ivanantonijevic Date: Tue, 4 Aug 2026 13:22:21 +0200 Subject: [PATCH] feat: implement semantic search for memory blocks using configurable embeddings and lazy backfill --- .claude/settings.json | 21 +- .env.example | 8 + CLAUDE.md | 1 + documents/DYNAMIC_MEMORY_INSTRUCTIONS.md | 5 + .../mysql/V024__memory_block_embeddings.sql | 7 + .../V024__memory_block_embeddings.sql | 7 + .../sqlite/V024__memory_block_embeddings.sql | 7 + .../test_memory_blocks_semantic_search.py | 223 ++++++++++++++++++ shared/utils/embedding_service.py | 74 ++++++ shared/utils/memory_blocks_service.py | 92 ++++++++ shared/utils/models.py | 3 + shared/utils/tools/memory_blocks_tools.py | 43 ++++ 12 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 shared/sql/migrations/mysql/V024__memory_block_embeddings.sql create mode 100644 shared/sql/migrations/postgresql/V024__memory_block_embeddings.sql create mode 100644 shared/sql/migrations/sqlite/V024__memory_block_embeddings.sql create mode 100644 shared/test/test_memory_blocks_semantic_search.py create mode 100644 shared/utils/embedding_service.py diff --git a/.claude/settings.json b/.claude/settings.json index d9a19a4..2f51163 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -50,7 +50,26 @@ "Bash(git check-ignore *)", "Bash(git pull *)", "Bash(curl -s -o /dev/null -w \"%{http_code}\" https://ai.antonijevic.rs/static/villa-demo.html)", - "Bash(curl -s https://openrouter.ai/api/v1/models)" + "Bash(curl -s https://openrouter.ai/api/v1/models)", + "Bash(sqlite3 my_agent_data.db \"PRAGMA table_info\\(memory_blocks\\);\")", + "Read(//private/tmp/**)", + "Bash(/Users/ivanantonijevic/development/bt/adk/mate/.venv/bin/python -c \"import hashlib; print\\('sha256 ok:', hashlib.sha256\\(b'x'\\).hexdigest\\(\\)[:8]\\)\")", + "Bash(/Users/ivanantonijevic/.pyenv/versions/3.11.11/bin/python -c \"import _blake2\")", + "Bash(echo \"--- exit: $?\")", + "Bash(/Users/ivanantonijevic/.pyenv/versions/3.11.11/bin/python -c \"import ssl; print\\(ssl.OPENSSL_VERSION\\)\")", + "Read(//opt/homebrew/opt/**)", + "Bash(brew list *)", + "Read(//opt/homebrew/Cellar/**)", + "Read(//opt/homebrew/lib/**)", + "Bash(brew info *)", + "Bash(brew install *)", + "Bash(/Users/ivanantonijevic/development/bt/adk/mate/.venv/bin/python -c \"import hashlib; print\\('blake2b ok:', hashlib.blake2b\\(b'x'\\).hexdigest\\(\\)[:8]\\); print\\('sha256 ok:', hashlib.sha256\\(b'x'\\).hexdigest\\(\\)[:8]\\)\")", + "Bash(pkill -f \"auth_server.py\")", + "Bash(pkill -f \"adk_main.py\")", + "Bash(lsof -ti:8000 -ti:8001)", + "Bash(docker info *)", + "Bash(/Users/ivanantonijevic/development/bt/adk/mate/.venv/bin/python -c ' *)", + "Bash(otool -L /Users/ivanantonijevic/.pyenv/versions/3.11.11/lib/python3.11/lib-dynload/_blake2.cpython-311-darwin.so)" ] } } diff --git a/.env.example b/.env.example index e9c15c9..b51e04f 100644 --- a/.env.example +++ b/.env.example @@ -79,6 +79,14 @@ OPENROUTER_API_KEY= # Ollama (local model hosting) OLLAMA_API_BASE=http://localhost:11434 +# Embedding model for semantic search over memory blocks (litellm format). +# Any litellm-supported embedding model works, e.g.: +# gemini/gemini-embedding-001 (default, uses GOOGLE_API_KEY) +# text-embedding-3-small (OpenAI) +# mistral/mistral-embed +# When no key/model is available, search falls back to keyword (LIKE) matching. +# EMBEDDING_MODEL=gemini/gemini-embedding-001 + # Alternative Local API Servers (defaults are used if not set) # LM_STUDIO_BASE_URL=http://localhost:1234/v1 # LLAMACPP_BASE_URL=http://localhost:8080/v1 diff --git a/CLAUDE.md b/CLAUDE.md index 6a7067b..d4d73cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -96,6 +96,7 @@ Follow PEP 8 with type hints on all function signatures. Use f-strings for forma | `TRUSTED_PROXY_HOSTS` | Proxy hosts trusted for `X-Forwarded-*` headers | | `ADK_HOST` / `ADK_PORT` | ADK server address (default `127.0.0.1:8001`) | | `ARTIFACT_SERVICE` | `local_folder`, `supabase`, or `s3` | +| `EMBEDDING_MODEL` | Embedding model for memory block semantic search (litellm format, default `gemini/gemini-embedding-001`) | | `RATE_LIMIT_ENABLED` | Enable per-user/agent/project budgets | | `OTEL_TRACING_ENABLED` | OpenTelemetry distributed tracing (ADK runtime) | | `LANGSMITH_TRACING` / `LANGSMITH_API_KEY` | LangSmith run tracing (langgraph runtime) | diff --git a/documents/DYNAMIC_MEMORY_INSTRUCTIONS.md b/documents/DYNAMIC_MEMORY_INSTRUCTIONS.md index 452389e..ac374d0 100644 --- a/documents/DYNAMIC_MEMORY_INSTRUCTIONS.md +++ b/documents/DYNAMIC_MEMORY_INSTRUCTIONS.md @@ -28,6 +28,9 @@ LAZY LOADING PROTOCOL: - IF the user asks for "visualization", "frontend data", or "smart object": - THEN call `list_shared_blocks(label="smart_object_output_format_json")`. - AND use that schema to format your response. +- IF you need information but do NOT know the exact block label: +- THEN call `search_shared_blocks(query="")` (semantic search by meaning). +- AND call `get_shared_block(block_id="