Skip to content

Commit 9be6751

Browse files
authored
Merge pull request #79 from redis/fix/index-already-exists-error
Remove deprecated ensure_search_index_exists function
2 parents 36da6e2 + 0d9ef34 commit 9be6751

File tree

8 files changed

+129
-133
lines changed

8 files changed

+129
-133
lines changed

agent_memory_server/cli.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
migrate_add_memory_hashes_1,
2222
migrate_add_memory_type_3,
2323
)
24-
from agent_memory_server.utils.redis import ensure_search_index_exists, get_redis_conn
24+
from agent_memory_server.utils.redis import get_redis_conn
2525

2626

2727
logger = get_logger(__name__)
@@ -46,11 +46,26 @@ def rebuild_index():
4646
"""Rebuild the search index."""
4747
import asyncio
4848

49+
from agent_memory_server.vectorstore_adapter import RedisVectorStoreAdapter
50+
from agent_memory_server.vectorstore_factory import get_vectorstore_adapter
51+
4952
configure_logging()
5053

5154
async def setup_and_run():
52-
redis = await get_redis_conn()
53-
await ensure_search_index_exists(redis, overwrite=True)
55+
# Get the vectorstore adapter
56+
adapter = await get_vectorstore_adapter()
57+
58+
# Only Redis adapter supports index rebuilding
59+
if isinstance(adapter, RedisVectorStoreAdapter):
60+
index = adapter.vectorstore.index
61+
logger.info(f"Dropping and recreating index '{index.name}'")
62+
index.create(overwrite=True)
63+
logger.info("Index rebuilt successfully")
64+
else:
65+
logger.error(
66+
"Index rebuilding is only supported for Redis vectorstore. "
67+
"Current vectorstore does not support this operation."
68+
)
5469

5570
asyncio.run(setup_and_run())
5671

@@ -200,8 +215,7 @@ def schedule_task(task_path: str, args: list[str]):
200215
sys.exit(1)
201216

202217
async def setup_and_run_task():
203-
redis = await get_redis_conn()
204-
await ensure_search_index_exists(redis)
218+
await get_redis_conn()
205219

206220
# Import the task function
207221
module_path, function_name = task_path.rsplit(".", 1)
@@ -269,14 +283,8 @@ async def _ensure_stream_and_group():
269283
raise
270284

271285
async def _run_worker():
272-
# Ensure Redis stream/consumer group and search index exist before starting worker
273286
await _ensure_stream_and_group()
274-
try:
275-
redis = await get_redis_conn()
276-
# Don't overwrite if an index already exists; just ensure it's present
277-
await ensure_search_index_exists(redis, overwrite=False)
278-
except Exception as e:
279-
logger.warning(f"Failed to ensure search index exists: {e}")
287+
await get_redis_conn()
280288
await Worker.run(
281289
docket_name=settings.docket_name,
282290
url=settings.redis_url,

agent_memory_server/long_term_memory.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,7 @@
4949
rerank_with_recency,
5050
update_memory_hash_if_text_changed,
5151
)
52-
from agent_memory_server.utils.redis import (
53-
ensure_search_index_exists,
54-
get_redis_conn,
55-
)
52+
from agent_memory_server.utils.redis import get_redis_conn
5653
from agent_memory_server.vectorstore_factory import get_vectorstore_adapter
5754

5855

@@ -614,19 +611,6 @@ async def compact_long_term_memories(
614611
index_name = Keys.search_index_name()
615612
logger.info(f"Using index '{index_name}' for semantic duplicate compaction.")
616613

617-
# Check if the index exists before proceeding
618-
try:
619-
await redis_client.execute_command(f"FT.INFO {index_name}")
620-
except Exception as info_e:
621-
if "unknown index name" in str(info_e).lower():
622-
logger.info(f"Search index {index_name} doesn't exist, creating it")
623-
# Ensure 'get_search_index' is called with the correct name to create it if needed
624-
await ensure_search_index_exists(redis_client, index_name=index_name)
625-
else:
626-
logger.warning(
627-
f"Error checking index '{index_name}': {info_e} - attempting to proceed."
628-
)
629-
630614
# Get all memories using the vector store adapter
631615
try:
632616
# Convert filters to adapter format

agent_memory_server/main.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from agent_memory_server.logging import get_logger
1515
from agent_memory_server.utils.redis import (
1616
_redis_pool as connection_pool,
17-
ensure_search_index_exists,
1817
get_redis_conn,
1918
)
2019

@@ -75,29 +74,9 @@ async def lifespan(app: FastAPI):
7574
"Long-term memory requires OpenAI for embeddings, but OpenAI API key is not set"
7675
)
7776

