@sylix/coworker 2.0.11 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/commands/slash/config.d.ts.map +1 -1
  2. package/dist/commands/slash/config.js +22 -4
  3. package/dist/commands/slash/config.js.map +1 -1
  4. package/dist/core/CoWorkerAgent.d.ts.map +1 -1
  5. package/dist/core/CoWorkerAgent.js +6 -3
  6. package/dist/core/CoWorkerAgent.js.map +1 -1
  7. package/dist/skills/defaults/accessibility/screen-reader-testing.md +545 -0
  8. package/dist/skills/defaults/accessibility/wcag-audit-patterns.md +555 -0
  9. package/dist/skills/defaults/ai-ml/rag.md +276 -0
  10. package/dist/skills/defaults/backend-development/api-design-principles.md +528 -0
  11. package/dist/skills/defaults/backend-development/api-design.md +285 -0
  12. package/dist/skills/defaults/backend-development/architecture-patterns.md +494 -0
  13. package/dist/skills/defaults/backend-development/async-python.md +237 -0
  14. package/dist/skills/defaults/backend-development/auth-implementation-patterns.md +638 -0
  15. package/dist/skills/defaults/backend-development/bazel-build-optimization.md +387 -0
  16. package/dist/skills/defaults/backend-development/billing-automation/SKILL.md +566 -0
  17. package/dist/skills/defaults/backend-development/code-review-excellence.md +538 -0
  18. package/dist/skills/defaults/backend-development/cqrs-implementation.md +554 -0
  19. package/dist/skills/defaults/backend-development/database-design.md +305 -0
  20. package/dist/skills/defaults/backend-development/debugging-strategies.md +536 -0
  21. package/dist/skills/defaults/backend-development/e2e-testing-patterns.md +544 -0
  22. package/dist/skills/defaults/backend-development/error-handling-patterns.md +641 -0
  23. package/dist/skills/defaults/backend-development/fastapi-templates.md +559 -0
  24. package/dist/skills/defaults/backend-development/fastapi.md +309 -0
  25. package/dist/skills/defaults/backend-development/git-advanced-workflows.md +405 -0
  26. package/dist/skills/defaults/backend-development/microservices-patterns.md +595 -0
  27. package/dist/skills/defaults/backend-development/microservices.md +284 -0
  28. package/dist/skills/defaults/backend-development/monorepo-management.md +623 -0
  29. package/dist/skills/defaults/backend-development/nodejs-backend-patterns.md +1048 -0
  30. package/dist/skills/defaults/backend-development/nx-workspace-patterns.md +457 -0
  31. package/dist/skills/defaults/backend-development/paypal-integration/SKILL.md +478 -0
  32. package/dist/skills/defaults/backend-development/pci-compliance/SKILL.md +480 -0
  33. package/dist/skills/defaults/backend-development/python-anti-patterns.md +349 -0
  34. package/dist/skills/defaults/backend-development/python-background-jobs.md +364 -0
  35. package/dist/skills/defaults/backend-development/python-code-style.md +360 -0
  36. package/dist/skills/defaults/backend-development/python-configuration.md +368 -0
  37. package/dist/skills/defaults/backend-development/python-design-patterns.md +296 -0
  38. package/dist/skills/defaults/backend-development/python-error-handling.md +323 -0
  39. package/dist/skills/defaults/backend-development/python-packaging.md +887 -0
  40. package/dist/skills/defaults/backend-development/python-performance-optimization.md +874 -0
  41. package/dist/skills/defaults/backend-development/python-project-structure.md +252 -0
  42. package/dist/skills/defaults/backend-development/python-resilience.md +376 -0
  43. package/dist/skills/defaults/backend-development/python-resource-management.md +421 -0
  44. package/dist/skills/defaults/backend-development/python-type-safety.md +428 -0
  45. package/dist/skills/defaults/backend-development/sql-optimization-patterns.md +509 -0
  46. package/dist/skills/defaults/backend-development/stripe-integration/SKILL.md +522 -0
  47. package/dist/skills/defaults/backend-development/turborepo-caching.md +376 -0
  48. package/dist/skills/defaults/blockchain/defi-protocol-templates.md +430 -0
  49. package/dist/skills/defaults/blockchain/nft-standards.md +364 -0
  50. package/dist/skills/defaults/blockchain/solidity-security.md +514 -0
  51. package/dist/skills/defaults/blockchain/web3-testing.md +360 -0
  52. package/dist/skills/defaults/business/competitive-landscape/SKILL.md +527 -0
  53. package/dist/skills/defaults/business/market-sizing-analysis/SKILL.md +451 -0
  54. package/dist/skills/defaults/business/startup-financial-modeling/SKILL.md +494 -0
  55. package/dist/skills/defaults/business/startup-metrics-framework/SKILL.md +564 -0
  56. package/dist/skills/defaults/business/team-composition-analysis.md +437 -0
  57. package/dist/skills/defaults/compliance/employment-contract-templates/SKILL.md +527 -0
  58. package/dist/skills/defaults/compliance/gdpr-data-handling/SKILL.md +630 -0
  59. package/dist/skills/defaults/data-engineering/airflow-dag-patterns.md +436 -0
  60. package/dist/skills/defaults/data-engineering/airflow.md +519 -0
  61. package/dist/skills/defaults/data-engineering/data-quality.md +583 -0
  62. package/dist/skills/defaults/data-engineering/dbt-transformation-patterns.md +482 -0
  63. package/dist/skills/defaults/data-engineering/dbt.md +556 -0
  64. package/dist/skills/defaults/data-engineering/ml-pipeline-workflow/SKILL.md +247 -0
  65. package/dist/skills/defaults/data-engineering/spark-optimization.md +348 -0
  66. package/dist/skills/defaults/data-engineering/spark.md +411 -0
  67. package/dist/skills/defaults/database/postgresql.md +202 -0
  68. package/dist/skills/defaults/debugging/systematic-debugging.md +249 -0
  69. package/dist/skills/defaults/devops/architecture-decision-records.md +448 -0
  70. package/dist/skills/defaults/devops/changelog-automation.md +580 -0
  71. package/dist/skills/defaults/devops/cicd.md +314 -0
  72. package/dist/skills/defaults/devops/cloud.md +263 -0
  73. package/dist/skills/defaults/devops/code-review-excellence.md +299 -0
  74. package/dist/skills/defaults/devops/cost-optimization.md +295 -0
  75. package/dist/skills/defaults/devops/deployment-pipeline-design.md +356 -0
  76. package/dist/skills/defaults/devops/docker.md +281 -0
  77. package/dist/skills/defaults/devops/git-workflows.md +205 -0
  78. package/dist/skills/defaults/devops/github-actions.md +311 -0
  79. package/dist/skills/defaults/devops/gitlab-ci-patterns.md +266 -0
  80. package/dist/skills/defaults/devops/hybrid-cloud-networking.md +241 -0
  81. package/dist/skills/defaults/devops/istio-traffic-management.md +327 -0
  82. package/dist/skills/defaults/devops/kubernetes.md +339 -0
  83. package/dist/skills/defaults/devops/linkerd-patterns.md +311 -0
  84. package/dist/skills/defaults/devops/multi-cloud-architecture.md +181 -0
  85. package/dist/skills/defaults/devops/observability.md +243 -0
  86. package/dist/skills/defaults/devops/openapi-spec-generation.md +1024 -0
  87. package/dist/skills/defaults/devops/postmortem-writing.md +396 -0
  88. package/dist/skills/defaults/devops/prometheus-configuration.md +265 -0
  89. package/dist/skills/defaults/devops/secrets-management.md +341 -0
  90. package/dist/skills/defaults/devops/service-mesh-observability.md +385 -0
  91. package/dist/skills/defaults/devops/terraform-module-library.md +244 -0
  92. package/dist/skills/defaults/finance/backtesting-frameworks/SKILL.md +663 -0
  93. package/dist/skills/defaults/finance/risk-metrics-calculation/SKILL.md +557 -0
  94. package/dist/skills/defaults/frontend/accessibility-compliance.md +420 -0
  95. package/dist/skills/defaults/frontend/design-system-patterns.md +337 -0
  96. package/dist/skills/defaults/frontend/interaction-design.md +327 -0
  97. package/dist/skills/defaults/frontend/javascript.md +311 -0
  98. package/dist/skills/defaults/frontend/modern-javascript-patterns.md +927 -0
  99. package/dist/skills/defaults/frontend/react-native-design.md +440 -0
  100. package/dist/skills/defaults/frontend/react.md +345 -0
  101. package/dist/skills/defaults/frontend/responsive-design.md +472 -0
  102. package/dist/skills/defaults/frontend/tailwind-design-system.md +337 -0
  103. package/dist/skills/defaults/frontend/typescript-advanced-types.md +724 -0
  104. package/dist/skills/defaults/frontend/typescript.md +334 -0
  105. package/dist/skills/defaults/frontend/visual-design-foundations.md +326 -0
  106. package/dist/skills/defaults/frontend/web-component-design.md +279 -0
  107. package/dist/skills/defaults/game-development/godot-gdscript-patterns.md +188 -0
  108. package/dist/skills/defaults/game-development/unity-ecs-patterns.md +594 -0
  109. package/dist/skills/defaults/kubernetes/gitops-workflow.md +285 -0
  110. package/dist/skills/defaults/kubernetes/gitops.md +280 -0
  111. package/dist/skills/defaults/kubernetes/helm-chart-scaffolding.md +553 -0
  112. package/dist/skills/defaults/kubernetes/helm.md +343 -0
  113. package/dist/skills/defaults/kubernetes/k8s-manifest-generator.md +501 -0
  114. package/dist/skills/defaults/kubernetes/k8s-security-policies.md +342 -0
  115. package/dist/skills/defaults/kubernetes/manifests.md +330 -0
  116. package/dist/skills/defaults/kubernetes/security.md +337 -0
  117. package/dist/skills/defaults/llm-application/embedding-strategies.md +608 -0
  118. package/dist/skills/defaults/llm-application/hybrid-search-implementation.md +570 -0
  119. package/dist/skills/defaults/llm-application/hybrid-search.md +570 -0
  120. package/dist/skills/defaults/llm-application/langchain-architecture.md +666 -0
  121. package/dist/skills/defaults/llm-application/langchain.md +259 -0
  122. package/dist/skills/defaults/llm-application/llm-evaluation.md +695 -0
  123. package/dist/skills/defaults/llm-application/prompt-engineering-patterns.md +449 -0
  124. package/dist/skills/defaults/llm-application/prompt-engineering.md +219 -0
  125. package/dist/skills/defaults/llm-application/rag-implementation.md +434 -0
  126. package/dist/skills/defaults/llm-application/similarity-search-patterns.md +560 -0
  127. package/dist/skills/defaults/llm-application/similarity-search.md +560 -0
  128. package/dist/skills/defaults/llm-application/vector-index-tuning.md +523 -0
  129. package/dist/skills/defaults/mobile/mobile-android-design.md +440 -0
  130. package/dist/skills/defaults/mobile/mobile-ios-design.md +266 -0
  131. package/dist/skills/defaults/monitoring/distributed-tracing.md +436 -0
  132. package/dist/skills/defaults/monitoring/grafana-dashboards.md +370 -0
  133. package/dist/skills/defaults/monitoring/prometheus-configuration.md +379 -0
  134. package/dist/skills/defaults/monitoring/slo-implementation.md +323 -0
  135. package/dist/skills/defaults/refactoring/code-refactoring.md +349 -0
  136. package/dist/skills/defaults/security/anti-reversing-techniques/SKILL.md +559 -0
  137. package/dist/skills/defaults/security/auditor.md +168 -0
  138. package/dist/skills/defaults/security/binary-analysis-patterns/SKILL.md +438 -0
  139. package/dist/skills/defaults/security/memory-forensics/SKILL.md +483 -0
  140. package/dist/skills/defaults/security/mtls-configuration.md +349 -0
  141. package/dist/skills/defaults/security/protocol-reverse-engineering/SKILL.md +520 -0
  142. package/dist/skills/defaults/security/sast-configuration.md +182 -0
  143. package/dist/skills/defaults/security/security.md +313 -0
  144. package/dist/skills/defaults/security/stride-analysis.md +273 -0
  145. package/dist/skills/defaults/security/threat-mitigation-mapping.md +290 -0
  146. package/dist/skills/defaults/systems/bash-defensive-patterns/SKILL.md +539 -0
  147. package/dist/skills/defaults/systems/bats-testing-patterns/SKILL.md +631 -0
  148. package/dist/skills/defaults/systems/go-concurrency-patterns.md +657 -0
  149. package/dist/skills/defaults/systems/memory-safety-patterns.md +605 -0
  150. package/dist/skills/defaults/systems/rust-async-patterns.md +519 -0
  151. package/dist/skills/defaults/systems/shellcheck-configuration/SKILL.md +456 -0
  152. package/dist/skills/defaults/team-collaboration/multi-reviewer-patterns.md +126 -0
  153. package/dist/skills/defaults/team-collaboration/parallel-feature-development.md +151 -0
  154. package/dist/skills/defaults/testing/javascript-testing-patterns.md +1021 -0
  155. package/dist/skills/defaults/testing/python-testing-patterns.md +351 -0
  156. package/dist/skills/defaults/testing/testing.md +332 -0
  157. package/dist/skills/defaults/workflows/context-driven-development.md +384 -0
  158. package/dist/skills/defaults/workflows/track-management.md +592 -0
  159. package/dist/skills/defaults/workflows/workflow-patterns.md +622 -0
  160. package/dist/skills/index.d.ts +11 -0
  161. package/dist/skills/index.d.ts.map +1 -0
  162. package/dist/skills/index.js +129 -0
  163. package/dist/skills/index.js.map +1 -0
  164. package/dist/utils/character.js +4 -4
  165. package/dist/utils/character.js.map +1 -1
  166. package/dist/utils/inputbar.d.ts.map +1 -1
  167. package/dist/utils/inputbar.js +7 -0
  168. package/dist/utils/inputbar.js.map +1 -1
  169. package/package.json +1 -1
@@ -0,0 +1,570 @@
1
+ ---
2
+ name: hybrid-search-implementation
3
+ description: Combine vector and keyword search for improved retrieval. Use when implementing RAG systems, building search engines, or when neither approach alone provides sufficient recall.
4
+ ---
5
+
6
+ # Hybrid Search Implementation
7
+
8
+ Patterns for combining vector similarity and keyword-based search.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Building RAG systems with improved recall
13
+ - Combining semantic understanding with exact matching
14
+ - Handling queries with specific terms (names, codes)
15
+ - Improving search for domain-specific vocabulary
16
+ - When pure vector search misses keyword matches
17
+
18
+ ## Core Concepts
19
+
20
+ ### 1. Hybrid Search Architecture
21
+
22
+ ```
23
+ Query → ┬─► Vector Search ──► Candidates ─┐
24
+ │ │
25
+ └─► Keyword Search ─► Candidates ─┴─► Fusion ─► Results
26
+ ```
27
+
28
+ ### 2. Fusion Methods
29
+
30
+ | Method | Description | Best For |
31
+ | ----------------- | ------------------------ | --------------- |
32
+ | **RRF** | Reciprocal Rank Fusion | General purpose |
33
+ | **Linear** | Weighted sum of scores | Tunable balance |
34
+ | **Cross-encoder** | Rerank with neural model | Highest quality |
35
+ | **Cascade** | Filter then rerank | Efficiency |
36
+
37
+ ## Templates
38
+
39
+ ### Template 1: Reciprocal Rank Fusion
40
+
41
+ ```python
42
+ from typing import List, Dict, Tuple
43
+ from collections import defaultdict
44
+
45
+ def reciprocal_rank_fusion(
46
+ result_lists: List[List[Tuple[str, float]]],
47
+ k: int = 60,
48
+ weights: List[float] = None
49
+ ) -> List[Tuple[str, float]]:
50
+ """
51
+ Combine multiple ranked lists using RRF.
52
+
53
+ Args:
54
+ result_lists: List of (doc_id, score) tuples per search method
55
+ k: RRF constant (higher = more weight to lower ranks)
56
+ weights: Optional weights per result list
57
+
58
+ Returns:
59
+ Fused ranking as (doc_id, score) tuples
60
+ """
61
+ if weights is None:
62
+ weights = [1.0] * len(result_lists)
63
+
64
+ scores = defaultdict(float)
65
+
66
+ for result_list, weight in zip(result_lists, weights):
67
+ for rank, (doc_id, _) in enumerate(result_list):
68
+ # RRF formula: 1 / (k + rank)
69
+ scores[doc_id] += weight * (1.0 / (k + rank + 1))
70
+
71
+ # Sort by fused score
72
+ return sorted(scores.items(), key=lambda x: x[1], reverse=True)
73
+
74
+
75
+ def linear_combination(
76
+ vector_results: List[Tuple[str, float]],
77
+ keyword_results: List[Tuple[str, float]],
78
+ alpha: float = 0.5
79
+ ) -> List[Tuple[str, float]]:
80
+ """
81
+ Combine results with linear interpolation.
82
+
83
+ Args:
84
+ vector_results: (doc_id, similarity_score) from vector search
85
+ keyword_results: (doc_id, bm25_score) from keyword search
86
+ alpha: Weight for vector search (1-alpha for keyword)
87
+ """
88
+ # Normalize scores to [0, 1]
89
+ def normalize(results):
90
+ if not results:
91
+ return {}
92
+ scores = [s for _, s in results]
93
+ min_s, max_s = min(scores), max(scores)
94
+ range_s = max_s - min_s if max_s != min_s else 1
95
+ return {doc_id: (score - min_s) / range_s for doc_id, score in results}
96
+
97
+ vector_scores = normalize(vector_results)
98
+ keyword_scores = normalize(keyword_results)
99
+
100
+ # Combine
101
+ all_docs = set(vector_scores.keys()) | set(keyword_scores.keys())
102
+ combined = {}
103
+
104
+ for doc_id in all_docs:
105
+ v_score = vector_scores.get(doc_id, 0)
106
+ k_score = keyword_scores.get(doc_id, 0)
107
+ combined[doc_id] = alpha * v_score + (1 - alpha) * k_score
108
+
109
+ return sorted(combined.items(), key=lambda x: x[1], reverse=True)
110
+ ```
111
+
112
+ ### Template 2: PostgreSQL Hybrid Search
113
+
114
+ ```python
115
+ import asyncpg
116
+ from typing import List, Dict, Optional
117
+ import numpy as np
118
+
119
+ class PostgresHybridSearch:
120
+ """Hybrid search with pgvector and full-text search."""
121
+
122
+ def __init__(self, pool: asyncpg.Pool):
123
+ self.pool = pool
124
+
125
+ async def setup_schema(self):
126
+ """Create tables and indexes."""
127
+ async with self.pool.acquire() as conn:
128
+ await conn.execute("""
129
+ CREATE EXTENSION IF NOT EXISTS vector;
130
+
131
+ CREATE TABLE IF NOT EXISTS documents (
132
+ id TEXT PRIMARY KEY,
133
+ content TEXT NOT NULL,
134
+ embedding vector(1536),
135
+ metadata JSONB DEFAULT '{}',
136
+ ts_content tsvector GENERATED ALWAYS AS (
137
+ to_tsvector('english', content)
138
+ ) STORED
139
+ );
140
+
141
+ -- Vector index (HNSW)
142
+ CREATE INDEX IF NOT EXISTS documents_embedding_idx
143
+ ON documents USING hnsw (embedding vector_cosine_ops);
144
+
145
+ -- Full-text index (GIN)
146
+ CREATE INDEX IF NOT EXISTS documents_fts_idx
147
+ ON documents USING gin (ts_content);
148
+ """)
149
+
150
+ async def hybrid_search(
151
+ self,
152
+ query: str,
153
+ query_embedding: List[float],
154
+ limit: int = 10,
155
+ vector_weight: float = 0.5,
156
+ filter_metadata: Optional[Dict] = None
157
+ ) -> List[Dict]:
158
+ """
159
+ Perform hybrid search combining vector and full-text.
160
+
161
+ Uses RRF fusion for combining results.
162
+ """
163
+ async with self.pool.acquire() as conn:
164
+ # Build filter clause
165
+ where_clause = "1=1"
166
+ params = [query_embedding, query, limit * 3]
167
+
168
+ if filter_metadata:
169
+ for key, value in filter_metadata.items():
170
+ params.append(value)
171
+ where_clause += f" AND metadata->>'{key}' = ${len(params)}"
172
+
173
+ results = await conn.fetch(f"""
174
+ WITH vector_search AS (
175
+ SELECT
176
+ id,
177
+ content,
178
+ metadata,
179
+ ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) as vector_rank,
180
+ 1 - (embedding <=> $1::vector) as vector_score
181
+ FROM documents
182
+ WHERE {where_clause}
183
+ ORDER BY embedding <=> $1::vector
184
+ LIMIT $3
185
+ ),
186
+ keyword_search AS (
187
+ SELECT
188
+ id,
189
+ content,
190
+ metadata,
191
+ ROW_NUMBER() OVER (ORDER BY ts_rank(ts_content, websearch_to_tsquery('english', $2)) DESC) as keyword_rank,
192
+ ts_rank(ts_content, websearch_to_tsquery('english', $2)) as keyword_score
193
+ FROM documents
194
+ WHERE ts_content @@ websearch_to_tsquery('english', $2)
195
+ AND {where_clause}
196
+ ORDER BY ts_rank(ts_content, websearch_to_tsquery('english', $2)) DESC
197
+ LIMIT $3
198
+ )
199
+ SELECT
200
+ COALESCE(v.id, k.id) as id,
201
+ COALESCE(v.content, k.content) as content,
202
+ COALESCE(v.metadata, k.metadata) as metadata,
203
+ v.vector_score,
204
+ k.keyword_score,
205
+ -- RRF fusion
206
+ COALESCE(1.0 / (60 + v.vector_rank), 0) * $4::float +
207
+ COALESCE(1.0 / (60 + k.keyword_rank), 0) * (1 - $4::float) as rrf_score
208
+ FROM vector_search v
209
+ FULL OUTER JOIN keyword_search k ON v.id = k.id
210
+ ORDER BY rrf_score DESC
211
+ LIMIT $3 / 3
212
+ """, *params, vector_weight)
213
+
214
+ return [dict(row) for row in results]
215
+
216
+ async def search_with_rerank(
217
+ self,
218
+ query: str,
219
+ query_embedding: List[float],
220
+ limit: int = 10,
221
+ rerank_candidates: int = 50
222
+ ) -> List[Dict]:
223
+ """Hybrid search with cross-encoder reranking."""
224
+ from sentence_transformers import CrossEncoder
225
+
226
+ # Get candidates
227
+ candidates = await self.hybrid_search(
228
+ query, query_embedding, limit=rerank_candidates
229
+ )
230
+
231
+ if not candidates:
232
+ return []
233
+
234
+ # Rerank with cross-encoder
235
+ model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
236
+
237
+ pairs = [(query, c["content"]) for c in candidates]
238
+ scores = model.predict(pairs)
239
+
240
+ for candidate, score in zip(candidates, scores):
241
+ candidate["rerank_score"] = float(score)
242
+
243
+ # Sort by rerank score and return top results
244
+ reranked = sorted(candidates, key=lambda x: x["rerank_score"], reverse=True)
245
+ return reranked[:limit]
246
+ ```
247
+
248
+ ### Template 3: Elasticsearch Hybrid Search
249
+
250
+ ```python
251
+ from elasticsearch import Elasticsearch
252
+ from typing import List, Dict, Optional
253
+
254
+ class ElasticsearchHybridSearch:
255
+ """Hybrid search with Elasticsearch and dense vectors."""
256
+
257
+ def __init__(
258
+ self,
259
+ es_client: Elasticsearch,
260
+ index_name: str = "documents"
261
+ ):
262
+ self.es = es_client
263
+ self.index_name = index_name
264
+
265
+ def create_index(self, vector_dims: int = 1536):
266
+ """Create index with dense vector and text fields."""
267
+ mapping = {
268
+ "mappings": {
269
+ "properties": {
270
+ "content": {
271
+ "type": "text",
272
+ "analyzer": "english"
273
+ },
274
+ "embedding": {
275
+ "type": "dense_vector",
276
+ "dims": vector_dims,
277
+ "index": True,
278
+ "similarity": "cosine"
279
+ },
280
+ "metadata": {
281
+ "type": "object",
282
+ "enabled": True
283
+ }
284
+ }
285
+ }
286
+ }
287
+ self.es.indices.create(index=self.index_name, body=mapping, ignore=400)
288
+
289
+ def hybrid_search(
290
+ self,
291
+ query: str,
292
+ query_embedding: List[float],
293
+ limit: int = 10,
294
+ boost_vector: float = 1.0,
295
+ boost_text: float = 1.0,
296
+ filter: Optional[Dict] = None
297
+ ) -> List[Dict]:
298
+ """
299
+ Hybrid search using Elasticsearch's built-in capabilities.
300
+ """
301
+ # Build the hybrid query
302
+ search_body = {
303
+ "size": limit,
304
+ "query": {
305
+ "bool": {
306
+ "should": [
307
+ # Vector search (kNN)
308
+ {
309
+ "script_score": {
310
+ "query": {"match_all": {}},
311
+ "script": {
312
+ "source": f"cosineSimilarity(params.query_vector, 'embedding') * {boost_vector} + 1.0",
313
+ "params": {"query_vector": query_embedding}
314
+ }
315
+ }
316
+ },
317
+ # Text search (BM25)
318
+ {
319
+ "match": {
320
+ "content": {
321
+ "query": query,
322
+ "boost": boost_text
323
+ }
324
+ }
325
+ }
326
+ ],
327
+ "minimum_should_match": 1
328
+ }
329
+ }
330
+ }
331
+
332
+ # Add filter if provided
333
+ if filter:
334
+ search_body["query"]["bool"]["filter"] = filter
335
+
336
+ response = self.es.search(index=self.index_name, body=search_body)
337
+
338
+ return [
339
+ {
340
+ "id": hit["_id"],
341
+ "content": hit["_source"]["content"],
342
+ "metadata": hit["_source"].get("metadata", {}),
343
+ "score": hit["_score"]
344
+ }
345
+ for hit in response["hits"]["hits"]
346
+ ]
347
+
348
+ def hybrid_search_rrf(
349
+ self,
350
+ query: str,
351
+ query_embedding: List[float],
352
+ limit: int = 10,
353
+ window_size: int = 100
354
+ ) -> List[Dict]:
355
+ """
356
+ Hybrid search using Elasticsearch 8.x RRF.
357
+ """
358
+ search_body = {
359
+ "size": limit,
360
+ "sub_searches": [
361
+ {
362
+ "query": {
363
+ "match": {
364
+ "content": query
365
+ }
366
+ }
367
+ },
368
+ {
369
+ "query": {
370
+ "knn": {
371
+ "field": "embedding",
372
+ "query_vector": query_embedding,
373
+ "k": window_size,
374
+ "num_candidates": window_size * 2
375
+ }
376
+ }
377
+ }
378
+ ],
379
+ "rank": {
380
+ "rrf": {
381
+ "window_size": window_size,
382
+ "rank_constant": 60
383
+ }
384
+ }
385
+ }
386
+
387
+ response = self.es.search(index=self.index_name, body=search_body)
388
+
389
+ return [
390
+ {
391
+ "id": hit["_id"],
392
+ "content": hit["_source"]["content"],
393
+ "score": hit["_score"]
394
+ }
395
+ for hit in response["hits"]["hits"]
396
+ ]
397
+ ```
398
+
399
+ ### Template 4: Custom Hybrid RAG Pipeline
400
+
401
+ ```python
402
+ from typing import List, Dict, Optional, Callable
403
+ from dataclasses import dataclass
404
+
405
+ @dataclass
406
+ class SearchResult:
407
+ id: str
408
+ content: str
409
+ score: float
410
+ source: str # "vector", "keyword", "hybrid"
411
+ metadata: Dict = None
412
+
413
+
414
+ class HybridRAGPipeline:
415
+ """Complete hybrid search pipeline for RAG."""
416
+
417
+ def __init__(
418
+ self,
419
+ vector_store,
420
+ keyword_store,
421
+ embedder,
422
+ reranker=None,
423
+ fusion_method: str = "rrf",
424
+ vector_weight: float = 0.5
425
+ ):
426
+ self.vector_store = vector_store
427
+ self.keyword_store = keyword_store
428
+ self.embedder = embedder
429
+ self.reranker = reranker
430
+ self.fusion_method = fusion_method
431
+ self.vector_weight = vector_weight
432
+
433
+ async def search(
434
+ self,
435
+ query: str,
436
+ top_k: int = 10,
437
+ filter: Optional[Dict] = None,
438
+ use_rerank: bool = True
439
+ ) -> List[SearchResult]:
440
+ """Execute hybrid search pipeline."""
441
+
442
+ # Step 1: Get query embedding
443
+ query_embedding = self.embedder.embed(query)
444
+
445
+ # Step 2: Execute parallel searches
446
+ vector_results, keyword_results = await asyncio.gather(
447
+ self._vector_search(query_embedding, top_k * 3, filter),
448
+ self._keyword_search(query, top_k * 3, filter)
449
+ )
450
+
451
+ # Step 3: Fuse results
452
+ if self.fusion_method == "rrf":
453
+ fused = self._rrf_fusion(vector_results, keyword_results)
454
+ else:
455
+ fused = self._linear_fusion(vector_results, keyword_results)
456
+
457
+ # Step 4: Rerank if enabled
458
+ if use_rerank and self.reranker:
459
+ fused = await self._rerank(query, fused[:top_k * 2])
460
+
461
+ return fused[:top_k]
462
+
463
+ async def _vector_search(
464
+ self,
465
+ embedding: List[float],
466
+ limit: int,
467
+ filter: Dict
468
+ ) -> List[SearchResult]:
469
+ results = await self.vector_store.search(embedding, limit, filter)
470
+ return [
471
+ SearchResult(
472
+ id=r["id"],
473
+ content=r["content"],
474
+ score=r["score"],
475
+ source="vector",
476
+ metadata=r.get("metadata")
477
+ )
478
+ for r in results
479
+ ]
480
+
481
+ async def _keyword_search(
482
+ self,
483
+ query: str,
484
+ limit: int,
485
+ filter: Dict
486
+ ) -> List[SearchResult]:
487
+ results = await self.keyword_store.search(query, limit, filter)
488
+ return [
489
+ SearchResult(
490
+ id=r["id"],
491
+ content=r["content"],
492
+ score=r["score"],
493
+ source="keyword",
494
+ metadata=r.get("metadata")
495
+ )
496
+ for r in results
497
+ ]
498
+
499
+ def _rrf_fusion(
500
+ self,
501
+ vector_results: List[SearchResult],
502
+ keyword_results: List[SearchResult]
503
+ ) -> List[SearchResult]:
504
+ """Fuse with RRF."""
505
+ k = 60
506
+ scores = {}
507
+ content_map = {}
508
+
509
+ for rank, result in enumerate(vector_results):
510
+ scores[result.id] = scores.get(result.id, 0) + 1 / (k + rank + 1)
511
+ content_map[result.id] = result
512
+
513
+ for rank, result in enumerate(keyword_results):
514
+ scores[result.id] = scores.get(result.id, 0) + 1 / (k + rank + 1)
515
+ if result.id not in content_map:
516
+ content_map[result.id] = result
517
+
518
+ sorted_ids = sorted(scores.keys(), key=lambda x: scores[x], reverse=True)
519
+
520
+ return [
521
+ SearchResult(
522
+ id=doc_id,
523
+ content=content_map[doc_id].content,
524
+ score=scores[doc_id],
525
+ source="hybrid",
526
+ metadata=content_map[doc_id].metadata
527
+ )
528
+ for doc_id in sorted_ids
529
+ ]
530
+
531
+ async def _rerank(
532
+ self,
533
+ query: str,
534
+ results: List[SearchResult]
535
+ ) -> List[SearchResult]:
536
+ """Rerank with cross-encoder."""
537
+ if not results:
538
+ return results
539
+
540
+ pairs = [(query, r.content) for r in results]
541
+ scores = self.reranker.predict(pairs)
542
+
543
+ for result, score in zip(results, scores):
544
+ result.score = float(score)
545
+
546
+ return sorted(results, key=lambda x: x.score, reverse=True)
547
+ ```
548
+
549
+ ## Best Practices
550
+
551
+ ### Do's
552
+
553
+ - **Tune weights empirically** - Test on your data
554
+ - **Use RRF for simplicity** - Works well without tuning
555
+ - **Add reranking** - Significant quality improvement
556
+ - **Log both scores** - Helps with debugging
557
+ - **A/B test** - Measure real user impact
558
+
559
+ ### Don'ts
560
+
561
+ - **Don't assume one size fits all** - Different queries need different weights
562
+ - **Don't skip keyword search** - Handles exact matches better
563
+ - **Don't over-fetch** - Balance recall vs latency
564
+ - **Don't ignore edge cases** - Empty results, single word queries
565
+
566
+ ## Resources
567
+
568
+ - [RRF Paper](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf)
569
+ - [Vespa Hybrid Search](https://blog.vespa.ai/improving-text-ranking-with-few-shot-prompting/)
570
+ - [Cohere Rerank](https://docs.cohere.com/docs/reranking)