7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
353 lines
12 KiB
Python
353 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Hybrid Search Example for Local Deep Research
|
|
|
|
This example demonstrates how to combine multiple search sources:
|
|
1. Multiple named retrievers for different document types
|
|
2. Combining custom retrievers with web search
|
|
3. Analyzing and comparing sources from different origins
|
|
"""
|
|
|
|
from typing import List
|
|
from langchain_core.retrievers import Document, BaseRetriever
|
|
from langchain_community.vectorstores import FAISS
|
|
from langchain_ollama import OllamaEmbeddings
|
|
|
|
from local_deep_research.api import quick_summary, detailed_research
|
|
from local_deep_research.api.settings_utils import create_settings_snapshot
|
|
|
|
|
|
class TechnicalDocsRetriever(BaseRetriever):
|
|
"""Mock retriever for technical documentation."""
|
|
|
|
def get_relevant_documents(self, query: str) -> List[Document]:
|
|
"""Return mock technical documents."""
|
|
# In a real scenario, this would search actual technical docs
|
|
return [
|
|
Document(
|
|
page_content=f"Technical specification for {query}: Implementation requires careful consideration of system architecture, performance metrics, and scalability factors.",
|
|
metadata={
|
|
"source": "tech_docs",
|
|
"type": "specification",
|
|
"title": f"Technical Spec: {query}",
|
|
},
|
|
),
|
|
Document(
|
|
page_content=f"Best practices for {query}: Follow industry standards, implement proper error handling, and ensure comprehensive testing coverage.",
|
|
metadata={
|
|
"source": "tech_docs",
|
|
"type": "best_practices",
|
|
"title": f"Best Practices: {query}",
|
|
},
|
|
),
|
|
]
|
|
|
|
async def aget_relevant_documents(self, query: str) -> List[Document]:
|
|
"""Async version."""
|
|
return self.get_relevant_documents(query)
|
|
|
|
|
|
class BusinessDocsRetriever(BaseRetriever):
|
|
"""Mock retriever for business/strategy documents."""
|
|
|
|
def get_relevant_documents(self, query: str) -> List[Document]:
|
|
"""Return mock business documents."""
|
|
return [
|
|
Document(
|
|
page_content=f"Business implications of {query}: Consider market impact, ROI analysis, and strategic alignment with organizational goals.",
|
|
metadata={
|
|
"source": "business_docs",
|
|
"type": "strategy",
|
|
"title": f"Business Strategy: {query}",
|
|
},
|
|
),
|
|
Document(
|
|
page_content=f"Cost-benefit analysis for {query}: Initial investment requirements, expected returns, and risk assessment factors.",
|
|
metadata={
|
|
"source": "business_docs",
|
|
"type": "analysis",
|
|
"title": f"Cost Analysis: {query}",
|
|
},
|
|
),
|
|
]
|
|
|
|
async def aget_relevant_documents(self, query: str) -> List[Document]:
|
|
"""Async version."""
|
|
return self.get_relevant_documents(query)
|
|
|
|
|
|
def create_knowledge_base_retriever() -> BaseRetriever:
|
|
"""Create a FAISS-based retriever with sample knowledge base documents."""
|
|
documents = [
|
|
Document(
|
|
page_content="Machine learning models require training data, validation strategies, and performance metrics for evaluation.",
|
|
metadata={"source": "ml_knowledge_base", "topic": "ml_basics"},
|
|
),
|
|
Document(
|
|
page_content="Cloud computing provides scalable infrastructure, reducing capital expenditure and enabling flexible resource allocation.",
|
|
metadata={
|
|
"source": "cloud_knowledge_base",
|
|
"topic": "cloud_benefits",
|
|
},
|
|
),
|
|
Document(
|
|
page_content="Agile methodology emphasizes iterative development, customer collaboration, and responding to change.",
|
|
metadata={"source": "project_knowledge_base", "topic": "agile"},
|
|
),
|
|
Document(
|
|
page_content="Data privacy regulations like GDPR require explicit consent, data minimization, and user rights management.",
|
|
metadata={
|
|
"source": "compliance_knowledge_base",
|
|
"topic": "privacy",
|
|
},
|
|
),
|
|
]
|
|
|
|
# Create embeddings and vector store
|
|
embeddings = OllamaEmbeddings(model="nomic-embed-text")
|
|
vectorstore = FAISS.from_documents(documents, embeddings)
|
|
return vectorstore.as_retriever(search_kwargs={"k": 2})
|
|
|
|
|
|
def demonstrate_multiple_retrievers():
|
|
"""Show how to use multiple named retrievers for different document types."""
|
|
print("=" * 70)
|
|
print("MULTIPLE NAMED RETRIEVERS")
|
|
print("=" * 70)
|
|
print("""
|
|
Using multiple specialized retrievers:
|
|
- Technical documentation retriever
|
|
- Business documentation retriever
|
|
- Knowledge base retriever
|
|
Each provides different perspectives on the same topic.
|
|
""")
|
|
|
|
# Create different retrievers
|
|
tech_retriever = TechnicalDocsRetriever()
|
|
business_retriever = BusinessDocsRetriever()
|
|
kb_retriever = create_knowledge_base_retriever()
|
|
|
|
# Configure settings. Registered retrievers are addressable by name;
|
|
# with the default langgraph-agent strategy, every registered retriever
|
|
# is also exposed to the research agent as a search tool.
|
|
settings = create_settings_snapshot(
|
|
{
|
|
"search.tool": "knowledge_base", # Primary retriever
|
|
}
|
|
)
|
|
|
|
# Use multiple retrievers in research
|
|
result = quick_summary(
|
|
query="Implementing machine learning in production",
|
|
settings_snapshot=settings,
|
|
retrievers={
|
|
"technical": tech_retriever,
|
|
"business": business_retriever,
|
|
"knowledge_base": kb_retriever,
|
|
},
|
|
search_tool="knowledge_base", # Primary retriever (others stay available)
|
|
iterations=2,
|
|
questions_per_iteration=2,
|
|
programmatic_mode=True,
|
|
)
|
|
|
|
print("\nResearch Summary (first 400 chars):")
|
|
print(result["summary"][:400] + "...")
|
|
|
|
# Analyze sources by type
|
|
sources = result.get("sources", [])
|
|
print(f"\nTotal sources found: {len(sources)}")
|
|
|
|
# Group sources by retriever
|
|
source_types = {}
|
|
for source in sources:
|
|
if isinstance(source, dict):
|
|
source_type = source.get("metadata", {}).get("source", "unknown")
|
|
else:
|
|
source_type = "other"
|
|
source_types[source_type] = source_types.get(source_type, 0) + 1
|
|
|
|
print("\nSources by retriever:")
|
|
for stype, count in source_types.items():
|
|
print(f" - {stype}: {count} sources")
|
|
|
|
return result
|
|
|
|
|
|
def demonstrate_retriever_plus_web():
|
|
"""Show how to combine custom retrievers with web search."""
|
|
print("\n" + "=" * 70)
|
|
print("RETRIEVER + WEB SEARCH COMBINATION")
|
|
print("=" * 70)
|
|
print("""
|
|
Combining internal knowledge with web search:
|
|
- Internal: Custom retriever with proprietary knowledge
|
|
- External: Wikipedia for general context
|
|
This provides both specific and general information.
|
|
""")
|
|
|
|
# Create internal knowledge retriever
|
|
internal_retriever = create_knowledge_base_retriever()
|
|
|
|
# Configure settings to use both retriever and web
|
|
settings = create_settings_snapshot(
|
|
{
|
|
"search.tool": "wikipedia", # Also use Wikipedia
|
|
}
|
|
)
|
|
|
|
# Research combining internal and external sources
|
|
result = detailed_research(
|
|
query="Best practices for cloud migration",
|
|
settings_snapshot=settings,
|
|
retrievers={
|
|
"internal_kb": internal_retriever,
|
|
},
|
|
search_tool="wikipedia", # Also search Wikipedia
|
|
search_strategy="source-based",
|
|
iterations=2,
|
|
questions_per_iteration=3,
|
|
programmatic_mode=True,
|
|
)
|
|
|
|
print(f"\nResearch ID: {result['research_id']}")
|
|
print(f"Summary length: {len(result['summary'])} characters")
|
|
|
|
# Analyze source distribution
|
|
sources = result.get("sources", [])
|
|
internal_sources = 0
|
|
external_sources = 0
|
|
|
|
for source in sources:
|
|
if isinstance(source, dict) and "knowledge_base" in str(source):
|
|
internal_sources += 1
|
|
else:
|
|
external_sources += 1
|
|
|
|
print("\nSource distribution:")
|
|
print(f" - Internal knowledge base: {internal_sources} sources")
|
|
print(f" - External (Wikipedia): {external_sources} sources")
|
|
|
|
# Show how different sources complement each other
|
|
print("\nComplementary insights from hybrid search:")
|
|
print(
|
|
" - Internal sources provide: Specific procedures, proprietary knowledge"
|
|
)
|
|
print(
|
|
" - External sources provide: Industry context, general best practices"
|
|
)
|
|
|
|
return result
|
|
|
|
|
|
def demonstrate_source_analysis():
|
|
"""Show how to analyze and compare sources from different origins."""
|
|
print("\n" + "=" * 70)
|
|
print("SOURCE ANALYSIS AND COMPARISON")
|
|
print("=" * 70)
|
|
print("""
|
|
Analyzing source quality and relevance:
|
|
- Track source origins
|
|
- Compare information consistency
|
|
- Identify unique insights from each source type
|
|
""")
|
|
|
|
# Create multiple retrievers
|
|
tech_retriever = TechnicalDocsRetriever()
|
|
business_retriever = BusinessDocsRetriever()
|
|
|
|
settings = create_settings_snapshot(
|
|
{
|
|
"search.tool": "wikipedia",
|
|
}
|
|
)
|
|
|
|
# Run research with detailed source tracking
|
|
result = quick_summary(
|
|
query="Artificial intelligence implementation strategies",
|
|
settings_snapshot=settings,
|
|
retrievers={
|
|
"technical": tech_retriever,
|
|
"business": business_retriever,
|
|
},
|
|
search_tool="wikipedia", # Also use web search
|
|
iterations=2,
|
|
questions_per_iteration=2,
|
|
programmatic_mode=True,
|
|
)
|
|
|
|
# Detailed source analysis
|
|
print("\nSource Analysis:")
|
|
sources = result.get("sources", [])
|
|
|
|
# Categorize sources
|
|
source_categories = {"technical": [], "business": [], "web": []}
|
|
|
|
for source in sources:
|
|
if isinstance(source, dict):
|
|
source_type = source.get("metadata", {}).get("source", "")
|
|
if "tech" in source_type:
|
|
source_categories["technical"].append(source)
|
|
elif "business" in source_type:
|
|
source_categories["business"].append(source)
|
|
else:
|
|
source_categories["web"].append(source)
|
|
else:
|
|
source_categories["web"].append(source)
|
|
|
|
# Report on each category
|
|
for category, category_sources in source_categories.items():
|
|
print(f"\n{category.upper()} Sources ({len(category_sources)}):")
|
|
if category_sources:
|
|
for i, source in enumerate(category_sources[:2], 1): # Show first 2
|
|
if isinstance(source, dict):
|
|
title = source.get("metadata", {}).get("title", "Untitled")
|
|
print(f" {i}. {title}")
|
|
else:
|
|
print(f" {i}. {str(source)[:60]}...")
|
|
|
|
# Show findings breakdown
|
|
findings = result.get("findings", [])
|
|
print(f"\nTotal findings: {len(findings)}")
|
|
print("Findings provide integrated insights from all source types")
|
|
|
|
return result
|
|
|
|
|
|
def main():
|
|
"""Run all hybrid search demonstrations."""
|
|
print("=" * 70)
|
|
print("LOCAL DEEP RESEARCH - HYBRID SEARCH DEMONSTRATION")
|
|
print("=" * 70)
|
|
print("""
|
|
This example shows how to combine multiple search sources:
|
|
- Custom retrievers for proprietary knowledge
|
|
- Web search engines for public information
|
|
- Source analysis across origins
|
|
""")
|
|
|
|
# Run demonstrations
|
|
demonstrate_multiple_retrievers()
|
|
demonstrate_retriever_plus_web()
|
|
demonstrate_source_analysis()
|
|
|
|
print("\n" + "=" * 70)
|
|
print("KEY TAKEAWAYS")
|
|
print("=" * 70)
|
|
print("""
|
|
1. Multiple Retrievers: Use specialized retrievers for different document types
|
|
2. Hybrid Search: Combine internal knowledge with web search for comprehensive results
|
|
3. Source Analysis: Track and analyze sources to understand information origin
|
|
|
|
Best Practices:
|
|
- Name your retrievers descriptively for easy tracking
|
|
- Balance internal and external sources based on your needs
|
|
- Use source analysis to verify information consistency
|
|
""")
|
|
|
|
print("\n✓ Hybrid search demonstration complete!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|