81e7a5707a
* feat!: remove the auto and parallel meta search engines The langgraph-agent strategy (the default) selects search engines dynamically per tool call, making the LLM-based meta-pickers redundant: - 'auto' (MetaSearchEngine, alias 'meta') spent an extra LLM call picking 1-3 engines and returned the first success - 'parallel'/'parallel_scientific' (ParallelSearchEngine) fanned out blindly across engines and merged results Removal also deletes the entire meta-picker special-case surface in the egress policy: the factory skip-PEP tuple (every engine now goes through evaluate_engine), _META_PICKER_ENGINES, the strict_with_meta_picker / meta_picker_delegator reason codes, validate_strict_meta_combo, and the frontend STRICT-scope guard. Migration 0013 rewrites stored per-user data at DB open: search.tool settings -> searxng, orphaned search.engine.auto.* / search.engine.web.parallel.* rows deleted, news subscription engines NULLed (= use the user's default), queued research snapshots and saved benchmark configs rewritten. Also fixes /api/v1/quick_summary silently overriding the user's configured engine with 'auto' when search_tool was omitted, and the corrupted-settings repair paths re-introducing 'auto'. BREAKING CHANGE: search_tool='auto'/'parallel' now raises ValueError; pick a concrete engine. LDR_SEARCH_TOOL=auto env overrides must be updated. * test: exempt 0013's intentional no-op downgrade from the substantive-downgrade guard Restoring 'auto'/'parallel' references on downgrade would recreate broken state — those engines no longer exist. Same precedent as 0004. * fix: align merged main code with meta-engine removal The merge from main brought in _egress_audit_net (MCP) with the old search.tool 'auto' code default — switch it to 'searxng' like the other call sites. * test: fix LLM-provider tests denied by adaptive egress + private primary The fixture sweep changed search.tool from the removed 'auto' to the private 'library' engine — the only one the factory instantiates from these minimal snapshots. That passed while the missing-scope fallback was 'both', but merging main (#4465, fallback -> adaptive) made a private primary resolve to PRIVATE_ONLY, forcing local LLM and denying the remote OpenAI/OpenRouter providers these tests configure (provider_remote). Pin policy.egress_scope='both' in the affected fixtures so these stay LLM-provider-config tests, decoupled from egress-scope resolution.
4.4 KiB
4.4 KiB
LangChain Retriever Integration
LDR now supports using any LangChain retriever as a search engine. This allows you to use vector stores, databases, or any custom retriever implementation with LDR's research capabilities.
Quick Start
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from local_deep_research.api import quick_summary
# Create your retriever (any LangChain retriever works)
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents, embeddings)
retriever = vectorstore.as_retriever()
# Use with LDR
result = quick_summary(
query="What are our deployment procedures?",
retrievers={"company_kb": retriever},
search_tool="company_kb"
)
How It Works
- Pass retrievers as a dictionary to any research function
- Each retriever gets a name (the dictionary key)
- Use the retriever by setting
search_toolto its name - Retrievers work exactly like built-in search engines
Usage Examples
Single Retriever
result = quick_summary(
query="Your question",
retrievers={"my_kb": retriever},
search_tool="my_kb" # Use only this retriever
)
Multiple Retrievers
result = detailed_research(
query="Complex question",
retrievers={
"vector_db": vector_retriever,
"graph_db": graph_retriever,
"sql_db": sql_retriever
},
search_tool="vector_db" # Primary retriever; with the default
# langgraph-agent strategy, every registered retriever is also
# exposed to the research agent as a search tool
)
Hybrid Search (Retriever + Web)
result = quick_summary(
query="Compare internal and external practices",
retrievers={"internal": internal_retriever},
search_tool="searxng" # Web engine as primary; the langgraph-agent
# strategy can also query the registered "internal" retriever
)
Selective Usage
# Pass multiple retrievers but use only one
all_retrievers = {
"tech_docs": tech_retriever,
"legal_docs": legal_retriever,
"hr_docs": hr_retriever
}
# Use only tech docs for this query
result = quick_summary(
query="Technical question",
retrievers=all_retrievers,
search_tool="tech_docs" # Select specific retriever
)
Supported Retrievers
Any LangChain BaseRetriever implementation works:
- Vector Stores: FAISS, Chroma, Pinecone, Weaviate, Qdrant, etc.
- Cloud Services: Vertex AI, AWS Bedrock, Azure Cognitive Search
- Databases: PostgreSQL, MongoDB, Elasticsearch
- Custom: Any class inheriting from
BaseRetriever
API Reference
Parameters
All research functions (quick_summary, detailed_research, generate_report) accept:
retrievers: Optional[Dict[str, BaseRetriever]] - Dictionary of retrieverssearch_tool: str - Name of the retriever (or built-in engine) to use as the primary search source
Example with Complex Setup
from langchain.vectorstores import VertexAIVectorSearch
from langchain.embeddings import VertexAIEmbeddings
import os
# User handles proxy/auth setup
os.environ["HTTP_PROXY"] = "http://proxy.company.com:8080"
# Create retriever with complex configuration
embeddings = VertexAIEmbeddings(
project="my-project",
location="us-central1"
)
vectorstore = VertexAIVectorSearch(
project="my-project",
location="us-central1",
index="my-index",
endpoint="my-endpoint",
embeddings=embeddings
)
retriever = vectorstore.as_retriever()
# Use with LDR - all complexity is hidden
result = quick_summary(
query="Internal knowledge query",
retrievers={"vertex_ai": retriever},
search_tool="vertex_ai"
)
Benefits
- Zero Coupling: LDR doesn't need to know retriever internals
- Full Compatibility: Works with all LDR features (strategies, agentic engine selection, etc.)
- Clean API: Just pass a dictionary of retrievers
- Flexible: Mix retrievers with web search seamlessly
Testing Your Integration
from langchain.schema import Document, BaseRetriever
class TestRetriever(BaseRetriever):
def get_relevant_documents(self, query: str):
return [Document(page_content=f"Test doc about {query}")]
async def aget_relevant_documents(self, query: str):
return self.get_relevant_documents(query)
# Test it
result = quick_summary(
query="test",
retrievers={"test": TestRetriever()},
search_tool="test"
)