Deleted 31 completed task files: - M0.x: 8 tasks (cargo, domain types, recordsource, tokenizer, adapters, gate) - M1.x: 8 tasks (llm-chat, standing-query, prompt template, parser, loop, log, e2e, gate) - M3.x: 4 tasks (l2-synthesis, rerank, mem-query, gate) - M3.5.x: 8 tasks (http-server, ingest, query, federation, skills, projects, rate-limiting, gate) - M3.6.1: DocCorpusSource (heading-boundary chunking) - M4.1-2: skill-draft, derived-filter Updated INDEX.md: - Removed M0 & M1 phase sections (archived in git history) - Updated progress table: 65 active tasks (42✅ + 2🟡 + 21⬜) - Updated status: M0/M1 complete, M3/M3.5 gates passing, M4.1-2 done - Noted M3.5.10 JWT auth implementation complete (awaiting image rollout) - Cleaned up broken links to deleted task files Total test count: 239 passing, 2 ignored (up from 196 at M3.4) Ready for M4.3 gate composition, M5 post-training, M7 source connectors.
330 lines
7.7 KiB
Markdown
330 lines
7.7 KiB
Markdown
# Implementation Notes: Query Optimization Engine
|
|
|
|
## Status
|
|
|
|
✅ **Design Phase COMPLETE**
|
|
- Query Optimizer (query_optimizer.rs) — READY
|
|
- Hybrid Query Worker (hybrid_query_worker.rs) — STUB (needs API integration)
|
|
- Design Documentation (QUERY_OPTIMIZATION_ENGINE.md) — COMPLETE
|
|
|
|
⚠️ **API Integration Notes** (for Phase 2)
|
|
|
|
---
|
|
|
|
## VectorStore API Corrections
|
|
|
|
### Current Methods (Confirmed)
|
|
|
|
The actual `VectorStore` has level-based search methods:
|
|
|
|
```rust
|
|
// NOT available:
|
|
vector_store.search(&embedding, project, limit, None)
|
|
vector_store.search_with_ids(&embedding, project, limit, chunk_ids)
|
|
|
|
// ACTUALLY available:
|
|
vector_store.search_l1(project, &embedding, limit)
|
|
vector_store.search_l2(project, &embedding, limit)
|
|
vector_store.search_l3(project, &embedding, limit)
|
|
```
|
|
|
|
### Updated Semantic Retrieval
|
|
|
|
```rust
|
|
async fn retrieve_semantic(
|
|
&self,
|
|
project: &str,
|
|
query_ctx: &QueryContext,
|
|
limit: i64,
|
|
) -> Result<Vec<(String, f32)>> {
|
|
let embedding = query_ctx
|
|
.embedding
|
|
.as_ref()
|
|
.ok_or_else(|| anyhow::anyhow!("no embedding"))?;
|
|
|
|
// Use L1 (most specific level)
|
|
let results = self.vector_store.search_l1(project, embedding, limit).await?;
|
|
|
|
// Convert to (id, score) tuples
|
|
let scored: Vec<(String, f32)> = results
|
|
.into_iter()
|
|
.map(|r| (r.id, r.score))
|
|
.collect();
|
|
|
|
Ok(scored)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## OpenSearchClient API Corrections
|
|
|
|
### Issue: Method Visibility
|
|
|
|
The `lexical_search` method in `opensearch_client.rs` is private:
|
|
|
|
```rust
|
|
// NOT public (private):
|
|
async fn lexical_search(...)
|
|
|
|
// NEEDED:
|
|
pub async fn lexical_search(...)
|
|
```
|
|
|
|
### Fix
|
|
|
|
Make the method public:
|
|
|
|
```rust
|
|
// In crates/mem-cli/src/opensearch_client.rs
|
|
pub async fn lexical_search(
|
|
&self,
|
|
query: &str,
|
|
limit: usize,
|
|
jwt_token: &str,
|
|
) -> Result<Vec<(String, f32, String, String, Vec<String>)>>
|
|
```
|
|
|
|
---
|
|
|
|
## Simplified Phase 2 Implementation
|
|
|
|
For Phase 2, instead of modifying http_server.rs extensively, create a wrapper:
|
|
|
|
```rust
|
|
/// In crates/mem-cli/src/http_server.rs
|
|
|
|
async fn query_handler(...) -> HttpResponse {
|
|
let (claims, token) = match validate_auth(&req, &state).await {
|
|
Ok(c) => c,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
// ... existing checks ...
|
|
|
|
// Try hybrid if available
|
|
#[cfg(feature = "hybrid_search")]
|
|
{
|
|
match state.hybrid_query_worker.query(
|
|
&project,
|
|
&question,
|
|
limit,
|
|
&token,
|
|
).await {
|
|
Ok(response) => return HttpResponse::Ok().json(response),
|
|
Err(e) => {
|
|
tracing::warn!("Hybrid query failed: {}, falling back", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: existing semantic search
|
|
match state.query_worker.query(&project, &question, Some(limit)).await {
|
|
Ok(results) => HttpResponse::Ok().json(json!({
|
|
"query": question,
|
|
"project": project,
|
|
"results": results,
|
|
"method": "semantic_only" // Indicate fallback
|
|
})),
|
|
Err(e) => {
|
|
tracing::error!("Query failed: {}", e);
|
|
HttpResponse::InternalServerError().json(json!({"error": "query_failed"}))
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Build Status
|
|
|
|
### Current Issues (Non-Blocking Design)
|
|
|
|
1. **hybrid_query_worker.rs** uses placeholder VectorStore API
|
|
- Fix: Use `search_l1()` instead of `search()`
|
|
- Status: TRIVIAL (rename methods)
|
|
|
|
2. **OpenSearchClient::lexical_search** is private
|
|
- Fix: Remove `async fn`, change to `pub async fn`
|
|
- Status: TRIVIAL (add `pub`)
|
|
|
|
3. **Embedding type mismatch** in line 89
|
|
- Fix: Use correct return type from embeddings crate
|
|
- Status: TRIVIAL (type annotation)
|
|
|
|
### Estimated Fix Time
|
|
|
|
**15-20 minutes** to update API calls and make methods public.
|
|
|
|
### No Design Changes Needed
|
|
|
|
All architectural decisions are sound:
|
|
- ✅ QueryOptimizer (6-stage pipeline) — No API dependency
|
|
- ✅ RRF Fusion algorithm — No API dependency
|
|
- ✅ Search strategy routing — No API dependency
|
|
- ⚠️ HybridQueryWorker — Needs VectorStore + OpenSearchClient API fixes
|
|
|
|
---
|
|
|
|
## Phase 2 Checklist (1 Week)
|
|
|
|
### Day 1-2: Fix Compilation
|
|
|
|
- [ ] Update `hybrid_query_worker.rs` to use actual VectorStore API
|
|
- [ ] Make `OpenSearchClient::lexical_search` public
|
|
- [ ] Fix type mismatches in embedding handling
|
|
- [ ] `cargo check` passes without errors
|
|
|
|
### Day 3-4: Integration
|
|
|
|
- [ ] Update `/memory/query` handler to attempt hybrid search
|
|
- [ ] Add fallback strategy (hybrid → semantic → error)
|
|
- [ ] Forward JWT token through query pipeline
|
|
- [ ] Update response format to include metrics + score breakdown
|
|
|
|
### Day 5: Testing
|
|
|
|
- [ ] Write 10+ integration tests
|
|
- [ ] Test fallback scenarios (OpenSearch unavailable)
|
|
- [ ] Measure latency (hybrid vs semantic)
|
|
- [ ] Verify score breakdown accuracy
|
|
|
|
### Day 6-7: Buffer + Deployment
|
|
|
|
- [ ] Performance profiling
|
|
- [ ] Documentation updates
|
|
- [ ] Deploy to staging
|
|
- [ ] Manual E2E testing
|
|
|
|
---
|
|
|
|
## Reference: Actual API Signatures
|
|
|
|
### VectorStore (from mem-store)
|
|
|
|
```rust
|
|
pub async fn search_l1(
|
|
&self,
|
|
project: &str,
|
|
embedding: &[f32],
|
|
limit: i64,
|
|
) -> Result<Vec<SearchResult>>;
|
|
|
|
pub struct SearchResult {
|
|
pub id: String,
|
|
pub score: f32,
|
|
pub item: ChunkWithMetadata,
|
|
}
|
|
|
|
pub struct ChunkWithMetadata {
|
|
pub id: String,
|
|
pub content: String,
|
|
pub source: String,
|
|
pub level: Option<String>,
|
|
pub breadcrumb: Option<Vec<String>>,
|
|
}
|
|
```
|
|
|
|
### EmbeddingsClient (from mem-llm)
|
|
|
|
```rust
|
|
pub async fn embed(&self, text: &str) -> Result<Vec<f32>>;
|
|
|
|
// Returns: 384-dim (all-MiniLM) or 1536-dim (OpenAI)
|
|
```
|
|
|
|
### OpenSearchClient (current, needs pub)
|
|
|
|
```rust
|
|
pub async fn lexical_search(
|
|
&self,
|
|
query: &str,
|
|
limit: usize,
|
|
jwt_token: &str,
|
|
) -> Result<Vec<(String, f32, String, String, Vec<String>)>>;
|
|
// (id, score, chunk, source, breadcrumb)
|
|
```
|
|
|
|
---
|
|
|
|
## Code Diff Preview (Phase 2)
|
|
|
|
### Fix 1: Make OpenSearch method public
|
|
|
|
```diff
|
|
- async fn lexical_search(
|
|
+ pub async fn lexical_search(
|
|
```
|
|
|
|
### Fix 2: Update HybridQueryWorker to use real API
|
|
|
|
```diff
|
|
- let results = self.vector_store.search(&embedding, project, limit, None).await?;
|
|
+ let results = self.vector_store.search_l1(project, embedding, limit).await?;
|
|
|
|
- let scored: Vec<(String, f32)> = results
|
|
- .into_iter()
|
|
- .map(|(id, score, _)| (id, score))
|
|
- .collect();
|
|
|
|
+ let scored: Vec<(String, f32)> = results
|
|
+ .into_iter()
|
|
+ .map(|r| (r.id, r.score))
|
|
+ .collect();
|
|
```
|
|
|
|
### Fix 3: Update http_server.rs to use HybridQueryWorker
|
|
|
|
```diff
|
|
- match state.query_worker.query(&project, &question, Some(limit)).await {
|
|
+ // Try hybrid first
|
|
+ match state.hybrid_query_worker.query(
|
|
+ &project,
|
|
+ &question,
|
|
+ limit,
|
|
+ &jwt_token,
|
|
+ ).await {
|
|
Ok(response) => return HttpResponse::Ok().json(response),
|
|
+ Err(_) => {
|
|
+ // Fallback to semantic
|
|
}
|
|
+ }
|
|
+
|
|
+ match state.query_worker.query(...).await {
|
|
```
|
|
|
|
---
|
|
|
|
## Design Validation
|
|
|
|
✅ **All design decisions validated:**
|
|
|
|
| Component | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| Query Optimizer (6-stage) | ✅ READY | No API dependency |
|
|
| RRF Algorithm | ✅ READY | No API dependency |
|
|
| Query Routing | ✅ READY | Pure logic |
|
|
| Cascading Strategy | ✅ READY | Uses existing APIs |
|
|
| Hybrid Strategy | ⚠️ STUB | Needs VectorStore API fix |
|
|
| Fallback Pattern | ✅ READY | Uses existing query_worker |
|
|
|
|
**None of these require architectural changes.**
|
|
|
|
---
|
|
|
|
## Next: Week 2 Task
|
|
|
|
**"Implement Phase 2: Hybrid Integration"**
|
|
|
|
**Time estimate:** 3-4 days (15-20 min for compilation fixes + 2-3 days for integration + testing)
|
|
|
|
**Deliverables:**
|
|
1. ✅ `/memory/query` now attempts hybrid search
|
|
2. ✅ Score breakdown + metrics in response
|
|
3. ✅ Fallback to semantic if OpenSearch unavailable
|
|
4. ✅ 10+ integration tests
|
|
5. ✅ Latency measurements (hybrid vs semantic)
|
|
|
|
---
|
|
|