78-
# Set up RediSearch index if long-term memory is enabled
77+
# Set up Redis connection if long-term memory is enabled
7978
if settings.long_term_memory:
80-
redis = await get_redis_conn()
81-
82-
# Get embedding dimensions from model config
83-
embedding_model_config = MODEL_CONFIGS.get(settings.embedding_model)
84-
vector_dimensions = (
85-
str(embedding_model_config.embedding_dimensions)
86-
if embedding_model_config
87-
else "1536"
88-
)
89-
distance_metric = "COSINE"
90-
91-
try:
92-
await ensure_search_index_exists(
93-
redis,
94-
index_name=settings.redisvl_index_name,
95-
vector_dimensions=vector_dimensions,
96-
distance_metric=distance_metric,
97-
)
98-
except Exception as e:
99-
logger.error(f"Failed to ensure RediSearch index: {e}")
100-
raise
79+
await get_redis_conn()
10180

10281
# Initialize Docket for background tasks if enabled
10382
if settings.use_docket:

agent_memory_server/mcp.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,10 @@ async def call_tool(self, name, arguments):
158158
return await super().call_tool(name, arguments)
159159

160160
async def run_sse_async(self):
161-
"""Ensure Redis search index exists before starting SSE server."""
162-
from agent_memory_server.utils.redis import (
163-
ensure_search_index_exists,
164-
get_redis_conn,
165-
)
161+
"""Start SSE server."""
162+
from agent_memory_server.utils.redis import get_redis_conn
166163

167-
redis = await get_redis_conn()
168-
await ensure_search_index_exists(redis)
164+
await get_redis_conn()
169165

170166
# Run the SSE server using our custom implementation
171167
import uvicorn
@@ -176,14 +172,10 @@ async def run_sse_async(self):
176172
).serve()
177173

178174
async def run_stdio_async(self):
179-
"""Ensure Redis search index exists before starting STDIO MCP server."""
180-
from agent_memory_server.utils.redis import (
181-
ensure_search_index_exists,
182-
get_redis_conn,
183-
)
175+
"""Start STDIO MCP server."""
176+
from agent_memory_server.utils.redis import get_redis_conn
184177

185-
redis = await get_redis_conn()
186-
await ensure_search_index_exists(redis)
178+
await get_redis_conn()
187179
return await super().run_stdio_async()
188180

189181

agent_memory_server/utils/redis.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@
44
from typing import Any
55

66
from redis.asyncio import Redis
7-
from redisvl.index import AsyncSearchIndex
87

98
from agent_memory_server.config import settings
10-
from agent_memory_server.vectorstore_adapter import RedisVectorStoreAdapter
11-
from agent_memory_server.vectorstore_factory import get_vectorstore_adapter
129

1310

1411
logger = logging.getLogger(__name__)
1512
_redis_pool: Redis | None = None
16-
_index: AsyncSearchIndex | None = None
1713

1814

1915
async def get_redis_conn(url: str = settings.redis_url, **kwargs) -> Redis:
@@ -35,39 +31,6 @@ async def get_redis_conn(url: str = settings.redis_url, **kwargs) -> Redis:
3531
return _redis_pool
3632

3733

38-
async def ensure_search_index_exists(
39-
redis: Redis,
40-
index_name: str = settings.redisvl_index_name,
41-
vector_dimensions: str = settings.redisvl_vector_dimensions,
42-
distance_metric: str = settings.redisvl_distance_metric,
43-
overwrite: bool = True,
44-
) -> None:
45-
"""
46-
Ensure that the async search index exists, create it if it doesn't.
47-
This function is deprecated and only exists for compatibility.
48-
The VectorStore adapter now handles index creation automatically.
49-
50-
Args:
51-
redis: A Redis client instance
52-
vector_dimensions: Dimensions of the embedding vectors
53-
distance_metric: Distance metric to use (default: COSINE)
54-
index_name: The name of the index
55-
"""
56-
# If this is Redis, creating the adapter will create the index.
57-
adapter = await get_vectorstore_adapter()
58-
59-
if overwrite:
60-
if isinstance(adapter, RedisVectorStoreAdapter):
61-
index = adapter.vectorstore.index
62-
if index is not None:
63-
index.create(overwrite=True)
64-
else:
65-
logger.warning(
66-
"Overwriting the search index is only supported for RedisVectorStoreAdapter. "
67-
"Consult your vector store's documentation to learn how to recreate the index."
68-
)
69-
70-
7134
def safe_get(doc: Any, key: str, default: Any | None = None) -> Any:
7235
"""Get a value from a Document, returning a default if the key is not present.
7336

docker-compose-task-workers.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
services:
2+
# For testing a production-like setup, you can run this API and the
3+
# task-worker container. This API container does NOT use --no-worker, so when
4+
# it starts background work, the task-worker will process those tasks.
5+
api:
6+
build:
7+
context: .
8+
dockerfile: Dockerfile
9+
ports:
10+
- "8000:8000"
11+
environment:
12+
- REDIS_URL=redis://redis:6379
13+
- PORT=8000
14+
# Add your API keys here or use a .env file
15+
- OPENAI_API_KEY=${OPENAI_API_KEY}
16+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
17+
# Optional configurations with defaults
18+
- LONG_TERM_MEMORY=True
19+
- GENERATION_MODEL=gpt-4o-mini
20+
- EMBEDDING_MODEL=text-embedding-3-small
21+
- ENABLE_TOPIC_EXTRACTION=True
22+
- ENABLE_NER=True
23+
depends_on:
24+
- redis
25+
volumes:
26+
- ./agent_memory_server:/app/agent_memory_server
27+
healthcheck:
28+
test: [ "CMD", "curl", "-f", "http://localhost:8000/v1/health" ]
29+
interval: 30s
30+
timeout: 10s
31+
retries: 3
32+
command: ["agent-memory", "api", "--host", "0.0.0.0", "--port", "8000"]
33+
34+
35+
mcp:
36+
build:
37+
context: .
38+
dockerfile: Dockerfile
39+
environment:
40+
- REDIS_URL=redis://redis:6379
41+
- PORT=9050
42+
# Add your API keys here or use a .env file
43+
- OPENAI_API_KEY=${OPENAI_API_KEY}
44+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
45+
ports:
46+
- "9050:9000"
47+
depends_on:
48+
- redis
49+
command: ["agent-memory", "mcp", "--mode", "sse"]
50+
51+
task-worker:
52+
build:
53+
context: .
54+
dockerfile: Dockerfile
55+
environment:
56+
- REDIS_URL=redis://redis:6379
57+
# Add your API keys here or use a .env file
58+
- OPENAI_API_KEY=${OPENAI_API_KEY}
59+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
60+
# Optional configurations with defaults
61+
depends_on:
62+
- redis
63+
command: ["agent-memory", "task-worker"]
64+
volumes:
65+
- ./agent_memory_server:/app/agent_memory_server
66+
restart: unless-stopped
67+
68+
redis:
69+
image: redis:8
70+
ports:
71+
- "16380:6379" # Redis port
72+
volumes:
73+
- redis_data:/data
74+
command: redis-server --save "30 1" --loglevel warning --appendonly no --stop-writes-on-bgsave-error no
75+
healthcheck:
76+
test: [ "CMD", "redis-cli", "ping" ]
77+
interval: 30s
78+
timeout: 10s
79+
retries: 3
80+
81+
volumes:
82+
redis_data:

tests/test_cli.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,30 @@ def test_version_command(self):
3636
class TestRebuildIndex:
3737
"""Tests for the rebuild_index command."""
3838

39-
@patch("agent_memory_server.cli.ensure_search_index_exists")
40-
@patch("agent_memory_server.cli.get_redis_conn")
41-
def test_rebuild_index_command(self, mock_get_redis_conn, mock_ensure_index):
39+
@patch("agent_memory_server.vectorstore_factory.get_vectorstore_adapter")
40+
def test_rebuild_index_command(self, mock_get_adapter):
4241
"""Test rebuild_index command execution."""
43-
# Use AsyncMock which returns completed awaitables
44-
mock_redis = Mock()
45-
mock_get_redis_conn.return_value = mock_redis
46-
mock_ensure_index.return_value = None
42+
from agent_memory_server.vectorstore_adapter import RedisVectorStoreAdapter
43+
44+
# Create a mock adapter with a mock index
45+
mock_index = Mock()
46+
mock_index.name = "test_index"
47+
mock_index.create = Mock()
48+
49+
mock_vectorstore = Mock()
50+
mock_vectorstore.index = mock_index
51+
52+
mock_adapter = Mock(spec=RedisVectorStoreAdapter)
53+
mock_adapter.vectorstore = mock_vectorstore
54+
55+
mock_get_adapter.return_value = mock_adapter
4756

4857
runner = CliRunner()
4958
result = runner.invoke(rebuild_index)
5059

5160
assert result.exit_code == 0
52-
mock_get_redis_conn.assert_called_once()
53-
mock_ensure_index.assert_called_once_with(mock_redis, overwrite=True)
61+
mock_get_adapter.assert_called_once()
62+
mock_index.create.assert_called_once_with(overwrite=True)
5463

5564

5665
class TestMigrateMemories:
@@ -440,7 +449,6 @@ def test_schedule_task_argument_parsing(self):
440449
class TestTaskWorker:
441450
"""Tests for the task_worker command."""
442451

443-
@patch("agent_memory_server.cli.ensure_search_index_exists")
444452
@patch("agent_memory_server.cli.get_redis_conn")
445453
@patch("docket.Worker.run")
446454
@patch("agent_memory_server.cli.settings")
@@ -449,7 +457,6 @@ def test_task_worker_success(
449457
mock_settings,
450458
mock_worker_run,
451459
mock_get_redis_conn,
452-
mock_ensure_index,
453460
redis_url,
454461
):
455462
"""Test successful task worker start."""
@@ -460,7 +467,6 @@ def test_task_worker_success(
460467
mock_worker_run.return_value = None
461468
mock_redis = AsyncMock()
462469
mock_get_redis_conn.return_value = mock_redis
463-
mock_ensure_index.return_value = None
464470

465471
runner = CliRunner()
466472
result = runner.invoke(
@@ -481,7 +487,6 @@ def test_task_worker_docket_disabled(self, mock_settings):
481487
assert result.exit_code == 1
482488
assert "Docket is disabled in settings" in result.output
483489

484-
@patch("agent_memory_server.cli.ensure_search_index_exists")
485490
@patch("agent_memory_server.cli.get_redis_conn")
486491
@patch("docket.Worker.run")
487492
@patch("agent_memory_server.cli.settings")
@@ -490,7 +495,6 @@ def test_task_worker_default_params(
490495
mock_settings,
491496
mock_worker_run,
492497
mock_get_redis_conn,
493-
mock_ensure_index,
494498
redis_url,
495499
):
496500
"""Test task worker with default parameters."""
@@ -501,7 +505,6 @@ def test_task_worker_default_params(
501505
mock_worker_run.return_value = None
502506
mock_redis = AsyncMock()
503507
mock_get_redis_conn.return_value = mock_redis
504-
mock_ensure_index.return_value = None
505508

506509
runner = CliRunner()
507510
result = runner.invoke(task_worker)

0 commit comments

Comments
 (0)