feat(adapters): Mem0 memory adapter with push/pull/reconcile protocol (Phase 3)#79
Conversation
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
📝 Walkthrough
WalkthroughAdds an opt-in external memory adapter system: a Changes
Sequence Diagram(s)sequenceDiagram
participant Gradata as Gradata Code
participant Adapter as Mem0Adapter
participant MemClient as Mem0MemoryClient
participant Mem0Backend as Mem0 Backend
rect rgba(100, 150, 200, 0.5)
Note over Gradata,Mem0Backend: push_correction Flow
Gradata->>Adapter: push_correction(draft, final, summary, tags, metadata)
Adapter->>Adapter: Enrich metadata with source="gradata" and kind="correction"
Adapter->>MemClient: add(messages=[...], metadata={...}, user_id=...)
MemClient->>Mem0Backend: POST /add
Mem0Backend-->>MemClient: Return response (various shapes)
MemClient-->>Adapter: Response object
Adapter->>Adapter: Extract memory ID or return None on failure
Adapter-->>Gradata: str | None
end
rect rgba(150, 200, 100, 0.5)
Note over Gradata,Mem0Backend: pull_memory_for_context Flow
Gradata->>Adapter: pull_memory_for_context(query, k=5, filters=...)
Adapter->>MemClient: search(query, limit=k, filters=...)
MemClient->>Mem0Backend: POST /search
Mem0Backend-->>MemClient: Return results
MemClient-->>Adapter: Search response (varied shapes)
Adapter->>Adapter: Normalize to list[{text, metadata, score}]
Adapter-->>Gradata: list[dict[str, Any]]
end
rect rgba(200, 150, 100, 0.5)
Note over Gradata,Mem0Backend: reconcile Flow
Gradata->>Adapter: reconcile(gradata_memory_ids=[...])
Adapter->>MemClient: get_all(user_id=...)
MemClient->>Mem0Backend: GET /memories
Mem0Backend-->>MemClient: Return all memories
MemClient-->>Adapter: Response with memory list
Adapter->>Adapter: Extract remote IDs and compute diff (only_local, only_remote)
Adapter-->>Gradata: dict[str, Any] with reconciliation results
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pyproject.toml`:
- Around line 33-39: The pyproject dependency entries reference a non-existent
mem0ai version; update both occurrences of "mem0ai>=0.1.0" (in the adapters-mem0
extra and the all extras list) to a valid release such as "mem0ai>=1.0.7" so the
package can be resolved during installation and keep both entries consistent.
In `@src/gradata/adapters/mem0.py`:
- Around line 118-128: The current meta construction lets caller-provided
metadata overwrite internal keys because of meta.update(metadata) after setting
base keys; to preserve base values change the update order or sanitize the
incoming dict: either apply metadata first and then set meta =
{"source":"gradata","kind":"correction", ...} (ensuring source/kind override
caller keys), or keep the current order but remove reserved keys from the
incoming `metadata` before calling `meta.update(metadata)` (e.g., pop or ignore
"source" and "kind"); this change should be made around the `meta` creation
where `meta: dict[str, Any] = {...}` and `meta.update(metadata)` are used.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 451290b2-ee57-4d8d-a433-404aed7fac51
📒 Files selected for processing (5)
pyproject.tomlsrc/gradata/adapters/__init__.pysrc/gradata/adapters/base.pysrc/gradata/adapters/mem0.pytests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: test (3.12)
- GitHub Check: test (3.13)
- GitHub Check: test (3.11)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
src/gradata/**/*.py
⚙️ CodeRabbit configuration file
src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].
Files:
src/gradata/adapters/__init__.pysrc/gradata/adapters/base.pysrc/gradata/adapters/mem0.py
tests/**
⚙️ CodeRabbit configuration file
tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.
Files:
tests/test_mem0_adapter.py
🔇 Additional comments (14)
src/gradata/adapters/mem0.py (6)
264-269: Minor:orchaining treats empty string as falsy.The expression
item.get("memory") or item.get("text") or ...will skip an explicit empty string""and try the next key. This is likely fine since empty memory text isn't meaningful, but be aware if the SDK ever returns""intentionally.
1-40: LGTM!Module header follows coding guidelines: includes
from __future__ import annotations, usesloggingmodule instead ofprint(), and provides clear usage documentation.
64-92: LGTM!Constructor validation is correct: enforces non-empty
user_id, supports dependency injection for testing viaclient=, and provides a helpfulImportErrormessage whenmem0aiis missing. The lazy import pattern keeps the module importable without the optional dependency.
153-177: LGTM!The dual-layer exception handling for SDK backward compatibility is well-implemented. The
TypeErrorcatch for older SDKs that don't acceptfilterstriggers a retry without that kwarg, and any other exception correctly logs and returns[]per the protocol contract.
179-205: LGTM!The
reconcileimplementation correctly computes the set difference between local and remote memory IDs. Usingsorted()ensures deterministic output, and the exception handling follows the protocol contract.
214-297: LGTM!The response-shape helper functions handle multiple Mem0 SDK envelope formats robustly. The type guards (
isinstancechecks) prevent runtime errors on unexpected shapes, and returning empty sentinels on parse failure aligns with the adapter's fail-safe design.src/gradata/adapters/base.py (1)
1-93: LGTM!The
MemoryAdapterprotocol is well-designed with clear failure-mode contracts (return sentinels instead of raising). The@runtime_checkabledecorator enablesisinstance()checks, and all methods have proper type annotations with comprehensive docstrings.src/gradata/adapters/__init__.py (1)
1-22: LGTM!Clean package interface with explicit
__all__. The eager import ofMem0Adapteris fine since the actualmem0aiimport insideMem0Adapter.__init__is lazy—users can import the class without having the optional dependency installed.tests/test_mem0_adapter.py (6)
22-63: LGTM!The
_FakeMem0Clienttest double is well-designed: it records all calls for assertion, supports configurable responses and failure modes, and simulates theaccepts_filters=Falsebehavior for SDK version compatibility testing.
71-83: LGTM!Constructor tests properly verify: empty
user_idraisesValueError, dependency injection works withoutapi_key, and the adapter satisfies theMemoryAdapterprotocol (runtime-checkableisinstance).
91-172: LGTM!Comprehensive test coverage for
push_correction: verifies all four response shape variants (results envelope, flat id, list, string), unknown shape handling, exception logging, and metadata merging. Assertions check specific values rather than just truthiness.
180-259: LGTM!Thorough coverage of
pull_memory_for_context: result normalization, bare-list fallback, SDK backward-compat retry, exception handling with log verification, andNoneresponse handling. The floating-point score comparisons (0.91, 0.77, 0.5) are fine since they're canned fixture values, not computed results.
267-301: LGTM!Reconcile tests verify the set-difference logic correctly:
only_local,only_remote, andremote_countare all checked with specific expected values. Exception handling with empty dict return and warning log is properly verified.
309-327: LGTM!Integration smoke test is properly gated with
@pytest.mark.integrationand@pytest.mark.skipifon missingMEM0_API_KEY. The minimal assertions (non-None ID, list return type) are appropriate for a smoke test that validates connectivity without over-specifying API response details.
…erved-keys protection)
- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
not override source="gradata" / kind="correction".
Co-Authored-By: Gradata <noreply@gradata.ai>
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/test_mem0_adapter.py`:
- Around line 123-144: Consolidate the four nearly identical tests
(test_push_correction_handles_flat_id_response,
test_push_correction_handles_list_response,
test_push_correction_handles_string_response,
test_push_correction_returns_none_on_unknown_shape) into a single
pytest.mark.parametrize test that iterates over different add_response values
passed to _FakeMem0Client and checks Mem0Adapter(user_id="oliver",
client=fake).push_correction(draft="a", final="b") against the expected id
(including None for the unknown shape); reference the _FakeMem0Client,
Mem0Adapter, and push_correction symbols when locating the code to replace.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 91f8b207-8fd9-40f2-9674-e87ec36f3a45
📒 Files selected for processing (3)
pyproject.tomlsrc/gradata/adapters/mem0.pytests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (3.11)
- GitHub Check: test (3.12)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
tests/**
⚙️ CodeRabbit configuration file
tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.
Files:
tests/test_mem0_adapter.py
src/gradata/**/*.py
⚙️ CodeRabbit configuration file
src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].
Files:
src/gradata/adapters/mem0.py
🔇 Additional comments (11)
pyproject.toml (1)
33-39: LGTM!The
mem0ai>=1.0.11version constraint is consistent between theadapters-mem0extra and theallextra. The previous review concern about the non-existent>=0.1.0version has been addressed.src/gradata/adapters/mem0.py (5)
64-92: LGTM!The constructor properly validates
user_id, supports dependency injection for testing, provides lazy importing ofmem0ai, and includes helpful error messages for missing dependencies.
118-127: LGTM!The metadata handling correctly applies caller-provided metadata first, then sets internal reserved keys (
source,kind,summary,tags) afterward to prevent callers from overwriting them. This addresses the previous review concern.
141-177: LGTM!The method correctly handles SDK version compatibility by catching
TypeErrorfor thefiltersparameter and retrying without it. Exception handling follows the protocol contract (returns[], logs warning, doesn't raise).
179-205: LGTM!The reconcile method correctly computes set differences between local and remote memory IDs, returns sorted results for determinism, and follows the protocol contract (returns
{}on failure, logs warning, doesn't raise).
214-297: LGTM!The response-shape helper functions are robust, handling
None, various envelope structures ({"id": ...},{"results": [...]}, bare lists, strings), and defensive type checking throughout. This isolates SDK version differences cleanly.tests/test_mem0_adapter.py (5)
22-63: LGTM!The
_FakeMem0Clientis a well-designed test double that records calls for assertion, supports configurable responses, can simulate exceptions, and can emulate older SDK behavior (nofilterssupport). This enables comprehensive offline testing.
71-83: LGTM!Construction tests properly verify validation (empty
user_id), dependency injection, and protocol conformance with specific assertions.
213-292: LGTM!The
pull_memory_for_contexttests comprehensively cover result normalization, bare list handling, SDK version compatibility (TypeError retry), exception handling, andNoneresponse scenarios. All assertions check specific values.
300-334: LGTM!The
reconciletests properly verify diff computation (set differences), handle both envelope and bare list response formats, and test exception handling. Assertions check specific values.
342-361: LGTM with minor observation.The integration smoke test is appropriately gated by
MEM0_API_KEYand marked for CI skip. The assertions verify basic contract compliance. For a smoke test, theisinstance(hits, list)check is acceptable, though you could optionally verify the list is non-empty or that returned items have the expected structure (text,metadata,scorekeys).
| def test_push_correction_handles_flat_id_response() -> None: | ||
| fake = _FakeMem0Client(add_response={"id": "mem-flat"}) | ||
| adapter = Mem0Adapter(user_id="oliver", client=fake) | ||
| assert adapter.push_correction(draft="a", final="b") == "mem-flat" | ||
|
|
||
|
|
||
| def test_push_correction_handles_list_response() -> None: | ||
| fake = _FakeMem0Client(add_response=[{"id": "mem-list"}]) | ||
| adapter = Mem0Adapter(user_id="oliver", client=fake) | ||
| assert adapter.push_correction(draft="a", final="b") == "mem-list" | ||
|
|
||
|
|
||
| def test_push_correction_handles_string_response() -> None: | ||
| fake = _FakeMem0Client(add_response="mem-str") | ||
| adapter = Mem0Adapter(user_id="oliver", client=fake) | ||
| assert adapter.push_correction(draft="a", final="b") == "mem-str" | ||
|
|
||
|
|
||
| def test_push_correction_returns_none_on_unknown_shape() -> None: | ||
| fake = _FakeMem0Client(add_response={"no": "id"}) | ||
| adapter = Mem0Adapter(user_id="oliver", client=fake) | ||
| assert adapter.push_correction(draft="a", final="b") is None |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider parametrizing response shape tests.
The tests for different response shapes (flat_id, list, string, unknown) follow the same pattern. They could be consolidated into a parametrized test for improved maintainability, though the current form is acceptable.
♻️ Optional: Parametrize response shape tests
`@pytest.mark.parametrize`(
"add_response,expected_id",
[
({"id": "mem-flat"}, "mem-flat"),
([{"id": "mem-list"}], "mem-list"),
("mem-str", "mem-str"),
({"no": "id"}, None),
],
ids=["flat_dict", "list", "string", "unknown_shape"],
)
def test_push_correction_handles_response_shapes(
add_response: Any, expected_id: str | None
) -> None:
fake = _FakeMem0Client(add_response=add_response)
adapter = Mem0Adapter(user_id="oliver", client=fake)
assert adapter.push_correction(draft="a", final="b") == expected_id🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/test_mem0_adapter.py` around lines 123 - 144, Consolidate the four
nearly identical tests (test_push_correction_handles_flat_id_response,
test_push_correction_handles_list_response,
test_push_correction_handles_string_response,
test_push_correction_returns_none_on_unknown_shape) into a single
pytest.mark.parametrize test that iterates over different add_response values
passed to _FakeMem0Client and checks Mem0Adapter(user_id="oliver",
client=fake).push_correction(draft="a", final="b") against the expected id
(including None for the unknown shape); reference the _FakeMem0Client,
Mem0Adapter, and push_correction symbols when locating the code to replace.
…erved-keys protection)
- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
not override source="gradata" / kind="correction".
Co-Authored-By: Gradata <noreply@gradata.ai>
1bacd7f to
b23c574
Compare
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
First external-memory adapter. Ships the MemoryAdapter protocol (runtime_checkable) plus a Mem0 concrete implementation with push_correction, pull_memory_for_context, and reconcile. Opt-in via the new adapters-mem0 extra in pyproject.toml. Not wired into the event pipeline; users call adapter methods themselves. Adapter never raises on backend failure: writes return None, reads return []. Full fake-client test coverage (18 tests) plus a gated integration smoke test that runs only when MEM0_API_KEY is set. Co-Authored-By: Gradata <noreply@gradata.ai>
…erved-keys protection)
- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
not override source="gradata" / kind="correction".
Co-Authored-By: Gradata <noreply@gradata.ai>
…it; rebase onto main Co-Authored-By: Gradata <noreply@gradata.ai>
b23c574 to
bc4b9f9
Compare
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/test_mem0_adapter.py`:
- Around line 229-233: The test currently asserts an exact float match for
hits[0]["score"] (and similarly at the other assertion) which should use
approximate comparison; update the assertion so the score is checked with
pytest.approx (e.g., assert hits[0]["score"] == pytest.approx(0.91)) or replace
the whole dict comparison with matching for text and metadata but compare the
numeric score using pytest.approx; ensure you import pytest if not already
present and apply the same change to the second float assertion referenced in
the comment.
- Around line 354-355: The current test only asserts that hits is a list, which
is too weak; update the test around adapter.pull_memory_for_context("greeting
style", k=3) to assert concrete expectations from the roundtrip: check that hits
is non-empty (len(hits) > 0), that its length is <= 3, and that each hit has the
expected structure/fields (e.g., contains a "text" or "content" attribute or
that the first item's text contains "greeting" or matches the known sample
inserted earlier). Use adapter.pull_memory_for_context and the hits variable
names to locate the test and replace the loose isinstance check with these
stronger assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: cab50929-8e85-46eb-9be7-243b81f3374f
📒 Files selected for processing (5)
pyproject.tomlsrc/gradata/adapters/__init__.pysrc/gradata/adapters/base.pysrc/gradata/adapters/mem0.pytests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Test (Python 3.12)
- GitHub Check: test (3.13)
- GitHub Check: test (3.11)
- GitHub Check: test (3.12)
- GitHub Check: Test (Python 3.11)
- GitHub Check: Test (Python 3.13)
- GitHub Check: Python 3.11
- GitHub Check: Python 3.12
- GitHub Check: Python 3.12
- GitHub Check: Python 3.11
- GitHub Check: Test (Python 3.11)
- GitHub Check: Test (Python 3.13)
- GitHub Check: Test (Python 3.12)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
src/gradata/**/*.py
⚙️ CodeRabbit configuration file
src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].
Files:
src/gradata/adapters/__init__.pysrc/gradata/adapters/base.pysrc/gradata/adapters/mem0.py
tests/**
⚙️ CodeRabbit configuration file
tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.
Files:
tests/test_mem0_adapter.py
🔇 Additional comments (5)
pyproject.toml (1)
33-39: Optional dependency wiring looks correct.
adapters-mem0and theallextra are consistent, and the version constraint now points to a valid mem0ai release line.src/gradata/adapters/__init__.py (1)
17-22: Clean public export surface for adapters.The module keeps imports minimal and exposes a clear public API through
__all__.src/gradata/adapters/base.py (1)
22-93: Protocol contract is clear and implementation-ready.
@runtime_checkableplus explicit sentinel return contracts provides a strong adapter boundary.tests/test_mem0_adapter.py (1)
123-139: Great consolidation with parametrized response-shape coverage.This keeps the ID-shape matrix easy to extend and maintain.
src/gradata/adapters/mem0.py (1)
118-177: Good defensive behavior on metadata and backend failures.The reserved metadata key enforcement (Line 121-127) and sentinel-return failure handling in push/pull are solid.
| assert hits[0] == { | ||
| "text": "user prefers full names", | ||
| "metadata": {"tags": ["greeting"]}, | ||
| "score": 0.91, | ||
| } |
There was a problem hiding this comment.
Use pytest.approx for float score checks.
At Line 232 and Line 246, float comparisons are exact; these should use pytest.approx per test policy.
🎯 Suggested update for float assertions
- assert hits[0] == {
- "text": "user prefers full names",
- "metadata": {"tags": ["greeting"]},
- "score": 0.91,
- }
+ assert hits[0]["text"] == "user prefers full names"
+ assert hits[0]["metadata"] == {"tags": ["greeting"]}
+ assert hits[0]["score"] == pytest.approx(0.91)
@@
- assert hits == [{"text": "plain text memory", "metadata": {}, "score": 0.5}]
+ assert hits[0]["text"] == "plain text memory"
+ assert hits[0]["metadata"] == {}
+ assert hits[0]["score"] == pytest.approx(0.5)As per coding guidelines, tests/**: floating point comparisons use pytest.approx.
Also applies to: 246-246
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/test_mem0_adapter.py` around lines 229 - 233, The test currently
asserts an exact float match for hits[0]["score"] (and similarly at the other
assertion) which should use approximate comparison; update the assertion so the
score is checked with pytest.approx (e.g., assert hits[0]["score"] ==
pytest.approx(0.91)) or replace the whole dict comparison with matching for text
and metadata but compare the numeric score using pytest.approx; ensure you
import pytest if not already present and apply the same change to the second
float assertion referenced in the comment.
| hits = adapter.pull_memory_for_context("greeting style", k=3) | ||
| assert isinstance(hits, list) |
There was a problem hiding this comment.
Roundtrip smoke test assertion is too weak to validate retrieval.
At Line 355, assert isinstance(hits, list) passes even when retrieval returns unrelated/empty results, so the “roundtrip” can silently regress.
💡 Suggested tightening of the integration assertion
def test_real_mem0_roundtrip() -> None:
@@
memory_id = adapter.push_correction(
@@
assert memory_id is not None
hits = adapter.pull_memory_for_context("greeting style", k=3)
- assert isinstance(hits, list)
+ assert any(
+ isinstance(hit, dict) and "greeting" in str(hit.get("text", "")).lower()
+ for hit in hits
+ )As per coding guidelines, tests/**: assertions check specific values not just truthiness.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| hits = adapter.pull_memory_for_context("greeting style", k=3) | |
| assert isinstance(hits, list) | |
| hits = adapter.pull_memory_for_context("greeting style", k=3) | |
| assert any( | |
| isinstance(hit, dict) and "greeting" in str(hit.get("text", "")).lower() | |
| for hit in hits | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/test_mem0_adapter.py` around lines 354 - 355, The current test only
asserts that hits is a list, which is too weak; update the test around
adapter.pull_memory_for_context("greeting style", k=3) to assert concrete
expectations from the roundtrip: check that hits is non-empty (len(hits) > 0),
that its length is <= 3, and that each hit has the expected structure/fields
(e.g., contains a "text" or "content" attribute or that the first item's text
contains "greeting" or matches the known sample inserted earlier). Use
adapter.pull_memory_for_context and the hits variable names to locate the test
and replace the loose isinstance check with these stronger assertions.
Summary
First external-memory adapter. Greenfield
src/gradata/adapters/module withMemoryAdapterProtocol + concreteMem0Adapter. Lays the pattern for Letta/EverMind/Hermes follow-ups.API
All three methods swallow backend exceptions, log at WARNING, return sentinel (
None/[]/{}).Optional dep
Added
adapters-mem0 = ["mem0ai>=0.1.0"]extra inpyproject.tomland folded intoall. Lazy-imported inside__init__, sofrom gradata.adapters import Mem0Adapterworks without the extra installed.Wiring
NOT wired into the event pipeline. Users call
adapter.push_correction()themselves. Auto-mirror viaBrain(...)constructor option is a follow-up.Divergences from plan (intentional)
client=DI kwarg — only clean way to hit "fake-client tests pass without network" without monkeypatching._extract_memory_id,_normalise_search_results,_extract_all_ids) —mem0aishipped 3 different envelope shapes across versions.Tests
2419 pass (+18 fake-client + 1 gated integration test on
MEM0_API_KEY). Ruff clean.Commits
0ede7e7feat(adapters): Mem0 memory adapter with push/pull/reconcile protocolCo-Authored-By: Gradata noreply@gradata.ai