TreeSearch: Structure-Aware Document Retrieval

repository·main·Indexed 42 days ago

https://github.com/shibing624/treesearch

A structure-aware document retrieval library and CLI that uses SQLite FTS5 keyword matching instead of vector embeddings. It preserves document hierarchies, such as Markdown headings and code ASTs, to provide high-precision search for codebases and technical documents. Features include three search modes (fts5, tree, and auto), a Python API (pytreesearch v1.1.1), and a Rust-based CLI tool.

Tokens
28.4K
Snippets
95
Records
133
Agent score
50%

What's inside TreeSearch

  1. Explore TreeSearch Examples and Demos

    main

    The examples/ directory contains various scripts demonstrating different capabilities of the library:

    FileDescription
    01_basic_demo.pySimplest search example: indexing + searching.
    02_index_and_search.pyManaging index lifecycle and pre-building indexes.
    03_cli_workflow.pyUsing the CLI command-line tool.
    04_multi_doc_search.pyBatch searching across multiple documents.
    05_directory_and_grep.pyDirectory traversal combined with RipGrep-style search.
    06_pdf_demo.pyPDF document parsing and searching.
    07_tree_search_demo.pyDemonstrating Tree Search mode (including Auto mode for intelligent selection).
  2. Implement the Retain / Recall / Reflect operational loop

    main

    The memory system follows a three-stage operational loop to manage information from raw logs to structured knowledge:

    1. Retain: Normalize daily narrative logs into self-contained, tagged "facts". Instead of tiny snippets, store narrative bullets that preserve context. Use a ## Retain section in daily Markdown files (memory/YYYY-MM-DD.md) with specific type prefixes:

      • W (World): Objective facts.
      • B (Biographical/Experience): What the agent did.
      • O (Opinion): Subjective preferences or judgments, optionally with confidence O(c=0.95).
      • S (Observation/Summary): Generated summaries.
      • Entities: Mention entities using the @ symbol (e.g., @Peter) to link to entity pages.
    2. Recall: Query the derived index (SQLite) to retrieve facts. Recall should support lexical (FTS5), entity-based, temporal, and opinion-based queries. Results should be agent-friendly, returning kind, timestamp, entities, content, and a source (e.g., memory/2025-11-27.md#L12).

    3. Reflect: A scheduled job that updates the structured bank/ directory. It updates entity summaries in bank/entities/*.md, evolves opinions in bank/opinions.md by adjusting confidence based on new evidence, and proposes edits to the core memory.md file.

    ## Retain
    - W @Peter: Currently in Marrakech (Nov 27–Dec 1, 2025) for Andy’s birthday.
    - B @warelay: I fixed the Baileys WS crash by wrapping connection.update handlers in try/catch (see memory/2025-11-27.md).
    - O(c=0.95) @Peter: Prefers concise replies (<1500 chars) on WhatsApp; long content goes into files.
  3. Use Tree Mode for hierarchical documents

    main

    For academic papers or technical documentation with deep heading hierarchies, use search_mode="tree". This enables structure-aware best-first search, which provides both ranked nodes and traversal paths showing how results connect through the document hierarchy.

    from treesearch import TreeSearch
    
    ts = TreeSearch("papers/", "docs/")
    # Use tree mode for hierarchical structure
    results = ts.search("experimental methodology", search_mode="tree")
    
    # Access ranked nodes
    for doc in results["documents"]:
        for node in doc["nodes"]:
            print(f"[{node['score']:.2f}] {node['title']}")
    
    # Access tree traversal paths
    for path in results["paths"]:
        chain = " > ".join(p["title"] for p in path["path"])
        print(f"[{path['score']:.2f}] {chain}")
        print(f"  {path['snippet'][:200]}")
  4. How Tree Search mode handles different source types

    main

    Tree Search is designed to be adaptive. When using mode="tree", the system automatically selects the most effective retrieval strategy based on the source_type of the documents. This ensures high performance for structured documents while avoiding the overhead and noise of tree walking on flat code structures.

    source_typeeffective_modePipeline
    markdowntreeFull pipeline (optimized for hierarchical structure)
    pdftreeFull pipeline (optimized for hierarchical structure)
    texttreeFull pipeline
    codeflat (auto)FTS5 + GrepFilter (avoids tree walk noise)
    mixedadaptiveTree mode for docs, flat for code subset
  5. How Tree Mode works for hierarchical documents

    main

    Tree mode is optimized for academic papers and technical documents with deep heading hierarchies. It uses a process of anchor retrieval → tree walk → path aggregation to provide context-aware results.

    When using search_mode="tree", the results include an additional paths field that shows the hierarchical relationship between nodes.

    from treesearch import TreeSearch
    
    # Use tree mode for hierarchical documents
    ts = TreeSearch("papers/", "docs/")
    results = ts.search("experimental method", search_mode="tree")
    
    # Standard node results
    for doc in results["documents"]:
        for node in doc["nodes"]:
            print(f"[{node['score']:.2f}] {node['title']}")
    
    # Hierarchical path results
    for path in results["paths"]:
        chain = " > ".join(p["title"] for p in path["path"])
        print(f"[{path['score']:.2f}] {chain}")
        print(f"  {path['snippet'][:200]}")

    Choosing a Search Mode

    ModeBest ForDescription
    "auto" (Default)General useAutomatically chooses between tree and flat based on document types and depth.
    "tree"Papers & DocsBest for documents with clear heading hierarchies (e.g., Markdown, JSON).
    "flat"Code SearchBest for keyword-dense, non-hierarchical files (e.g., Python, JS, C++).
    ts = TreeSearch("papers/", "docs/")
    results = ts.search("实验方法", search_mode="tree")
    
    for path in results["paths"]:
        chain = " > ".join(p["title"] for p in path["path"])
        print(f"[{path['score']:.2f}] {chain}")
        print(f"  {path['snippet'][:200]}")
  6. Understand TreeSearch Search Modes

    main

    TreeSearch provides three primary search strategies to balance accuracy and speed depending on your document structure:

    1. fts5 (Fastest): A full-text search mode optimized for flat documents (e.g., PDFs, plain text, or code). It is extremely fast but lacks structural awareness.
    2. tree (Most Accurate for Structured Docs): A structure-aware mode designed for hierarchical documents (e.g., Markdown with deep heading levels). It uses a 7-stage reranking pipeline to leverage parent-child relationships and section context.
    3. auto (Recommended/Default): The optimal production strategy. It automatically routes queries based on document characteristics:
      • Markdown/JSON with sufficient depth $\ge 2$ and $\ge 30%$ hierarchical structure $\rightarrow$ tree mode.
      • Code/PDF/TXT or shallow/flat structures $\rightarrow$ fts5 mode.

    Decision Logic for auto mode:

    • Layer 1: Maps source_type (e.g., markdown $\rightarrow$ tree candidate; pdf $\rightarrow$ flat candidate).
    • Layer 2: Verifies actual tree depth (must be $\ge 2$).
    • Layer 3: Checks hierarchy ratio (if $\ge 30%$ of documents have hierarchy $\rightarrow$ tree, else flat).
  7. How the Tree mode ranking pipeline works

    main

    The final ranking in Tree mode is determined by the _build_flat_nodes() function, which processes nodes through four distinct stages to balance text similarity with document structure:

    1. FTS5 Base Scores: All nodes with FTS5 scores serve as the foundation.
    2. Generic Section Demotion: Nodes identified as generic sections (e.g., Abstract, Introduction, Conclusion) at low depths (0-1) are penalized (e.g., by a 0.60x factor) to prevent them from dominating results due to text length.
    3. Title-Prefix Propagation: Nodes with near-zero FTS5 scores can inherit a portion (e.g., 30%) of a high-scoring parent node's score if their titles match the parent's title as a prefix.
    4. Walk Boost: Nodes discovered during the Tree Walk process receive a structural bonus (e.g., 15%) to their combined score.
    5. Term Density Boost: For queries with multiple terms, nodes that show high coverage (e.g., ≥60% of query terms) receive an additional density-based bonus.

    Finally, nodes are sorted by their calculated score in descending order.

    Stage 1: FTS5 Base Scores
        ↓ All nodes with FTS5 scores as base
    Stage 1b: Generic Section Demotion
        ↓ Abstract/Introduction/Conclusion etc. nodes ×0.60
    Stage 2: Title-Prefix Propagation
        ↓ FTS5=0 child nodes inherit 30% score from high-score parent
    Stage 3: Walk Boost
        ↓ Walked nodes get 15% structural bonus
    Stage 4: Term Density Boost
        ↓ Multi-term queries: high term coverage nodes get 10% bonus
        ↓
    Final: Sort by score descending → flat_nodes
  8. How TreeSearch retrieval modes work

    main

    TreeSearch uses different retrieval strategies depending on the document structure:

    • Flat Mode (Default): Uses SQLite FTS5 with structure-aware columns (title, summary, body, code, front_matter). It provides fast, keyword-based results using column weighting. No LLM is required.
    • Tree Mode: Designed for hierarchical documents (like papers or books). It uses a best-first search: FTS5 finds 'anchor nodes', then performs a tree walk (checking parents, children, and siblings) using heuristic scoring (term overlap, IDF weighting, etc.) to find the most relevant path through the hierarchy.
    • Auto Mode (Recommended): Automatically routes queries to either Flat or Tree mode based on the document structure. This provides the best balance of speed and accuracy without manual configuration.
    • Source-Type Routing: For code files, the system automatically combines GrepFilter (ripgrep-accelerated) with FTS5 for precise symbol matching, controlled by the PREFILTER_ROUTING logic.
  9. Configure TreeSearch Search Modes

    main

    You can control how TreeSearch retrieves documents using the --mode option. This determines whether the tool uses flat FTS-style retrieval or hierarchical tree traversal.

    • auto (default): Automatically selects flat or tree based on source type mapping, document depth, and ratio thresholds.
    • flat: Forces FTS-style flat retrieval.
    • tree: Forces tree traversal retrieval.

    Examples:

    ts "query" .               # auto (default)
    ts "query" . --mode flat      # force flat
    ts "query" . --mode tree      # force tree
    ts "query" . --mode tree
  10. Understand TreeSearch search modes: FTS5 vs Tree

    main

    TreeSearch v1.0.1 provides two primary search modes for document retrieval:

    1. FTS5 (flat): Uses SQLite FTS5 BM25 scoring to rank and return nodes directly based on text similarity.
    2. Tree (Best-First Search): A structure-aware mode that builds upon FTS5 scoring by utilizing the document's hierarchical structure. The workflow is:
      • FTS5 Pre-Scoring: Initial scoring of nodes.
      • Anchor Selection: Identifying top-k entry nodes.
      • Tree Walk: Expanding from anchors using Breadth-First Search (BFS) along parent, child, and sibling relationships.
      • Path Aggregation: Collecting root-to-leaf paths.
      • Flat Node Ranking: Final ranking using a combination of FTS5 base scores and structural reranking.
  11. How the TreeSearch search architecture works

    main

    The search process follows a three-stage pipeline designed for millisecond-level latency without requiring LLMs or vector embeddings:

    1. Stage 1 - FTS5 Pre-Scoring: The FTS5Index scores all tree nodes using a SQLite FTS5 inverted index. This index uses Markdown-aware columns with hierarchical field weighting (e.g., title > summary > body) to prioritize matches in important structural areas.
    2. Stage 2 - Source-Type Routing: The system automatically selects pre-filters based on the document's source_type via PREFILTER_ROUTING.
      • Code files: Uses a combination of GrepFilter (for exact symbol matching) and FTS5 scoring.
      • Markdown/Text files: Uses FTS5 scoring only.
    3. Stage 3 - Ranked Results: The system returns the top-scoring nodes, including their text content, section titles, and line number references (for code files).
  12. Understand Walk-Only Node Injection

    main

    To overcome the limitations of FTS5 (BM25) which might miss nodes that are structurally relevant but lack exact term matches, Tree Search implements Walk-Only Node Injection.

    When a node is discovered via tree walking (parent/child/sibling) but has an FTS5 score of 0, it can still be injected into the results if it meets these criteria:

    1. Term Overlap: The node's text/title must have a term overlap $\ge 40%$ with the query terms.
    2. Hop Limit: The node must be within $\le 2$ hops from the anchor node.
    3. Scoring: An inject_score is calculated using the overlap and a hop_decay (e.g., $1.0$ for hop 1, $0.7$ for hop 2) to ensure structural relevance is weighted conservatively compared to high FTS5 scores.