Skip to content

Commit c032f23

Browse files
authored
feat: add Qdrant vector storage integration (M9 Phase 1) (#63)
* feat: add Qdrant vector storage integration (M9 Phase 1) Implements vector storage and similarity search via Qdrant for M9: Semantic Memory. Core Changes: - New QdrantStore module with store/search/has_embedding methods - SQLite embeddings_metadata table with foreign key constraints - PRAGMA foreign_keys enabled via SqliteConnectOptions - Docker Compose Qdrant service on ports 6333/6334 Breaking Changes (pre-1.0): - SqliteStore::save_message() returns Result<i64> for message_id - QdrantStore::store() requires model parameter - ENV_KEYS replaces LLM_ENV_KEYS constant Tests: - 12 new tests (3 unit + 2 integration + 1 CASCADE DELETE + 3 config) - 98% coverage for new code - All checks pass: fmt, clippy, nextest Closes #60 * ci: allow thiserror 1.x/2.x duplicate for qdrant-client compat * chore: update Qdrant Docker image to v1.16.3 * feat(test): add testcontainers for Qdrant integration tests Add testcontainers-rs 0.27 for automated Qdrant container lifecycle in integration tests. Replace ignored tests with testcontainers-powered equivalents that run in CI. Changes: - Add testcontainers 0.27 workspace dependency (MIT/Apache-2.0, dev-only) - Create crates/zeph-memory/tests/qdrant_integration.rs with: - Custom GenericImage for qdrant/qdrant:v1.13.6 - setup_with_qdrant() helper with WaitFor::message_on_stdout - 3 tests: ensure_collection_is_idempotent, store_and_search_vector, search_with_conversation_filter - Remove 2 #[ignore] tests from qdrant.rs (moved to integration tests) - Add integration job to CI workflow (ubuntu-latest, Docker required) - Document testcontainers usage in CONTRIBUTING.md All 153 tests pass (6 skipped Ollama/Claude tests require external services). * fix: correct import order and formatting in qdrant_integration.rs * feat(test): add nextest profiles and fix CI test separation Add nextest configuration to properly separate unit tests from integration tests in CI. Fix macOS test failures by excluding testcontainers integration tests from the main test job. Changes: - Create .config/nextest.toml with default and ci profiles - Update CI test job to run only unit tests: --lib --bins (excludes tests/) - Update CI integration job to use --profile ci for explicit inclusion - Update CONTRIBUTING.md with nextest commands and test separation docs Test separation: - test job: Unit tests only (--lib --bins), runs on ubuntu + macos - integration job: Integration tests with testcontainers, ubuntu-latest only - coverage job: All tests for coverage metrics Fixes: macOS test failures due to Docker socket not available Error: SocketNotFoundError("/var/run/docker.sock") Solution: Integration tests now run only in dedicated integration job
1 parent a9d6385 commit c032f23

16 files changed

Lines changed: 1683 additions & 108 deletions

File tree

.config/nextest.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Nextest configuration for zeph project
2+
# https://nexte.st/book/configuration.html
3+
4+
[profile.default]
5+
# Default profile for local development
6+
# No default filter - run all unit tests
7+
# Integration tests are in separate test binaries (tests/*_integration.rs)
8+
# and can be run explicitly with --test flag or using the ci profile
9+
10+
[profile.ci]
11+
# CI profile that runs ALL tests including integration tests
12+
# Override with: cargo nextest run --profile ci
13+
default-filter = "all()"
14+
15+
[profile.ci.junit]
16+
# Store JUnit XML report for CI
17+
path = "target/nextest/ci/junit.xml"

.github/workflows/ci.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,27 @@ jobs:
7171
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7272
- uses: mozilla-actions/[email protected]
7373
- name: Run tests
74-
run: cargo nextest run --workspace
74+
run: cargo nextest run --workspace --lib --bins
75+
env:
76+
RUSTC_WRAPPER: sccache
77+
SCCACHE_GHA_ENABLED: "true"
78+
79+
integration:
80+
name: Integration Tests
81+
needs: [lint-fmt, lint-clippy]
82+
runs-on: ubuntu-latest
83+
timeout-minutes: 20
84+
steps:
85+
- uses: actions/checkout@v6
86+
- uses: moonrepo/setup-rust@v1
87+
with:
88+
bins: cargo-nextest
89+
cache-target: release
90+
env:
91+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92+
- uses: mozilla-actions/[email protected]
93+
- name: Run integration tests (testcontainers)
94+
run: cargo nextest run --workspace --profile ci --test '*integration*'
7595
env:
7696
RUSTC_WRAPPER: sccache
7797
SCCACHE_GHA_ENABLED: "true"

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88

99
### Added
1010

11+
#### M9 Phase 1: Qdrant Integration (Issue #60)
12+
- New `QdrantStore` module in zeph-memory for vector storage and similarity search
13+
- `QdrantStore::store()` persists embeddings to Qdrant and tracks metadata in SQLite
14+
- `QdrantStore::search()` performs cosine similarity search with filtering by conversation_id and role
15+
- `QdrantStore::has_embedding()` checks if message has associated embedding
16+
- `QdrantStore::ensure_collection()` idempotently creates Qdrant collection with 768-dimensional vectors
17+
- SQLite migration `002_embeddings_metadata.sql` for embedding metadata tracking
18+
- `embeddings_metadata` table with foreign key constraint to messages (ON DELETE CASCADE)
19+
- PRAGMA foreign_keys enabled in SqliteStore via SqliteConnectOptions
20+
- `SearchFilter` and `SearchResult` types for flexible query construction
21+
- `MemoryConfig.qdrant_url` field with `ZEPH_QDRANT_URL` environment variable override (default: http://localhost:6334)
22+
- Docker Compose Qdrant service (qdrant/qdrant:v1.13.6) on ports 6333/6334 with persistent storage
23+
- Integration tests for Qdrant operations (ignored by default, require running Qdrant instance)
24+
- Unit tests for SQLite metadata operations with 98% coverage
25+
- 12 new tests total (3 unit + 2 integration for QdrantStore, 1 CASCADE DELETE test for SqliteStore, 3 config tests)
26+
1127
#### M8: Embeddings support (Issue #54)
1228
- `LlmProvider` trait extended with `embed(&str) -> Result<Vec<f32>>` for generating text embeddings
1329
- `LlmProvider` trait extended with `supports_embeddings() -> bool` for capability detection
@@ -22,6 +38,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
2238
- Docker Compose configuration: added `ZEPH_LLM_EMBEDDING_MODEL` environment variable
2339

2440
### Changed
41+
42+
**BREAKING CHANGES** (pre-1.0.0):
43+
- `SqliteStore::save_message()` now returns `Result<i64>` instead of `Result<()>` to enable embedding workflow
44+
- `SqliteStore::new()` uses `sqlx::migrate!()` macro instead of INIT_SQL constant for proper migration management
45+
- `QdrantStore::store()` requires `model: &str` parameter for multi-model support
46+
- Config constant `LLM_ENV_KEYS` renamed to `ENV_KEYS` to reflect inclusion of non-LLM variables
47+
48+
**Migration:**
49+
```rust
50+
// Before:
51+
let _ = store.save_message(conv_id, "user", "hello").await?;
52+
53+
// After:
54+
let message_id = store.save_message(conv_id, "user", "hello").await?;
55+
```
56+
2557
- `OllamaProvider::new()` now accepts `embedding_model` parameter (breaking change, pre-v1.0)
2658
- Config schema: added `llm.embedding_model` field with serde default for backward compatibility
2759

CONTRIBUTING.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,36 @@ cargo build
2020
### Test
2121

2222
```bash
23-
cargo nextest run
23+
# Run unit tests only (exclude integration tests)
24+
cargo nextest run --workspace --lib --bins
25+
26+
# Run all tests including integration tests (requires Docker)
27+
cargo nextest run --workspace --profile ci
2428
```
2529

30+
**Nextest profiles** (`.config/nextest.toml`):
31+
- `default`: Runs all tests (unit + integration)
32+
- `ci`: CI environment, runs all tests with JUnit XML output for reporting
33+
34+
### Integration Tests
35+
36+
Integration tests use [testcontainers-rs](https://github.com/testcontainers/testcontainers-rs) to automatically spin up Docker containers for external services (Qdrant, etc.).
37+
38+
**Prerequisites:** Docker must be running on your machine.
39+
40+
```bash
41+
# Run only integration tests
42+
cargo nextest run --workspace --test '*integration*'
43+
44+
# Run unit tests only (skip integration tests)
45+
cargo nextest run --workspace --lib --bins
46+
47+
# Run all tests
48+
cargo nextest run --workspace
49+
```
50+
51+
Integration test files are located in each crate's `tests/` directory and follow the `*_integration.rs` naming convention.
52+
2653
### Lint
2754

2855
```bash
@@ -43,15 +70,15 @@ cargo llvm-cov --all-features --workspace
4370
| `zeph-core` | Agent loop, config, channel trait |
4471
| `zeph-llm` | LlmProvider trait, Ollama + Claude backends |
4572
| `zeph-skills` | SKILL.md parser, registry, prompt formatter |
46-
| `zeph-memory` | SQLite conversation persistence |
73+
| `zeph-memory` | SQLite conversation persistence, Qdrant vector search |
4774
| `zeph-channels` | Telegram adapter |
4875

4976
## Pull Requests
5077

5178
1. Create a feature branch: `feat/<scope>/<description>` or `fix/<scope>/<description>`
52-
2. Keep changes focused one logical change per PR
79+
2. Keep changes focused -- one logical change per PR
5380
3. Add tests for new functionality
54-
4. Ensure all checks pass: `cargo +nightly fmt`, `cargo clippy`, `cargo nextest run`
81+
4. Ensure all checks pass: `cargo +nightly fmt`, `cargo clippy`, `cargo nextest run --lib --bins`
5582
5. Write a clear PR description following the template
5683

5784
## Commit Messages
@@ -64,7 +91,7 @@ cargo llvm-cov --all-features --workspace
6491

6592
- Follow workspace clippy lints (pedantic enabled)
6693
- Use `cargo +nightly fmt` for formatting
67-
- Avoid unnecessary comments code should be self-explanatory
94+
- Avoid unnecessary comments -- code should be self-explanatory
6895
- Comments are only for cognitively complex blocks
6996

7097
## License

0 commit comments

Comments
 (0)