diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ef4f6f..b297352 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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. diff --git a/README.md b/README.md index 2cbbe72..6812d9d 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4db785c..a73c064 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/tests/test_search.py b/tests/test_search.py index 16cad57..e1910c8 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -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. @@ -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) @@ -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) diff --git a/uv.lock b/uv.lock index 56f7156..a2f06b3 100644 --- a/uv.lock +++ b/uv.lock @@ -2023,7 +2023,7 @@ wheels = [ [[package]] name = "pixelmemory" -version = "0.1.0" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "pixeltable" },