Metadata-Version: 2.1
Name: perplexity-at-home
Version: 0.1.1
Summary: Perplexity-style local research agents built with LangGraph and Tavily
Author-Email: Will <you@example.com>
License: MIT
Project-URL: Homepage, https://github.com/pr1m8/perplexity-at-home
Project-URL: Repository, https://github.com/pr1m8/perplexity-at-home
Project-URL: Issues, https://github.com/pr1m8/perplexity-at-home/issues
Project-URL: Documentation, https://perplexity-at-home.readthedocs.io/
Project-URL: Changelog, https://github.com/pr1m8/perplexity-at-home/releases
Requires-Python: <3.14,>=3.13
Requires-Dist: requests>=2.32.5
Requires-Dist: urllib3>=2.6.3
Requires-Dist: charset-normalizer>=3.4.4
Requires-Dist: neo4j>=6.1.0
Requires-Dist: alembic>=1.18.4
Requires-Dist: pydantic>=2.12.5
Requires-Dist: pydantic-settings>=2.13.1
Requires-Dist: sqlalchemy[asyncio]>=2.0.49
Requires-Dist: fastapi>=0.135.3
Requires-Dist: uvicorn[standard]>=0.44.0
Requires-Dist: sse-starlette>=3.3.4
Requires-Dist: pyrate-limiter>=4.1.0
Requires-Dist: tenacity>=9.1.4
Requires-Dist: psycopg[binary,pool]
Requires-Dist: opentelemetry-api>=1.40.0
Requires-Dist: opentelemetry-sdk>=1.40.0
Requires-Dist: opentelemetry-exporter-otlp>=1.40.0
Requires-Dist: opentelemetry-instrumentation>=0.61b0
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.61b0
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.61b0
Requires-Dist: opentelemetry-instrumentation-psycopg>=0.61b0
Requires-Dist: opentelemetry-instrumentation-logging>=0.61b0
Requires-Dist: prometheus-client>=0.24.1
Requires-Dist: temporalio[opentelemetry]>=1.24.0
Requires-Dist: minio>=7.2.20
Requires-Dist: fastmcp==3.0.2
Requires-Dist: langchain>=1.2.15
Requires-Dist: unstructured>=0.21.5
Provides-Extra: app
Requires-Dist: fastapi>=0.135.3; extra == "app"
Requires-Dist: uvicorn[standard]>=0.44.0; extra == "app"
Requires-Dist: sse-starlette>=3.3.4; extra == "app"
Requires-Dist: pyrate-limiter>=4.1.0; extra == "app"
Requires-Dist: tenacity>=9.1.4; extra == "app"
Requires-Dist: pydantic>=2.12.5; extra == "app"
Requires-Dist: pydantic-settings>=2.13.1; extra == "app"
Provides-Extra: async
Requires-Dist: anyio>=4.13.0; extra == "async"
Requires-Dist: httpx>=0.28.1; extra == "async"
Requires-Dist: redis>=7.4.0; extra == "async"
Requires-Dist: aiofiles>=25.1.0; extra == "async"
Requires-Dist: asgi-lifespan>=2.1.0; extra == "async"
Requires-Dist: watchfiles>=1.1.1; extra == "async"
Provides-Extra: db
Requires-Dist: sqlalchemy[asyncio]>=2.0.49; extra == "db"
Requires-Dist: alembic>=1.18.4; extra == "db"
Requires-Dist: psycopg[binary,pool]; extra == "db"
Provides-Extra: worker
Requires-Dist: celery>=5.6.3; extra == "worker"
Requires-Dist: kombu>=5.6.2; extra == "worker"
Provides-Extra: worker-redis
Requires-Dist: arq>=0.25.0; extra == "worker-redis"
Provides-Extra: workflow
Requires-Dist: temporalio[opentelemetry]>=1.24.0; extra == "workflow"
Provides-Extra: storage
Requires-Dist: minio>=7.2.20; extra == "storage"
Provides-Extra: storage-async
Requires-Dist: aioboto3>=15.5.0; extra == "storage-async"
Requires-Dist: aiobotocore==2.25.1; extra == "storage-async"
Provides-Extra: redis
Requires-Dist: redis>=7.4.0; extra == "redis"
Provides-Extra: logging
Requires-Dist: rich>=14.3.3; extra == "logging"
Requires-Dist: structlog>=25.5.0; extra == "logging"
Provides-Extra: observability
Requires-Dist: opentelemetry-api>=1.40.0; extra == "observability"
Requires-Dist: opentelemetry-sdk>=1.40.0; extra == "observability"
Requires-Dist: opentelemetry-exporter-otlp>=1.40.0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-asgi>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-psycopg>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-logging>=0.61b0; extra == "observability"
Requires-Dist: prometheus-client>=0.24.1; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-redis>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-httpx>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-celery>=0.61b0; extra == "observability"
Requires-Dist: opentelemetry-instrumentation-system-metrics>=0.61b0; extra == "observability"
Requires-Dist: prometheus-fastapi-instrumentator>=7.1.0; extra == "observability"
Provides-Extra: observability-extra
Requires-Dist: opentelemetry-instrumentation-urllib3>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-botocore>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-grpc>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-pika>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-aio-pika>=0.61b0; extra == "observability-extra"
Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.61b0; extra == "observability-extra"
Provides-Extra: cli
Requires-Dist: typer<0.22.0,>=0.12.5; extra == "cli"
Requires-Dist: rich>=14.3.3; extra == "cli"
Provides-Extra: tui
Requires-Dist: textual>=8.2.3; extra == "tui"
Provides-Extra: perf
Requires-Dist: msgspec>=0.20.0; extra == "perf"
Provides-Extra: dashboard
Requires-Dist: streamlit; extra == "dashboard"
Requires-Dist: pandas; extra == "dashboard"
Requires-Dist: pyarrow; extra == "dashboard"
Requires-Dist: plotly; extra == "dashboard"
Requires-Dist: duckdb; extra == "dashboard"
Requires-Dist: altair; extra == "dashboard"
Requires-Dist: streamlit-extras; extra == "dashboard"
Requires-Dist: folium; extra == "dashboard"
Requires-Dist: streamlit-folium; extra == "dashboard"
Requires-Dist: streamlit-aggrid; extra == "dashboard"
Requires-Dist: streamlit-echarts[pyecharts]; extra == "dashboard"
Provides-Extra: core
Requires-Dist: langchain>=1.2.10; extra == "core"
Requires-Dist: langchain-core>=1.2.17; extra == "core"
Requires-Dist: langchain-community>=0.4.1; extra == "core"
Requires-Dist: langchain-text-splitters>=1.1.1; extra == "core"
Requires-Dist: langgraph>=1.0.10; extra == "core"
Requires-Dist: langsmith>=0.7.11; extra == "core"
Requires-Dist: pydantic>=2.12.5; extra == "core"
Requires-Dist: pydantic-settings>=2.13.1; extra == "core"
Requires-Dist: tiktoken>=0.12.0; extra == "core"
Provides-Extra: runtime
Requires-Dist: deepagents>=0.4.3; extra == "runtime"
Requires-Dist: deepagents-acp>=0.0.4; extra == "runtime"
Requires-Dist: langmem>=0.0.30; extra == "runtime"
Requires-Dist: langgraph-checkpoint-postgres>=2.0.0; extra == "runtime"
Requires-Dist: yfinance>=1.2.0; extra == "runtime"
Requires-Dist: fredapi>=0.5.2; extra == "runtime"
Requires-Dist: statsmodels>=0.14.6; extra == "runtime"
Requires-Dist: arch>=8.0.0; extra == "runtime"
Requires-Dist: pykalman>=0.11.2; extra == "runtime"
Requires-Dist: hmmlearn>=0.3.3; extra == "runtime"
Requires-Dist: ruptures>=1.1.10; extra == "runtime"
Requires-Dist: hurst>=0.0.5; extra == "runtime"
Requires-Dist: scikit-learn>=1.8.0; extra == "runtime"
Requires-Dist: matplotlib>=3.10.0; extra == "runtime"
Requires-Dist: seaborn>=0.13.0; extra == "runtime"
Requires-Dist: httpx>=0.28.0; extra == "runtime"
Requires-Dist: markdownify>=1.0.0; extra == "runtime"
Requires-Dist: python-dotenv>=1.0.0; extra == "runtime"
Requires-Dist: sentence-transformers>=3.0.0; extra == "runtime"
Requires-Dist: PyWavelets>=1.8.0; extra == "runtime"
Provides-Extra: runtime-http
Requires-Dist: starlette>=0.52.1; extra == "runtime-http"
Requires-Dist: uvicorn>=0.41.0; extra == "runtime-http"
Provides-Extra: langgraph-runtime
Requires-Dist: langgraph-cli[inmem]>=0.4.14; extra == "langgraph-runtime"
Requires-Dist: langgraph-api>=0.7.62; extra == "langgraph-runtime"
Requires-Dist: langgraph-sdk>=0.3.9; extra == "langgraph-runtime"
Requires-Dist: langgraph-prebuilt>=1.0.8; extra == "langgraph-runtime"
Requires-Dist: langgraph-runtime-inmem>=0.26.0; extra == "langgraph-runtime"
Requires-Dist: langgraph-executor>=0.0.1a16; extra == "langgraph-runtime"
Provides-Extra: langgraph-extras
Requires-Dist: langgraph-supervisor>=0.0.31; extra == "langgraph-extras"
Requires-Dist: langgraph-swarm>=0.1.0; extra == "langgraph-extras"
Requires-Dist: langgraph-bigtool>=0.0.3; extra == "langgraph-extras"
Requires-Dist: langmem>=0.0.30; extra == "langgraph-extras"
Provides-Extra: sandboxes
Requires-Dist: langchain-sandbox>=0.0.3; extra == "sandboxes"
Requires-Dist: deepagents>=0.4.3; extra == "sandboxes"
Requires-Dist: deepagents-cli>=0.0.25; extra == "sandboxes"
Requires-Dist: deepagents-acp>=0.0.4; extra == "sandboxes"
Requires-Dist: langchain-daytona>=0.0.2; extra == "sandboxes"
Requires-Dist: langchain-modal>=0.0.1; extra == "sandboxes"
Requires-Dist: langchain-runloop>=0.0.2; extra == "sandboxes"
Provides-Extra: mcp
Requires-Dist: mcp>=1.26.0; extra == "mcp"
Requires-Dist: langchain-mcp-adapters>=0.2.1; extra == "mcp"
Requires-Dist: langsmith-mcp-server>=0.1.1; extra == "mcp"
Requires-Dist: mcpdoc>=0.0.10; extra == "mcp"
Requires-Dist: o2mcp>=0.0.3; extra == "mcp"
Requires-Dist: fastmcp==3.0.2; extra == "mcp"
Provides-Extra: hub
Requires-Dist: streamlit>=1.40.0; extra == "hub"
Requires-Dist: httpx>=0.28.0; extra == "hub"
Provides-Extra: jupyter
Requires-Dist: jupyter>=1.0; extra == "jupyter"
Requires-Dist: notebook; extra == "jupyter"
Requires-Dist: jupyterlab; extra == "jupyter"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
Requires-Dist: mkdocs-material>=9.6.14; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.30.1; extra == "docs"
Requires-Dist: pymdown-extensions>=10.16.1; extra == "docs"
Provides-Extra: providers-a
Requires-Dist: langchain-openai>=1.1.10; extra == "providers-a"
Requires-Dist: langchain-anthropic>=1.3.4; extra == "providers-a"
Requires-Dist: langchain-google-genai>=4.2.1; extra == "providers-a"
Requires-Dist: langchain-google-vertexai>=3.2.2; extra == "providers-a"
Requires-Dist: langchain-google-community>=3.0.5; extra == "providers-a"
Requires-Dist: langchain-azure-ai>=1.0.0; extra == "providers-a"
Requires-Dist: langchain-mistralai>=1.1.1; extra == "providers-a"
Requires-Dist: langchain-ollama>=1.0.1; extra == "providers-a"
Requires-Dist: langchain-openrouter>=0.0.2; extra == "providers-a"
Requires-Dist: langchain-litellm>=0.6.1; extra == "providers-a"
Provides-Extra: providers-b
Requires-Dist: databricks-langchain>=0.16.1; extra == "providers-b"
Requires-Dist: langchain-deepseek>=1.0.1; extra == "providers-b"
Provides-Extra: hf-local
Requires-Dist: langchain-huggingface>=1.2.1; extra == "hf-local"
Requires-Dist: huggingface-hub>=0.36.2; extra == "hf-local"
Requires-Dist: transformers>=4.57.6; extra == "hf-local"
Requires-Dist: datasets>=4.6.1; extra == "hf-local"
Requires-Dist: accelerate>=1.13.0; extra == "hf-local"
Requires-Dist: sentence-transformers>=5.2.3; extra == "hf-local"
Requires-Dist: sentencepiece>=0.2.1; extra == "hf-local"
Requires-Dist: safetensors>=0.7.0; extra == "hf-local"
Requires-Dist: tokenizers>=0.22.2; extra == "hf-local"
Requires-Dist: hf-transfer>=0.1.9; extra == "hf-local"
Requires-Dist: hf-xet>=1.3.2; extra == "hf-local"
Requires-Dist: peft>=0.18.1; extra == "hf-local"
Provides-Extra: hf-local-bnb
Requires-Dist: bitsandbytes>=0.49.2; sys_platform == "linux" and platform_machine == "x86_64" and extra == "hf-local-bnb"
Provides-Extra: loaders-docling
Requires-Dist: langchain-docling>=2.0.0; extra == "loaders-docling"
Requires-Dist: docling>=2.76.0; extra == "loaders-docling"
Provides-Extra: loaders-unstructured
Requires-Dist: langchain-unstructured>=1.0.1; extra == "loaders-unstructured"
Requires-Dist: unstructured>=0.21.5; extra == "loaders-unstructured"
Provides-Extra: loaders-pdf-advanced
Requires-Dist: langchain-pymupdf4llm>=0.5.0; extra == "loaders-pdf-advanced"
Requires-Dist: pymupdf4llm>=0.1.9; extra == "loaders-pdf-advanced"
Requires-Dist: pdfplumber>=0.11.9; extra == "loaders-pdf-advanced"
Requires-Dist: pdfminer-six>=20251230; extra == "loaders-pdf-advanced"
Requires-Dist: pypdfium2>=5.5.0; extra == "loaders-pdf-advanced"
Provides-Extra: loaders-browser
Requires-Dist: playwright>=1.58.0; extra == "loaders-browser"
Requires-Dist: selenium>=4.41.0; extra == "loaders-browser"
Provides-Extra: loaders-web
Requires-Dist: beautifulsoup4>=4.14.3; extra == "loaders-web"
Requires-Dist: lxml>=6.0.2; extra == "loaders-web"
Requires-Dist: trafilatura>=2.0.0; extra == "loaders-web"
Requires-Dist: readability-lxml>=0.8.4.1; extra == "loaders-web"
Requires-Dist: httpx>=0.28.1; extra == "loaders-web"
Requires-Dist: aiohttp>=3.13.3; extra == "loaders-web"
Requires-Dist: tldextract>=5.3.1; extra == "loaders-web"
Provides-Extra: loaders-firecrawl
Requires-Dist: firecrawl-py>=4.18.0; extra == "loaders-firecrawl"
Provides-Extra: websearch
Requires-Dist: langchain-tavily>=0.2.17; extra == "websearch"
Requires-Dist: tavily-python>=0.7.22; extra == "websearch"
Requires-Dist: langchain-exa>=1.0.0; extra == "websearch"
Requires-Dist: exa-py>=1.16.1; extra == "websearch"
Requires-Dist: duckduckgo-search>=8.1.1; extra == "websearch"
Requires-Dist: langchain-google-community>=3.0.5; extra == "websearch"
Requires-Dist: google-api-python-client>=2.191.0; extra == "websearch"
Requires-Dist: google-search-results>=2.4.2; extra == "websearch"
Provides-Extra: research-scholarly
Requires-Dist: arxiv>=2.4.0; extra == "research-scholarly"
Requires-Dist: pymupdf>=1.27.1; extra == "research-scholarly"
Requires-Dist: semanticscholar>=0.11.0; extra == "research-scholarly"
Requires-Dist: pyalex>=0.21; extra == "research-scholarly"
Requires-Dist: crossrefapi>=1.7.0; extra == "research-scholarly"
Requires-Dist: habanero>=2.3.0; extra == "research-scholarly"
Requires-Dist: scholarly>=1.7.11; extra == "research-scholarly"
Requires-Dist: wikipedia>=1.4.0; extra == "research-scholarly"
Provides-Extra: web-crawl
Requires-Dist: scrapy>=2.14.1; extra == "web-crawl"
Requires-Dist: scrapy-playwright>=0.0.46; extra == "web-crawl"
Provides-Extra: retrieval-rerank
Requires-Dist: flashrank>=0.2.10; extra == "retrieval-rerank"
Provides-Extra: retrieval-bm25
Requires-Dist: rank-bm25>=0.2.2; extra == "retrieval-bm25"
Requires-Dist: nltk>=3.9.3; extra == "retrieval-bm25"
Provides-Extra: neo4j-driver
Requires-Dist: neo4j>=6.1.0; extra == "neo4j-driver"
Provides-Extra: neo4j
Requires-Dist: langchain-neo4j>=0.8.0; extra == "neo4j"
Requires-Dist: langchain-experimental>=0.4.1; extra == "neo4j"
Requires-Dist: perplexity-at-home[neo4j-driver]; extra == "neo4j"
Provides-Extra: vectordb-clients-a
Requires-Dist: chromadb>=1.5.2; extra == "vectordb-clients-a"
Requires-Dist: sqlalchemy>=2.0.48; extra == "vectordb-clients-a"
Requires-Dist: asyncpg>=0.31.0; extra == "vectordb-clients-a"
Requires-Dist: psycopg[binary]>=3.3.3; extra == "vectordb-clients-a"
Requires-Dist: qdrant-client>=1.17.0; extra == "vectordb-clients-a"
Requires-Dist: weaviate-client>=4.20.1; extra == "vectordb-clients-a"
Requires-Dist: pymilvus>=2.6.9; extra == "vectordb-clients-a"
Requires-Dist: pinecone>=8.1.0; extra == "vectordb-clients-a"
Requires-Dist: elasticsearch>=9.3.0; extra == "vectordb-clients-a"
Requires-Dist: pgvector<0.4,>=0.3.1; extra == "vectordb-clients-a"
Requires-Dist: faiss-cpu>=1.13.2; extra == "vectordb-clients-a"
Requires-Dist: hnswlib>=0.8.0; extra == "vectordb-clients-a"
Requires-Dist: annoy>=1.17.3; extra == "vectordb-clients-a"
Requires-Dist: scann>=1.4.2; platform_system == "Linux" and platform_machine == "x86_64" and extra == "vectordb-clients-a"
Provides-Extra: vectordb-clients-b
Requires-Dist: redis>=7.2.1; extra == "vectordb-clients-b"
Requires-Dist: pymongo>=4.16.0; extra == "vectordb-clients-b"
Requires-Dist: astrapy>=2.1.0; extra == "vectordb-clients-b"
Requires-Dist: couchbase>=4.5.0; extra == "vectordb-clients-b"
Requires-Dist: snowflake-connector-python>=4.3.0; extra == "vectordb-clients-b"
Requires-Dist: ibm-db>=3.2.8; extra == "vectordb-clients-b"
Requires-Dist: pyodbc>=5.3.0; extra == "vectordb-clients-b"
Requires-Dist: neo4j>=6.1.0; extra == "vectordb-clients-b"
Provides-Extra: dev
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
Requires-Dist: commitizen>=3.18.3; extra == "dev"
Requires-Dist: nox>=2026.2.9; extra == "dev"
Requires-Dist: tox>=4.27.0; extra == "dev"
Requires-Dist: yamlfix>=1.19.1; extra == "dev"
Provides-Extra: lint
Requires-Dist: ruff>=0.15.4; extra == "lint"
Requires-Dist: pylint>=4.0.5; extra == "lint"
Requires-Dist: vulture>=2.14; extra == "lint"
Requires-Dist: radon>=6.0.1; extra == "lint"
Requires-Dist: xenon>=0.9.3; extra == "lint"
Provides-Extra: typecheck
Requires-Dist: mypy>=1.19.1; extra == "typecheck"
Requires-Dist: pyright>=1.1.408; extra == "typecheck"
Provides-Extra: typecov
Requires-Dist: mypy>=1.19.1; extra == "typecov"
Requires-Dist: interrogate>=1.7.0; extra == "typecov"
Provides-Extra: test
Requires-Dist: pytest>=9.0.2; extra == "test"
Requires-Dist: pytest-asyncio>=1.3.0; extra == "test"
Requires-Dist: pytest-cov>=7.1.0; extra == "test"
Requires-Dist: respx>=0.22.0; extra == "test"
Requires-Dist: hypothesis>=6.151.11; extra == "test"
Requires-Dist: pytest-xdist>=3.8.0; extra == "test"
Requires-Dist: pytest-timeout>=2.4.0; extra == "test"
Requires-Dist: pytest-rerunfailures>=16.1; extra == "test"
Requires-Dist: syrupy>=5.1.0; extra == "test"
Requires-Dist: pytest-mock>=3.15.1; extra == "test"
Requires-Dist: typeguard>=4.5.1; extra == "test"
Requires-Dist: pytest-sugar>=1.1.1; extra == "test"
Requires-Dist: pytest-instafail>=0.5.0; extra == "test"
Requires-Dist: pytest-clarity>=1.0.1; extra == "test"
Requires-Dist: pytest-icdiff>=0.9; extra == "test"
Requires-Dist: pytest-richer>=0.1.1; extra == "test"
Provides-Extra: report
Requires-Dist: pytest-html>=4.2.0; extra == "report"
Requires-Dist: diff-cover>=10.2.0; extra == "report"
Requires-Dist: genbadge>=1.1.3; extra == "report"
Requires-Dist: coverage-badge>=1.1.2; extra == "report"
Requires-Dist: codecov>=2.1.13; extra == "report"
Requires-Dist: coveralls>=4.1.0; extra == "report"
Requires-Dist: pytest-html-plus>=0.5.0; extra == "report"
Requires-Dist: pytest-md>=0.2.0; extra == "report"
Requires-Dist: allure-pytest>=2.15.3; extra == "report"
Provides-Extra: coverage
Requires-Dist: coverage>=7.13.4; extra == "coverage"
Provides-Extra: security
Requires-Dist: bandit>=1.9.4; extra == "security"
Requires-Dist: pip-audit>=2.10.0; extra == "security"
Requires-Dist: detect-secrets>=1.5.0; extra == "security"
Requires-Dist: safety>=3.7.0; extra == "security"
Provides-Extra: otel
Requires-Dist: opentelemetry-distro[otlp]>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-api>=1.39.1; extra == "otel"
Requires-Dist: opentelemetry-sdk>=1.39.1; extra == "otel"
Requires-Dist: opentelemetry-semantic-conventions>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.39.1; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.39.1; extra == "otel"
Requires-Dist: opentelemetry-exporter-prometheus>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-logging>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-urllib>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-urllib3>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-asgi>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-starlette>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-wsgi>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-grpc>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-psycopg2>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-redis>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-system-metrics>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-threading>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-click>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-resource-detector-containerid>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-exporter-richconsole>=0.60b1; extra == "otel"
Requires-Dist: opentelemetry-propagator-aws-xray>=1.0.2; extra == "otel"
Requires-Dist: opentelemetry-sdk-extension-aws>=2.1.0; extra == "otel"
Requires-Dist: opentelemetry-util-genai>=0.3b0; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-openai-v2>=2.3b0; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-openai-agents-v2>=0.1.0; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-mcp>=0.52.6; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.52.6; extra == "otel"
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.52.6; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp>=1.39.1; extra == "otel"
Description-Content-Type: text/markdown

