Spaces:
Runtime error
Runtime error
| title: Vantage | |
| emoji: π | |
| colorFrom: blue | |
| colorTo: indigo | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| # Vantage β Agentic Market Intelligence | |
| Vantage is an agentic RAG system that surfaces **emerging content gaps** β topics the | |
| market is demanding but your corpus barely covers. It fuses live demand signals with | |
| vector coverage and a knowledge graph, then lets a LangGraph agent reason over all three. | |
| - **Google Trends signals** β demand volume + velocity per topic | |
| - **Qdrant** β hybrid (dense + sparse) vector search over a ~200k-doc corpus | |
| - **Neo4j** β knowledge graph of `(:Topic)-[:COVERED_BY]->(:Document)` edges | |
| - **LangGraph agent** β orchestrates gap analysis, RAG search, and signals with multi-turn memory | |
| **Gap score** = `Demand Γ (1 β Coverage)` β high demand, low coverage β a gap worth filling. | |
| --- | |
| ## Architecture | |
| Vantage runs as two planes that share the same core library and the same two databases: | |
| an **offline pipeline** that *populates* Qdrant + Neo4j, and an **online service** that only | |
| *reads* them. The app never writes corpus data at request time β it serves what the pipeline | |
| already built. | |
| ### Serving plane (what runs at request time) | |
| ```mermaid | |
| flowchart LR | |
| User([Browser]) --> Web[React + Vite UI] | |
| Web -->|REST /api| API[FastAPI<br/>chat Β· gaps Β· graph Β· signals] | |
| API --> Agent[LangGraph ReAct agent] | |
| Agent -->|rag_search| RAG[Hybrid retrieval<br/>+ rerank] | |
| Agent -->|gap_analysis Β· trending_topics<br/>coverage_lookup Β· graph_lookup| GRAPH[(Neo4j<br/>TopicβDocument)] | |
| Agent -->|estimate_gap| LIVE[Live Trends + coverage] | |
| RAG --> QDRANT[(Qdrant<br/>dense + sparse)] | |
| RAG --> OPENAI{{OpenAI<br/>gpt-4o-mini}} | |
| LIVE --> TRENDS{{Google Trends}} | |
| LIVE --> QDRANT | |
| ``` | |
| **Request lifecycle:** the UI calls a FastAPI route β the agent picks tools from their | |
| docstrings β each tool runs through a **domain port** (`rag`, `retrieval`, `gap`, `coverage`, | |
| `topic_index`) backed by an **adapter** (Qdrant, Neo4j, OpenAI, sentence-transformers, | |
| pytrends) β the agent synthesises a grounded answer, citing sources and surfacing the numbers. | |
| ### Ingestion plane (offline β run once, before serving) | |
| ```mermaid | |
| flowchart LR | |
| SEEDS[build_gartner_seeds<br/>+ mine_gdelt_topics] --> TOPICS[(topic vocab)] | |
| CORPUS[download_data] --> INGEST[ingest.py<br/>chunk + embed] | |
| INGEST --> QDRANT[(Qdrant)] | |
| TOPICS --> TAG[tag_topics] --> COV[topic_coverage.csv] | |
| TOPICS --> TIDX[ingest_topics] --> QDRANT | |
| COV --> BUILD[build_graph<br/>coverage Γ signals Γ gap] --> NEO[(Neo4j)] | |
| ``` | |
| This plane is **bash/Unix-only** and is what Windows users cannot run (see the Windows note at the end). | |
| ### Layering | |
| All business logic lives in `packages/vantage_core` and follows a **hexagonal** design: | |
| | Layer | Location | Responsibility | | |
| |-------|----------|----------------| | |
| | **Ports** (domain) | `vantage_core/domain/` | What the app does β retrieval, RAG, gap & coverage scoring β independent of any vendor | | |
| | **Adapters** | `vantage_core/adapters/` | How it's done β Qdrant, Neo4j, OpenAI, sentence-transformers, pytrends | | |
| | **Agent** | `vantage_core/agent/` | LangGraph ReAct loop + the six tools that wrap the ports | | |
| | **Entry points** | `apps/api`, `scripts/` | Thin shells: the HTTP API and the pipeline/dev CLIs | | |
| --- | |
| ## Prerequisites | |
| You need accounts / endpoints for four external services (all have free tiers): | |
| | Service | Used for | Required | | |
| |----------------|---------------------------------|----------| | |
| | Qdrant Cloud | Vector search | β | | |
| | Neo4j AuraDB | Knowledge graph | β | | |
| | OpenAI | LLM (default `gpt-4o-mini`) | β | | |
| | Langfuse Cloud | LLM tracing | optional | | |
| Plus: **Python 3.11**, **Node 20+**, and (optionally) **Docker**. | |
| --- | |
| ## Setup | |
| ```bash | |
| # 1. Create the Python env (conda) and install the package + API deps | |
| conda env create -f environment.yaml # creates the 'py311' env | |
| conda activate py311 | |
| make install # pip install -e vantage_core + API reqs | |
| # 2. Configure credentials | |
| cp .env.example .env | |
| $EDITOR .env # fill in Qdrant / Neo4j / OpenAI keys | |
| # 3. Verify every external service is reachable (pass/fail gate) | |
| make healthcheck | |
| ``` | |
| `make healthcheck` pings Qdrant, Neo4j, OpenAI, and Langfuse and prints a colour-coded | |
| status line for each. Don't proceed until it's green. | |
| --- | |
| ## Data pipeline β run this once, in order | |
| This populates Qdrant and Neo4j. Every step is **idempotent** (safe to re-run). The app | |
| won't return anything until these have run, because `start.sh` only *serves* what's | |
| already in the databases. | |
| ```bash | |
| # ββ 1. TOPICS β build the topic vocabulary (the "get topics" step) ββββββββββββ | |
| python scripts/build_gartner_seeds.py # β seeds/topics.csv + data/topics_seed.json | |
| python scripts/mine_gdelt_topics.py # augment with fresh GDELT demand-side topics (BigQuery) | |
| # ββ 2. DATA β fetch the HuffPost corpus into ./data (gitignored) ββββββββββββββ | |
| bash scripts/download_data.sh # see script header for auth options | |
| # ββ 3. INGEST β chunk + embed docs β Qdrant ββββββββββββββββββββββββββββββββββ | |
| python scripts/ingest.py # honours INGEST_LIMIT (default 2000; 0 = full) | |
| python scripts/peek.py # verify: point count + sample payload | |
| # ββ 4. TAG β label each chunk with the topics it mentions βββββββββββββββββββββ | |
| python scripts/tag_topics.py # β topic_coverage.csv (read by step 6) | |
| # ββ 5. TOPIC INDEX β embed the vocabulary into its own collection βββββββββββββ | |
| python scripts/ingest_topics.py # β vantage_topics collection (semantic gap match) | |
| # ββ 6. GRAPH β coverage Γ signals Γ gap score β Neo4j βββββββββββββββββββββββββ | |
| python scripts/build_graph.py # builds (:Topic)-[:COVERED_BY]->(:Document) | |
| ``` | |
| > **Order matters at two points:** topics (1) must exist before tagging (4) and the topic | |
| > index (5); the graph (6) reads `topic_coverage.csv` from tagging (4). Everything else is | |
| > independent. To start the graph over, run `python scripts/wipe_graph.py` first. | |
| Quick sanity checks (read-only, no writes): | |
| ```bash | |
| python scripts/gap_report.py # ranked GapScore table | |
| python scripts/search.py "your query" # hybrid retrieval + rerank | |
| python scripts/ask.py "your question" # full RAG answer with citations | |
| python scripts/agent_cli.py # interactive agent REPL | |
| python scripts/demo_queries.py # 4 canned end-to-end agent queries | |
| ``` | |
| --- | |
| ## Run the app | |
| Once the pipeline above has populated the databases, start the services. **This is the | |
| last step.** | |
| ```bash | |
| make start # or: bash start.sh | |
| ``` | |
| - **API** β http://localhost:8000 (FastAPI + agent; logs in `api.log`) | |
| - **Web** β http://localhost:5173 (React/Vite UI; logs in `web.log`) | |
| `start.sh` stops anything stale on ports 8000/5173, launches the API, polls its | |
| healthcheck until ready, then launches the frontend. To stop everything: | |
| ```bash | |
| make stop # or: bash stop.sh | |
| ``` | |
| > **Note:** `start.sh` currently invokes Python via a hard-coded conda path | |
| > (`/Users/sourabh/opt/miniconda3/envs/py311/bin/python`). If your env lives elsewhere, | |
| > edit that line or run uvicorn directly: | |
| > `python -m uvicorn apps.api.main:app --port 8000 --reload`. | |
| --- | |
| ## Run with Docker (single container) | |
| The `Dockerfile` builds the frontend, bakes the ML models in for offline inference, and | |
| serves the API + static UI from one image on port **7860** (Hugging Face Spaces' default). | |
| Secrets are injected at runtime β never baked in. | |
| ```bash | |
| docker build -t vantage . | |
| docker run --rm -p 7860:7860 --env-file .env vantage | |
| # β http://localhost:7860 | |
| ``` | |
| The databases are still external β the container expects an already-populated Qdrant and | |
| Neo4j via the `.env` credentials. | |
| --- | |
| ## Project structure | |
| ``` | |
| packages/vantage_core/ # all business logic (hexagonal) | |
| domain/ # ports: rag, retrieval, gap, coverage, topic_index | |
| adapters/ # impls: qdrant, neo4j, openai, sentence-transformers, pytrends | |
| agent/ # LangGraph ReAct agent (graph.py, tools.py, trace.py) | |
| seeds/ # gartner_topics.csv (curated SSOT), topics.csv (generated) | |
| apps/api/ # FastAPI app + routers (chat, gaps, graph, signals) | |
| apps/web/ # React + Vite + Tailwind frontend | |
| scripts/ # data pipeline + dev CLIs (see below) | |
| contracts/openapi.yaml # generated API contract (scripts/export_openapi.py) | |
| docs/ # plans, ADRs | |
| ``` | |
| ### Scripts reference | |
| | Script | Purpose | | |
| |--------|---------| | |
| | `healthcheck.py` | Verify all external services are reachable | | |
| | `build_gartner_seeds.py` | Generate `topics.csv` + `topics_seed.json` from the Gartner SSOT | | |
| | `mine_gdelt_topics.py` | Augment topics with GDELT demand signals via BigQuery | | |
| | `download_data.sh` | Fetch the HuffPost corpus into `./data` | | |
| | `ingest.py` / `peek.py` | Chunk+embed docs into Qdrant / verify the collection | | |
| | `tag_topics.py` | Tag each chunk with the topics it mentions | | |
| | `ingest_topics.py` | Embed the topic vocabulary into its own Qdrant collection | | |
| | `build_graph.py` / `wipe_graph.py` | Build the Neo4j graph / reset it | | |
| | `gap_report.py` Β· `refresh_signals.py` | Inspect gap scores / momentum signals | | |
| | `search.py` Β· `ask.py` Β· `agent_cli.py` Β· `demo_queries.py` | Retrieval / RAG / agent CLIs | | |
| | `export_openapi.py` | Regenerate `contracts/openapi.yaml` | | |
| --- | |
| ## Configuration | |
| All runtime config is read from `.env` (validated at startup by `vantage_core/config.py`, | |
| fails fast on missing required vars). See `.env.example` for the full list. Notable knobs: | |
| - `OPENAI_MODEL` β defaults to `gpt-4o-mini` | |
| - `INGEST_LIMIT` β docs to ingest (default `2000`; set `0` for the full ~200k corpus) | |
| - `LANGFUSE_*` β optional; tracing is skipped if unset | |
| --- | |
| ## β οΈ Windows users β read this | |
| The **data-ingestion pipeline cannot be run natively on Windows.** Every orchestration | |
| path assumes a Unix shell and Unix tooling: `download_data.sh` and `start.sh` depend on | |
| `bash`, `lsof`, and `curl`; `start.sh` invokes a Unix conda path; and the Makefile targets | |
| call `python3.11`, which doesn't exist as a binary on Windows. Running the Β§Data pipeline or | |
| Β§Run the app sections from `cmd`/PowerShell will fail. | |
| **What Windows users *can* do:** run the prebuilt **Docker image** (see Β§Run with Docker). | |
| That serves the full dashboard and agent against **already-populated** Qdrant + Neo4j | |
| databases. You can: | |
| - β browse the **Knowledge Graph** | |
| - β view the **Gap Dashboard** | |
| - β chat with the **assistant** (incl. live gap estimation) | |
| **What you cannot do from Windows:** build, refresh, or re-ingest the corpus and graph β | |
| the ingestion scripts (`ingest.py`, `tag_topics.py`, `build_graph.py`, β¦) are pipeline-only | |
| and require the databases to be seeded by someone on macOS/Linux first. If you need to run | |
| the pipeline yourself, use **macOS, Linux, or WSL2** β not a native Windows shell. | |