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

# Quickstart: install Infino and run your first search

> Install Infino, then index a small knowledge base and retrieve over it four ways (BM25, vector, hybrid, 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 four ways: by keyword, by meaning, by both
at once (hybrid), and with SQL. Pick your language; each step builds on the one before it.

**How do you want to use Infino?** This walkthrough uses the SDKs; the CLI and the MCP
server do the same retrieval from the terminal or an AI agent.

> [card] [SDK](#install)
>
> Python, Node, or Rust — the walkthrough on this page.

> [card] [CLI](https://infino.ai/docs/cli.md)
>
> Create, ingest, and search from the terminal or a coding agent — no code.

> [card] [MCP server](https://infino.ai/docs/integrations/mcp.md)
>
> Expose these searches as tools to an AI agent (Claude, Cursor, …).

**1. Install**

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

> [accordion] Node: the examples are ESM
>
> The Node examples below use `import`, so the file has to be a module. `npm init -y`
> writes `"type": "commonjs"`, and running an `import` under that fails with
> `Cannot use import statement outside a module`. Either add `"type": "module"` to
> your `package.json` or save the file as `.mjs`.

> [accordion] Rust: Arrow types and allocator
>
> Infino re-exports the Arrow crates it uses, so you don't add them separately or match
> versions — reach them as `infino::arrow_array` and `infino::arrow_schema` (as the examples
> below do). `update` / `delete` take DataFusion predicates (`col`, `lit`); if you use them,
> add `datafusion-expr` matching Infino's DataFusion version (`cargo tree -p 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 = { default-features = false }`.

**2. 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. To run the same walkthrough against the hosted service, point it
at `"https://<host>/<database>"` and pass an API key: see the
[Infino Cloud quickstart](https://infino.ai/docs/cloud/quickstart.md). Everything after the connect line is the
same either way.

```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 infino::arrow_array::{FixedSizeListArray, Float32Array, LargeStringArray, RecordBatch};
use infino::arrow_schema::{DataType, Field, Schema};
use infino::{connect, Bm25SearchOptions, BoolMode, IndexSpec, Metric};

let db = connect("memory://")?;
```

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

```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, "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, "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, Metric::Cosine),
)?;
```

**4. 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](https://infino.ai/docs/guides/embeddings.md).

```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)),
    ],
)?)?;
```

**5. Search it**

Now the part you came for. The same table answers every kind of query: keyword,
meaning, hybrid, and SQL.

```python Python icon="python" theme={null}
# Searches return `_id` + `score` by default; pass `projection` to name the
# columns you want back (here, `body`).
keyword  = docs.bm25_search("body", "cancel subscription", 5, projection=["body"])   # BM25
semantic = docs.vector_search("embedding", embed(0), 5, projection=["body"])         # vector kNN
# hybrid: BM25 + vector, fused with reciprocal-rank fusion:
hybrid   = docs.hybrid_search("body", "cancel subscription", "embedding", embed(0), 5, projection=["body"])
billing  = db.query_sql("SELECT body FROM docs WHERE source = 'help-center'")        # SQL

for name, hits in [("keyword", keyword), ("semantic", semantic), ("hybrid", hybrid), ("sql", billing)]:
    print(name, hits.column("body").to_pylist())
```

```typescript Node.js icon="node-js" theme={null}
// Searches return `_id` + `score` by default; pass `projection` to name the
// columns you want back (here, `body`).
const keyword  = docs.bm25Search("body", "cancel subscription", 5, { projection: ["body"] });   // BM25
const semantic = docs.vectorSearch("embedding", embed(0), 5, { projection: ["body"] });         // vector kNN
// hybrid: BM25 + vector, fused with reciprocal-rank fusion:
const hybrid   = docs.hybridSearch("body", "cancel subscription", "embedding", embed(0), 5, { projection: ["body"] });
const billing  = db.querySql("SELECT body FROM docs WHERE source = 'help-center'");             // SQL

for (const [name, hits] of [["keyword", keyword], ["semantic", semantic], ["hybrid", hybrid], ["sql", billing]]) {
  console.log(name, hits.map((r) => r.body));
}
```

```rust Rust icon="rust" theme={null}
// Searches return `_id` + `score` by default; pass a projection to name the
// columns you want back (here, `body`).
let keyword = docs.bm25_search(
    "body", "cancel subscription", 5, Bm25SearchOptions::new(), Some(&["body"]),
)?; // BM25
let semantic = docs.vector_search(
    "embedding", &embed(0), 5, None, Some(&["body"]),
)?; // vector kNN
// hybrid: BM25 + vector, fused with reciprocal-rank fusion:
let hybrid = docs.hybrid_search(
    "body", "cancel subscription", BoolMode::Or,
    "embedding", &embed(0), 5, Some(&["body"]),
)?;
let billing = db.query_sql("SELECT body FROM docs WHERE source = 'help-center'")?; // SQL
```

Each search returns rows carrying the projected `body`. With this tiny corpus you get:

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

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

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

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; `hybrid`
fuses the two rankings, so the exact keyword match leads while the semantically close
note follows; `sql` returns the two `help-center` rows. From here, feed the retrieved
passages to your model as grounding context.

## Next steps

> [card] [Core concepts](https://infino.ai/docs/core-concepts.md)
>
> The mental model: one Parquet copy, indexed and queried four ways.

> [card] [Guides](https://infino.ai/docs/guides/tables.md)
>
> Tables, embeddings, indexing, search, and storage.

> [card] [Infino Cloud](https://infino.ai/docs/cloud/quickstart.md)
>
> Run the same searches against a hosted database, no storage to manage.

> [card] [SQL Reference](https://infino.ai/docs/sql-reference.md)
>
> Query and compose search with SQL.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.