From e58d4f1cc5f8d2763ac0a3d987199e71d09017bb Mon Sep 17 00:00:00 2001 From: harshitanand Date: Fri, 10 Apr 2026 13:51:52 +0530 Subject: [PATCH 1/2] fix: add max_items limit to fetch_all_edges to prevent unbounded memory growth (#513) fetch_all_nodes already had a max_items guard (default 2000) but fetch_all_edges had no such safeguard, allowing unbounded memory growth on graphs with large numbers of edges. Add _MAX_EDGES = 5000 constant and mirror the same loop-guard pattern from fetch_all_nodes: cap the result list, emit a warning log, and break pagination once the limit is reached. --- backend/app/utils/zep_paging.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/app/utils/zep_paging.py b/backend/app/utils/zep_paging.py index 943cd1ae..8e40226c 100644 --- a/backend/app/utils/zep_paging.py +++ b/backend/app/utils/zep_paging.py @@ -19,6 +19,7 @@ logger = get_logger('mirofish.zep_paging') _DEFAULT_PAGE_SIZE = 100 _MAX_NODES = 2000 +_MAX_EDGES = 5000 _DEFAULT_MAX_RETRIES = 3 _DEFAULT_RETRY_DELAY = 2.0 # seconds, doubles each retry @@ -106,10 +107,11 @@ def fetch_all_edges( client: Zep, graph_id: str, page_size: int = _DEFAULT_PAGE_SIZE, + max_items: int = _MAX_EDGES, max_retries: int = _DEFAULT_MAX_RETRIES, retry_delay: float = _DEFAULT_RETRY_DELAY, ) -> list[Any]: - """分页获取图谱所有边,返回完整列表。每页请求自带重试。""" + """分页获取图谱所有边,最多返回 max_items 条(默认 5000)。每页请求自带重试。""" all_edges: list[Any] = [] cursor: str | None = None page_num = 0 @@ -132,6 +134,10 @@ def fetch_all_edges( break all_edges.extend(batch) + if len(all_edges) >= max_items: + all_edges = all_edges[:max_items] + logger.warning(f"Edge count reached limit ({max_items}), stopping pagination for graph {graph_id}") + break if len(batch) < page_size: break From 7d6f6bb1354b119ca952be32bfe347314dd2ec7a Mon Sep 17 00:00:00 2001 From: 666ghj <670939375@qq.com> Date: Wed, 22 Jul 2026 01:22:47 +0800 Subject: [PATCH 2/2] fix: preserve edge paging call compatibility --- backend/app/utils/zep_paging.py | 2 +- backend/tests/test_zep_edge_paging.py | 44 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 backend/tests/test_zep_edge_paging.py diff --git a/backend/app/utils/zep_paging.py b/backend/app/utils/zep_paging.py index 8e40226c..22c20526 100644 --- a/backend/app/utils/zep_paging.py +++ b/backend/app/utils/zep_paging.py @@ -107,9 +107,9 @@ def fetch_all_edges( client: Zep, graph_id: str, page_size: int = _DEFAULT_PAGE_SIZE, - max_items: int = _MAX_EDGES, max_retries: int = _DEFAULT_MAX_RETRIES, retry_delay: float = _DEFAULT_RETRY_DELAY, + max_items: int = _MAX_EDGES, ) -> list[Any]: """分页获取图谱所有边,最多返回 max_items 条(默认 5000)。每页请求自带重试。""" all_edges: list[Any] = [] diff --git a/backend/tests/test_zep_edge_paging.py b/backend/tests/test_zep_edge_paging.py new file mode 100644 index 00000000..d0e92ff4 --- /dev/null +++ b/backend/tests/test_zep_edge_paging.py @@ -0,0 +1,44 @@ +from types import SimpleNamespace + +from app.utils import zep_paging + + +def _client(): + edge_api = SimpleNamespace(get_by_graph_id=lambda *args, **kwargs: []) + return SimpleNamespace(graph=SimpleNamespace(edge=edge_api)) + + +def test_edge_cap_stops_pagination_at_requested_limit(monkeypatch): + pages = [ + [SimpleNamespace(uuid_="e1"), SimpleNamespace(uuid_="e2")], + [SimpleNamespace(uuid_="e3"), SimpleNamespace(uuid_="e4")], + ] + calls = [] + + def fake_fetch(*args, **kwargs): + calls.append(kwargs) + return pages[len(calls) - 1] + + monkeypatch.setattr(zep_paging, "_fetch_page_with_retry", fake_fetch) + + result = zep_paging.fetch_all_edges(_client(), "graph", page_size=2, max_items=3) + + assert [edge.uuid_ for edge in result] == ["e1", "e2", "e3"] + assert len(calls) == 2 + assert calls[1]["uuid_cursor"] == "e2" + + +def test_existing_positional_retry_arguments_keep_their_meaning(monkeypatch): + observed = {} + + def fake_fetch(*args, **kwargs): + observed.update(kwargs) + return [] + + monkeypatch.setattr(zep_paging, "_fetch_page_with_retry", fake_fetch) + + zep_paging.fetch_all_edges(_client(), "graph", 25, 7, 0.25) + + assert observed["limit"] == 25 + assert observed["max_retries"] == 7 + assert observed["retry_delay"] == 0.25