Semantic Search Guide¶
Master semantic search in Sekha to find conversations by meaning, not just keywords.
Overview¶
Semantic search uses AI embeddings to understand the meaning of your query, not just exact word matches.
Traditional keyword search:
Query: "database"
Finds: Conversations with word "database"
Misses: "PostgreSQL setup", "SQL optimization"
Semantic search:
Query: "database"
Finds: "PostgreSQL setup" (95% match)
"SQL query optimization" (87% match)
"database connection pooling" (92% match)
"NoSQL vs relational" (78% match)
How It Works¶
1. Embedding Generation¶
When you store a conversation, Sekha:
- Sends text to embedding model (Ollama + nomic-embed-text)
- Receives vector representation (768-dimensional)
- Stores vectors in ChromaDB
Example:
Text: "Implement OAuth2 authentication with JWT tokens"
↓
Embedding Model (nomic-embed-text)
↓
Vector: [0.023, -0.145, 0.891, ..., 0.234] # 768 dimensions
↓
Stored in ChromaDB collection
2. Query Processing¶
When you search:
- Query is converted to same 768-dimensional vector
- ChromaDB computes cosine similarity to all stored vectors
- Returns top N most similar conversations
Similarity scores: - 0.95-1.00 - Nearly identical meaning - 0.85-0.94 - Very relevant - 0.75-0.84 - Moderately relevant
- 0.65-0.74 - Somewhat relevant - <0.65 - Weakly related
API Usage¶
Basic Search¶
curl -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"query": "how to handle authentication",
"limit": 10
}'
Response:
{
"results": [
{
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"score": 0.92,
"content": "Implementing OAuth2 with JWT tokens...",
"label": "Authentication Implementation",
"folder": "/work/api-design",
"timestamp": "2026-01-20T14:30:00Z"
}
],
"total": 10,
"page": 1,
"page_size": 10
}
With Filters¶
curl -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-d '{
"query": "database optimization",
"limit": 5,
"filters": {
"folder": "/work/engineering",
"importance_min": 7
}
}'
Available filters: - folder - Path filter - label - Label filter
- importance_min - Minimum importance score - importance_max - Maximum importance score - date_from - Start date (ISO 8601) - date_to - End date (ISO 8601)
Improving Search Quality¶
1. Write Better Queries¶
✅ Good Queries:
✅ "How did I implement user authentication?"
✅ "Performance optimization for database queries"
✅ "React hooks usage patterns"
✅ "Kubernetes deployment configuration"
❌ Bad Queries:
❌ "auth" # Too short
❌ "stuff about databases" # Too vague
❌ "???" # Non-descriptive
❌ "meeting" # Too generic
Tips:
- Use natural language - "How to..." instead of single keywords
- Be specific - "PostgreSQL connection pooling" not "database"
- Include context - "React hooks in TypeScript" not "hooks"
- Add qualifiers - "production deployment" vs "local development"
2. Store Content Properly¶
High-quality input = better search:
# ✅ Good: Descriptive, contextual
client.conversations.create(
label="OAuth2 Implementation with JWT Tokens",
folder="/work/api-design/authentication",
messages=[
{
"role": "user",
"content": "How do I implement OAuth2 authentication using JWT tokens for API access?"
},
{
"role": "assistant",
"content": """To implement OAuth2 with JWT:
1. Set up authorization server
2. Generate JWT tokens on successful auth
3. Validate tokens on each request
4. Implement token refresh flow
[detailed implementation]
"""
}
]
)
# ❌ Bad: No context, vague
client.conversations.create(
label="Code",
folder="/work",
messages=[{"role": "user", "content": "some code"}]
)
3. Choose Right Embedding Model¶
Sekha defaults to nomic-embed-text (768 dimensions).
Model comparison:
| Model | Dimensions | Speed | Quality | Use Case |
|---|---|---|---|---|
| nomic-embed-text | 768 | Fast | High | Default - Recommended |
| all-MiniLM-L6-v2 | 384 | Fastest | Good | Low-resource environments |
| bge-large-en | 1024 | Slow | Highest | Maximum accuracy needed |
Changing model:
Pull new model:
Trade-offs: - Larger models = better quality but slower - Smaller models = faster but may miss nuance - Change affects ALL future embeddings
4. Tune Similarity Threshold¶
Filter by minimum score:
results = client.query(
query="authentication patterns",
limit=50
)
# Only show high-confidence matches
high_confidence = [
r for r in results
if r.score >= 0.80
]
Threshold recommendations:
- 0.90+ - Nearly exact matches only
- 0.80-0.89 - High relevance (recommended default)
- 0.70-0.79 - Moderate relevance
- 0.60-0.69 - Exploratory searches
- <0.60 - May include noise
Common Search Patterns¶
Time-Based Search¶
# Conversations from last month
from datetime import datetime, timedelta
one_month_ago = datetime.now() - timedelta(days=30)
results = client.query(
query="database optimization",
filters={
"date_from": one_month_ago.isoformat()
}
)
Project-Scoped Search¶
# Only search within specific project
results = client.query(
query="API endpoint design",
filters={
"folder": "/work/projects/alpha"
}
)
High-Priority Search¶
# Only important conversations
results = client.query(
query="architecture decisions",
filters={
"importance_min": 8
}
)
Multi-Topic Search¶
# Search for multiple related topics
topics = [
"database indexing",
"query performance",
"connection pooling"
]
all_results = []
for topic in topics:
results = client.query(topic, limit=5)
all_results.extend(results)
# Deduplicate by conversation_id
seen = set()
unique = []
for r in all_results:
if r.conversation_id not in seen:
seen.add(r.conversation_id)
unique.append(r)
Troubleshooting¶
Search Returns No Results¶
1. Check if conversations exist:
2. Verify embeddings are generated:
3. Test with exact label match:
4. Check Ollama is running:
Search Returns Irrelevant Results¶
Causes:
- Query too vague - Add more context
- Low-quality stored content - Improve labels and messages
- Wrong embedding model - Consider switching models
- Not enough data - Semantic search needs 10+ conversations minimum
Fix:
# Before: Vague query
results = client.query("code")
# After: Specific query with context
results = client.query(
"React functional component with useState hook",
filters={"folder": "/work/frontend"}
)
Slow Search Performance¶
Expected latency:
- First query: ~500ms (model loading)
- Subsequent queries: ~50-150ms
- Large collections (10k+): ~200-500ms
If slower:
1. Check Ollama performance:
2. Check ChromaDB performance:
# Check collection size
curl http://localhost:8000/api/v1/collections/sekha_memories/count
# Monitor resource usage
docker stats sekha-chroma
3. Optimize ChromaDB:
# Rebuild index (if collection is huge)
curl -X POST http://localhost:8000/api/v1/collections/sekha_memories/rebuild
Advanced Techniques¶
Hybrid Search (Semantic + Keyword)¶
# Semantic search
semantic_results = client.query(
query="authentication implementation",
limit=20
)
# Keyword filter on results
filtered = [
r for r in semantic_results
if "oauth" in r.content.lower() or "jwt" in r.content.lower()
]
Re-Ranking Results¶
# Get initial results
results = client.query(
query="database optimization",
limit=50
)
# Re-rank by importance and recency
from datetime import datetime
def score_result(r):
# Combine similarity, importance, and recency
age_days = (datetime.now() - r.timestamp).days
recency_score = max(0, 1 - (age_days / 365)) # Decay over 1 year
return (
r.score * 0.5 + # Semantic similarity: 50%
(r.importance / 10) * 0.3 + # Importance: 30%
recency_score * 0.2 # Recency: 20%
)
ranked = sorted(results, key=score_result, reverse=True)
Context Assembly¶
Use the context assembly endpoint for LLM-ready context:
curl -X POST http://localhost:8080/api/v1/context/assemble \
-H "Content-Type: application/json" \
-d '{
"query": "Continue our authentication discussion",
"preferred_labels": ["authentication", "security"],
"context_budget": 8000,
"excluded_folders": ["/personal"]
}'
Returns:
Messages array ready to insert into LLM prompt, optimized to fit within token budget.
Best Practices¶
Query Writing¶
- Use 5-15 words - Not too short, not too long
- Ask questions naturally - "How do I..." works well
- Include key entities - Names, technologies, project names
- Add context when needed - "production" vs "development"
- Iterate queries - Refine based on initial results
Content Storage¶
- Write clear labels - Descriptive, keyword-rich
- Add sufficient detail - More context = better embeddings
- Use consistent terminology - Helps clustering
- Include code + explanation - Not just raw code
- Store conversations, not snippets - Full context matters
System Tuning¶
- Monitor search quality - Track relevance of top results
- Adjust similarity threshold - Based on your use case
- Consider model upgrade - If accuracy is critical
- Prune low-quality content - Bad data hurts search
- Rebuild embeddings - If you change models
Metrics & Monitoring¶
Track Search Quality¶
import statistics
# Run test queries
test_queries = [
"authentication implementation",
"database optimization",
"API design patterns"
]
scores = []
for query in test_queries:
results = client.query(query, limit=10)
if results:
scores.append(results[0].score) # Top result score
avg_score = statistics.mean(scores)
print(f"Average top result score: {avg_score:.3f}")
if avg_score < 0.75:
print("WARNING: Search quality may be low")
Monitor Performance¶
import time
start = time.time()
results = client.query("test query")
latency = time.time() - start
print(f"Search latency: {latency*1000:.0f}ms")
if latency > 1.0:
print("WARNING: Search is slow")
Next Steps¶
- Organizing Memory - Structure your conversations
- REST API - Full API reference
- Configuration - Tune embedding settings
- Performance - Optimize search speed
Support¶
- Issues: GitHub Issues
- Discord: Join Community
- Documentation: docs.sekha.dev