Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.env
.venv/
.langgraph_api/
__pycache__/
ollama_deep_researcher.egg-info/

postgres.Dockerfile
43 changes: 43 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"cSpell.words": [
"ainvoke",
"astream",
"checkpointer",
"checkpointing",
"deepseek",
"dotenv",
"duckduckgo",
"fastapi",
"healthcheck",
"httpx",
"inmem",
"isort",
"isready",
"langchain",
"langgraph",
"LANGSMITH",
"libpq",
"LLMSTUDIO",
"LMSTUDIO",
"markdownify",
"mypy",
"Ollama",
"psycopg",
"pycodestyle",
"pydocstyle",
"pyflakes",
"pyproject",
"PYTHONPATH",
"PYTHONUNBUFFERED",
"qwen",
"rustc",
"searx",
"setuptools",
"sslmode",
"tavily",
"tini",
"tvly",
"uvicorn",
"venv"
]
}
145 changes: 95 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ Short summary video:
## 📺 Video Tutorials

See it in action or build it yourself? Check out these helpful video tutorials:

- [Overview of Local Deep Researcher with R1](https://www.youtube.com/watch?v=sGUjmyfof4Q) - Load and test [DeepSeek R1](https://api-docs.deepseek.com/news/news250120) [distilled models](https://ollama.com/library/deepseek-r1).
- [Building Local Deep Researcher from Scratch](https://www.youtube.com/watch?v=XGuTzHoqlj8) - Overview of how this is built.

## 🚀 Quickstart

Clone the repository:

```shell
git clone https://github.com/langchain-ai/local-deep-researcher.git
cd local-deep-researcher
```

Then edit the `.env` file to customize the environment variables according to your needs. These environment variables control the model selection, search tools, and other configuration settings. When you run the application, these values will be automatically loaded via `python-dotenv` (because `langgraph.json` point to the "env" file).

```shell
cp .env.example .env
```
Expand All @@ -31,16 +34,18 @@ cp .env.example .env
1. Download the Ollama app for Mac [here](https://ollama.com/download).

2. Pull a local LLM from [Ollama](https://ollama.com/search). As an [example](https://ollama.com/library/deepseek-r1:8b):
```shell
ollama pull deepseek-r1:8b
```

3. Optionally, update the `.env` file with the following Ollama configuration settings.
```shell
ollama pull deepseek-r1:8b
```

3. Optionally, update the `.env` file with the following Ollama configuration settings.

- If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.

* If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.
```shell
LLM_PROVIDER=ollama
OLLAMA_BASE_URL="http://localhost:11434" # Ollama service endpoint, defaults to `http://localhost:11434`
OLLAMA_BASE_URL="http://localhost:11434" # Ollama service endpoint, defaults to `http://localhost:11434`
LOCAL_LLM=model # the model to use, defaults to `llama3.2` if not set
```

Expand All @@ -49,14 +54,16 @@ LOCAL_LLM=model # the model to use, defaults to `llama3.2` if not set
1. Download and install LMStudio from [here](https://lmstudio.ai/).

2. In LMStudio:

- Download and load your preferred model (e.g., qwen_qwq-32b)
- Go to the "Local Server" tab
- Start the server with the OpenAI-compatible API
- Note the server URL (default: http://localhost:1234/v1)
- Note the server URL (default: <http://localhost:1234/v1>)

3. Optionally, update the `.env` file with the following LMStudio configuration settings.

3. Optionally, update the `.env` file with the following LMStudio configuration settings.
- If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.

* If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.
```shell
LLM_PROVIDER=lmstudio
LOCAL_LLM=qwen_qwq-32b # Use the exact model name as shown in LMStudio
Expand All @@ -65,7 +72,8 @@ LMSTUDIO_BASE_URL=http://localhost:1234/v1

### Selecting search tool

By default, it will use [DuckDuckGo](https://duckduckgo.com/) for web search, which does not require an API key. But you can also use [SearXNG](https://docs.searxng.org/), [Tavily](https://tavily.com/) or [Perplexity](https://www.perplexity.ai/hub/blog/introducing-the-sonar-pro-api) by adding their API keys to the environment file. Optionally, update the `.env` file with the following search tool configuration and API keys. If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.
By default, it will use [DuckDuckGo](https://duckduckgo.com/) for web search, which does not require an API key. But you can also use [SearXNG](https://docs.searxng.org/), [Tavily](https://tavily.com/) or [Perplexity](https://www.perplexity.ai/hub/blog/introducing-the-sonar-pro-api) by adding their API keys to the environment file. Optionally, update the `.env` file with the following search tool configuration and API keys. If set, these values will take precedence over the defaults set in the `Configuration` class in `configuration.py`.

```shell
SEARCH_API=xxx # the search API to use, such as `duckduckgo` (default)
TAVILY_API_KEY=xxx # the tavily API key to use
Expand All @@ -79,74 +87,73 @@ FETCH_FULL_PAGE=xxx # fetch the full page content (with `duckduckgo`), defaults
#### Mac

1. (Recommended) Create a virtual environment:
```bash
python -m venv .venv
source .venv/bin/activate
```

```bash python -m venv .venv
source .venv/bin/activate
```

2. Launch LangGraph server:

```bash
# Install uv package manager
curl -LsSf https://astral.sh/uv/install.sh | sh
uvx --refresh --from "langgraph-cli[inmem]" --with-editable . --python 3.11 langgraph dev
```
```bash
# Install uv package manager
curl -LsSf https://astral.sh/uv/install.sh | sh
uvx --refresh --from "langgraph-cli[inmem]" --with-editable . --python 3.11 langgraph dev
```

#### Windows

1. (Recommended) Create a virtual environment:
1. (Recommended) Create a virtual environment:

* Install `Python 3.11` (and add to PATH during installation).
* Restart your terminal to ensure Python is available, then create and activate a virtual environment:
- Install `Python 3.11` (and add to PATH during installation).
- Restart your terminal to ensure Python is available, then create and activate a virtual environment:

```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
```
```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
```

2. Launch LangGraph server:

```powershell
# Install dependencies
pip install -e .
pip install -U "langgraph-cli[inmem]"
```powershell
# Install dependencies
pip install -e .
pip install -U "langgraph-cli[inmem]"

# Start the LangGraph server
langgraph dev
```
# Start the LangGraph server
langgraph dev
```

### Using the LangGraph Studio UI

When you launch LangGraph server, you should see the following output and Studio will open in your browser:
> Ready!

> API: http://127.0.0.1:2024

> Docs: http://127.0.0.1:2024/docs

> LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
> Ready!
> API: <http://127.0.0.1:2024>
> Docs: <http://127.0.0.1:2024/docs>
> LangGraph Studio Web UI: <https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024>

Open `LangGraph Studio Web UI` via the URL above. In the `configuration` tab, you can directly set various assistant configurations. Keep in mind that the priority order for configuration values is:

```
```txt
1. Environment variables (highest priority)
2. LangGraph UI configuration
3. Default values in the Configuration class (lowest priority)
```

<img width="1621" alt="Screenshot 2025-01-24 at 10 08 31 PM" src="https://github.com/user-attachments/assets/7cfd0e04-28fd-4cfa-aee5-9a556d74ab21" />
![Screenshot 2025-01-24 at 10 08 31 PM](https://github.com/user-attachments/assets/7cfd0e04-28fd-4cfa-aee5-9a556d74ab21)

Give the assistant a topic for research, and you can visualize its process!

<img width="1621" alt="Screenshot 2025-01-24 at 10 08 22 PM" src="https://github.com/user-attachments/assets/4de6bd89-4f3b-424c-a9cb-70ebd3d45c5f" />
![Screenshot 2025-01-24 at 10 08 22 PM](https://github.com/user-attachments/assets/4de6bd89-4f3b-424c-a9cb-70ebd3d45c5f)

### Model Compatibility Note

When selecting a local LLM, set steps use structured JSON output. Some models may have difficulty with this requirement, and the assistant has fallback mechanisms to handle this. As an example, the [DeepSeek R1 (7B)](https://ollama.com/library/deepseek-llm:7b) and [DeepSeek R1 (1.5B)](https://ollama.com/library/deepseek-r1:1.5b) models have difficulty producing required JSON output, and the assistant will use a fallback mechanism to handle this.

### Browser Compatibility Note

When accessing the LangGraph Studio UI:

- Firefox is recommended for the best experience
- Safari users may encounter security warnings due to mixed content (HTTPS/HTTP)
- If you encounter issues, try:
Expand All @@ -157,6 +164,7 @@ When accessing the LangGraph Studio UI:
## How it works

Local Deep Researcher is inspired by [IterDRAG](https://arxiv.org/html/2410.04343v1#:~:text=To%20tackle%20this%20issue%2C%20we,used%20to%20generate%20intermediate%20answers.). This approach will decompose a query into sub-queries, retrieve documents for each one, answer the sub-query, and then build on the answer by retrieving docs for the second sub-query. Here, we do similar:

- Given a user-provided topic, use a local LLM (via [Ollama](https://ollama.com/search) or [LMStudio](https://lmstudio.ai/)) to generate a web search query
- Uses a search engine / tool to find relevant sources
- Uses LLM to summarize the findings from web search related to the user-provided research topic
Expand All @@ -182,33 +190,70 @@ There are [various ways](https://langchain-ai.github.io/langgraph/concepts/#depl
## TypeScript Implementation

A TypeScript port of this project (without Perplexity search) is available at:
https://github.com/PacoVK/ollama-deep-researcher-ts
<https://github.com/PacoVK/ollama-deep-researcher-ts>

## Running as a Docker container

The included `Dockerfile` only runs LangChain Studio with local-deep-researcher as a service, but does not include Ollama as a dependant service. You must run Ollama separately and configure the `OLLAMA_BASE_URL` environment variable. Optionally you can also specify the Ollama model to use by providing the `LOCAL_LLM` environment variable.

Clone the repo and build an image:
```
$ docker build -t local-deep-researcher .

```sh
docker build -t local-deep-researcher .
```

Run the container:
```

```sh
$ docker run --rm -it -p 2024:2024 \
-e SEARCH_API="tavily" \
-e SEARCH_API="tavily" \
-e TAVILY_API_KEY="tvly-***YOUR_KEY_HERE***" \
-e LLM_PROVIDER=ollama
-e OLLAMA_BASE_URL="http://host.docker.internal:11434/" \
-e LOCAL_LLM="llama3.2" \
-e LOCAL_LLM="llama3.2" \
local-deep-researcher
```

NOTE: You will see log message:
```

```sh
2025-02-10T13:45:04.784915Z [info ] 🎨 Opening Studio in your browser... [browser_opener] api_variant=local_dev message=🎨 Opening Studio in your browser...
URL: https://smith.langchain.com/studio/?baseUrl=http://0.0.0.0:2024
```

...but the browser will not launch from the container.

Instead, visit this link with the correct baseUrl IP address: [`https://smith.langchain.com/studio/thread?baseUrl=http://127.0.0.1:2024`](https://smith.langchain.com/studio/thread?baseUrl=http://127.0.0.1:2024)

## Running with PostgreSQL Persistence via Docker Compose

The `langgraph dev` method described previously uses in-memory storage, which means the state of your research runs (conversation history, intermediate steps) is lost when the server stops.

To enable **persistent storage**—allowing you to resume runs, inspect past states, and ensure durability across restarts—you can use the provided Docker Compose setup which includes a PostgreSQL database for checkpointing.

This method uses the `langgraph.json` configuration file to generate a specific `Dockerfile` tailored for this persistent setup, then builds and runs the application using Docker Compose.

**Steps:**

1. **(Prerequisite)** A `langgraph.json` file (see `langgraph-postgres.json`) that correctly specifies dependencies (including `langgraph-checkpoint-postgres`, `psycopg2-binary`, and your graph's needs) and points to your graph definition.
2. **(Prerequisite)** Make sure your `.env` file is configured with necessary API keys (like `LANGSMITH_API_KEY`) and settings (like `LLM_PROVIDER`, `OLLAMA_BASE_URL` or `LLMSTUDIO_BASE_URL`, and `LOCAL_LLM`).
3. **Generate the Dockerfile:** Run the following command to generate the Dockerfile based on `langgraph-postgres.json`:

```bash
langgraph dockerfile > postgres.Dockerfile
```

_Note: In this repository, the autogenerated `postgres.Dockerfile` is `.gitignore`'d to avoid committing it to git by mistake_

4. **Run Docker Compose:** Build and start the services defined in `compose.yaml`:

```bash
docker compose up --build -d
```

- `--build`: Builds the `langgraph-api` image using the generated `postgres.Dockerfile`.
- `-d`: Runs the services in detached mode (in the background).

5. **Access LangGraph Studio UI:** Once the services are up (check with `docker compose ps`), open your browser and navigate to [`https://smith.langchain.com/studio/?baseUrl=http://localhost:8100`](https://smith.langchain.com/studio/?baseUrl=http://localhost:8100), pointing it to the port exposed in `compose.yaml` (port `8100` in the example):

With this setup, the state of your research runs will be saved in the `langgraph-postgres` container's database volume. You can stop (`docker compose down`) and restart (`docker compose up -d`) the services, and the state of previously run threads will be loaded from PostgreSQL, allowing you to resume or inspect them via the LangGraph Studio UI. If you update `langgraph-postgres.json`, simply regenerate the `postgres.Dockerfile` before running `docker compose up --build -d` again.
44 changes: 44 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
build:
context: .
dockerfile: postgres.Dockerfile
ports:
- "8100:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
env_file:
- .env
environment:
REDIS_URI: redis://langgraph-redis:6379
LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
POSTGRES_URI: postgresql://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
24 changes: 24 additions & 0 deletions langgraph-postgres.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"dockerfile_lines": [
"USER root",
"RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev && rm -rf /var/lib/apt/lists/*"
],
"graphs": {
"ollama_deep_researcher": "./src/ollama_deep_researcher/graph.py:graph"
},
"python_version": "3.11",
"env": "./.env",
"dependencies": [
".",
"langgraph-checkpoint-postgres",
"psycopg2-binary",
"langchain-ollama",
"langchain-openai",
"langchain-community",
"tavily-python",
"duckduckgo-search",
"markdownify",
"httpx",
"requests"
]
}