Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,34 @@ jobs:
PIXELTABLE_HOME: ${{ runner.temp }}/pxt-release
run: |
uv venv /tmp/verify --python 3.12
VIRTUAL_ENV=/tmp/verify uv pip install dist/*.whl
VIRTUAL_ENV=/tmp/verify uv pip list | grep -E "pixelmemory|pixeltable"
/tmp/verify/bin/python -c "
uv pip install --python /tmp/verify/bin/python dist/*.whl
uv pip check --python /tmp/verify/bin/python
uv pip list --python /tmp/verify/bin/python | grep -E "pixelmemory|pixeltable"
/tmp/verify/bin/python -I -c "
from pathlib import Path
import sys
import pixelmemory
assert Path(pixelmemory.__file__).resolve().is_relative_to(Path(sys.prefix).resolve()), pixelmemory.__file__
from pixelmemory import Memory
from pixelmemory.context import Text
m = Memory([Text(id='body', embed=False)], namespace='release_check')
assert m.table.columns() == ['body'], m.table.columns()
print('built wheel installs and builds a memory')
m.add(m.Entry(body='persisted from the built wheel'))
m = Memory([Text(id='body', embed=False)], namespace='release_check')
assert m.table.collect()[0]['body'] == 'persisted from the built wheel'
print('built wheel installs, inserts and reopens a memory')
"

- name: Test the installed wheel
env:
HF_HUB_DISABLE_PROGRESS_BARS: "1"
run: |
uv export --locked --only-group dev --no-emit-project --no-hashes -o /tmp/dev-requirements.txt
uv pip install --python /tmp/verify/bin/python -r /tmp/dev-requirements.txt
uv pip check --python /tmp/verify/bin/python
cd "$RUNNER_TEMP"
/tmp/verify/bin/python -I -m pytest "$GITHUB_WORKSPACE/tests" -c "$GITHUB_WORKSPACE/pyproject.toml" -v

- uses: actions/upload-artifact@v4
with:
name: dist
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
pull_request:
workflow_dispatch:
schedule:
# Weekly canary. The install step below takes Pixeltable unpinned, so the
# Weekly canary. One matrix entry takes Pixeltable unpinned, so the
# release that breaks pixelmemory turns this build red before a user finds it.
- cron: "0 6 * * 1"

Expand All @@ -24,7 +24,11 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]
dependencies: [locked]
include:
- python-version: "3.12"
dependencies: latest
steps:
- uses: actions/checkout@v4

Expand All @@ -36,13 +40,18 @@ jobs:
run: uv sync --locked --python ${{ matrix.python-version }}

- name: Take Pixeltable unpinned
if: matrix.dependencies == 'latest'
# Deliberately ignores the `<0.8` pin in pyproject.toml. This is the canary.
# Every step after this uses --no-sync, or uv would restore the pinned version.
run: uv pip install --upgrade pixeltable
run: uv pip install --python .venv/bin/python --upgrade pixeltable

- name: Record the Pixeltable version under test
run: uv run --no-sync python -c "import pixeltable; print(pixeltable.__version__)"

- name: Check installed dependencies
if: matrix.dependencies == 'locked'
run: uv pip check --python .venv/bin/python

- name: Lint
run: uv run --no-sync ruff check .

Expand Down
64 changes: 58 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,23 @@ Most AI applications today are stateless - they forget everything between sessio
pip install pixelmemory
```

*Note: Pixeltable is automatically installed as a dependency - no additional setup required.*
Python 3.11+ is required. Pixelmemory 0.2.0 uses Pixeltable 0.7.17+ (below
0.8) and sentence-transformers 5.4+. Both are installed automatically. The
default local embedding model downloads its weights on first use.

Install the extras for the contexts you use:

| Context | Additional setup |
| --- | --- |
| Text, Document | None for direct text search and token-based document chunks |
| Text with `use_chunking=True` | `pip install 'pixelmemory[sentence]'`, then `python -m spacy download en_core_web_sm` |
| Image | `pip install 'pixelmemory[openai]'` and `OPENAI_API_KEY`, or `pixelmemory[anthropic]` and `ANTHROPIC_API_KEY` |
| Audio, Video | `pip install 'pixelmemory[openai,sentence]'`, `OPENAI_API_KEY`, and `python -m spacy download en_core_web_sm` |

Video uses OpenAI transcription even when frame captions use Anthropic. Add
the `anthropic` extra and key for that configuration. `use_clip=True` also
requires a compatible `torchvision` installation and downloads CLIP weights.
Document splitting with `separators="sentence"` needs the same sentence setup.

## Quick Start

Expand All @@ -74,6 +90,10 @@ for r in results:
print(r["score"], r["text"])
```

`search()` returns `context_id`, `text` and `score`. It searches text columns,
document chunks, transcripts and image/frame captions. Use `memory.views()`
and raw Pixeltable queries when you need source metadata or a CLIP index.

## Architecture

```mermaid
Expand Down Expand Up @@ -155,7 +175,7 @@ def chat_with_memory(session_id: str, message: str):

All examples use the context-based API and are ready to run:

**Basic Examples** (no external dependencies):
**Basic Examples** (local embedding model; no provider key):
- `examples/getting_started/01_basic_memory.py`
- `examples/multimodal/text.py`

Expand All @@ -168,6 +188,7 @@ All examples use the context-based API and are ready to run:
- `examples/integrations/langchain_chat_history.py` (requires OpenAI API key)
- `examples/integrations/crewai_agentic_rag.py` (requires `pip install crewai`)
- `examples/fastapi/memory_service.py` (requires `pip install fastapi uvicorn`)
- `examples/mcp/memory.py` (requires `pip install 'mcp>=2.3'`; serves task tools over Streamable HTTP)


## Next Steps
Expand All @@ -187,7 +208,7 @@ All examples use the context-based API and are ready to run:

## Migrating from 0.1.x

0.2.0 runs on Pixeltable 0.7.6. On 0.5.6+ the Document, Audio and Video
0.2.0 runs on Pixeltable 0.7.17+. On 0.5.6+ the Document, Audio and Video
memories raised on construction, so most of 0.1.x did not work. Upgrade with
`pip install -U pixelmemory`.

Expand Down Expand Up @@ -216,13 +237,44 @@ results = (chunk_view.order_by(sim, asc=False).limit(3)
results = memory.search(query, on="report", limit=3)
```

`Memory(...)`, `memory.Entry`, `memory.add()`, the attribute passthrough and
every other `Context` field name are unchanged.
`Memory(...)`, `memory.Entry`, `memory.add()` and the attribute passthrough
keep the same API. Use `required=False` for a nullable context; its Entry field
can be omitted and defaults to `None`. Optional contexts can also be embedded.

Embedding index names are scoped to each context so multiple embedded fields
work together. Existing legacy indexes are reused. For a raw Pixeltable query,
read the index name from `memory.search_targets` instead of assuming
`"similarity"`; `memory.search()` selects it for you.

One behaviour change worth knowing: views and indexes now survive
reconstruction. Building the same `Memory` twice used to drop and rebuild
every view and index, re-running transcription and re-embedding everything.

Reopen a memory with the same context configuration. Reopening does not update
existing computed expressions, splitter settings or embedding models. Use
Pixeltable's explicit schema/index APIs for those changes, or a new namespace;
`if_exists="replace_force"` deletes the existing table and dependent views.

## Development and verification

```bash
uv sync --locked --python 3.12
uv run ruff check .
uv run python -m pytest
uv build
```

Tests use an isolated catalog and remove provider credentials. They cover real
Pixeltable inserts, search, reconstruction, and multimodal iterators. Provider
responses are simulated in multimodal tests; text ranking also runs a real
local sentence-transformers model. Sentence tests use spaCy's local sentencizer
without downloading the English model. Live provider compatibility requires
separate credentialed testing.

CI tests the locked dependencies on Python 3.11–3.13 and an unpinned Pixeltable
canary on Python 3.12. The release workflow verifies the installed wheel from
outside the source tree before publishing. See [RELEASING.md](RELEASING.md).

## When not to use pixelmemory

If your memory schema is fixed when you write the code, you do not need a
Expand All @@ -240,4 +292,4 @@ in the right dependency order.

## License

Apache 2.0 License
Apache 2.0 License
11 changes: 10 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ git tag vX.Y.Z && git push origin vX.Y.Z

The tag push runs `.github/workflows/release.yml`, which builds the sdist and
wheel, checks the tag matches `pyproject.toml`, installs the built wheel into a
clean environment and exercises it, then publishes to PyPI.
clean environment and exercises it, then publishes to PyPI. The smoke test
uses isolated Python import resolution (`-I`) and verifies that `pixelmemory`
is loaded from that environment. It inserts a row and reopens the memory,
then runs the full test suite against the installed wheel from outside the
checkout. Provider calls in the suite are simulated; publishing does not
establish live-provider compatibility.

Before tagging, check that the test workflow passes for the intended commit
and run the release workflow with `dry_run` checked. The tag is the publish
action; building locally or opening a PR does not update PyPI.

## There is no PyPI token

Expand Down
Loading
Loading