# How the Ask.py Graph Implements Multi-Search Strategy for Retrieval and Synthesis in Open Notebook

> Discover how the Ask.py graph leverages its multi-search strategy for efficient retrieval and synthesis. See how parallel searches and SurrealDB vector search deliver coherent answers.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: deep-dive
- Published: 2026-07-06

---

**The Ask.py graph orchestrates a dynamic retrieval workflow where an LLM generates a JSON strategy with up to five parallel searches, executes them simultaneously via SurrealDB vector search, and synthesizes the results into a final coherent answer.**

The Open Notebook repository provides an intelligent query interface through its `Ask` graph implementation. This **Ask.py graph multi-search strategy** enables dynamic retrieval planning by allowing the language model to decide how many information sources to query, executing them in parallel branches, and aggregating partial results into comprehensive responses.

## Strategy Generation and Dynamic Search Planning

The retrieval process begins in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py) with the `agent` node, implemented by the `call_model_with_messages` function. This node sends the user's question to a strategy model provisioned via `provision_langchain_model` from [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py).

The strategy model returns a `Strategy` object containing two critical components:
- A free-form `reasoning` string explaining the analytical approach
- A list of up to five `Search` specifications, each containing a search term and specific instructions for the answer model

This design allows the system to adapt its retrieval breadth based on query complexity, generating anywhere from a single targeted search to five parallel information-gathering operations.

## Parallel Query Dispatch via trigger_queries

Once the strategy is generated, the graph uses the `trigger_queries` function to create parallel execution branches. This conditional edge constructs a `Send` object for every search entry in the strategy, allowing the StateGraph to spawn concurrent `provide_answer` nodes.

The graph architecture eschews sequential retrieval in favor of parallel execution, significantly reducing latency when multiple information sources are required.

## Vector Search Execution and Answer Generation

Each parallel branch executes the `provide_answer` node, which performs the actual retrieval and intermediate answer generation through two distinct phases:

1. **Document Retrieval**: The node calls `vector_search` from [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py), which generates query embeddings and executes SurrealDB's native `fn::vector_search` function to fetch the top-10 most relevant records.

2. **Answer Synthesis**: The node invokes the answer model (configured via `provision_langchain_model`) with the retrieved context and search-specific instructions. The LLM's response is processed through `clean_thinking_content` to remove internal reasoning artifacts before returning the cleaned string to the graph state.

## Final Answer Synthesis

After all parallel search branches complete, the graph transitions to the `write_final_answer` node. This node assembles the original user question, the strategy's `reasoning` field, and all intermediate answers into a final prompt using the template `ask/final_answer` from `open_notebook/prompts/ask/`.

The final-answer model processes this aggregated context to produce the definitive response that is returned to the client, incorporating insights from all parallel retrieval streams.

## Model Configuration and Provisioning

The `provision_langchain_model` function in [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py) handles intelligent model selection across the graph. It automatically selects large-context models for lengthy prompts, respects explicit user overrides from the configuration dictionary, or falls back to default models for specific request types such as "strategy" or "answer".

## Implementation Example

To invoke the Ask graph programmatically from your application:

```python
from open_notebook.graphs.ask import graph

# The input state contains the user question only.

state = {"question": "How does Open Notebook store embeddings?"}

# Configurable model IDs can be passed via the `configurable` dict.

config = {
    "configurable": {
        "strategy_model": "gpt-4o-mini",
        "answer_model": "gpt-4o",
        "final_answer_model": "gpt-4o"
    }
}

result = await graph.ainvoke(state, config=config)
print(result["final_answer"])

```

To customize the number of parallel searches, modify the strategy prompt to request between one and five search objects:

```python
strategy_prompt = """
You are a research planner. For the question below, decide how many
different information sources you need and output a JSON list of up to
five searches. Each search must contain:
  - term: the keyword(s) to look up
  - instructions: what the answer model should extract.
Question: {question}
"""

```

## Summary

- The **Ask.py graph** implements a three-stage retrieval workflow: strategy generation, parallel execution, and final synthesis.
- The `agent` node in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py) generates a JSON strategy with up to five search specifications using `call_model_with_messages`.
- The `trigger_queries` function dispatches parallel `provide_answer` nodes via LangGraph's `Send` mechanism.
- Each branch executes **vector search** against SurrealDB through [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py), retrieving top-10 relevant records per query.
- The **final synthesis** combines intermediate answers with the original strategy reasoning using the `ask/final_answer` prompt template.
- Model selection is handled dynamically by `provision_langchain_model` in [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py) based on context length and user configuration.

## Frequently Asked Questions

### How does the Ask graph decide how many searches to run?

The strategy model determines the number of searches dynamically based on the complexity of the user question. According to the source code in [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py), the LLM can return a JSON list containing up to five `Search` objects, each targeting different aspects of the query or different information sources.

### What database powers the vector search in Open Notebook?

The vector search implementation resides in [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py) and utilizes **SurrealDB**. The `vector_search` function generates embeddings for the query and invokes SurrealDB's native `fn::vector_search` function to retrieve the most relevant documents.

### Can I configure different models for strategy generation and final synthesis?

Yes. The graph accepts a `configurable` dictionary that allows you to specify distinct model IDs for each phase. You can set `strategy_model` for planning, `answer_model` for intermediate retrieval, and `final_answer_model` for synthesis, all routed through `provision_langchain_model` in [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py).

### How does the system handle the LLM's internal reasoning content?

The `provide_answer` node applies `clean_thinking_content` to strip internal reasoning artifacts from model outputs before returning answers. This ensures that only the relevant retrieved content and synthesis are passed to subsequent nodes or returned to the user.