fix(langgraph): normalize episode type strings#1344
Conversation
|
@haosenwang1018 thank you for the pull request submission. Please sign your commits, resolve the unit test failures, and review the CoPilot feedback. You only need to resolve the relevant items. Thanks. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes LangGraph → client type mismatches and expands the client’s filtering API to accept raw filter strings in addition to key/value filter_dict.
Changes:
- Normalize
episode_typewhen passed as a string in LangGraph tools before callingMemory.add(). - Add optional raw
filterstring support toMemory.search()andMemory.list()and thread it through LangGraph search tooling. - Update unit/integration tests to validate raw-filter composition and updated filter_dict key conventions (
metadata.*).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/client/src/memmachine_client/memory.py | Adds raw filter parameter and combines it with built-in + filter_dict filters. |
| packages/client/src/memmachine_client/langgraph.py | Normalizes episode_type and forwards raw filter into Memory.search(). |
| packages/client/client_tests/test_memory.py | Adds coverage for raw filter strings; adjusts filter_dict keys to metadata.*. |
| packages/client/client_tests/test_langgraph.py | Adds coverage for episode_type string normalization and raw filter passthrough. |
| packages/client/client_tests/test_integration_complete.py | Updates integration test to use metadata.* keys in filter_dict. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| page_size: int = 100, | ||
| page_num: int = 0, | ||
| filter_dict: dict[str, str] | None = None, | ||
| filter: str | None = None, | ||
| set_metadata: dict[str, JsonValue] | None = None, | ||
| timeout: int | None = None, |
There was a problem hiding this comment.
Adding filter in the middle of list(...)’s positional parameters is a backward-compatibility hazard: callers passing set_metadata/timeout positionally will now bind to filter/set_metadata instead. Make filter keyword-only (e.g., add a * before filter) or move filter to the end of the signature to avoid breaking positional call sites.
| dict_filter_str = self._dict_to_filter_string(merged_filters) if merged_filters else "" | ||
| explicit_filter = filter.strip() if filter else "" | ||
|
|
||
| if dict_filter_str and explicit_filter: | ||
| filter_str = f"{dict_filter_str} AND ({explicit_filter})" | ||
| else: | ||
| filter_str = dict_filter_str or explicit_filter |
There was a problem hiding this comment.
When both built-in filters and user filters are absent, filter_str becomes an empty string, which will still be serialized (it’s not None) and sent to the API as filter: \"\". If the server treats empty filter as invalid (or different from missing), this can cause unexpected failures/behavior. Prefer passing None when the final filter is empty (e.g., filter = filter_str or None) and apply the same fix in both search() and list().
| filter_dict: dict[str, str] | None = None, | ||
| timeout: int | None = None, | ||
| *, | ||
| filter: str | None = None, |
There was a problem hiding this comment.
The PR title/description focuses on normalizing episode_type, but this PR also introduces a new raw filter parameter and filter-composition behavior in Memory.search()/Memory.list() and LangGraph tooling. Please either (a) update the PR title/description to include the raw-filter feature, or (b) split the raw-filter addition into a separate PR to keep scope aligned.
| normalized_episode_type = ( | ||
| EpisodeType(episode_type) | ||
| if isinstance(episode_type, str) | ||
| else episode_type | ||
| ) |
There was a problem hiding this comment.
EpisodeType(episode_type) will raise a bare ValueError for unsupported strings, which can be hard to diagnose for LangGraph users. Consider catching that exception and re-raising with a clearer message that includes the provided value and the allowed EpisodeType values (or normalize common variants like enum names/casing if you intend to accept them).
| dict_filter_str = self._dict_to_filter_string(merged_filters) if merged_filters else "" | ||
| explicit_filter = filter.strip() if filter else "" | ||
|
|
||
| if dict_filter_str and explicit_filter: | ||
| filter_str = f"{dict_filter_str} AND ({explicit_filter})" | ||
| else: | ||
| filter_str = dict_filter_str or explicit_filter |
There was a problem hiding this comment.
The filter-combination logic is duplicated in both search() and list(). To reduce drift (e.g., future changes to parenthesization/empty handling), consider extracting a small helper (private method/function) that takes merged_filters + filter and returns the final filter string/None.
Add three small features to the Python client and its LangGraph wrapper so callers can pass structured filter expressions and either-enum-or-string episode types directly. 1. `Memory.search(filter=...)` and `Memory.list(filter=...)` Accept an optional raw filter string alongside `filter_dict`. When both are provided, the two are combined with `AND`. The raw filter is passed through to the v2 `SearchMemoriesSpec.filter` / `ListMemoriesSpec.filter` fields unchanged. 2. `MemMachineTools.search_memory(filter=...)` Pipes the same raw filter through the LangGraph search-memory tool. 3. `MemMachineTools.add_memory(episode_type=...)` Accept either an `EpisodeType` enum or its string value (e.g. `"message"`), normalizing strings via `EpisodeType(...)` before delegating to `Memory.add`. The factory tool's return-type annotation was widened to match. The `filter` parameter shadows the Python builtin, which is the same trade-off `memmachine_common.api.SearchMemoriesSpec` already made for its `filter:` field — keeping the parameter name aligned with the API field. `# noqa: A002` is applied at the three call sites with a comment pointing at the API spec. This commit consolidates the substantive work from haosenwang1018's 9-commit stack (MemMachine#1341 → MemMachine#1349) into a single rebased+linted commit against current `main`. The original stack's prefix-style doc and test changes have been omitted because they have already landed on `main` via MemMachine#1352 and MemMachine#1311. The original commits authored by haosenwang1018: - 921b55f feat(client): support raw filter strings - e0849bb feat(langgraph): support raw filter strings - 53b489e fix(langgraph): normalize episode type strings Closes MemMachine#1341, MemMachine#1342, MemMachine#1343, MemMachine#1344, MemMachine#1345, MemMachine#1346, MemMachine#1347, MemMachine#1348, MemMachine#1349 Co-authored-by: Steve Scargall <steve.scargall@gmail.com> Signed-off-by: Steve Scargall <37674041+sscargal@users.noreply.github.com>
…ion (#1403) * feat(client+langgraph): raw filter strings and EpisodeType normalization Add three small features to the Python client and its LangGraph wrapper so callers can pass structured filter expressions and either-enum-or-string episode types directly. 1. `Memory.search(filter=...)` and `Memory.list(filter=...)` Accept an optional raw filter string alongside `filter_dict`. When both are provided, the two are combined with `AND`. The raw filter is passed through to the v2 `SearchMemoriesSpec.filter` / `ListMemoriesSpec.filter` fields unchanged. 2. `MemMachineTools.search_memory(filter=...)` Pipes the same raw filter through the LangGraph search-memory tool. 3. `MemMachineTools.add_memory(episode_type=...)` Accept either an `EpisodeType` enum or its string value (e.g. `"message"`), normalizing strings via `EpisodeType(...)` before delegating to `Memory.add`. The factory tool's return-type annotation was widened to match. The `filter` parameter shadows the Python builtin, which is the same trade-off `memmachine_common.api.SearchMemoriesSpec` already made for its `filter:` field — keeping the parameter name aligned with the API field. `# noqa: A002` is applied at the three call sites with a comment pointing at the API spec. This commit consolidates the substantive work from haosenwang1018's 9-commit stack (#1341 → #1349) into a single rebased+linted commit against current `main`. The original stack's prefix-style doc and test changes have been omitted because they have already landed on `main` via #1352 and #1311. The original commits authored by haosenwang1018: - 921b55f feat(client): support raw filter strings - e0849bb feat(langgraph): support raw filter strings - 53b489e fix(langgraph): normalize episode type strings Closes #1341, #1342, #1343, #1344, #1345, #1346, #1347, #1348, #1349 Co-authored-by: Steve Scargall <steve.scargall@gmail.com> Signed-off-by: Steve Scargall <37674041+sscargal@users.noreply.github.com> * docs(langgraph): document filter and episode type support --------- Signed-off-by: Steve Scargall <37674041+sscargal@users.noreply.github.com> Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com> Co-authored-by: Shu Wang <33640803+malatewang@users.noreply.github.com>
|
Closed by #1403 |
Purpose of the change
Fix the LangGraph integration so string
episode_typevalues are normalized before being passed into the Python client memory API.Description
Fixes #1002
The current LangGraph demo/integration path can pass
episode_typeas a string, but the Python client expects anEpisodeTypeenum inMemory.add(). That mismatch causes runtime failures like:AttributeError: 'str' object has no attribute 'value'This change updates the LangGraph wrapper to accept
episode_type: EpisodeType | str | Noneand normalize strings viaEpisodeType(...)before callingmemory.add(...).Also updated tests in
packages/client/client_tests/test_langgraph.pyto cover:episode_type=Noneepisode_typenormalization toEpisodeType.MESSAGEType of change
How Has This Been Tested?
Test command:
cd packages/client && PYTHONPATH=/home/node/.openclaw/workspace/MemMachine/packages/client/src:/home/node/.openclaw/workspace/MemMachine/packages/common/src python -m pytest client_tests/test_langgraph.py -qChecklist
Maintainer Checklist