Semantic Clustering & Hybrid Pipelines
Why pay LLM tokens for every paragraph when cosine clustering can group them first?
CosineStrategy
combining clustering with downstream LLM extraction
01/concept
Concept
CosineStrategy embeds chunks of content and groups semantically similar text before downstream processing. It is ideal for discovery tasks: finding FAQ entries, support articles, or reviews that belong to the same topic without writing a schema.
The real power comes from hybrid pipelines: cluster first, then run a smaller LLM extraction only on the representative chunks. This cuts token spend and reduces noise from unrelated sections of a page.
02/use-cases
Use cases
- Group support articles by topic before summarizing each cluster.
- Find duplicate or near-duplicate reviews across a product catalog.
- Feed only the most relevant chunks to an LLM extraction strategy.
03/watch
Video walkthrough
04/run
Code example
python
Cosine clustering
import asyncio
import json
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai import CosineStrategy
async def main():
strategy = CosineStrategy(
semantic_filter="customer reviews",
word_count_threshold=20,
sim_threshold=0.4,
top_k=5,
)
run_cfg = CrawlerRunConfig(extraction_strategy=strategy)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://example.com/reviews",
config=run_cfg,
)
if result.success and result.extracted_content:
clusters = json.loads(result.extracted_content)
print(f"Clusters: {len(clusters)}")
for c in clusters[:2]:
print(c["text"][:300])
asyncio.run(main())
python
Hybrid pipeline: cosine then LLM
import json
import os
from pydantic import BaseModel
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, LLMConfig
from crawl4ai import CosineStrategy, LLMExtractionStrategy
class Review(BaseModel):
author: str
rating: int
body: str
async def extract_reviews(url: str):
cosine = CosineStrategy(
semantic_filter="customer reviews",
word_count_threshold=20,
top_k=3,
)
async with AsyncWebCrawler() as crawler:
first = await crawler.arun(url=url, config=CrawlerRunConfig(extraction_strategy=cosine))
if not first.success or not first.extracted_content:
return []
clusters = json.loads(first.extracted_content)
combined = "\n\n".join(c["text"] for c in clusters)
llm = LLMExtractionStrategy(
llm_config=LLMConfig(provider="openai/gpt-4o-mini", api_token=os.getenv("OPENAI_API_KEY")),
schema=Review.model_json_schema(),
extraction_type="schema",
instruction="Extract structured reviews from the text.",
)
second = await crawler.arun(
url=f"raw://{combined}",
config=CrawlerRunConfig(extraction_strategy=llm),
)
return json.loads(second.extracted_content) if second.success else []
05/build
Practice lab
Lab 4.2
Practice lab
0/3
Objective: Use CosineStrategy to isolate the main article content from a noisy page.
Steps
- Choose a content-heavy page with navigation and sidebars.
- Create a
CosineStrategywith a semantic filter likemain article content. - Crawl the page and parse the clusters.
- Print the number of clusters and the first 400 characters of the top cluster.
Success criteria
Expected output
Cluster count and a clean article snippet.
Hints
- Raise
word_count_thresholdto filter out short navigation snippets. - If the result is too broad, raise
sim_threshold.
Solution
python
Complete solution
import asyncio
import json
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai import CosineStrategy
async def main():
strategy = CosineStrategy(
semantic_filter="main article content",
word_count_threshold=100,
top_k=1,
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://example.com/article",
config=CrawlerRunConfig(extraction_strategy=strategy),
)
if result.success and result.extracted_content:
clusters = json.loads(result.extracted_content)
print(f"Clusters: {len(clusters)}")
print(clusters[0]["text"][:400])
asyncio.run(main())
06/check
Common mistakes
TRAP 01
Running LLM extraction on every chunk instead of only representative ones.
TRAP 02
Tuning clustering without inspecting the resulting groups.
TRAP 03
Forgetting that cosine grouping needs enough text per chunk to produce meaningful embeddings.