> ## Documentation Index
> Fetch the complete documentation index at: https://docs.infino.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Install Infino, then index a small knowledge base and retrieve over it three ways (BM25, vector, and SQL) in Python, Node.js, or Rust.

Let's get Infino running in a few minutes. We'll build a small knowledge base (a
handful of help-center notes) and search it three ways: by keyword, by meaning, and
with SQL. Pick your language; each step builds on the one before it.

<Steps>
  <Step title="Install">
    <CodeGroup>
      ```bash Python icon="python" theme={null}
      pip install infino
      # Or with uv (https://docs.astral.sh/uv/):
      uv pip install infino
      ```

      ```bash Node.js icon="node-js" theme={null}
      npm install @infino-ai/infino
      ```

      ```bash Rust icon="rust" theme={null}
      cargo add infino
      ```
    </CodeGroup>

    <Accordion title="Rust: companion crates and allocator">
      The Rust examples build Arrow data, and `update` / `delete` take DataFusion predicates
      (`col`, `lit`), so add these alongside `infino`, matching the Arrow and DataFusion
      versions Infino uses (Arrow 53 and DataFusion 53 for `infino` 0.1.x):

      ```toml theme={null}
      [dependencies]
      infino = "0.1"
      arrow-array = "53"
      arrow-schema = "53"
      datafusion-expr = "53"  # only needed for update / delete
      ```

      Run `cargo tree -p infino` to check the versions if you are on a newer Infino.

      Infino also installs the [mimalloc](https://github.com/microsoft/mimalloc) global
      allocator by default. If you embed it in a process that already sets a global allocator,
      turn it off: `infino = { version = "0.1", default-features = false }`.
    </Accordion>
  </Step>

  <Step title="Connect">
    First, open a connection. `"memory://"` keeps everything in memory for this
    walkthrough; point it at `"./data"` or an `"s3://bucket/prefix"` URI when you want your
    data to stick around.

    <CodeGroup>
      ```python Python icon="python" theme={null}
      import infino
      import pyarrow as pa

      db = infino.connect("memory://")
      ```

      ```typescript Node.js icon="node-js" theme={null}
      import { connect, IndexSpec } from "@infino-ai/infino";

      const db = connect("memory://");
      ```

      ```rust Rust icon="rust" theme={null}
      use std::sync::Arc;
      use arrow_array::{FixedSizeListArray, Float32Array, LargeStringArray, RecordBatch};
      use arrow_schema::{DataType, Field, Schema};
      use infino::{connect, BoolMode, IndexSpec, Metric, VectorFilter, VectorSearchOptions};

      let db = connect("memory://")?;
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a table">
    Now create a table. You give it a schema and say which columns to index: a full-text
    index on the text, and a vector index on the embedding.

    <CodeGroup>
      ```python Python icon="python" theme={null}
      schema = pa.schema([
          pa.field("source", pa.large_utf8(), nullable=False),
          pa.field("body", pa.large_utf8(), nullable=False),
          pa.field("embedding", pa.list_(pa.float32(), 16), nullable=False),
      ])
      docs = db.create_table(
          "docs", schema,
          infino.IndexSpec().fts("body").vector("embedding", 16, 1, "cosine"),
      )
      ```

      ```typescript Node.js icon="node-js" theme={null}
      const docs = db.createTable(
        "docs",
        { source: "large_utf8", body: "large_utf8", embedding: { vector: 16 } },
        new IndexSpec().fts("body").vector("embedding", 16, 1, "cosine"),
      );
      ```

      ```rust Rust icon="rust" theme={null}
      let item = Arc::new(Field::new("item", DataType::Float32, true));
      let schema = Arc::new(Schema::new(vec![
          Field::new("source", DataType::LargeUtf8, false),
          Field::new("body", DataType::LargeUtf8, false),
          Field::new("embedding", DataType::FixedSizeList(item.clone(), 16), false),
      ]));
      let docs = db.create_table(
          "docs",
          schema.clone(),
          IndexSpec::new().fts("body").vector("embedding", 16, 1, Metric::Cosine),
      )?;
      ```
    </CodeGroup>
  </Step>

  <Step title="Add data">
    Let's add a few notes. The `embed` helper stands in for a real embedding model so the
    example runs on its own. It's a 16-dim one-hot by topic (`0` = billing, `1` =
    appearance). Your own embeddings will be dense and higher-dimensional; see
    [Embeddings](/guides/embeddings).

    <CodeGroup>
      ```python Python icon="python" theme={null}
      def embed(topic):
          v = [0.0] * 16
          v[topic] = 1.0
          return v

      docs.append([
          {"source": "help-center", "body": "To cancel a subscription, open Settings then Billing.", "embedding": embed(0)},
          {"source": "help-center", "body": "Refunds return to the original payment method.",         "embedding": embed(0)},
          {"source": "blog",        "body": "Enable dark mode under Settings then Appearance.",        "embedding": embed(1)},
      ])
      ```

      ```typescript Node.js icon="node-js" theme={null}
      const embed = (topic) => { const v = Array(16).fill(0.0); v[topic] = 1.0; return v; };

      docs.append([
        { source: "help-center", body: "To cancel a subscription, open Settings then Billing.", embedding: embed(0) },
        { source: "help-center", body: "Refunds return to the original payment method.",         embedding: embed(0) },
        { source: "blog",        body: "Enable dark mode under Settings then Appearance.",        embedding: embed(1) },
      ]);
      ```

      ```rust Rust icon="rust" theme={null}
      fn embed(topic: usize) -> Vec<f32> {
          let mut v = vec![0.0_f32; 16];
          v[topic] = 1.0;
          v
      }

      let flat: Vec<f32> = [0usize, 0, 1].iter().flat_map(|&t| embed(t)).collect();
      docs.append(&RecordBatch::try_new(
          schema,
          vec![
              Arc::new(LargeStringArray::from(vec!["help-center", "help-center", "blog"])),
              Arc::new(LargeStringArray::from(vec![
                  "To cancel a subscription, open Settings then Billing.",
                  "Refunds return to the original payment method.",
                  "Enable dark mode under Settings then Appearance.",
              ])),
              Arc::new(FixedSizeListArray::new(item, 16, Arc::new(Float32Array::from(flat)), None)),
          ],
      )?)?;
      ```
    </CodeGroup>
  </Step>

  <Step title="Search it">
    Now the part you came for. The same table answers all three kinds of query: keyword,
    meaning, and SQL.

    <CodeGroup>
      ```python Python icon="python" theme={null}
      keyword  = docs.bm25_search("body", "cancel subscription", 5)               # BM25
      semantic = docs.vector_search("embedding", embed(0), 5)                     # vector kNN
      # vector kNN, restricted to rows whose body matches a keyword (pushdown filter):
      filtered = docs.vector_search("embedding", embed(0), 5, filter_column="body", filter_query="billing")
      billing  = db.query_sql("SELECT body FROM docs WHERE source = 'help-center'")  # SQL
      ```

      ```typescript Node.js icon="node-js" theme={null}
      const keyword  = docs.bm25Search("body", "cancel subscription", 5);            // BM25
      const semantic = docs.vectorSearch("embedding", embed(0), 5);                  // vector kNN
      // vector kNN, restricted to rows whose body matches a keyword (pushdown filter):
      const filtered = docs.vectorSearch("embedding", embed(0), 5, { filter: { column: "body", query: "billing" } });
      const billing  = db.querySql("SELECT body FROM docs WHERE source = 'help-center'");  // SQL
      ```

      ```rust Rust icon="rust" theme={null}
      let keyword = docs.bm25_search("body", "cancel subscription", 5, BoolMode::Or, None)?; // BM25
      let semantic =
          docs.vector_search("embedding", &embed(0), 5, VectorSearchOptions::new(), None, None)?; // vector kNN
      // vector kNN, restricted to rows whose body matches a keyword (pushdown filter):
      let filtered = docs.vector_search(
          "embedding", &embed(0), 5, VectorSearchOptions::new(),
          Some(VectorFilter { column: "body", query: "billing", mode: BoolMode::Or }), None,
      )?;
      let billing = db.query_sql("SELECT body FROM docs WHERE source = 'help-center'")?; // SQL
      ```
    </CodeGroup>

    Each search returns Arrow rows. With this tiny corpus, the `body` of the rows you get
    back looks like this:

    ```text Expected output theme={null}
    keyword   "To cancel a subscription, open Settings then Billing."

    semantic  "Refunds return to the original payment method."
              "To cancel a subscription, open Settings then Billing."
              "Enable dark mode under Settings then Appearance."

    filtered  "To cancel a subscription, open Settings then Billing."

    sql       "To cancel a subscription, open Settings then Billing."
              "Refunds return to the original payment method."
    ```

    `keyword` matched the BM25 terms; `semantic` ranks the billing notes first; `filtered`
    keeps only the note whose text matches `billing`; `sql` returns the two `help-center`
    rows. From here, feed the retrieved passages to your model as grounding context.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Core concepts" icon="diagram-project" href="/core-concepts">
    The mental model: one Parquet copy, indexed and queried four ways.
  </Card>

  <Card title="Guides" icon="book" href="/guides/tables">
    Tables, embeddings, indexing, search, and storage.
  </Card>

  <Card title="Integrations" icon="puzzle-piece" href="/integrations">
    LangChain, Vercel AI SDK, CrewAI, and MCP.
  </Card>

  <Card title="SQL Reference" icon="database" href="/sql-reference">
    Query and compose search with SQL.
  </Card>
</CardGroup>
