7a0da7932b
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
201 lines
6.4 KiB
Python
Executable File
201 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
LDR Research Script
|
|
|
|
Reads a research query from stdin and uses Local Deep Research to find
|
|
relevant documentation, sources, and context. Returns JSON with the
|
|
research output.
|
|
|
|
Usage:
|
|
# Pipe a query from stdin
|
|
echo "What is RAG?" | python scripts/ldr-research.py
|
|
|
|
# Or pass a file
|
|
python scripts/ldr-research.py < query.txt
|
|
|
|
# With CLI arguments (easier local testing)
|
|
python scripts/ldr-research.py --provider openrouter --model gpt-4o < query.txt
|
|
|
|
Output: JSON with research results, sources, and findings.
|
|
|
|
Environment variables (can be overridden by CLI args):
|
|
OPENROUTER_API_KEY - API key for OpenRouter
|
|
SERPER_API_KEY - API key for Serper.dev search
|
|
LDR_PROVIDER - LLM provider (default: openrouter)
|
|
LDR_SEARCH_TOOL - Search tool (default: serper)
|
|
LDR_RESEARCH_MODEL - Model name (default: google/gemini-2.0-flash-001 for openrouter)
|
|
LDR_STRATEGY - Search strategy (default: langgraph-agent)
|
|
|
|
Note: This uses the programmatic API and does NOT require a running LDR server.
|
|
"""
|
|
|
|
import argparse
|
|
import faulthandler
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
# Dump a Python traceback to stderr on SIGABRT/SIGSEGV/SIGFPE/SIGBUS/SIGILL.
|
|
faulthandler.enable()
|
|
|
|
|
|
def make_serializable(obj):
|
|
"""Convert objects to JSON-serializable format."""
|
|
if obj is None:
|
|
return None
|
|
if isinstance(obj, (str, int, float, bool)):
|
|
return obj
|
|
if isinstance(obj, dict):
|
|
return {k: make_serializable(v) for k, v in obj.items()}
|
|
if isinstance(obj, (list, tuple)):
|
|
return [make_serializable(item) for item in obj]
|
|
# Handle LangChain Document objects
|
|
if hasattr(obj, "page_content") and hasattr(obj, "metadata"):
|
|
return {
|
|
"content": obj.page_content,
|
|
"metadata": make_serializable(obj.metadata),
|
|
}
|
|
# Handle other objects with __dict__
|
|
if hasattr(obj, "__dict__"):
|
|
return make_serializable(obj.__dict__)
|
|
# Fallback to string representation
|
|
return str(obj)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Run LDR research on a query read from stdin"
|
|
)
|
|
parser.add_argument(
|
|
"--provider",
|
|
default=os.environ.get("LDR_PROVIDER", "openrouter"),
|
|
help="LLM provider (default: openrouter)",
|
|
)
|
|
parser.add_argument(
|
|
"--search-tool",
|
|
default=os.environ.get("LDR_SEARCH_TOOL", "serper"),
|
|
help="Search tool (default: serper)",
|
|
)
|
|
parser.add_argument(
|
|
"--model",
|
|
default=os.environ.get("LDR_RESEARCH_MODEL"),
|
|
help="Model name (default: provider's default)",
|
|
)
|
|
parser.add_argument(
|
|
"--iterations",
|
|
type=int,
|
|
default=None,
|
|
help=(
|
|
"Number of research iterations. If unset, the strategy uses "
|
|
"its own default (e.g. langgraph-agent reads "
|
|
"langgraph_agent.max_iterations from settings)."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--strategy",
|
|
default=os.environ.get("LDR_STRATEGY", "langgraph-agent"),
|
|
help="Search strategy name (default: langgraph-agent)",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
# Flush in finally: SIGABRT during interpreter shutdown won't drain the stdout buffer.
|
|
try:
|
|
args = parse_args()
|
|
|
|
# Read query from stdin
|
|
query = sys.stdin.read().strip()
|
|
if not query:
|
|
print(json.dumps({"error": "No query provided on stdin"}))
|
|
sys.exit(1)
|
|
|
|
# Default model for OpenRouter if not specified
|
|
model_name = args.model
|
|
if not model_name and args.provider == "openrouter":
|
|
model_name = "google/gemini-2.0-flash-001"
|
|
|
|
# Check required API keys
|
|
if args.provider == "openrouter" and not os.environ.get(
|
|
"OPENROUTER_API_KEY"
|
|
):
|
|
print(json.dumps({"error": "OPENROUTER_API_KEY not set"}))
|
|
sys.exit(1)
|
|
|
|
if args.search_tool == "serper" and not os.environ.get(
|
|
"SERPER_API_KEY"
|
|
):
|
|
print(json.dumps({"error": "SERPER_API_KEY not set"}))
|
|
sys.exit(1)
|
|
|
|
try:
|
|
from local_deep_research.api import quick_summary
|
|
from local_deep_research.api.settings_utils import (
|
|
create_settings_snapshot,
|
|
)
|
|
|
|
# Build settings overrides
|
|
overrides = {
|
|
"search.tool": args.search_tool,
|
|
"llm.provider": args.provider,
|
|
}
|
|
if model_name:
|
|
overrides["llm.model"] = model_name
|
|
|
|
# Add API keys from environment
|
|
if os.environ.get("OPENROUTER_API_KEY"):
|
|
overrides["llm.openrouter.api_key"] = os.environ[
|
|
"OPENROUTER_API_KEY"
|
|
]
|
|
if os.environ.get("SERPER_API_KEY"):
|
|
overrides["search.engine.web.serper.api_key"] = os.environ[
|
|
"SERPER_API_KEY"
|
|
]
|
|
|
|
settings = create_settings_snapshot(overrides=overrides)
|
|
|
|
# Build kwargs
|
|
kwargs = {
|
|
"query": query,
|
|
"provider": args.provider,
|
|
"search_tool": args.search_tool,
|
|
"settings_snapshot": settings,
|
|
"programmatic_mode": True,
|
|
"search_strategy": args.strategy,
|
|
}
|
|
if model_name:
|
|
kwargs["model_name"] = model_name
|
|
if args.iterations is not None:
|
|
kwargs["iterations"] = args.iterations
|
|
|
|
result = quick_summary(**kwargs)
|
|
|
|
# Use formatted_findings if available (already properly formatted with sources)
|
|
# Fall back to summary if not
|
|
research_output = result.get("formatted_findings") or result.get(
|
|
"summary", str(result)
|
|
)
|
|
|
|
# Build output - make sure everything is JSON serializable
|
|
output = {
|
|
"research": research_output,
|
|
"sources": make_serializable(result.get("sources", [])),
|
|
"findings": make_serializable(result.get("findings", [])),
|
|
"iterations": result.get("iterations"),
|
|
}
|
|
|
|
print(json.dumps(output))
|
|
|
|
except Exception as e:
|
|
print(json.dumps({"error": str(e)}))
|
|
sys.exit(1)
|
|
finally:
|
|
try:
|
|
sys.stdout.flush()
|
|
except Exception: # noqa: silent-exception
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|