diff --git a/docs/web/basics/agentic-skills.mdx b/docs/web/basics/agentic-skills.mdx index 6a6a37bdb..319e770b4 100644 --- a/docs/web/basics/agentic-skills.mdx +++ b/docs/web/basics/agentic-skills.mdx @@ -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: ```text Claude Code @@ -35,27 +40,252 @@ npx plugins add lancedb/lancedb-agent-plugins ``` -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: + + +```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 +``` + + + +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 ` 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. + + +### 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. + + + {PyCamelotSchema} + + +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: + + + {PyCamelotBatches} + + +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. + + + {PyCamelotOssIngestion} + + +If you're using LanceDB Enterprise, the plugin mentions that this step is not needed +because LanceDB Enterprise handles maintenance automatically. + + + +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. + + +### 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 | +| --- | --- | --- | +| King Arthur | King Arthur | King of Camelot | +| Merlin | Merlin | Wizard and Advisor | +| Queen Guinevere | 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. - - Install the plugin and have an agent build a multimodal ingestion pipeline - end to end. - Isolate agent-driven experiments on a branch, evaluate them on a fixed test set, and promote only the winner. -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. diff --git a/docs/web/basics/apis.mdx b/docs/web/basics/apis.mdx index 6446fb223..9eca49d41 100644 --- a/docs/web/basics/apis.mdx +++ b/docs/web/basics/apis.mdx @@ -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} diff --git a/docs/web/basics/catalog.mdx b/docs/web/basics/catalog.mdx index 0826ab673..580e99546 100644 --- a/docs/web/basics/catalog.mdx +++ b/docs/web/basics/catalog.mdx @@ -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). diff --git a/docs/web/basics/guarantees.mdx b/docs/web/basics/guarantees.mdx index 42710f5b5..59a89c32d 100644 --- a/docs/web/basics/guarantees.mdx +++ b/docs/web/basics/guarantees.mdx @@ -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} diff --git a/docs/web/basics/jobs.mdx b/docs/web/basics/jobs.mdx index 925c21602..aa6c9cda5 100644 --- a/docs/web/basics/jobs.mdx +++ b/docs/web/basics/jobs.mdx @@ -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. - - -Jobs are listed in SQL with [`SHOW JOBS`](/sql/show#jobs), and their failures with [`SHOW ERRORS`](/sql/show#errors). - - -## 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. - - -```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"); -``` - - -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. - - -```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 -``` - - -`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. - - -SQL requires a deployment. See [SDKs and SQL](/basics/apis). - - -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} + +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. + + +## 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. -### Submitting and waiting {#waiting} +## Submitting and waiting {#waiting} ```python Python icon="python" @@ -184,7 +82,7 @@ The TypeScript `wait()` resolves to `void`. Read the outcome from `result` after it resolves. -### 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. diff --git a/docs/web/basics/limits.mdx b/docs/web/basics/limits.mdx index 5ef4e5063..d615fade5 100644 --- a/docs/web/basics/limits.mdx +++ b/docs/web/basics/limits.mdx @@ -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} diff --git a/docs/web/basics/query.mdx b/docs/web/basics/query.mdx new file mode 100644 index 000000000..b643d0f35 --- /dev/null +++ b/docs/web/basics/query.mdx @@ -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. + + +```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"); +``` + + +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. + + +```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 +``` + + +`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. + + +SQL requires a deployment. See [SDKs and SQL](/basics/apis). + + +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). diff --git a/docs/web/build-with-ai-agents.mdx b/docs/web/build-with-ai-agents.mdx deleted file mode 100644 index 4bfd6ab97..000000000 --- a/docs/web/build-with-ai-agents.mdx +++ /dev/null @@ -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: - - -```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 -``` - - -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: - - -```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 -``` - - - -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 ` 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. - - -## 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. - - - {PyCamelotSchema} - - -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: - - - {PyCamelotBatches} - - -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. - - - {PyCamelotOssIngestion} - - -If you're using LanceDB Enterprise, the plugin mentions that this step is not needed -because LanceDB Enterprise handles maintenance automatically. - - - -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. - - -### 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 | -| --- | --- | --- | -| King Arthur | King Arthur | King of Camelot | -| Merlin | Merlin | Wizard and Advisor | -| Queen Guinevere | 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. diff --git a/docs/web/docs.json b/docs/web/docs.json index ef0635e85..e15887823 100644 --- a/docs/web/docs.json +++ b/docs/web/docs.json @@ -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": [ diff --git a/docs/web/sql/index.mdx b/docs/web/sql/index.mdx index 63f44116d..c8701c494 100644 --- a/docs/web/sql/index.mdx +++ b/docs/web/sql/index.mdx @@ -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. diff --git a/docs/web/sql/show.mdx b/docs/web/sql/show.mdx index 4356581b3..14537d3da 100644 --- a/docs/web/sql/show.mdx +++ b/docs/web/sql/show.mdx @@ -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}