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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
enable-cache: true

- name: Install
run: uv sync --python ${{ matrix.python-version }}
run: uv sync --locked --python ${{ matrix.python-version }}

- name: Take Pixeltable unpinned
# Deliberately ignores the `<0.8` pin in pyproject.toml. This is the canary.
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,59 @@ All examples use the context-based API and are ready to run:

**Remember**: Pixelmemory is a reference implementation. Use it as inspiration to build your own memory architecture using [Pixeltable's](https://github.com/pixeltable/pixeltable) flexible primitives.

## Migrating from 0.1.x

0.2.0 runs on Pixeltable 0.7.6. 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`.

Three things changed for callers:

- **Python 3.11+ is required.** Pixeltable dropped 3.10 in 0.7.2.
- **Two splitter parameter dataclasses are renamed.** `AudioSplitterParams`
takes `duration` / `overlap` / `min_segment_duration` (was
`chunk_duration_sec` / `overlap_sec` / `min_chunk_duration_sec`), and
`DocumentSplitterParams` takes `skip_tags` (was `html_skip_tags`). These
names are passed straight through to Pixeltable's iterators, so they are the
API rather than a naming choice, and the old ones were the bug.
- **`memory.chunk_views[...]` and `memory.frame_views[...]` are replaced by
`memory.search()` and `memory.views()`.** Nothing breaks in practice:
`chunk_views` is a list on `resources`, not a mapping on `Memory`, so those
subscripts always raised `AttributeError`.

```python
# before -- raised AttributeError
chunk_view = memory.chunk_views["report"]
sim = chunk_view.text.similarity(query)
results = (chunk_view.order_by(sim, asc=False).limit(3)
.select(chunk_view.text, similarity=sim).collect())

# after
results = memory.search(query, on="report", limit=3)
```

`Memory(...)`, `memory.Entry`, `memory.add()`, the attribute passthrough and
every other `Context` field name are unchanged.

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.

## When not to use pixelmemory

If your memory schema is fixed when you write the code, you do not need a
wrapper. Declare it directly with Pixeltable's `TableModel` and
`FastAPIRouter` and run `pxt schema update` / `pxt service update`; the
[starter kit](https://github.com/pixeltable/pixeltable-starter-kit) has
working apps in that shape.

pixelmemory earns its keep when the schema is chosen at **runtime** -- when
the `Context` list comes from a config file or a request -- and for the
multimodal ingest pipelines it wires up for you: audio to segments to
transcription to sentences to embeddings, and video to frames plus captions,
in the right dependency order.


## License

Apache 2.0 License
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[project]
name = "pixelmemory"
version = "0.1.0"
version = "0.2.0"
description = "Multimodal Memory for AI Agents and MCP."
license = "Apache-2.0"
readme = "README.md"
Expand Down
20 changes: 18 additions & 2 deletions tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ def test_search_min_score_filters(namespace):
assert m.search("cat", min_score=1.1) == []


def _scannable(path):
"""File text with the migration guide stripped out.

A "Migrating from 0.1.x" section has to quote the old, broken calls to show
what to replace, so scanning it for those calls would always fail.
"""
text = path.read_text()
marker = "## Migrating from 0.1.x"
if marker not in text:
return text
head, rest = text.split(marker, 1)
# resume scanning at the next top-level heading after the migration section
tail = rest.split("\n## ", 1)
return head + (tail[1] if len(tail) > 1 else "")


def test_no_positional_similarity_in_examples():
"""Positional .similarity(query) is deprecated; the keyword form is required.

Expand All @@ -94,7 +110,7 @@ def test_no_positional_similarity_in_examples():
root = pathlib.Path(__file__).resolve().parent.parent
offenders = []
for path in list(root.glob("examples/**/*.py")) + [root / "README.md"]:
for i, line in enumerate(path.read_text().splitlines(), 1):
for i, line in enumerate(_scannable(path).splitlines(), 1):
if re.search(r"\.similarity\((?!string=)", line):
offenders.append(f"{path.relative_to(root)}:{i}: {line.strip()}")
assert not offenders, "positional .similarity() found:\n" + "\n".join(offenders)
Expand All @@ -108,7 +124,7 @@ def test_examples_do_not_use_chunk_views():
offenders = [
f"{p.relative_to(root)}:{i}"
for p in list(root.glob("examples/**/*.py")) + [root / "README.md"]
for i, line in enumerate(p.read_text().splitlines(), 1)
for i, line in enumerate(_scannable(p).splitlines(), 1)
if ".chunk_views[" in line or ".frame_views[" in line
]
assert not offenders, "stale chunk_views/frame_views access:\n" + "\n".join(offenders)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading