02 — Hybrid Search Patterns¶
Jupyter notebook
This page shows the code of 02_hybrid_search.ipynb without outputs: the notebook needs a running service to execute and is not run at build time.
View on GitHub · Open in nbviewer · Open in Colab · Raw .ipynb
Kernel: Python 3 · Environment: fractal_rag package (pip install -e fractal_rag) with a running Weaviate instance · Companion page: fractal-rag/
This notebook digs into the three knobs that most change retrieval quality:
target_vector—content_vecvstitle_vecvssummary_vec.alpha— slider between pure-keyword (0.0) and pure-vector (1.0).filters— restrict bysource_type,year, or other properties.
We'll run the same query under different settings so you can build intuition for when to reach for which.
from fractal_rag.client import client_session
from fractal_rag.config import get_settings
from weaviate.classes.query import Filter, MetadataQuery
settings = get_settings(refresh=True)
QUERY = 'Barnsley fern iterated function system'
def hybrid(target_vector, alpha=0.5, filters=None, limit=5):
with client_session() as client:
art = client.collections.get(settings.artifact_collection)
return art.query.hybrid(
query=QUERY, limit=limit, target_vector=target_vector,
alpha=alpha, filters=filters,
return_metadata=MetadataQuery(distance=True),
).objects
def show(objs, label):
print(f'--- {label} ---')
for o in objs:
p = o.properties
print(f" [{p.get('source_type'):>10}] {p.get('title')[:70]}")
target_vector matters¶
The same query against different named vectors returns different rankings. Use title_vec for 'find me the notebook/page called X' and content_vec for 'find passages about X'.
show(hybrid('content_vec'), 'content_vec, alpha=0.5')
show(hybrid('title_vec'), 'title_vec, alpha=0.5')
show(hybrid('summary_vec'), 'summary_vec, alpha=0.5')
alpha shifts the keyword/vector balance¶
alpha=0.0 is pure BM25 (lexical), alpha=1.0 is pure cosine. For technical vocabulary that the embedding model may not know, lower alpha. For paraphrases and conceptual matches, raise alpha.
show(hybrid('content_vec', alpha=0.0), 'alpha=0.0 (keyword)')
show(hybrid('content_vec', alpha=0.5), 'alpha=0.5 (balanced)')
show(hybrid('content_vec', alpha=1.0), 'alpha=1.0 (vector only)')
Filtering by source_type¶
Often you want notebooks only, or articles only. Pass a Filter from weaviate.classes.query.
notebook_only = Filter.by_property('source_type').contains_any(['notebook'])
articles_only = Filter.by_property('source_type').contains_any(['json_corpus'])
show(hybrid('content_vec', filters=notebook_only), 'notebooks only')
show(hybrid('content_vec', filters=articles_only), 'json_corpus only')
Filtering by year¶
Useful for historical asks. Combine with Filter.all_of(...) if you need multiple conditions.
before_1990 = Filter.all_of([
Filter.by_property('source_type').contains_any(['json_corpus']),
Filter.by_property('year').less_or_equal(1990),
])
show(hybrid('content_vec', filters=before_1990, limit=8), 'pre-1990 articles')
near_object: 'more like this'¶
Once you find a single strong hit, near_object returns its neighbors in the embedding space. This is the workhorse for synthesis: find one anchor, then expand.
anchor = hybrid('content_vec', limit=1)[0]
with client_session() as client:
art = client.collections.get(settings.artifact_collection)
neighbors = art.query.near_object(
near_object=str(anchor.uuid),
limit=5,
target_vector='content_vec',
return_metadata=MetadataQuery(distance=True),
).objects
print(f"Anchor: {anchor.properties.get('title')[:70]}")
print('Neighbors:')
for n in neighbors:
d = getattr(n.metadata, 'distance', None)
print(f" [{d:.3f}] {n.properties.get('title')[:70]}")
Next steps¶
- 03 — Concept ontology — follow cross-refs to find every artifact that mentions a given concept.