# perplexity-at-home

[![CI](https://github.com/pr1m8/perplexity-at-home/actions/workflows/ci.yml/badge.svg)](https://github.com/pr1m8/perplexity-at-home/actions/workflows/ci.yml)
[![Docs](https://github.com/pr1m8/perplexity-at-home/actions/workflows/docs.yml/badge.svg)](https://github.com/pr1m8/perplexity-at-home/actions/workflows/docs.yml)
[![Live E2E](https://github.com/pr1m8/perplexity-at-home/actions/workflows/e2e.yml/badge.svg)](https://github.com/pr1m8/perplexity-at-home/actions/workflows/e2e.yml)
[![Read the Docs](https://img.shields.io/readthedocs/perplexity-at-home)](https://perplexity-at-home.readthedocs.io/)
[![PyPI](https://img.shields.io/pypi/v/perplexity-at-home)](https://pypi.org/project/perplexity-at-home/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/perplexity-at-home)](https://pypi.org/project/perplexity-at-home/)
[![License](https://img.shields.io/github/license/pr1m8/perplexity-at-home)](LICENSE)

`perplexity-at-home` is an open-source, Perplexity-style research runtime built
with LangGraph, Tavily, OpenAI, and optional Postgres persistence. It ships
three distinct research modes, defaults to `openai:gpt-5.4`, exposes a packaged
Streamlit dashboard, and keeps runtime configuration centralized in Pydantic
Settings.

The point of the package is simple: quick answers, broader synthesis, and real
deep research should not be the same graph.

Think of the package as a ladder:
`quick-search -> pro-search -> deep-research`.
Each lane adds more planning, source coverage, and synthesis depth.

## Why This Exists

- `quick-search` for fast cited answers
- `pro-search` for broader multi-source synthesis
- `deep-research` for iterative report-style investigation
- one package surface for CLI, dashboard, and LangGraph runtime entrypoints
- optional Postgres-backed persistence for long-running threads and reloadable state

## One-Minute Tour

```bash
make setup
make quick QUESTION="What changed in LangGraph recently?"
make pro QUESTION="Compare Tavily and Exa for agent retrieval."
make deep QUESTION="Research the tradeoffs between Tavily, Exa, and Perplexity."
make dashboard
```

## Research Lanes

| Workflow        | Shape                                                           | Best for                     | Output                                      |
| --------------- | --------------------------------------------------------------- | ---------------------------- | ------------------------------------------- |
| `quick-search`  | `query -> search -> fetch -> summarize -> answer`               | fast factual questions       | concise markdown answer with citations      |
| `pro-search`    | `clarify -> decompose -> parallel search -> read -> synthesize` | broader web-backed synthesis | structured markdown answer                  |
| `deep-research` | `scope -> plan -> retrieve/read/analyze -> loop -> report`      | report-style investigation   | long-form brief with evidence and citations |

## Example Questions

- `quick-search`: `What changed in LangGraph recently?`
- `pro-search`: `Compare Tavily and Exa for agent retrieval.`
- `deep-research`: `Research best practices for packaging a multi-workflow research agent.`

Runnable demos also live under [`examples/`](examples/), including
[`examples/quick_search_demo.py`](examples/quick_search_demo.py),
[`examples/pro_search_answer_demo.py`](examples/pro_search_answer_demo.py), and
[`examples/deep_research_demo.py`](examples/deep_research_demo.py).

## System Map

```mermaid
flowchart LR
    U[User question] --> S{Surface}
    S --> CLI[CLI]
    S --> DASH[Streamlit dashboard]
    S --> LG[LangGraph runtime]

    CLI --> W{Workflow}
    DASH --> W
    LG --> W

    W --> QS[Quick Search]
    W --> PS[Pro Search]
    W --> DR[Deep Research]

    QS --> LLM[GPT-5.4 family]
    PS --> LLM
    DR --> LLM

    QS --> TV[Tavily tools]
    PS --> TV
    DR --> TV

    QS -->|optional| P[(Postgres store + checkpointer)]
    PS -->|optional| P
    DR -->|optional| P

    QS -->|optional| LS[LangSmith tracing]
    PS -->|optional| LS
    DR -->|optional| LS
```

## Workflow Graphs

The diagrams below are the public workflow shapes used by the package docs and
dashboard. They intentionally show the product-level flow, not every internal
node name.

### Quick Search

```mermaid
flowchart LR
    Q[Question] --> QUERY[Frame focused query]
    QUERY --> SEARCH[Search the web]
    SEARCH --> FETCH[Fetch or extract the best source]
    FETCH --> SUMMARIZE[Summarize the evidence]
    SUMMARIZE --> ANSWER[Return a fast cited answer]
```

`quick-search` stays intentionally thin. The implementation is still a compact
single-agent surface, but the mental model is shallow retrieval:
`query -> search -> fetch -> summarize -> answer`.

### Pro Search

```mermaid
flowchart TD
    Q[Question] --> CHECK[Complexity check]
    CHECK --> CLARIFY{Need clarification or refinement?}
    CLARIFY -->|yes| REFINE[Refine scope]
    CLARIFY -->|no| DECOMP[Decompose question]
    REFINE --> DECOMP
    DECOMP --> SEARCH[Run parallel searches]
    SEARCH --> READ[Read the strongest sources]
    READ --> AGGREGATE[Aggregate evidence]
    AGGREGATE --> SYNTHESIZE[Synthesize grounded answer]
    SYNTHESIZE --> ANSWER[Markdown answer with citations]
```

`pro-search` is the middle lane. In code today it is a tight deterministic
graph around planning, batched Tavily execution, aggregation, and synthesis.
At the package level, the intended flow is:
`query -> complexity/refinement -> decomposition -> parallel search -> read -> aggregate -> synthesize`.

### Deep Research

```mermaid
flowchart TD
    Q[Question] --> SCOPE[Scope check]
    SCOPE -->|needs clarification| CLARIFY[Clarify request]
    SCOPE -->|ready| PLAN[Build research plan]
    PLAN --> SUBQ[Generate subquestions]

    subgraph Retrieval Loop
        SUBQ --> SEARCH[Search and retrieve]
        SEARCH --> READ[Read and extract evidence]
        READ --> ANALYZE[Analyze gaps and confidence]
        ANALYZE -->|coverage incomplete| SEARCH
    end

    ANALYZE -->|coverage sufficient| REPORT[Write final report]
```

`deep-research` is the real DAG-heavy lane. The concrete graph routes follow-up
work through retrieval strategies such as `requery`, `extract`, `map`,
`crawl`, and `research`, but the public shape is the iterative research loop
from the design notes: scope, plan, search, read, analyze, repeat, then write.

## How To Run

Install the package dependencies:

```bash
make setup
```

See the local command surface at any time with:

```bash
make help
```

Minimal environment:

- `OPENAI_API_KEY`
- `TAVILY_API_KEY`
- `PERPLEXITY_AT_HOME_DEFAULT_MODEL` if you want to override `openai:gpt-5.4`

Run each lane:

```bash
make quick QUESTION="What is Tavily?"
make pro QUESTION="What changed recently in Tavily's LangChain integration?"
make deep QUESTION="Compare Tavily, Exa, and Perplexity for agent retrieval."
```

Turn on durable state:

```bash
make up
make db-setup
make deep-persistent QUESTION="What is Tavily?"
```

Launch the dashboard:

```bash
make dashboard
```

You can also use the packaged CLI directly:

```bash
pdm run perplexity-at-home quick-search "What is Tavily?"
pdm run perplexity-at-home pro-search "Compare Tavily and Exa for agent retrieval."
pdm run perplexity-at-home deep-research "Research the current LangGraph persistence story."
```

The dashboard is built around workflow visibility: research output, sources,
workflow graph, and run-state inspection. It is state-first today, not a
token-stream demo surface pretending to be an agent runtime.

## Dashboard Flow

Use this when you want the shortest local path:

```bash
make setup
make dashboard
```

If you want persistent runs in the dashboard:

```bash
make up
make db-setup
make dashboard
```

Inside the dashboard:

- pick `quick-search` first if you just want to smoke-test the app
- leave persistence off unless Postgres is already up
- use `pro-search` or `deep-research` once keys are confirmed working
- create a new thread when switching question families
- inspect `Sources`, `Workflow Graph`, and `Run State` after each run

## Settings, Persistence, and Runtime Surfaces

- `src/perplexity_at_home/settings.py` owns OpenAI, Tavily, LangSmith, model, and nested Postgres settings.
- `src/perplexity_at_home/core/` owns the async LangGraph store, checkpointer, and persistence wrapper.
- `langgraph.json` exposes `quick_search`, `pro_search`, and `deep_research`, plus the custom store and checkpointer entrypoints.
- Workflow-specific model overrides are supported with settings such as `PERPLEXITY_AT_HOME_QUICK_SEARCH_MODEL` and `PERPLEXITY_AT_HOME_DEEP_RESEARCH_RETRIEVAL_MODEL`.

## Verified Paths

Live runs were re-verified locally on **April 23, 2026** against real OpenAI,
Tavily, and Postgres:

- `quick-search` completed in memory.
- `pro-search` completed in memory.
- `deep-research` completed in memory.
- `deep-research --persistent --setup-persistence` completed against Postgres.

The repository now also includes a gated live E2E suite plus a GitHub Actions
workflow for it:

```bash
make test-e2e
```

The live suite is opt-in through `PERPLEXITY_AT_HOME_RUN_E2E=true` so normal CI
stays fast and deterministic.

## Repository Layout

```text
src/perplexity_at_home/
  agents/
    quick_search/
    pro_search/
    deep_research/
  core/                 # persistence + serializer helpers
  dashboard/            # packaged Streamlit app
  tools/                # Tavily factories and normalization
  settings.py           # Pydantic settings + model selection
  cli.py                # package CLI
docs/                   # MkDocs + Read the Docs
examples/               # runnable demos
infra/                  # local Docker Compose
tests/                  # unit, integration, and gated live E2E tests
```

## Docs, Release, and Quality Gates

- Docs build with MkDocs Material and publish through Read the Docs.
- GitHub Actions cover CI, docs, live E2E, and release publishing.
- `pdm build` produces the wheel and source distribution for PyPI.
- `make lint`, `make test`, `make docs-build`, and `make release-check` are the main local gates.

## Releasing To PyPI

The canonical release path is a version tag from `main`:

```bash
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
```

The `Release` workflow then:

- installs the locked environment
- runs Ruff, pytest, and the docs build
- builds the wheel and sdist
- runs `twine check`
- publishes to PyPI through GitHub trusted publishing
- creates the matching GitHub release

There is also a manual `workflow_dispatch` path for release preflight checks
when you want to validate the release job without pushing a tag.

Full package docs live at <https://perplexity-at-home.readthedocs.io/>.
