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

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 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.

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, 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 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:

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:

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 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, 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 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, 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 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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →