From 8f4747590ad097bf2ec928659d1e9413861cd27f Mon Sep 17 00:00:00 2001 From: Pierre Brunelle Date: Sat, 12 Sep 2026 08:33:27 -0700 Subject: [PATCH] Use the canonical Pixeltable skill repository --- .agents/skills/pixeltable/SKILL.md | 222 ------------ .agents/skills/pixeltable/agents/openai.yaml | 4 - .../pixeltable/references/anti-patterns.md | 90 ----- .agents/skills/pixeltable/references/cli.md | 249 ------------- .../skills/pixeltable/references/core-api.md | 342 ------------------ .../skills/pixeltable/references/providers.md | 57 --- .../skills/pixeltable/references/workflows.md | 82 ----- .github/workflows/ci.yml | 3 +- AGENTS.md | 6 +- docs/pixeltable-0.7.7-upgrade.md | 2 +- skills-lock.json | 11 - 11 files changed, 6 insertions(+), 1062 deletions(-) delete mode 100644 .agents/skills/pixeltable/SKILL.md delete mode 100644 .agents/skills/pixeltable/agents/openai.yaml delete mode 100644 .agents/skills/pixeltable/references/anti-patterns.md delete mode 100644 .agents/skills/pixeltable/references/cli.md delete mode 100644 .agents/skills/pixeltable/references/core-api.md delete mode 100644 .agents/skills/pixeltable/references/providers.md delete mode 100644 .agents/skills/pixeltable/references/workflows.md delete mode 100644 skills-lock.json diff --git a/.agents/skills/pixeltable/SKILL.md b/.agents/skills/pixeltable/SKILL.md deleted file mode 100644 index 523bea6..0000000 --- a/.agents/skills/pixeltable/SKILL.md +++ /dev/null @@ -1,222 +0,0 @@ ---- -name: pixeltable -description: > - Build multimodal AI apps with Pixeltable. One application file (app.py) - declares TableModel tables and FastAPIRouter routes. Create tables with - pxt schema update. Start HTTP with pxt service update. Insert a row or - POST to try the app. Use computed columns instead of LangChain, - pandas-as-store, or a separate vector DB. Use when building RAG, - processing images/video/audio/documents, or serving an API. Do NOT use for - general Python or direct PostgreSQL administration. -license: Apache-2.0 -allowed-tools: [] -metadata: - author: Pixeltable - version: 2.10.1 - type: documentation - executes-code: false - category: data-infrastructure - tags: [multimodal, ai, data, tables, embeddings, rag, udf, video, audio, images, documents, agents, tools, fastapi, declarative, computed-columns, vector-search] - documentation: https://docs.pixeltable.com/ - support: https://github.com/pixeltable/pixeltable/discussions - priority: 6 - pathPatterns: ["**/*.py"] - importPatterns: ["pixeltable", "import pixeltable as pxt", "from pixeltable"] - bashPatterns: ['^\s*pxt(?:\s|$)'] - promptSignals: - phrases: ["pixeltable", "computed column", "embedding index", "add_embedding_index", "TableModel", "create_view", "document_splitter", "invoke_tools", "pxt schema", "pxt service", "pxt shell", "pxt errors", "pxt dashboard"] - minScore: 6 ---- - -## STOP - -If you find yourself importing any of these, you are off-path: - -1. **Do not use LangChain / LlamaIndex / Haystack / LangGraph.** Chunking is `document_splitter`. Search is `.similarity()`. Tools are `pxt.tools()` + `invoke_tools()`. -2. **Do not use pandas as a working store.** Tables are the store. `.collect().to_pandas()` is export only. -3. **Do not write `for row in ...:` loops calling models.** Wrap the call in a computed column. -4. **Do not install a separate vector database.** In an app, `__indexes__ = [pxt.EmbeddingIndex(...)]` on the model. In a notebook, `t.add_embedding_index(col, embedding=fn)`. Search with `.similarity(string=query)`. -5. **Do not write `while not done:` agent loops.** Insert a row. The computed-column chain runs. - -See [anti-patterns.md](references/anti-patterns.md) (6 macros). - -## What is Pixeltable? - -One application file (`app.py`) is the backend. - -- `pxt schema update`: creates tables from `TableModel` classes. Does not start HTTP. -- Insert a sample, `.select()`, `pxt dashboard`, or `pxt schema diff`. Compute runs on insert. After `pxt service update`, curl POST. -- `pxt service update`: starts HTTP (local or `pxt://`). `pxt service list` prints the URL. This is the serving command; do not reach for `pxt service run`. - -`pxt db update` sets hosted image, secrets, and workers. It does not insert rows and does not start app HTTP. - -First run: [Quickstart](https://docs.pixeltable.com/overview/quick-start). Why: [Why Pixeltable](https://docs.pixeltable.com/overview/pixeltable). - -## Starting a new project - -```bash -pip install 'pixeltable[serve]' # Python 3.11+ -pxt init -pxt service example --out app.py -pxt schema check app.py # validates the file; warns if 'app' is shadowed -pxt schema update app.py my_app -pxt service update app.py my_app -pxt service list # assigned port; do not hard-code :8000 -``` - -`pxt service example` writes models plus a `FastAPIRouter`. Schema only (no HTTP): `pxt schema example --brief --out app.py`. Then edit `app.py` and run `pxt schema update` again. After a schema change, run `pxt service update` again if routes exist. Do not `python app.py`. Full flags: [cli.md](references/cli.md). - -The last argument (`my_app`, or `pxt://org:db` on Cloud) is a catalog directory, not a folder on disk. `pxt init` marks the project root. Schema does not start HTTP. Service does not create tables. Non-interactive: `pxt service update ... -f`. Local handle: `pxt.get_table('my_app.docs')`. - -Same file on Cloud: set `PIXELTABLE_API_KEY`, add `[[pixeltable.database]]` with `name = 'pxt://org:db'`, then `pxt db update pxt://org:db -f`, then `pxt schema update app.py pxt://org:db -f`, then `pxt service update app.py pxt://org:db -f`. Cloud handle: `pxt.get_table('pxt://org:db/docs')`. Cloud databases store media in their managed home bucket by default; set a column `destination=` only to override it. On Cloud, try the app with dashboard insert plus `pxt schema diff`, and inspect failures with `pxt service logs` / `pxt db logs`. [Cloud](https://docs.pixeltable.com/howto/deployment/cloud). - -## The application file - -`pxt service example --out app.py` writes this shape. Edit it. Then `pxt schema update app.py my_app`. - -```python -import pixeltable as pxt -import pixeltable.functions as pxtf -from pixeltable.serving import FastAPIRouter - -TableModel = pxt.model_base() - - -@pxt.udf -def excerpt(text: str, n: int = 12) -> str: - return text if len(text) <= n else f'{text[:n]}...' - - -class Docs(TableModel, name='docs'): - doc_id: pxt.Int - title: pxt.String - body: pxt.String | None - title_upper = pxtf.string.upper(title) - summary = excerpt(title) - - -ingest = FastAPIRouter(name='ingest') -ingest.add_insert_route( - Docs, path='/docs', inputs=[Docs.doc_id, Docs.title, Docs.body], - outputs=[Docs.title_upper, Docs.summary], -) -ingest.add_compute_route(Docs, path='/titles', inputs=[Docs.title], outputs=[Docs.title_upper]) -``` - -Annotation is a stored column. Assignment is a computed column. Optional is `T | None`. Primary key is `pxt.Column(..., primary_key=True)`. Indexes on the model: `__indexes__ = [pxt.EmbeddingIndex(...)]`. `from pixeltable.serving import FastAPIRouter`. - -Already have FastAPI: after schema update, `ingest.bind('my_app')` then `app.include_router(ingest)`. Call `pxt.get_table()` inside custom handlers. [workflows.md](references/workflows.md). - -RAG, views, and search: [workflows.md](references/workflows.md). Do not add Hugging Face or spaCy unless the user asked. - -## Apps vs notebooks - -- **Apps:** `app.py` + `pxt schema update` + `pxt service update`. Indexes on the model. -- **Notebooks / REPL:** `pxt.create_table()`, `add_computed_column()`, `add_embedding_index()`. The appendix below uses that form. - -## Where to look - -| Need | Open | -|------|------| -| `pxt schema`, `pxt service`, inspect | [cli.md](references/cli.md) | -| Types, views, UDFs, UDAs | [core-api.md](references/core-api.md) | -| Provider import and output shape | [providers.md](references/providers.md) | -| Serving, FastAPIRouter, routes | [workflows.md](references/workflows.md) | -| Wrong stack | [anti-patterns.md](references/anti-patterns.md) | - -Add video, audio, agents, or a UI by editing `app.py`. A view is either a filter (`base=Docs.where(...)`) or an iterator (`frame_iterator`, `audio_splitter`, `document_splitter`, `video_splitter`, `string_splitter`, `list_iterator`, `tile_iterator`). Check `pixeltable.functions` before writing a UDF. Start from `pxt service example` or `pxt schema example`. Do not invent a second `pxt schema update` path. - -## API traps - -| Wrong | Correct | -|-------|---------| -| `openai.vision(...)` | Deprecated (the only deprecated function in `pixeltable.functions`). Use `chat_completions` with `image_url`, or `responses` | -| `from pixeltable.iterators import ...` | The whole `pixeltable.iterators` package is a deprecated shim (`FrameIterator`, `VideoSplitter`, `DocumentSplitter`, `StringSplitter`, `AudioSplitter`, `TileIterator`). Import the function from `pixeltable.functions.*` -- e.g. `from pixeltable.functions.video import frame_iterator` | -| `similarity(query)` | `similarity(string=query)`. Also `image=` / `audio=` / `video=` / `document=` / `vector=`; `idx=` picks among several indexes on one column | -| Re-run with `if_exists='ignore'` to fix logic | Notebook: `add_computed_column(..., if_exists='replace')`. App: **rename** the column, then `pxt schema update --allow-destructive` | -| Edit a computed column's expression in place, then `--allow-destructive` | Editing an existing column's expression is `UNSUPPORTED`; the flag does not help and the whole update applies nothing. Rename the column | -| `t.summary_errortype` | `t.summary.errortype` / `t.summary.errormsg`, on stored computed or media columns. `t..fileurl` / `.localpath` for media | -| `pxt.Required[pxt.String]` | Non-nullable by default. Optional: `T \| None` | -| `@pxt.udf def f(x: str)` fed a nullable column | A non-nullable parameter that receives `None` **skips the call**: the cell is `None` and `errormsg` is empty. Annotate `x: str \| None` and handle `None` in the body | -| `whisper.load_model(...)` inside a UDF body | Weights reload on every row. Use the shipped wrapper (`pxtf.whisper.transcribe`, `clip.using(...)`), or a module-scope cached loader | -| `recompute_columns(columns=['summary'])` | `t.recompute_columns('summary', errors_only=True)` | -| TOML routes or a retired serve CLI | `FastAPIRouter` + `pxt schema update` + `pxt service update` | -| `add_embedding_index()` in `app.py` | `__indexes__` on the TableModel. Note the DSL names an index `name=`, the SDK `idx_name=` | -| `make_video(order_by=...)` / `stitch_tiles(order_by=...)` | Both are `requires_order_by` UDAs: the ordering expression is the **first positional** argument -- `make_video(t.pos, t.frame, fps=25)`. `order_by=` raises | -| `pxt.create_table()` / `get_table()` at import in `app.py` | `TableModel` + `pxt schema update`. Import must not mutate the catalog | -| `EmbeddingIndex(frame, image_embed=clip)` | `embedding=clip` (covers text and image). Or both `string_embed=` and `image_embed=`. `image_embed=` alone cannot answer `similarity(string=...)` | -| `uuid.astype(pxt.String)` | `uuid.to_string()` (`from pixeltable.functions.uuid import to_string`). `astype` is not UUID→String | - -Extract the field (`.text`, `.choices[0].message.content`). Cast Json with `.astype(pxt.String)` only before embedding or concatenating. - -## Notebook / REPL appendix - -```python -import pixeltable as pxt - -pxt.create_dir('my_project', if_exists='ignore') -t = pxt.create_table('my_project.documents', { - 'title': pxt.String, - 'content': pxt.String, - 'image': pxt.Image, - 'video': pxt.Video, - 'audio': pxt.Audio, - 'doc': pxt.Document, -}, if_exists='ignore') -``` - -Types are non-nullable by default. Optional is `T | None`. Do not use `pxt.Required`. - -```python -from pixeltable.functions.uuid import uuid7 - -t = pxt.create_table('my_project.items', { - 'content': pxt.String, - 'uuid': uuid7(), -}, primary_key=['uuid'], if_exists='ignore') -``` - -Insert: `t.insert([{...}])`. Computed column: - -```python -from pixeltable.functions.openai import chat_completions - -t.add_computed_column( - summary=chat_completions( - messages=[{'role': 'user', 'content': t.content}], - model='gpt-4o-mini', - ).choices[0].message.content, - if_exists='ignore', -) -``` - -Views: `document_splitter`, `frame_iterator` (from `pixeltable.functions.video`), `string_splitter`, `audio_splitter`. Notebook indexes: `t.add_embedding_index('content', embedding=embed_fn, if_exists='ignore')`. - -Query: `t.where(...).select(...).collect()`. Similarity: `t.content.similarity(string=query)`. In `@pxt.query`, alias as `score=sim`. - -UDFs are recorded as a module path relative to the project root (`app.excerpt`). - -Always `if_exists='ignore'` on notebook `create_*` / `add_*`. Failed cells: `t.recompute_columns('summary', errors_only=True)`. `string_splitter` / `document_splitter(..., separators='sentence')` need spaCy. Embedding indexes need `.using(...)`. - -## pxt CLI - -```bash -pxt init -pxt service example --out app.py -pxt schema check app.py -pxt schema update app.py my_app -pxt service update app.py my_app -pxt service list -pxt ls -l -pxt errors my_app/docs -pxt dashboard -``` - -[cli.md](references/cli.md). - -## Resources - -- [Quickstart](https://docs.pixeltable.com/overview/quick-start) -- [CLI](https://docs.pixeltable.com/platform/cli) -- [MCP Server](https://github.com/pixeltable/mcp-server-pixeltable-developer) -- [Docs](https://docs.pixeltable.com/llms-full.txt) diff --git a/.agents/skills/pixeltable/agents/openai.yaml b/.agents/skills/pixeltable/agents/openai.yaml deleted file mode 100644 index 6af36cd..0000000 --- a/.agents/skills/pixeltable/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Pixeltable" - short_description: "Build multimodal apps with declarative Pixeltable tables" - default_prompt: "Use $pixeltable to build or debug this Pixeltable application." diff --git a/.agents/skills/pixeltable/references/anti-patterns.md b/.agents/skills/pixeltable/references/anti-patterns.md deleted file mode 100644 index 8e500f0..0000000 --- a/.agents/skills/pixeltable/references/anti-patterns.md +++ /dev/null @@ -1,90 +0,0 @@ -# Anti-Patterns - -Apps use `app.py` plus `pxt schema update`. This file is notebook form unless noted. These priors are wrong for Pixeltable. - -## 1. Framework addiction (LangChain / LlamaIndex / Haystack / LangGraph) - -**Wrong:** RecursiveCharacterTextSplitter + Chroma + RetrievalQA. - -**Right:** - -```python -class Chunks( - TableModel, - name='chunks', - base=Docs, - iterator=pxtf.document.document_splitter(Docs.document, separators='token_limit', limit=512), -): - __indexes__ = [ - pxt.EmbeddingIndex(text, embedding=embeddings.using(model='text-embedding-3-small'), name='chunks_embed') - ] # type: ignore[name-defined] -``` - -Full pattern: [workflows.md](workflows.md). - -Chunking is `document_splitter`. Search is `.similarity()`. Tools are `pxt.tools()` + `invoke_tools()`. - -## 2. pandas as a working store - -**Wrong:** `df['summary'] = df['text'].apply(call_openai)` then parquet as the store. - -**Right:** columns on the model (or `add_computed_column` in a notebook). `.collect().to_pandas()` is export only. - -## 3. For-loops calling models - -**Wrong:** `for row in df.iterrows(): openai.chat.completions.create(...)`. - -**Right:** assignment on the model / computed column. Retry: `t.recompute_columns('summary', errors_only=True)`. - -## 4. Separate vector database - -**Wrong:** Pinecone, Chroma, FAISS, Qdrant, Weaviate, pgvector. - -**Right:** `__indexes__ = [pxt.EmbeddingIndex(col, embedding=fn.using(...), name='...')]`. Notebook: `add_embedding_index`. Query: `col.similarity(string=query)`. - -## 5. While-loop agents - -**Wrong:** `while True:` tool loop that loses state on failure. - -**Right:** insert a row. The computed-column chain runs (`chat_completions` → `invoke_tools` → final). `invoke_tools` is per provider. - -## 6. Loading a model inside the UDF body - -**Wrong:** `whisper.load_model('tiny.en')` or `Model.from_pretrained(...)` called inside `@pxt.udf`. The weights reload on every row: 100ms of work becomes 1.8s. - -**Right:** the shipped wrapper. It keeps a process-level model cache keyed on (model, device), so the weights load once. - -```python -transcript = pxtf.whisper.transcribe(audio, model='tiny.en').text -``` - -Embeddings the same way: `clip.using(model_id=...)`, `sentence_transformer.using(model_id=...)`. [providers.md](providers.md) lists the wrappers. - -If nothing ships for your model, load it once at module scope and cache the handle: - -```python -import functools - - -@functools.cache -def _scorer(): - from my_lib import Scorer - - return Scorer.load('checkpoint.pt') - - -@pxt.udf -def score(text: str) -> float: - return _scorer().score(text) -``` - -## Also wrong - -| Prior | Do this | -|-------|---------| -| `python app.py` for models + router | `pxt schema update` then `pxt service update` | -| Drop + recreate tables as "init" | Edit `app.py`, then `pxt schema update` | -| Hard-coded `api_key=` | Env or config.toml | -| `psycopg2` against `~/.pixeltable/pgdata` | SDK / CLI only | -| Chat history in Redis | A table | -| `def f(x: str)` to "handle" a nullable column | A non-nullable parameter that receives `None` skips the call and leaves the cell `None`. Annotate `x: str \| None` | diff --git a/.agents/skills/pixeltable/references/cli.md b/.agents/skills/pixeltable/references/cli.md deleted file mode 100644 index d1880ea..0000000 --- a/.agents/skills/pixeltable/references/cli.md +++ /dev/null @@ -1,249 +0,0 @@ -# Pixeltable CLI Reference (`pxt`) - -Agent-focused map of the `pxt` CLI. Official source: [platform/cli.md](https://docs.pixeltable.com/platform/cli.md). Always run `pxt --help` for version-specific flags -- never guess. - -Python 3.11+. There is no `pxt serve`, no `pxt deploy`, no `pxt app`, no `pxt db create` (the CLI's own help string wrongly lists one -- `pxt db update` creates), and no `[tool.pixeltable.service]` TOML. - -## Two surfaces - -| Surface | Purpose | Requires | -|---------|---------|----------| -| **Catalog** | Inspect, query, mutate tables/views/dirs | `pip install pixeltable` | -| **Schema / service** | Apply a `TableModel` file; run `FastAPIRouter` services | `pip install 'pixeltable[serve]'` for `pxt service` | - -Verify: `pxt --help` and `pxt health`. - -## Project root - -`pxt init` marks this directory as a project root. Schema and service refuse an application file with no project root. Fresh dir: writes `pixeltable.toml`. Already configured: no-op. Existing `pyproject.toml`: appends `[[tool.pixeltable.database]]` (does not write `pixeltable.toml`). Nested under another root: refused (exit 3). Start from `pxt service example --out app.py` (models plus routes) or `pxt schema example --brief --out app.py` (models only). - -```bash -pxt init # project root; see cases above -pxt schema update app.py my_app # creates catalog dir + tables; does NOT start HTTP -pxt service update app.py my_app # starts local HTTP; does NOT create tables -pxt service update app.py my_app -f # CI / no TTY when changes are pending -``` - -`my_app` is a catalog directory, not a folder on disk. After apply: `t = pxt.get_table('my_app.docs')`. Cloud: `t = pxt.get_table('pxt://org:db/docs')`. Tables live under `~/.pixeltable`, not in the repo. Directory names from the project root to `app.py` must be Python identifiers. Do not `python app.py` if the file only declares models and routers. After a schema change, run `pxt service update` again if routes exist. - -## Daemon - -On the first catalog command, `pxt` auto-spawns a daemon at `127.0.0.1:22089` (~40 ms per command after warm-up). Override with `PXT_PORT`. Lifecycle: `pxt daemon status`, `pxt daemon stop`, `pxt daemon start`. - -## Command categories - -| Category | Commands | -|----------|----------| -| **Project** | `init` | -| **Inspection** | `ls`, `describe`, `columns`, `computed`, `idxs`, `history`, `status`, `config` | -| **Query** | `rows`, `get`, `count`, `errors` | -| **Mutation** | `drop`, `drop-dir`, `rename`, `mv`, `revert` | -| **Schema** | `schema diff`, `schema update`, `schema prune`, `schema check`, `schema example` | -| **Serving** | `service diff`, `service update`, `service run`, `service prune`, `service stop`, `service list`, `service check`, `service example` | -| **Cloud** | `db`, `org`, `secret` | -| **Interactive** | `shell`, `cd`, `pwd` | -| **Lifecycle** | `daemon`, `dashboard`, `localproxy`, `health` | - -`cd` / `pwd` set and print a working directory prepended to relative paths. It is scoped to the invoking shell's process, so it does **not** survive between separate tool calls: always pass full catalog paths instead. `localproxy` manages the daemons behind `pxt://local:` URIs and is not part of the normal app loop. - -## Universal flags - -| Flag | Description | -|------|-------------| -| `-h`, `--help` | Every command | -| `--json` | Machine-readable output on catalog commands, `schema` / `service` verbs, `db`, `org`, `secret`, `daemon status`. Not on `shell`, `dashboard`, or `daemon start`/`stop`. `health` is always JSON. | -| `-n`, `--dry-run` | Catalog mutations (`drop`, `drop-dir`, `rename`, `mv`, `revert`) plus `schema update`, `schema prune`, `service update`, `service prune`, and `db update` | -| `-f`, `--force` | Skip `[y/N]` on `drop`, `drop-dir`, `revert`, schema/service update and prune, and `db update`. Use it in non-interactive runs when a pending plan can prompt. Additive or no-op schema updates do not prompt. Not on `rename`/`mv`. | - -## Agent workflows - -| Task | Prefer CLI | Example | -|------|-----------|---------| -| Mark a project root | `pxt init` | no-op if already configured; exit 3 if nested | -| Write a starting file | `pxt service example` | `pxt service example --out app.py`. Models only: `pxt schema example --brief --out app.py` | -| Validate a file | `pxt schema check`, `pxt service check` | no `TARGET`; reads no catalog | -| Apply tables | `pxt schema update` | `pxt schema update app.py my_app` | -| Review schema drift | `pxt schema diff` | exit `0` in sync, `2` pending | -| Start HTTP | `pxt service update` | `pxt service update app.py my_app -f` (force pending changes in CI) | -| Inspect catalog | `pxt ls -l`, `pxt describe`, `pxt columns --computed` | `pxt ls --json \| jq '.entries[] \| select(.kind == "table")'` | -| Debug failed columns | `pxt errors`, `pxt rows --cols` | `pxt errors my_app/docs --col embedding` | -| Debug a service | `pxt service logs` | `pxt service logs my_app/ingest --since 10m --tail 50` | -| Check runtime/config | `pxt status`, `pxt config` | `pxt config --section openai` | -| Many commands in sequence | `pxt shell` | amortizes startup; errors don't kill session | -| Visual inspection | `pxt dashboard` | read-only UI at daemon port | -| Hosted database | `pxt db update` | `pxt db update pxt://myorg:mydb -f`, then schema, then service | - -**SDK vs CLI:** Notebooks and one-off REPL use the Python SDK (`create_table`, `add_computed_column`). Apps use a `TableModel` file plus `pxt schema` / `pxt service`. Use CLI for inspect, debug, and CI drift checks. - -## Quick reference - -```bash -# project, then schema, then service -pxt init -pxt service example --out app.py -pxt schema update app.py my_app -pxt service update app.py my_app - -# inspect -pxt ls -l -pxt describe my_app/docs -pxt rows my_app/docs -n 5 - -# query / debug -pxt get my_app/docs 42 -pxt count my_app/docs -pxt errors my_app/docs - -# mutations (use -f in CI) -pxt drop my_app/docs -f -pxt revert my_app/docs --steps 3 -f - -# interactive -pxt shell -pxt dashboard -``` - -## Inspection highlights - -- **`pxt ls`**: `-l` (metadata), `--counts` (row counts), `--tree` -- **`pxt describe`**: schema; `--json` returns full `get_metadata()` dict -- **`pxt computed`**: shorthand for `pxt columns --computed` -- **`pxt idxs`**: `--embedding` for embedding indexes only -- **`pxt history`**: `-n N` for last N versions (run before `revert`) -- **`pxt status`**: daemon PID, version, total errors; `--sizes` for disk usage - -## Query highlights - -- **`pxt rows`**: `-n N` (default 10), `--cols a,b,c`. Unstored computed columns skipped unless listed in `--cols` (forces eval). -- **`pxt get`**: PK lookup; composite PKs in declared order. Table must have a primary key. -- **`pxt errors`**: rows where stored computed columns failed; `--col NAME` to filter. Table must have a primary key. - -## Mutation highlights - -- **`pxt drop`**: tables/views; `--cascade` drops dependent views; use `pxt drop-dir` for directories -- **`pxt schema prune`**: never force-drops, and drops a view before its base; a table something outside the pruned set depends on is left in place -- **`pxt drop-dir`**: `-r` for recursive directory removal -- **`pxt revert`**: irreversible -- run `pxt history` first - -Table paths accept `my_app/docs` or `my_app.docs`. - -## Schema (`pxt schema`) - -Reconcile a catalog directory with the `TableModel` classes in a Python file. Provisioning an empty target and evolving an existing one are the same command. - -| Command | Description | -|---------|-------------| -| `pxt schema diff APP TARGET` | What `update` would change. Read-only. Exit `2` if pending | -| `pxt schema update APP TARGET` | Create the catalog dir + tables; migrate existing ones. Does **not** start HTTP | -| `pxt schema prune APP TARGET` | Drop tables under `TARGET` that the file does not declare | -| `pxt schema check APP` | Validate the file only. No `TARGET`. Reads no catalog | -| `pxt schema example` | Write a working file (`--brief` for the minimal one) | - -```bash -pxt schema example --out app.py -pxt schema check app.py -pxt schema diff app.py my_app -pxt schema update app.py my_app -pxt schema update app.py my_app -n # plan only; exit 2 if pending -pxt schema update app.py my_app --allow-destructive -f # including column/index drops -pxt schema prune app.py my_app -n -``` - -`TARGET` is a catalog directory or a `pxt://org:db/...` URI. - -Reading a diff: `+` created / added, `-` dropped, `~` migrated, `=` already matches, `!` cannot be migrated in place. Each op is marked safe, DESTRUCTIVE, or UNSUPPORTED. - -- **DESTRUCTIVE** (dropping a column or index) needs `--allow-destructive`; exit `3` without it. Applying is **all-or-nothing** -- without the flag a destructive plan applies *nothing at all*, not the safe parts. -- **UNSUPPORTED** cannot be applied by any flag: a kind or iterator mismatch, or a column whose type or **value expression** changed. One unsupported table aborts the whole update, including other models' pending additive changes. Rename the column, or drop it and re-add it in a second pass. See [core-api.md](core-api.md#tables). - -The daemon imports the application file, so it must be readable there; the file's own directory joins `sys.path`, so it can import modules sitting next to it. - -**Run `pxt schema check APP` before the first update.** It validates the file with no catalog access, confirms every udf a column calls resolves to a module path another process can import, and warns when a top-level name in the project is shadowed: - -``` -app.py: an import of 'app' reads /.../site-packages/app/__init__.py, so this project -cannot record a udf under 'app'; rename it -``` - -The project root goes on `sys.path` *after* installed packages, so an installed distribution of the same name wins. `check` warns and still exits `0`; `schema update`, `service update` and `service run` do **not** warn -- they import the wrong module silently. Generic single-file names collide most often, so heed the warning and rename. - -A CI drift check: - -```bash -pxt schema diff app.py pxt://acme:main/prod # 0 = in sync, 2 = drift, 1 = error -``` - -## Serving (`pxt service`) - -Runs the `FastAPIRouter` instances an application file declares. Requires `pip install 'pixeltable[serve]'`. Same file as the models: apply tables first, then start HTTP. - -| Command | Description | -|---------|-------------| -| `pxt service diff APP TARGET` | What `update` would change. Exit `2` if pending | -| `pxt service update APP TARGET` | Start declared services in the background; restart those that changed. Does **not** create tables | -| `pxt service run APP TARGET [SERVICE]` | Serve one service in the foreground until interrupted. For a container entrypoint, which must not return; **not** the command to recommend otherwise -- use `update` | -| `pxt service prune APP TARGET` | Stop and forget services at `TARGET` that the file does not declare | -| `pxt service stop NAME...` | Stop named services (`ingest` or `my_app/ingest`) | -| `pxt service list [TARGET]` | What is running, and where | -| `pxt service check APP` | Validate the file only. No `TARGET`. Reads no catalog | -| `pxt service example` | Write a working application file | - -```bash -pxt service example --out app.py -pxt service check app.py -pxt schema update app.py my_app -pxt service update app.py my_app -f -pxt service list -pxt service logs ingest --since 10m --tail 50 -pxt service stop ingest -``` - -`update` starts one background process per service, each on its own port, and is the serving command to use. A no-op or dry run exits without prompting; pass `-f` when a pending update runs without a TTY. Adding a route is additive; changing or removing one needs `--allow-destructive`. OpenAPI docs are at `/docs`. `pxt service run` refuses a `pxt://` TARGET and does not record anything, so `list` and `stop` cannot find it. - -`pxt service logs NAME` accepts a bare service name or `TARGET/NAME`; use a full `pxt://org:db/path/name` for hosted services. `--since` accepts values such as `10m`, `--tail` is capped at 10,000 lines, and `--include-health` keeps health-probe requests. Local services report their log-file path. Hosted logs include request records and console output, including startup tracebacks. - -**Tracing.** `service diff`, `service update` and `service run` take `--otel`, which emits OpenTelemetry traces and needs `pip install 'pixeltable[otel]'` (`serve` and `otel` are the only two extras). The setting belongs to the running service, not to the file: a service already running without it restarts when `update` is given the flag, dropping the flag restarts it again, and `diff --otel` reports tracing that is off but was asked for as a pending change. - -Do **not** write `[tool.pixeltable.service]` TOML or call `pxt serve`. - -## Cloud (`pxt db`, `pxt org`, `pxt secret`) - -Require `PIXELTABLE_API_KEY`. URIs are `pxt://org` or `pxt://org:db`. - -```bash -pxt db update pxt://myorg:mydb -f # also: list, status, logs, start, stop, diff, build-image, delete -pxt db logs pxt://myorg:mydb --since 10m --tail 50 -pxt org status pxt://myorg # also: list -``` - -`pxt db update pxt://org:db` selects `[[pixeltable.database]]` by `name = 'pxt://org:db'`. A URI with no matching entry is an error. First `update` creates the hosted database. - -Hosted order: `pxt db update pxt://org:db -f` sets secrets, image, and workers, then `pxt schema update app.py pxt://org:db -f`, then `pxt service update app.py pxt://org:db -f`. Database capacity or secret changes can also require `--allow-destructive`. If `pxt db diff` says the database project is behind the working copy, run `pxt db update` first. - -Every Cloud database stores inserted and computed media in its managed home bucket by default. Set a column `destination=` to send that output elsewhere, or configure `input_media_dest` / `output_media_dest` to change the database defaults. - -A UDF is recorded as a module path relative to the project root (`app.excerpt`), not a raw file path. `pxt db update` packs the project so Cloud can import it. - -### Secrets - -```bash -pxt secret set pxt://myorg OPENAI_API_KEY=sk-... # also: list, delete -``` - -An org secret applies to every database in the org; a database secret wins on a key collision. A project declares database secrets under the `secrets` mapping, for example `secrets.openai_api_key = 'env:OPENAI_API_KEY'`; `pxt db update` sets them. A running database keeps the values it started with. Run `pxt db stop` then `pxt db start` to pick up a change. - -## Scripting with `--json` - -```bash -pxt ls --json | jq '.entries[] | select(.kind == "table")' -pxt get my_app/docs 42 --json | jq '.row' -pxt count my_app/docs --json | jq '.count' -pxt schema diff app.py my_app --json -pxt service diff app.py my_app --json -``` - -## Related references - -- [core-api.md → Serving](core-api.md#serving) -- `FastAPIRouter` Python API -- [workflows.md](workflows.md) -- application-file example -- [Configuration](https://docs.pixeltable.com/platform/configuration) -- API keys, paths, env vars diff --git a/.agents/skills/pixeltable/references/core-api.md b/.agents/skills/pixeltable/references/core-api.md deleted file mode 100644 index 14896f3..0000000 --- a/.agents/skills/pixeltable/references/core-api.md +++ /dev/null @@ -1,342 +0,0 @@ -# Pixeltable Core API Reference - -Apps use `app.py` plus `pxt schema update`. This file is notebook form (`pxt.create_table()`) unless noted. CLI: [cli.md](cli.md). Routes: [workflows.md](workflows.md). - -Types are non-nullable by default. Optional is `T | None`. Do not use `pxt.Required`. - -## Contents - -- [Tables](#tables) -- [Querying](#querying) -- [Computed columns](#computed-columns) -- [Views](#views) -- [Indexes](#indexes) -- [UDFs](#udfs) -- [UDAs](#udas) -- [Built-in functions](#built-in-functions) -- [Import and export](#import-and-export) -- [Serving](#serving) -- [Tools](#tools) - -## Tables - -App: - -```python -class Docs(TableModel, name='docs'): - title: pxt.String - body: pxt.String | None - title_upper = pxtf.string.upper(title) - uuid = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True) - # astype(pxt.String) fails on UUID; use uuid.to_string() -``` - -Notebook: - -```python -import pixeltable as pxt -from pixeltable.functions.uuid import uuid7 - -t = pxt.create_table('dir.docs', { - 'title': pxt.String, - 'body': pxt.String | None, - 'image': pxt.Image, - 'video': pxt.Video, - 'audio': pxt.Audio, - 'doc': pxt.Document, - 'tags': pxt.Json[list[str]], - 'records': pxt.Json[list[dict[str, str]]], - 'uuid': uuid7(), -}, primary_key=['uuid'], if_exists='ignore') -``` - -Types: `String`, `Int`, `Float`, `Bool`, `Image`, `Video`, `Audio`, `Document`, `Json`, `Timestamp`, `Date`, `UUID`, `Binary`, `Array[(3, 4), pxt.Float]`. - -`pxt.Column(...)` carries what an annotation cannot: `stored=False` (computed on read, never materialized), `media_validation='on_read'` (defer validation to first read; default `'on_write'`), and `destination=` (object store for computed media -- `s3`/`gs`/`az`/`r2`/`b2`/`tigris`/`http`/a local path/`pxtfs`). - -```python -thumbnail = pxt.Column(value=cover.rotate(90), stored=False) -scan = pxt.Column(type=pxt.Image, media_validation='on_read', comment='validated lazily') -``` - -From a file: `pxt.create_table('dir.data', source='data.csv', if_exists='ignore')`. - -Insert: `t.insert([{...}])`. `return_rows=True` returns computed columns on the status object. `source=` also takes a path or URL (`source_format='csv'|'excel'|'parquet'|'json'`, `schema_overrides=`), a DataFrame, a list of dicts or Pydantic models, another table or query, or a HF dataset. - -**Do not let one bad row abort a batch.** `insert(..., on_error='ignore')` and `add_computed_column(..., on_error='ignore')` keep the row, leave the failed cell `None`, and record the reason in `t..errortype` / `t..errormsg` (stored computed or media columns only). `t..fileurl` / `.localpath` locate a media cell. - -```python -t.update({'score': 1.0}, where=t.category == 'important') -t.delete(where=t.is_active == False) -t.recompute_columns('summary', errors_only=True) -``` - -Changing a computed column's logic: - -- **App.** Editing an existing column's expression in place is reported `UNSUPPORTED`. `--allow-destructive` does **not** help, and one unsupported table makes the whole `pxt schema update` apply nothing. **Rename** the column instead: the old name is a destructive drop, the new one an additive add, so it lands in one pass with `--allow-destructive`. Or drop it in one pass and re-add it in a second. Either way the data is destroyed and recomputed. `Table.rename_column()` preserves an existing column and its existing expression; it does not install new logic. `pxt.move()` moves a table or directory. -- **Notebook.** `t.add_computed_column(summary=..., if_exists='replace')` replaces it in one call when the column is directly replaceable and has no dependents. Otherwise drop dependents first, or `drop_column` and recreate; do not depend on one exception class for every rejected replacement. -- `if_exists='ignore'` never fixes logic -- it skips the call. - -## Querying - -```python -t.select(t.title, doubled=t.n * 2).where(t.n > 10).order_by(t.n, asc=False).limit(10).collect() -t.where(t.title.like('%pattern%')).count() -t.sample(n=100, seed=42).collect() -df = t.select(t.title).collect().to_pandas() # export only -``` - -One `.where()` per query. Compose extra predicates with `&` / `|` (`t.where((t.n > 10) & t.active)`); do not chain `.where()`. - -Aggregates run in queries, not computed columns: - -```python -t.select(t.amount.sum()).collect() -t.group_by(t.region).select(t.region, total=t.amount.sum()).collect() -``` - -`@pxt.query` compiles at decoration time: do not `.collect()` or `get_table()` a table that does not exist yet inside one. - -Local handle: `pxt.get_table('my_app.docs')`. Cloud: `pxt.get_table('pxt://org:db/docs')`. - -## Computed columns - -Notebook: `t.add_computed_column(...)`. App: assignment on the model. - -```python -from pixeltable.functions.openai import chat_completions - -t.add_computed_column( - summary=chat_completions( - messages=[{'role': 'user', 'content': t.body}], - model='gpt-4o-mini', - ).choices[0].message.content, - if_exists='ignore', -) -``` - -Extract the field (`.text`, `.choices[0].message.content`). Cast Json with `.astype(pxt.String)` only before embedding or concatenating. - -## Views - -A view is either a **filter** over a base or an **iterator** that expands each base row into many. Rows follow the base; you never insert into a view. - -### Filtered views - -`base=` takes a query over another model, not just the model: - -```python -class Titled(TableModel, name='titled', base=Docs.where(Docs.title != '')): - headline = Docs.title_upper + '!' # may reference the base's computed columns -``` - -Notebook: `pxt.create_view('dir.titled', t.where(t.title != ''), if_exists='ignore')`. Add `is_snapshot=True` to freeze it. - -### Iterator views - -Iterator output columns are reserved. Do not redeclare them. `string_splitter` / `document_splitter(..., separators='sentence')` need spaCy. `token_limit` needs `tiktoken`. - -```python -from pixeltable.functions.document import document_splitter -from pixeltable.functions.video import frame_iterator -from pixeltable.functions.string import string_splitter -from pixeltable.functions.audio import audio_splitter -from pixeltable.functions.json import list_iterator - -chunks = pxt.create_view( - 'dir.chunks', t, - iterator=document_splitter(t.doc, separators='token_limit', limit=300), - if_exists='ignore', -) -# Image elements only with separators='page' on PDFs -pages = pxt.create_view( - 'dir.pages', t, - iterator=document_splitter(t.doc, separators='page', elements=['text', 'image']), - if_exists='ignore', -) - -# Output columns: pos, frame, frame_attrs (timestamp is frame_attrs.time) -# Not frame_idx / pos_msec / pos_frame (legacy_frame_iterator only) -frames = pxt.create_view('dir.frames', t, iterator=frame_iterator(t.video, fps=1.0), if_exists='ignore') -frames.select(frames.pos, frames.frame, time=frames.frame_attrs.time).collect() - -# text -sentences = pxt.create_view( - 'dir.sentences', t, iterator=string_splitter(text=t.body, separators='sentence'), if_exists='ignore', -) - -audio = pxt.create_view( - 'dir.audio', t, iterator=audio_splitter(audio=t.audio, duration=30.0), if_exists='ignore', -) - -tags = pxt.create_view('dir.tags', t, iterator=list_iterator(tag=t.tags), if_exists='ignore') -records = pxt.create_view('dir.records', t, iterator=list_iterator(t.records), if_exists='ignore') -``` - -`list_iterator` requires typed Json. Use keyword arguments for typed scalar lists, such as `tag=t.tags`; the keyword becomes the output column. Its single positional form requires a typed list of dictionaries with compatible keys. Untyped `pxt.Json` is rejected because Pixeltable cannot infer the view schema. - -App: `base=` plus `iterator=` on the model. See [workflows.md](workflows.md). - -An unbound model class builds queries (`.where()`, `.select()`, `.order_by()`, `.group_by()`, `.limit()`, `.sample()`, `.join()`) -- that is what makes `base=Docs.where(...)` work. Anything that touches rows (`.collect()`, `.insert()`, `.count()`, `.update()`) needs the table to exist: use `pxt.get_table()`, or let a bound router reach it. - -Every iterator view also gets `pos`. The nine that ship: - -| Iterator | Module | Output columns | -|----------|--------|----------------| -| `document_splitter` | `functions.document` | subset of `text, image, title, heading, sourceline, page, bounding_box` per `elements=` / `metadata=` | -| `frame_iterator` | `functions.video` | `frame` (unstored), `frame_attrs` (`.time`, `.index`, `.key_frame`, ...) | -| `video_splitter` | `functions.video` | `segment_start`, `segment_start_pts`, `segment_end`, `segment_end_pts`, `video_segment` | -| `audio_splitter` | `functions.audio` | `segment_start`, `segment_end`, `audio_segment` | -| `string_splitter` | `functions.string` | `text` | -| `list_iterator` | `functions.json` | keys of `elements=`, or the kwarg names | -| `tile_iterator` | `functions.image` | `tile` (unstored), `tile_coord`, `tile_box` | - -`legacy_frame_iterator` (`frame_idx` / `pos_msec` / `pos_frame`) and `sam3_for_video_segmentation` also exist; prefer `frame_iterator`. - -## Indexes - -App: `__indexes__ = [pxt.EmbeddingIndex(col, embedding=fn, name='...'), pxt.BtreeIndex(col)]`. Do not call `add_embedding_index()` in `app.py`. Use `.using(...)` when provider or model arguments must be bound. A custom embedding UDF must return a fixed-length, one-dimensional `pxt.Array[(N,), pxt.Float]`; Json or an array with an unknown length is not a valid embedding index function. - -`embedding=` is tried against every modality and registers each one whose signature it matches, so a bidirectional function covers them all at once. That is why a CLIP index on an **image** column answers `similarity(string=...)`: - -```python -clip_embed = pxtf.huggingface.clip.using(model_id='openai/clip-vit-base-patch32') -__indexes__ = [pxt.EmbeddingIndex(frame, embedding=clip_embed, name='frames_clip')] -# frame is an Image column, yet this resolves: -Frames.frame.similarity(string='a red bicycle') -``` - -Do not pass only `image_embed=` if you will query with `similarity(string=...)`. - -Reach for the per-modality parameters -- `string_embed=`, `image_embed=`, `audio_embed=`, `video_embed=`, `document_embed=` -- when you want a *different* function per modality, or when you want a hard error: a per-modality argument that does not resolve raises, while `embedding=` quietly skips the modalities it cannot serve. Also `metric=` (`'cosine'` default), `precision=` (`'fp16'` default, `'fp32'` available). **The DSL names an index `name=`; `add_embedding_index()` names it `idx_name=`.** - -```python -from pixeltable.functions.openai import embeddings -from pixeltable.functions.huggingface import sentence_transformer - -embed_fn = embeddings.using(model='text-embedding-3-small') -# or: sentence_transformer.using(model_id='sentence-transformers/all-MiniLM-L6-v2') - -class Docs(TableModel, name='docs'): - body: pxt.String - __indexes__ = [pxt.EmbeddingIndex(body, embedding=embed_fn, name='body_idx')] - -t.add_embedding_index('body', embedding=embed_fn, if_exists='ignore') - -sim = t.body.similarity(string=query) -t.where(sim > 0.3).order_by(sim, asc=False).limit(10).select(t.body, score=sim).collect() -``` - -`similarity()` takes exactly one of `string=`, `image=`, `audio=`, `video=`, `document=`, `vector=` (a raw array of the index's dimensionality). A positional argument is deprecated. When a column carries more than one embedding index, `idx='name'` picks one. - -CLIP: `clip.using(model_id='openai/clip-vit-base-patch32')` then `similarity(string=...)` or `similarity(image=...)`. Metrics: `cosine` (default), `ip`, `l2`. - -## UDFs - -A UDF is recorded as a module path from the project root (`app.excerpt`). Type hints are required. - -```python -@pxt.udf -def excerpt(text: str, n: int = 12) -> str: - return text if len(text) <= n else f'{text[:n]}...' -``` - -### A non-nullable parameter that receives `None` skips the call - -The cell is set to `None`, nothing raises, and `errormsg` stays empty. This is by design, and it applies to the shipped UDFs as much as to yours. Annotate `T | None` when the argument can be null, and handle `None` in the body: - -```python -@pxt.udf -def label(severity: str | None) -> str: - return severity or 'unknown' -``` - -Binding a nullable argument to a non-nullable parameter also widens the column's declared type, so `excerpt(Docs.body)` over `body: pxt.String | None` is a `String | None` column. - -Load models at module scope, never in the UDF body: [anti-patterns.md](anti-patterns.md). - -```python -from pixeltable.func import Batch - -@pxt.udf(batch_size=32) -def batch_process(texts: Batch[str]) -> Batch[list[float]]: - return model.encode(texts).tolist() - -lookup_fn = pxt.retrieval_udf(t, name='lookup_items', description='Look up items by name', - parameters=['name'], limit=5) -``` - -## UDAs - -`@pxt.uda` is many rows → one value. Use in `select()` / `group_by()`, not `add_computed_column`. Subclass `pxt.Aggregator`: `__init__`, `update`, `value`. `__init__` args must be constants. - -```python -@pxt.uda -class avg_int(pxt.Aggregator): - def __init__(self): - self.sum = 0 - self.count = 0 - - def update(self, val: int) -> None: - if val is not None: - self.sum += val - self.count += 1 - - def value(self) -> float: - return self.sum / self.count if self.count > 0 else 0.0 - -t.select(avg_int(t.value)).collect() -t.group_by(t.category).select(t.category, avg_val=avg_int(t.value)).collect() -``` - -Built-ins: `make_video`, `concat_videos_agg` (`pixeltable.functions.video`), `make_list` (`json`), `stitch_tiles` (`image`), `mean_ap` (`vision`). Scalar `concat_videos` takes a **list** of videos. - -`requires_order_by` UDAs take the ordering expression as their **first positional argument**; passing `order_by=` raises. Built-ins include: - -```python -t.select(pxtf.video.make_video(t.pos, t.frame, fps=30)) # t.pos orders; order_by= raises -t.group_by(base).select(pxtf.image.stitch_tiles(t.pos, t.tile, t.tile_box, width, height)) -t.select(pxtf.video.concat_videos_agg(t.pos, t.video)) -``` - -## Built-in functions - -Before writing a UDF, check whether the operation already ships. `pixeltable.functions` (`pxtf`) covers strings, json, math, dates, arrays, images, audio, documents, and video (`video.editing`, `video.filters`, `video.scene_detect`), plus `vision` and `net`. Import the module and read its docs rather than guessing a name. - -The one path worth spelling out, because nothing else documents it -- video to transcript: - -```python -class Clips(TableModel, name='clips'): - video: pxt.Video - audio = pxtf.video.extract_audio(video, format='mp3') - transcript = pxtf.openai.transcriptions(audio=audio, model='whisper-1').text -``` - -`extract_audio` returns `pxt.Audio | None`, and `transcriptions` takes a non-nullable `audio`. A silent video therefore leaves `transcript` as `None` with no `errormsg`. See the skip rule under [UDFs](#udfs). - -## Import and export - -Do not hand-roll a reader or writer -- check `pxt.io.import_*` / `export_*` first (csv, json, parquet, excel, pandas, SQL, Iceberg, LanceDB, HuggingFace). - -## Serving - -`from pixeltable.serving import FastAPIRouter`. Start from `pxt service example --out app.py`. `add_update_route` requires the target's primary key. `add_delete_route` uses the primary key by default and can instead take a nonempty `match_columns=` list. - -Routes: `add_insert_route` (stores the row), `add_compute_route` (same request shape, computes without storing), `add_update_route`, `add_delete_route`, `add_query_route` (wraps a `@pxt.query`). - -```python -ingest.add_update_route(Docs, path='/update', inputs=[Docs.title], outputs=[Docs.title]) -``` - -Call `pxt.get_table()` inside custom FastAPI handlers. Do not `python app.py` if the file only declares models and routers. After a schema change, run `pxt service update` again. Worked example, upload and media URLs, and `background=True` job polling: [workflows.md](workflows.md). - -## Tools - -```python -from pixeltable.functions.openai import chat_completions, invoke_tools - -tools = pxt.tools(search_docs, lookup_fn) -# invoke_tools is per provider: openai.invoke_tools vs anthropic.invoke_tools -``` - -MCP: `pxt.mcp_udfs(url)` returns one UDF per remote tool over streamable HTTP; tools returning images or audio are not supported. Keys: env or [Configuration](https://docs.pixeltable.com/platform/configuration), not `api_key=` in calls. diff --git a/.agents/skills/pixeltable/references/providers.md b/.agents/skills/pixeltable/references/providers.md deleted file mode 100644 index 796ae3a..0000000 --- a/.agents/skills/pixeltable/references/providers.md +++ /dev/null @@ -1,57 +0,0 @@ -# Pixeltable AI Provider Reference - -Every provider is a module under `pixeltable.functions.`. Call it in a computed column; never in a `for` loop. In an app, embeddings go on `__indexes__`; in a notebook, `add_embedding_index()`. Embedding and index functions are bound with `.using(...)`. - -Keys come from the environment or [config](https://docs.pixeltable.com/platform/configuration), never `api_key=` in the call. - -## Quick reference - -| Provider | Module | Functions | Extract | -|----------|--------|-----------|---------| -| OpenAI | `openai` | `chat_completions`, `responses`, `embeddings`, `speech`, `transcriptions`, `translations`, `image_generations`, `image_edits`, `image_variations`, `moderations`, `invoke_tools` | chat: `.choices[0].message.content`; Responses: `.output_text` | -| Anthropic | `anthropic` | `messages`, `invoke_tools` | `.content[0].text` | -| Gemini | `gemini` | `generate_content`, `embed_content`, `generate_images`, `generate_videos`, `generate_speech`, `transcribe`, `invoke_tools` | content: Json; generation/transcription returns typed Image, Video, Audio, or String | -| Bedrock | `bedrock` | `converse`, `invoke_model`, `embed`, `invoke_tools` | `.output.message.content[0].text` | -| Groq | `groq` | `chat_completions`, `invoke_tools` | `.choices[0].message.content` | -| Together | `together` | `chat_completions`, `completions`, `embeddings`, `image_generations` | `.choices[0].message.content` | -| Mistral | `mistralai` | `chat_completions`, `fim_completions`, `embeddings` | `.choices[0].message.content` | -| Nebius | `nebius` | `chat_completions`, `embeddings` | `.choices[0].message.content` | -| Fireworks | `fireworks` | `chat_completions` | `.choices[0].message.content` | -| DeepSeek | `deepseek` | `chat_completions` | `.choices[0].message.content` | -| OpenRouter | `openrouter` | `chat_completions` | `.choices[0].message.content` | -| Fabric | `fabric` | `chat_completions`, `embeddings` | `.choices[0].message.content` | -| Ollama (local) | `ollama` | `chat`, `generate`, `embed` | chat: `.message.content`; generate: `.response` | -| llama.cpp (local) | `llama_cpp` | `create_chat_completion` | `.choices[0].message.content` | -| vLLM (local) | `vllm` | `chat_completions`, `generate` | `.choices[0].message.content` | -| Hugging Face | `huggingface` | `sentence_transformer`, `clip`, `cross_encoder`, `detr_for_object_detection`, `sam3_for_segmentation`, `image_captioning`, `summarization`, `text_to_image`, ~15 more | index fn, or Json | -| Whisper (local) | `whisper` | `transcribe` | `.text` | -| WhisperX (local) | `whisperx` | `transcribe` | Json with `segments` | -| Voyage AI | `voyageai` | `embeddings`, `rerank`, `multimodal_embed` | index fn / Json | -| Jina AI | `jina` | `embeddings`, `rerank` | index fn / Json | -| Twelve Labs | `twelvelabs` | `embed` | video index fn | -| BFL FLUX | `bfl` | `generate`, `edit`, `fill`, `expand` | `pxt.Image` | -| RunwayML | `runwayml` | `text_to_image`, `text_to_video`, `image_to_video`, `video_to_video` | image: `response['output'][0].astype(pxt.Image)`; video: `response['output'].astype(pxt.Video)` | -| fal.ai | `fal` | `run` | Json | -| Replicate | `replicate` | `run` | Json | -| YOLOX | `yolox` | `yolox`, `yolo_to_coco` | Json detections | - -Not model providers, but in the same namespace: `net.presigned_url` turns a blob-storage URI into a time-limited HTTP URL for serving media, and `vision` carries `eval_detections`, the `mean_ap` UDA, `bboxes_draw` / `overlay_segmentation` and the `bboxes_*` conversion family. - -## Shapes - -```python -from pixeltable.functions.openai import chat_completions, embeddings -from pixeltable.functions.huggingface import sentence_transformer - -summary = chat_completions(messages=[{'role': 'user', 'content': body}], model='gpt-4o-mini') \ - .choices[0].message.content - -embed_fn = embeddings.using(model='text-embedding-3-small') -# or local: sentence_transformer.using(model_id='sentence-transformers/all-MiniLM-L6-v2') -``` - -OpenAI-compatible chat-completion providers return `.choices[0].message.content`; OpenAI `responses` exposes simple text as `.output_text`. Anthropic returns `.content[0].text`. An image goes in a message as `{'type': 'image_url', 'image_url': {'url': t.image}}` -- `openai.vision` is deprecated. Tool calling is per provider: pair `pxt.tools(...)` with that module's own `invoke_tools`. - -Rerankers (`voyageai.rerank`, `jina.rerank`, `huggingface.cross_encoder`) score query/document pairs; run one over the rows `.similarity()` returned rather than reaching for a framework. - -**Model ids go stale.** The ids here are examples, not recommendations -- check the provider's current list. Pixeltable's own docstrings lag further behind than this file does. diff --git a/.agents/skills/pixeltable/references/workflows.md b/.agents/skills/pixeltable/references/workflows.md deleted file mode 100644 index ed42884..0000000 --- a/.agents/skills/pixeltable/references/workflows.md +++ /dev/null @@ -1,82 +0,0 @@ -# FastAPIRouter - -`from pixeltable.serving import FastAPIRouter`. One application file declares `TableModel` classes and routers. Start from `pxt service example --out app.py`. Apply tables with `pxt schema update`. Start HTTP with `pxt service update`. - -```python -# app.py -import pixeltable as pxt -import pixeltable.functions as pxtf -from pixeltable.functions.huggingface import sentence_transformer -from pixeltable.serving import FastAPIRouter - -TableModel = pxt.model_base() -embed_fn = sentence_transformer.using(model_id='intfloat/multilingual-e5-large-instruct') - - -class Docs(TableModel, name='docs'): - document: pxt.Document - timestamp: pxt.Timestamp - uuid = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True) - - -class Chunks( - TableModel, - name='chunks', - base=Docs, - iterator=pxtf.document.document_splitter( - Docs.document, separators='page, sentence', metadata='title,heading,page' - ), -): - __indexes__ = [pxt.EmbeddingIndex(text, embedding=embed_fn, name='chunks_embed')] # type: ignore[name-defined] - # Needs sentence-transformers + torch, and spaCy if separators include 'sentence'. - - -ingest = FastAPIRouter(name='ingest', prefix='/api', tags=['data']) -ingest.add_insert_route( - Docs, path='/upload', uploadfile_inputs=[Docs.document], inputs=[Docs.timestamp], - outputs=[Docs.document], background=True, -) -ingest.add_delete_route(Docs, path='/delete') - -@pxt.query -def list_docs(): - return Docs.select(Docs.document, Docs.timestamp).order_by(Docs.timestamp, asc=False) - -@pxt.query -def search_docs(query_text: str): - sim = Chunks.text.similarity(string=query_text) - return Chunks.where(sim > 0.3).order_by(sim, asc=False).select( - text=Chunks.text, score=sim).limit(20) - -ingest.add_query_route(path='/list', query=list_docs, method='get') -ingest.add_query_route(path='/search', query=search_docs, method='post') -``` - -```bash -pxt init -pxt schema update app.py my_app -pxt service update app.py my_app -``` - -After apply: `t = pxt.get_table('my_app.docs')`. - -Already have FastAPI: after schema update, bind the catalog, then include the router. Call `pxt.get_table()` inside custom handlers. - -```python -ingest.bind('my_app') -app.include_router(ingest) -``` - -- `add_insert_route`: POST from model columns. `uploadfile_inputs` for files. Persists the row. A file column is `uploadfile_inputs` or `inputs`, not both. -- `add_compute_route`: same request shape as insert, but `Table.compute()` — no row stored -- `add_query_route`: wraps `@pxt.query`. Default `{ "rows": [...] }`. `one_row=True` returns the object (0 rows → 404, >1 → 409). `return_fileresponse=True` returns the one media column as a file (implies one-row) -- `add_delete_route`: POST delete by primary key -- Indexes on the model (`__indexes__`) - -Media columns in JSON are URLs under `{prefix}/_pxt/media/...` (this file: `/api/_pxt/media/...`). Use that URL in a browser or `` / `