ragnar

repository·main·Indexed 41 days ago

https://github.com/tidyverse/ragnar

An R package for implementing Retrieval-Augmented Generation (RAG) workflows. It provides an end-to-end pipeline for document processing, semantic chunking, embedding, and storage using DuckDB. ragnar supports multiple embedding providers (OpenAI, Ollama, Bedrock, Databricks, Google Vertex) and integrates with ellmer::Chat to allow LLMs to query custom knowledge bases via tools like ragnar_register_tool_retrieve().

Tokens
2.4K
Snippets
7
Records
14
Agent score
51%

What's inside ragnar

  1. Overview of ragnar RAG workflows

    main

    ragnar is an R package designed to implement Retrieval-Augmented Generation (RAG) workflows. It provides a complete solution for building RAG pipelines with sensible defaults while allowing users to inspect intermediate steps to avoid a "black box" experience.

    A typical ragnar workflow follows these stages:

    1. Document Processing: Preparing markdown or HTML files.
    2. Text Chunking: Dividing documents into smaller, semantically meaningful pieces.
    3. Context Augmentation (Optional): Adding document-level context to individual chunks to improve retrieval.
    4. Embedding: Computing vector representations for chunks (typically via ellmer).
    5. Storage: Saving chunks and embeddings (typically in duckdb).
    6. Retrieval: Finding relevant chunks using cosine similarity and BM25 ranking.
    7. Re-ranking (Optional): Using specialized models to refine the order of retrieved chunks.
    8. Prompt Generation: Injecting retrieved context into an LLM prompt.
  2. How ragnar RAG workflows work

    main

    ragnar implements Retrieval-Augmented Generation (RAG) through a multi-step pipeline designed for transparency and control. The workflow consists of:

    1. Document Processing: Converting files or URLs to Markdown using MarkItDown via read_as_markdown().
    2. Text Chunking: Dividing documents into semantic chunks using markdown_chunk(). This step can optionally include Context Augmentation, where markdown_chunk() automatically associates chunks with their parent headings/subheadings.
    3. Embedding: Computing vector representations for chunks. While specific functions like embed_openai() exist, they are typically passed as a callback to ragnar_store_create() and called automatically during insertion and retrieval.
    4. Storage: Saving processed data in a DuckDB-optimized format using ragnar_store_create(), ragnar_store_connect(), and ragnar_store_insert().
    5. Retrieval: Finding relevant chunks using ragnar_retrieve() (which combines vector similarity search vss and bm25 text search) or specific methods like ragnar_retrieve_vss() and ragnar_retrieve_bm25().
    6. Chat Augmentation: Integrating the store into an ellmer::Chat object using ragnar_register_tool_retrieve() to allow LLMs to query the knowledge base on-demand.
  3. Install TailwindCLI on macOS arm64

    main

    If you are developing on an arm64 macOS machine, you can install the TailwindCLI standalone binary using the following commands:

    # Example for macOS arm64
    curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
    chmod +x tailwindcss-macos-arm64
    mv tailwindcss-macos-arm64 tailwindcss
  4. Update the Store Inspector CSS using TailwindCLI

    main

    The Store Inspector app uses TailwindCSS for styling. The styles are compiled from www/app.css into www/app.out.css using the TailwindCLI.

    To update the CSS, you must have the TailwindCLI binary available in your environment. It is recommended to use the standalone install to avoid managing an npm installation.

    # Compile the CSS once
    ./tailwindcss -i www/app.css -o www/app.out.css
    
    # Run with watch mode to re-compile automatically during development
    ./tailwindcss -i www/app.css -o www/app.out.css --watch
  5. Install ragnar

    main

    You can install ragnar from CRAN or the development version from GitHub.

    CRAN:

    install.packages("ragnar")

    GitHub (Development):

    # install.packages("pak")
    pak::pak("tidyverse/ragnar")
    install.packages("ragnar")
  6. Full Workflow Example: Creating and Querying a Knowledge Store

    main

    This example demonstrates the full pipeline: finding links, reading markdown, chunking, creating a store with OpenAI embeddings, ingesting data, and retrieving relevant chunks.

    library(ragnar)
    
    # 1. Setup: Find links and define store
    base_url <- "https://r4ds.hadley.nz"
    pages <- ragnar_find_links(base_url, url_filter = \(url) {
      url[startsWith(url, "https://r4ds.hadley.nz")]
    })
    
    store_location <- "r4ds.ragnar.duckdb"
    
    # 2. Create store with embedding function
    store <- ragnar_store_create(
      store_location,
      embed = \(x) ragnar::embed_openai(x, model = "text-embedding-3-small")
    )
    
    # 3. Ingest pages
    for (page in pages) {
      message("ingesting: ", page)
      chunks <- page |> read_as_markdown() |> markdown_chunk()
      ragnar_store_insert(store, chunks)
    }
    
    # 4. Build index
    ragnar_store_build_index(store)
    
    # 5. Retrieve
    store <- ragnar_store_connect(store_location, read_only = TRUE)
    text <- "How can I subset a dataframe with a logical vector?"
    relevant_chunks <- ragnar_retrieve(store, text)
    print(relevant_chunks)
    library(ragnar)
    
    base_url <- "https://r4ds.hadley.nz"
    pages <- ragnar_find_links(base_url, url_filter = \(url) {
      url[startsWith(url, "https://r4ds.hadley.nz")]
    })
    
    store_location <- "r4ds.ragnar.duckdb"
    
    store <- ragnar_store_create(
      store_location,
      embed = \(x) ragnar::embed_openai(x, model = "text-embedding-3-small")
    )
    
    for (page in pages) {
      message("ingesting: ", page)
      chunks <- page |> read_as_markdown() |> markdown_chunk()
      ragnar_store_insert(store, chunks)
    }
    
    ragnar_store_build_index(store)
    
    store <- ragnar_store_connect(store_location, read_only = TRUE)
    text <- "How can I subset a dataframe with a logical vector?"
    relevant_chunks <- ragnar_retrieve(store, text)
    print(relevant_chunks)
  7. Retrieve and re-rank chunks

    main

    After building your index, use ragnar_retrieve() to find relevant chunks based on a query. You can then refine these results using ragnar_arrange() with a re-ranking function (like rerank_voyager) and slice the final set using ragnar_slice_max() to fit within LLM context limits.

    chunks <- ragnar_retrieve(
      db = "myproject.duckdb",
      query = "foo")
    
    chunks <- ragnar_arrange(chunks, rerank_voyager(model = "rerank-2", top_k = 10))
    
    chunks <- ragnar_slice_max(chunks, n = 10, max_tokens = 2000)
  8. Build a RAG pipeline with ragnar_update()

    main

    Use ragnar_update() to ingest documents, chunk them, embed them, and store them in a database in a single workflow. This function automates the transition from raw files to a searchable vector store.

    library(ragnar)
    
    ragnar_update(
      db = "myproject.duckdb",
      documents = ragnar_read_html("path/to/docs"),
      chunk = split_semantic(chunk_size = 500, chunk_overlap = 50),
      embedding = embed_openai(model = "text-embedding-3-small"),
    )
  9. Manage and Query the Knowledge Store

    main

    The knowledge store is powered by DuckDB. Use these functions to manage the lifecycle of your data:

    • ragnar_store_create(location, embed): Creates a new store at location. The embed argument must be a function that takes text and returns embeddings.
    • ragnar_store_connect(location, read_only = FALSE): Connects to an existing store.
    • ragnar_store_insert(store, chunks): Inserts chunked data into the store.
    • ragnar_store_build_index(store): Builds the necessary search indices for the store.
    • ragnar_retrieve(store, text): High-level retrieval that performs both vss (vector similarity) and bm25 (text search) and de-overlaps the results.
    • ragnar_retrieve_vss(store, text): Retrieves using the DuckDB vss extension.
    • ragnar_retrieve_bm25(store, text): Retrieves using the DuckDB full-text search extension.
    • chunks_deoverlap(chunks): Consolidates retrieved chunks that overlap.
  10. Document Processing and Chunking

    main

    Use these functions to prepare your raw data for the RAG pipeline:

    • read_as_markdown(file_or_url): Converts a file or URL to Markdown.
    • ragnar_find_links(url, url_filter): Finds all links in a webpage, optionally filtered by a predicate function.
    • markdown_chunk(markdown_text): A semantic chunker that identifies boundaries and associates chunks with their relevant headings/subheadings for context augmentation.