mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-12 14:52:26 +00:00
docs: split Query from Job, and fold the agent tutorial into Agentic Skills
Query and Job were one page because they share the _async convention, but they are different things: a query returns rows and streams them, a job returns an outcome you wait for. Two pages now, query first, each carrying a short pointer to the other rather than the shared framing. Agentic Skills absorbs the agent plugin tutorial, which was a separate page in Use Cases describing the same plugin in more detail -- the two had diverged, with the tutorial carrying the fuller install instructions. It also moves to Getting Started: installing the plugin is something you do before writing anything, not a concept to understand.
This commit is contained in:
@@ -6,6 +6,12 @@ icon: robot
|
||||
keywords: ["agents", "plugin", "claude code", "codex", "cursor", "copilot"]
|
||||
---
|
||||
|
||||
import {
|
||||
PyCamelotSchema,
|
||||
PyCamelotBatches,
|
||||
PyCamelotOssIngestion,
|
||||
} from '/snippets/build_with_ai_agents.mdx';
|
||||
|
||||
A coding agent writing LanceDB code from memory writes whatever was in its
|
||||
training data -- often an older API, sometimes one that never existed. The
|
||||
LanceDB agent plugin fixes that by supplying the current reference at the point
|
||||
@@ -13,11 +19,10 @@ the agent needs it.
|
||||
|
||||
## The plugin {#plugin}
|
||||
|
||||
It ships as the `lancedb` plugin in the
|
||||
[lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
|
||||
repository, which doubles as a plugin marketplace. It covers the Python and
|
||||
TypeScript APIs, which code is portable between a local database and a
|
||||
deployment, ingestion performance, and branch operations.
|
||||
It ships as the `lancedb` plugin in
|
||||
the [lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
|
||||
repository, which is also a plugin marketplace. Install it with your agent's
|
||||
plugin manager:
|
||||
|
||||
<CodeGroup>
|
||||
```text Claude Code
|
||||
@@ -35,27 +40,252 @@ npx plugins add lancedb/lancedb-agent-plugins
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The cross-tool installer detects which agent CLIs are on your `PATH` -- Cursor,
|
||||
GitHub Copilot CLI, VS Code, Grok Build, Kimi Code, Claude Code, Codex -- and
|
||||
installs through each one's native plugin system.
|
||||
The [`plugins`](https://www.npmjs.com/package/plugins) installer shown under
|
||||
**Other agents** is a cross-tool option. It detects which agent CLIs are on your
|
||||
`PATH` — Cursor, GitHub Copilot CLI, VS Code, Grok Build, and Kimi Code, as well
|
||||
as Claude Code and Codex — and installs through each one's native plugin system.
|
||||
|
||||
The plugin supplements the agent's training data with current LanceDB
|
||||
instructions. To pick up later revisions, refresh the marketplace:
|
||||
|
||||
<CodeGroup>
|
||||
```text Claude Code
|
||||
/plugin marketplace update lancedb
|
||||
```
|
||||
|
||||
```bash Codex icon="terminal"
|
||||
codex plugin marketplace upgrade
|
||||
```
|
||||
|
||||
```bash Other agents icon="terminal"
|
||||
npx plugins add lancedb/lancedb-agent-plugins
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Accordion title="Where the plugin gets installed">
|
||||
Each plugin manager clones the repository, reads its marketplace manifest to find
|
||||
the `lancedb` plugin, and hands the plugin to the agent's own plugin store rather
|
||||
than copying files into your project. Claude Code and Codex keep it under
|
||||
`~/.claude/plugins` and `~/.codex/plugins` respectively, so the plugin is available
|
||||
in every project on the machine.
|
||||
|
||||
The `npx plugins` installer defaults to the same user-wide scope. Pass
|
||||
`--scope project` to record the plugin in the current repository instead, so that
|
||||
everyone working in it gets the same plugin, or `-t <target>` to install for a
|
||||
single agent rather than every one it detects:
|
||||
|
||||
```bash
|
||||
npx plugins add lancedb/lancedb-agent-plugins --scope project -t cursor
|
||||
```
|
||||
|
||||
Not every agent supports project scope; `npx plugins targets` lists what it
|
||||
found and what each target supports.
|
||||
</Accordion>
|
||||
|
||||
### Get started with the LanceDB agent plugin {#get-started-with-the-lancedb-agent-plugin}
|
||||
|
||||
This tutorial uses the Camelot dataset from the [quickstart](/quickstart), with
|
||||
a portrait added for each character. Each LanceDB row contains validated
|
||||
metadata and raw JPEG bytes. Text, images, and any embeddings you add later
|
||||
remain in the same table.
|
||||
|
||||
### 1\. Download the multimodal dataset {#1-download-the-multimodal-dataset}
|
||||
|
||||
From a new project directory, download the JSON file and portraits:
|
||||
|
||||
```bash
|
||||
mkdir -p data/img
|
||||
|
||||
BASE_URL="https://docs.lancedb.com/static/assets/tutorials/build-with-ai-agents/camelot/data"
|
||||
curl -fsSL "$BASE_URL/camelot.json" -o data/camelot.json
|
||||
|
||||
for image in \
|
||||
arthur.jpg \
|
||||
guinevere.jpg \
|
||||
merlin.jpg \
|
||||
mordred.jpg \
|
||||
sir_galahad.jpg \
|
||||
sir_gawain.jpg \
|
||||
sir_lancelot.jpg \
|
||||
sir_percival.jpg
|
||||
do
|
||||
curl -fsSL "$BASE_URL/img/$image" -o "data/img/$image"
|
||||
done
|
||||
```
|
||||
|
||||
Each JSON record has this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Merlin",
|
||||
"role": "Wizard and Advisor",
|
||||
"description": "A powerful wizard and prophet who mentors Arthur.",
|
||||
"stats": {
|
||||
"strength": 2,
|
||||
"courage": 4,
|
||||
"magic": 5,
|
||||
"wisdom": 5
|
||||
},
|
||||
"img": "data/img/merlin.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
JSON input may have missing fields, unexpected fields, or values of the wrong
|
||||
type. The LanceDB plugin tells the agent to validate each record with strict
|
||||
Pydantic models before writing it.
|
||||
|
||||
After the agent writes the pipeline, inspect the schema, batching, and write
|
||||
path rather than assuming it followed the plugin's guidance correctly.
|
||||
|
||||
### 2\. Prompt your agent to build the pipeline {#2-prompt-your-agent-to-build-the-pipeline}
|
||||
|
||||
Install the Python packages used by the example:
|
||||
|
||||
```bash icon="terminal"
|
||||
uv init
|
||||
uv add lancedb pyarrow pydantic
|
||||
```
|
||||
|
||||
If you're using LanceDB Enterprise, ask the agent to ingest into an Enterprise table,
|
||||
provide the relevant environment variables for connecting to your Enterprise deployment
|
||||
in a local `.env` file, and point the agent to it.
|
||||
|
||||
```text .env
|
||||
LANCEDB_URI=db://your_project_name
|
||||
LANCEDB_API_KEY=your_api_key_here
|
||||
LANCEDB_REGION=us-east-1
|
||||
LANCEDB_HOST_OVERRIDE=https://your-enterprise-endpoint.com
|
||||
```
|
||||
|
||||
If you're using LanceDB OSS, no connection settings are required, as it runs as an
|
||||
embedded retrieval library. A simple prompt like this should work:
|
||||
|
||||
```text Agent prompt
|
||||
# If using OSS
|
||||
Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
|
||||
OSS table.
|
||||
|
||||
# If using enterprise
|
||||
Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
|
||||
Enterprise table using the connection information in `.env`.
|
||||
```
|
||||
|
||||
Because the plugin is registered with the agent's own plugin system, the agent
|
||||
should pick it up on its own once you restart the session. If it does not,
|
||||
simply ask it to use the `lancedb` plugin in the prompt, as shown above.
|
||||
|
||||
That should be enough! The agent will create `ingest_multimodal.py`, or similar.
|
||||
The following sections inspect the script to verify that it follows the plugin's guidance.
|
||||
|
||||
#### Data validation {#data-validation}
|
||||
|
||||
The plugin encourages the agent to validate each record with Pydantic before writing it.
|
||||
The agent should ideally define a schema for the table and a nested schema for the
|
||||
`stats` field.
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotSchema}
|
||||
</CodeBlock>
|
||||
|
||||
In this case, our agent correctly defined `Character` and `Stats` Pydantic models
|
||||
and validated the JSON before adding it to the table.
|
||||
|
||||
#### Batched ingestion {#batched-ingestion}
|
||||
|
||||
Naively calling `table.add()` once per row is slow, and is considered an anti-pattern
|
||||
in LanceDB. The plugin encourages the agent to collect incoming rows into batches and
|
||||
write them with a single `table.add()` call. When you use the plugin, the agent should
|
||||
produce something like this:
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotBatches}
|
||||
</CodeBlock>
|
||||
|
||||
The script calls `Character.model_validate(...)` before adding a record to the
|
||||
batch. If validation fails, that batch is never written. The function yields up
|
||||
to `batch_size` rows at a time, providing an iterable of batches for the ingestion step,
|
||||
shown next.
|
||||
|
||||
#### Table maintenance {#table-maintenance}
|
||||
For LanceDB OSS, the plugin instructs the agent to call
|
||||
`table.optimize()` after the ingestion loop. This compacts small fragments, cleans up
|
||||
old versions according to the retention policy, and incorporates new data into indexes.
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotOssIngestion}
|
||||
</CodeBlock>
|
||||
|
||||
If you're using LanceDB Enterprise, the plugin mentions that this step is not needed
|
||||
because LanceDB Enterprise handles maintenance automatically.
|
||||
|
||||
|
||||
<Note>
|
||||
This example dataset has only eight rows, so the default batch size writes it in
|
||||
one call. Larger inputs should still avoid single-row write commits.
|
||||
</Note>
|
||||
|
||||
### 3\. Run the OSS pipeline {#3-run-the-oss-pipeline}
|
||||
|
||||
```bash
|
||||
uv run python ingest_multimodal.py
|
||||
```
|
||||
|
||||
The table now contains the validated character records and their JPEG bytes.
|
||||
Here are the first three rows:
|
||||
|
||||
| Image | Character | Role |
|
||||
| --- | --- | --- |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/arthur.jpg" alt="King Arthur" width="128" height="128" /> | King Arthur | King of Camelot |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/merlin.jpg" alt="Merlin" width="128" height="128" /> | Merlin | Wizard and Advisor |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/guinevere.jpg" alt="Queen Guinevere" width="128" height="128" /> | Queen Guinevere | Queen of Camelot |
|
||||
|
||||
Once your pipeline works, you can [run experiments on branches](/agent-branch-experiments)
|
||||
to try new embedding models, parsers, or search settings without touching `main`.
|
||||
|
||||
### Takeaways {#takeaways}
|
||||
|
||||
The example in this tutorial was small, but similar ideas apply to other workflows, too.
|
||||
Give the agent the data source, the constraints it must respect, and the
|
||||
artifacts it should return.
|
||||
|
||||
The plugin supplies LanceDB-specific guidance, but it's the user's responsibility to
|
||||
ensure the output makes sense for the application.
|
||||
|
||||
### Try the plugin with your own dataset {#try-the-plugin-with-your-own-dataset}
|
||||
|
||||
The plugin shown in this tutorial should generalize reasonably well to other use cases.
|
||||
If you find any issues, open [an issue](https://github.com/lancedb/lancedb-agent-plugins/issues)
|
||||
on GitHub, clearly describing the intended behavior.
|
||||
|
||||
You can choose OSS or Enterprise based on how the work will run:
|
||||
|
||||
Start with [LanceDB OSS](/quickstart) during the early stages of a project
|
||||
when an agent is helping you prototype,
|
||||
explore a dataset, or run small workflows on a subset of the data. The application owns
|
||||
the storage and lifecycle work, so ask the agent to validate inputs, write in
|
||||
batches, and it will include table maintenance operations such as `optimize()`
|
||||
where appropriate.
|
||||
|
||||
Choose [LanceDB Enterprise](/enterprise) when the resulting table becomes
|
||||
shared production infrastructure, and the workload needs distributed capacity,
|
||||
private deployment, or platform-managed operations. The underlying data format and table
|
||||
API stay the same, so the pipeline does not need to be redesigned. The agent
|
||||
instead connects to a remote `db://` table and lets the cluster handle
|
||||
maintenance and background work.
|
||||
|
||||
## What agents are good at here {#workflows}
|
||||
|
||||
Two things about LanceDB make it unusually agent-friendly, and both have a
|
||||
worked tutorial.
|
||||
The walkthrough above is one pattern. The other has its own tutorial.
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Build an ingestion pipeline" icon="robot" href="/build-with-ai-agents">
|
||||
Install the plugin and have an agent build a multimodal ingestion pipeline
|
||||
end to end.
|
||||
</Card>
|
||||
<Card title="Run experiments on branches" icon="flask" href="/agent-branch-experiments">
|
||||
Isolate agent-driven experiments on a branch, evaluate them on a fixed test
|
||||
set, and promote only the winner.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
The second is the more interesting pattern. [Branches](/management/table#versioning)
|
||||
It is the more interesting pattern. [Branches](/management/table#versioning)
|
||||
give an agent somewhere to be wrong safely: it can rewrite a schema, recompute a
|
||||
column, or try three embedding models without touching `main`, and you keep only
|
||||
what measured better.
|
||||
|
||||
@@ -73,7 +73,7 @@ table = reader.read_all()
|
||||
|
||||
`execute_query` returns a blocking Arrow reader once results start arriving; it
|
||||
does not wait for the whole query. `execute_query_async` returns a handle
|
||||
instead -- see [Query and job](/basics/jobs#queries).
|
||||
instead -- see [Queries](/basics/query).
|
||||
|
||||
## Choosing between them {#choosing}
|
||||
|
||||
|
||||
@@ -75,4 +75,4 @@ latency, or when the query is cheap.
|
||||
performance decision -- see [Maintenance](/indexing).
|
||||
|
||||
**Jobs** are not stored data at all; they are handles to work the server is
|
||||
doing. See [Query and job](/basics/jobs).
|
||||
doing. See [Jobs](/basics/jobs).
|
||||
|
||||
@@ -98,7 +98,7 @@ nothing.
|
||||
**A job id is opaque.** It correlates with server logs and the jobs API.
|
||||
Parsing it, or storing it to resume the job later, is not supported.
|
||||
|
||||
See [Query and job](/basics/jobs).
|
||||
See [Jobs](/basics/jobs).
|
||||
|
||||
## Schema {#schema}
|
||||
|
||||
|
||||
+16
-118
@@ -1,127 +1,25 @@
|
||||
---
|
||||
title: "Query and job"
|
||||
sidebarTitle: "Query & Job"
|
||||
description: "Sync and async in LanceDB: two different distinctions that use the same word."
|
||||
title: "Jobs"
|
||||
sidebarTitle: "Job"
|
||||
description: "Work that outlives the call that started it: submitting, waiting, inspecting, cancelling."
|
||||
icon: hourglass-half
|
||||
keywords: ["job", "async", "sync", "query", "background", "await"]
|
||||
keywords: ["job", "async", "background", "events", "cancel", "refresh"]
|
||||
---
|
||||
|
||||
"Async" means two unrelated things in LanceDB, and conflating them is the most
|
||||
common source of confusion about how work gets done.
|
||||
|
||||
| | What it is | What it changes |
|
||||
| --- | --- | --- |
|
||||
| **Async client** | `connect_async`, `await` | whether *your thread* blocks |
|
||||
| **Async execution** | `_async` methods returning a handle | whether *the work* outlives the call |
|
||||
|
||||
They are independent. A synchronous Python call can start work that runs for an
|
||||
hour; an `await` can wait for something that finishes instantly.
|
||||
|
||||
<Note>
|
||||
Jobs are listed in SQL with [`SHOW JOBS`](/sql/show#jobs), and their failures with [`SHOW ERRORS`](/sql/show#errors).
|
||||
</Note>
|
||||
|
||||
## Every `_async` method returns a handle {#handles}
|
||||
|
||||
An `_async` method submits work and hands back a **handle** to it. Which handle
|
||||
depends on what you submitted -- `execute_query_async` returns a query,
|
||||
everything else returns a [job](#jobs) -- but they are the same idea, and they
|
||||
answer the same four questions:
|
||||
|
||||
| | Query handle | Job handle |
|
||||
| --- | --- | --- |
|
||||
| Which one is this? | `id` | `id` |
|
||||
| How is it going? | `describe()` | `status()`, `refresh()`, `events()` |
|
||||
| Give me the result | `reader()` — streams batches as they arrive | `wait()` — blocks for the terminal result |
|
||||
| Stop it | `cancel()` | `cancel()` |
|
||||
|
||||
The difference is in the third row, and it follows from what the work produces.
|
||||
A query produces **rows**, so its handle streams them and you can start reading
|
||||
before the query finishes. A job produces **an outcome** -- an index built, a
|
||||
column filled -- so its handle has nothing to stream and gives you the result
|
||||
once, at the end.
|
||||
|
||||
Everything else is shared: both identify the work on the server, both report on
|
||||
it without blocking, and both can be told to stop.
|
||||
|
||||
## Async client: whether you block {#async-client}
|
||||
|
||||
A concern of your language, not of LanceDB. The work is identical either way.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python icon="python"
|
||||
db = lancedb.connect("db://my-db", api_key="...") # blocking
|
||||
tbl = db.open_table("documents")
|
||||
|
||||
db = await lancedb.connect_async("db://my-db", api_key="...") # coroutines
|
||||
tbl = await db.open_table("documents")
|
||||
```
|
||||
|
||||
```typescript TypeScript icon="square-js"
|
||||
// TypeScript is async throughout; there is no blocking form.
|
||||
const db = await lancedb.connect("db://my-db", { apiKey: "..." });
|
||||
const tbl = await db.openTable("documents");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Python offers both. `AsyncConnection` exposes the same surface as coroutines, so
|
||||
choosing between them is about the program you are writing -- a service wants
|
||||
the async form, a script does not care.
|
||||
|
||||
## Queries: reading before the query finishes {#queries}
|
||||
|
||||
A query is not a job, but it has the same shape of choice: wait for everything,
|
||||
or take a handle and start reading.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python icon="python"
|
||||
reader = db.execute_query("SELECT id, text FROM my_docs LIMIT 1000")
|
||||
table = reader.read_all()
|
||||
|
||||
query = db.execute_query_async("SELECT id, text FROM my_docs")
|
||||
```
|
||||
|
||||
```sql SQL
|
||||
SELECT id, text FROM my_docs LIMIT 1000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
`execute_query` returns a blocking Arrow reader **once results start arriving**
|
||||
-- it does not wait for the whole query to finish. Results stream as record
|
||||
batches, so a large result set does not have to fit in memory before you see its
|
||||
first row.
|
||||
|
||||
`execute_query_async` returns the query handle instead:
|
||||
|
||||
```python
|
||||
query = db.execute_query_async("SELECT id, text FROM my_docs")
|
||||
|
||||
query.id # identifies it on the server
|
||||
query.describe() # what it is doing, without consuming results
|
||||
reader = query.reader() # stream the rows when you want them
|
||||
query.cancel() # stop it
|
||||
```
|
||||
|
||||
`describe_query(id)` reports on one from elsewhere, the way `open_job(id)` does
|
||||
for a job.
|
||||
|
||||
<Note>
|
||||
SQL requires a deployment. See [SDKs and SQL](/basics/apis).
|
||||
</Note>
|
||||
|
||||
Query-builder searches -- vector, full-text, hybrid -- are ordinary calls that
|
||||
return results. They are fast enough not to need a handle. See
|
||||
[Query](/search).
|
||||
|
||||
## Jobs: work that outlives the call {#jobs}
|
||||
|
||||
Some operations take longer than a request should. Building an index over a
|
||||
large table, refreshing a computed column, registering a function -- these run
|
||||
on the server after the call that started them has returned.
|
||||
|
||||
Each is submitted the same way and returns the same handle.
|
||||
|
||||
### The `_async` convention {#convention}
|
||||
<Note>
|
||||
A [query](/basics/query) also has an `_async` form, but its handle streams rows
|
||||
rather than waiting for an outcome. Both answer the same four questions --
|
||||
which one is this, how is it going, give me the result, stop it -- and differ
|
||||
only in the third.
|
||||
</Note>
|
||||
|
||||
## The `_async` convention {#convention}
|
||||
|
||||
An operation that can run in the background has two forms. The plain name blocks
|
||||
until the work is done. The `_async` / `Async` name submits the work and returns
|
||||
@@ -140,7 +38,7 @@ waits. Reach for the `_async` form when you want to do something else meanwhile,
|
||||
run several at once, or survive the client going away.
|
||||
</Note>
|
||||
|
||||
### Submitting and waiting {#waiting}
|
||||
## Submitting and waiting {#waiting}
|
||||
|
||||
<CodeGroup>
|
||||
```python Python icon="python"
|
||||
@@ -184,7 +82,7 @@ The TypeScript `wait()` resolves to `void`. Read the outcome from `result` after
|
||||
it resolves.
|
||||
</Note>
|
||||
|
||||
### Inspecting a job {#inspecting}
|
||||
## Inspecting a job {#inspecting}
|
||||
|
||||
Submitting an operation returns only an id, so the detail fields are empty until
|
||||
the handle fetches the record. `refresh()` does that; `status()` does it as a
|
||||
@@ -217,7 +115,7 @@ job.failure; // why it failed, when it did
|
||||
Printing the handle renders every field it knows, which is usually faster than
|
||||
reading them one at a time.
|
||||
|
||||
### Progress, from events {#events}
|
||||
## Progress, from events {#events}
|
||||
|
||||
`result` only tells you anything once the job has finished. Events are written
|
||||
as it runs, and they outlive the workers that produced them -- so a job that is
|
||||
@@ -253,7 +151,7 @@ silently lose its tail unless you pass `limit`.
|
||||
Events are recorded server-side, so this raises for an operation that ran
|
||||
in-process.
|
||||
|
||||
### Finding and cancelling jobs {#managing}
|
||||
## Finding and cancelling jobs {#managing}
|
||||
|
||||
The connection lists and reopens jobs, so a handle lost to a restarted process
|
||||
is recoverable from its id.
|
||||
|
||||
@@ -45,7 +45,7 @@ page most likely to mislead you, because nothing in the result says it was cut.
|
||||
32 of them. Narrow with `FOR JOB` when you need a specific one rather than a
|
||||
survey.
|
||||
|
||||
See [Query and job](/basics/jobs).
|
||||
See [Jobs](/basics/jobs).
|
||||
|
||||
## Schema and types {#schema}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: "Queries"
|
||||
sidebarTitle: "Query"
|
||||
description: "Running a query, and reading its results before it finishes."
|
||||
icon: magnifying-glass
|
||||
keywords: ["query", "async", "streaming", "arrow", "reader", "execute_query"]
|
||||
---
|
||||
|
||||
A query returns rows. How you get them depends on two choices that are easy to
|
||||
conflate: whether **your thread** blocks, and whether you take the results all
|
||||
at once or stream them as they arrive.
|
||||
|
||||
## Blocking or not is a language concern {#async-client}
|
||||
|
||||
A concern of your language, not of LanceDB. The work is identical either way.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python icon="python"
|
||||
db = lancedb.connect("db://my-db", api_key="...") # blocking
|
||||
tbl = db.open_table("documents")
|
||||
|
||||
db = await lancedb.connect_async("db://my-db", api_key="...") # coroutines
|
||||
tbl = await db.open_table("documents")
|
||||
```
|
||||
|
||||
```typescript TypeScript icon="square-js"
|
||||
// TypeScript is async throughout; there is no blocking form.
|
||||
const db = await lancedb.connect("db://my-db", { apiKey: "..." });
|
||||
const tbl = await db.openTable("documents");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Python offers both. `AsyncConnection` exposes the same surface as coroutines, so
|
||||
choosing between them is about the program you are writing -- a service wants
|
||||
the async form, a script does not care.
|
||||
|
||||
## Reading before the query finishes {#queries}
|
||||
|
||||
A query is not a job, but it has the same shape of choice: wait for everything,
|
||||
or take a handle and start reading.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python icon="python"
|
||||
reader = db.execute_query("SELECT id, text FROM my_docs LIMIT 1000")
|
||||
table = reader.read_all()
|
||||
|
||||
query = db.execute_query_async("SELECT id, text FROM my_docs")
|
||||
```
|
||||
|
||||
```sql SQL
|
||||
SELECT id, text FROM my_docs LIMIT 1000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
`execute_query` returns a blocking Arrow reader **once results start arriving**
|
||||
-- it does not wait for the whole query to finish. Results stream as record
|
||||
batches, so a large result set does not have to fit in memory before you see its
|
||||
first row.
|
||||
|
||||
`execute_query_async` returns the query handle instead:
|
||||
|
||||
```python
|
||||
query = db.execute_query_async("SELECT id, text FROM my_docs")
|
||||
|
||||
query.id # identifies it on the server
|
||||
query.describe() # what it is doing, without consuming results
|
||||
reader = query.reader() # stream the rows when you want them
|
||||
query.cancel() # stop it
|
||||
```
|
||||
|
||||
`describe_query(id)` reports on one from elsewhere, the way `open_job(id)` does
|
||||
for a job.
|
||||
|
||||
<Note>
|
||||
SQL requires a deployment. See [SDKs and SQL](/basics/apis).
|
||||
</Note>
|
||||
|
||||
Query-builder searches -- vector, full-text, hybrid -- are ordinary calls that
|
||||
return results. They are fast enough not to need a handle. See
|
||||
[Query](/search).
|
||||
|
||||
## Searches are ordinary calls {#searches}
|
||||
|
||||
Query-builder searches -- [vector](/search/vector-search),
|
||||
[full-text](/search/full-text-search), [hybrid](/search/hybrid-search) -- return
|
||||
results directly. They are fast enough not to need a handle, and they have no
|
||||
SQL form. See [Semantic Search](/search).
|
||||
|
||||
## When the work outlives the call {#jobs}
|
||||
|
||||
Some operations take longer than a query should: building an index, refreshing a
|
||||
computed column, registering a function. Those return a **job** rather than
|
||||
rows, and a job is a different handle with a different shape. See
|
||||
[Jobs](/basics/jobs).
|
||||
@@ -1,270 +0,0 @@
|
||||
---
|
||||
title: "Tutorial: Use the LanceDB agent plugin"
|
||||
sidebarTitle: "Using LanceDB Agent Skill"
|
||||
description: "Install the LanceDB plugin and use an AI coding agent to quickly build a multimodal ingestion pipeline."
|
||||
icon: "robot"
|
||||
keywords: ["AI agents", "agent plugins", "multimodal", "Pydantic", "branches", "embeddings"]
|
||||
---
|
||||
|
||||
import {
|
||||
PyCamelotSchema,
|
||||
PyCamelotBatches,
|
||||
PyCamelotOssIngestion,
|
||||
} from '/snippets/build_with_ai_agents.mdx';
|
||||
|
||||
The LanceDB agent plugin gives coding agents a maintained reference for the
|
||||
Python and TypeScript APIs. It also covers portable OSS and Enterprise code,
|
||||
ingestion performance, and branch operations. It ships as the `lancedb` plugin in
|
||||
the [lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
|
||||
repository, which is also a plugin marketplace. Install it with your agent's
|
||||
plugin manager:
|
||||
|
||||
<CodeGroup>
|
||||
```text Claude Code
|
||||
/plugin marketplace add lancedb/lancedb-agent-plugins
|
||||
/plugin install lancedb@lancedb
|
||||
```
|
||||
|
||||
```bash Codex icon="terminal"
|
||||
codex plugin marketplace add lancedb/lancedb-agent-plugins
|
||||
codex plugin add lancedb@lancedb
|
||||
```
|
||||
|
||||
```bash Other agents icon="terminal"
|
||||
npx plugins add lancedb/lancedb-agent-plugins
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The [`plugins`](https://www.npmjs.com/package/plugins) installer shown under
|
||||
**Other agents** is a cross-tool option. It detects which agent CLIs are on your
|
||||
`PATH` — Cursor, GitHub Copilot CLI, VS Code, Grok Build, and Kimi Code, as well
|
||||
as Claude Code and Codex — and installs through each one's native plugin system.
|
||||
|
||||
The plugin supplements the agent's training data with current LanceDB
|
||||
instructions. To pick up later revisions, refresh the marketplace:
|
||||
|
||||
<CodeGroup>
|
||||
```text Claude Code
|
||||
/plugin marketplace update lancedb
|
||||
```
|
||||
|
||||
```bash Codex icon="terminal"
|
||||
codex plugin marketplace upgrade
|
||||
```
|
||||
|
||||
```bash Other agents icon="terminal"
|
||||
npx plugins add lancedb/lancedb-agent-plugins
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Accordion title="Where the plugin gets installed">
|
||||
Each plugin manager clones the repository, reads its marketplace manifest to find
|
||||
the `lancedb` plugin, and hands the plugin to the agent's own plugin store rather
|
||||
than copying files into your project. Claude Code and Codex keep it under
|
||||
`~/.claude/plugins` and `~/.codex/plugins` respectively, so the plugin is available
|
||||
in every project on the machine.
|
||||
|
||||
The `npx plugins` installer defaults to the same user-wide scope. Pass
|
||||
`--scope project` to record the plugin in the current repository instead, so that
|
||||
everyone working in it gets the same plugin, or `-t <target>` to install for a
|
||||
single agent rather than every one it detects:
|
||||
|
||||
```bash
|
||||
npx plugins add lancedb/lancedb-agent-plugins --scope project -t cursor
|
||||
```
|
||||
|
||||
Not every agent supports project scope; `npx plugins targets` lists what it
|
||||
found and what each target supports.
|
||||
</Accordion>
|
||||
|
||||
## Get started with the LanceDB agent plugin {#get-started-with-the-lancedb-agent-plugin}
|
||||
|
||||
This tutorial uses the Camelot dataset from the [quickstart](/quickstart), with
|
||||
a portrait added for each character. Each LanceDB row contains validated
|
||||
metadata and raw JPEG bytes. Text, images, and any embeddings you add later
|
||||
remain in the same table.
|
||||
|
||||
### 1\. Download the multimodal dataset {#1-download-the-multimodal-dataset}
|
||||
|
||||
From a new project directory, download the JSON file and portraits:
|
||||
|
||||
```bash
|
||||
mkdir -p data/img
|
||||
|
||||
BASE_URL="https://docs.lancedb.com/static/assets/tutorials/build-with-ai-agents/camelot/data"
|
||||
curl -fsSL "$BASE_URL/camelot.json" -o data/camelot.json
|
||||
|
||||
for image in \
|
||||
arthur.jpg \
|
||||
guinevere.jpg \
|
||||
merlin.jpg \
|
||||
mordred.jpg \
|
||||
sir_galahad.jpg \
|
||||
sir_gawain.jpg \
|
||||
sir_lancelot.jpg \
|
||||
sir_percival.jpg
|
||||
do
|
||||
curl -fsSL "$BASE_URL/img/$image" -o "data/img/$image"
|
||||
done
|
||||
```
|
||||
|
||||
Each JSON record has this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Merlin",
|
||||
"role": "Wizard and Advisor",
|
||||
"description": "A powerful wizard and prophet who mentors Arthur.",
|
||||
"stats": {
|
||||
"strength": 2,
|
||||
"courage": 4,
|
||||
"magic": 5,
|
||||
"wisdom": 5
|
||||
},
|
||||
"img": "data/img/merlin.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
JSON input may have missing fields, unexpected fields, or values of the wrong
|
||||
type. The LanceDB plugin tells the agent to validate each record with strict
|
||||
Pydantic models before writing it.
|
||||
|
||||
After the agent writes the pipeline, inspect the schema, batching, and write
|
||||
path rather than assuming it followed the plugin's guidance correctly.
|
||||
|
||||
### 2\. Prompt your agent to build the pipeline {#2-prompt-your-agent-to-build-the-pipeline}
|
||||
|
||||
Install the Python packages used by the example:
|
||||
|
||||
```bash icon="terminal"
|
||||
uv init
|
||||
uv add lancedb pyarrow pydantic
|
||||
```
|
||||
|
||||
If you're using LanceDB Enterprise, ask the agent to ingest into an Enterprise table,
|
||||
provide the relevant environment variables for connecting to your Enterprise deployment
|
||||
in a local `.env` file, and point the agent to it.
|
||||
|
||||
```text .env
|
||||
LANCEDB_URI=db://your_project_name
|
||||
LANCEDB_API_KEY=your_api_key_here
|
||||
LANCEDB_REGION=us-east-1
|
||||
LANCEDB_HOST_OVERRIDE=https://your-enterprise-endpoint.com
|
||||
```
|
||||
|
||||
If you're using LanceDB OSS, no connection settings are required, as it runs as an
|
||||
embedded retrieval library. A simple prompt like this should work:
|
||||
|
||||
```text Agent prompt
|
||||
# If using OSS
|
||||
Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
|
||||
OSS table.
|
||||
|
||||
# If using enterprise
|
||||
Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
|
||||
Enterprise table using the connection information in `.env`.
|
||||
```
|
||||
|
||||
Because the plugin is registered with the agent's own plugin system, the agent
|
||||
should pick it up on its own once you restart the session. If it does not,
|
||||
simply ask it to use the `lancedb` plugin in the prompt, as shown above.
|
||||
|
||||
That should be enough! The agent will create `ingest_multimodal.py`, or similar.
|
||||
The following sections inspect the script to verify that it follows the plugin's guidance.
|
||||
|
||||
#### Data validation {#data-validation}
|
||||
|
||||
The plugin encourages the agent to validate each record with Pydantic before writing it.
|
||||
The agent should ideally define a schema for the table and a nested schema for the
|
||||
`stats` field.
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotSchema}
|
||||
</CodeBlock>
|
||||
|
||||
In this case, our agent correctly defined `Character` and `Stats` Pydantic models
|
||||
and validated the JSON before adding it to the table.
|
||||
|
||||
#### Batched ingestion {#batched-ingestion}
|
||||
|
||||
Naively calling `table.add()` once per row is slow, and is considered an anti-pattern
|
||||
in LanceDB. The plugin encourages the agent to collect incoming rows into batches and
|
||||
write them with a single `table.add()` call. When you use the plugin, the agent should
|
||||
produce something like this:
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotBatches}
|
||||
</CodeBlock>
|
||||
|
||||
The script calls `Character.model_validate(...)` before adding a record to the
|
||||
batch. If validation fails, that batch is never written. The function yields up
|
||||
to `batch_size` rows at a time, providing an iterable of batches for the ingestion step,
|
||||
shown next.
|
||||
|
||||
#### Table maintenance {#table-maintenance}
|
||||
For LanceDB OSS, the plugin instructs the agent to call
|
||||
`table.optimize()` after the ingestion loop. This compacts small fragments, cleans up
|
||||
old versions according to the retention policy, and incorporates new data into indexes.
|
||||
|
||||
<CodeBlock filename="ingest_multimodal.py" language="Python" icon="python">
|
||||
{PyCamelotOssIngestion}
|
||||
</CodeBlock>
|
||||
|
||||
If you're using LanceDB Enterprise, the plugin mentions that this step is not needed
|
||||
because LanceDB Enterprise handles maintenance automatically.
|
||||
|
||||
|
||||
<Note>
|
||||
This example dataset has only eight rows, so the default batch size writes it in
|
||||
one call. Larger inputs should still avoid single-row write commits.
|
||||
</Note>
|
||||
|
||||
### 3\. Run the OSS pipeline {#3-run-the-oss-pipeline}
|
||||
|
||||
```bash
|
||||
uv run python ingest_multimodal.py
|
||||
```
|
||||
|
||||
The table now contains the validated character records and their JPEG bytes.
|
||||
Here are the first three rows:
|
||||
|
||||
| Image | Character | Role |
|
||||
| --- | --- | --- |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/arthur.jpg" alt="King Arthur" width="128" height="128" /> | King Arthur | King of Camelot |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/merlin.jpg" alt="Merlin" width="128" height="128" /> | Merlin | Wizard and Advisor |
|
||||
| <img src="/static/assets/tutorials/build-with-ai-agents/camelot/data/img/guinevere.jpg" alt="Queen Guinevere" width="128" height="128" /> | Queen Guinevere | Queen of Camelot |
|
||||
|
||||
Once your pipeline works, you can [run experiments on branches](/agent-branch-experiments)
|
||||
to try new embedding models, parsers, or search settings without touching `main`.
|
||||
|
||||
## Takeaways {#takeaways}
|
||||
|
||||
The example in this tutorial was small, but similar ideas apply to other workflows, too.
|
||||
Give the agent the data source, the constraints it must respect, and the
|
||||
artifacts it should return.
|
||||
|
||||
The plugin supplies LanceDB-specific guidance, but it's the user's responsibility to
|
||||
ensure the output makes sense for the application.
|
||||
|
||||
### Try the plugin with your own dataset {#try-the-plugin-with-your-own-dataset}
|
||||
|
||||
The plugin shown in this tutorial should generalize reasonably well to other use cases.
|
||||
If you find any issues, open [an issue](https://github.com/lancedb/lancedb-agent-plugins/issues)
|
||||
on GitHub, clearly describing the intended behavior.
|
||||
|
||||
You can choose OSS or Enterprise based on how the work will run:
|
||||
|
||||
Start with [LanceDB OSS](/quickstart) during the early stages of a project
|
||||
when an agent is helping you prototype,
|
||||
explore a dataset, or run small workflows on a subset of the data. The application owns
|
||||
the storage and lifecycle work, so ask the agent to validate inputs, write in
|
||||
batches, and it will include table maintenance operations such as `optimize()`
|
||||
where appropriate.
|
||||
|
||||
Choose [LanceDB Enterprise](/enterprise) when the resulting table becomes
|
||||
shared production infrastructure, and the workload needs distributed capacity,
|
||||
private deployment, or platform-managed operations. The underlying data format and table
|
||||
API stay the same, so the pipeline does not need to be redesigned. The agent
|
||||
instead connects to a remote `db://` table and lets the cluster handle
|
||||
maintenance and background work.
|
||||
+8
-9
@@ -138,6 +138,10 @@
|
||||
{
|
||||
"source": "/training",
|
||||
"destination": "/training/data-loading"
|
||||
},
|
||||
{
|
||||
"source": "/build-with-ai-agents",
|
||||
"destination": "/basics/agentic-skills"
|
||||
}
|
||||
],
|
||||
"navigation": {
|
||||
@@ -150,7 +154,8 @@
|
||||
"pages": [
|
||||
"index",
|
||||
"quickstart",
|
||||
"architecture"
|
||||
"architecture",
|
||||
"basics/agentic-skills"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -158,12 +163,12 @@
|
||||
"pages": [
|
||||
"basics/lance",
|
||||
"basics/catalog",
|
||||
"basics/query",
|
||||
"basics/jobs",
|
||||
"tables/consistency",
|
||||
"basics/guarantees",
|
||||
"basics/limits",
|
||||
"basics/apis",
|
||||
"basics/agentic-skills"
|
||||
"basics/apis"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -393,12 +398,6 @@
|
||||
"tab": "Use Cases",
|
||||
"pages": [
|
||||
"tutorials/index",
|
||||
{
|
||||
"group": "General",
|
||||
"pages": [
|
||||
"build-with-ai-agents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Search",
|
||||
"pages": [
|
||||
|
||||
@@ -30,7 +30,7 @@ Queries execute on the server and results come back as Arrow record batches --
|
||||
nothing is converted to pandas or Python objects unless you ask. `execute_query`
|
||||
hands back a reader as soon as the first batch is ready rather than when the
|
||||
query finishes, so a large result set does not have to fit in memory before you
|
||||
start reading it. See [Query and job](/basics/jobs#queries).
|
||||
start reading it. See [Queries](/basics/query).
|
||||
|
||||
Served over an [Arrow Flight SQL](https://arrow.apache.org/docs/format/FlightSql.html)
|
||||
endpoint, so any Flight SQL client reaches it.
|
||||
|
||||
@@ -49,7 +49,7 @@ SHOW JOBS
|
||||
```
|
||||
|
||||
Every job the server knows about — id, type, state, creation time. The SQL
|
||||
counterpart of `list_jobs`; see [Query and job](/basics/jobs).
|
||||
counterpart of `list_jobs`; see [Jobs](/basics/jobs).
|
||||
|
||||
## SHOW ERRORS {#errors}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user