Skip to content

Commit cd57d69

Browse files
Add 'auto' scope to ADK graph search tool (#519)
* Add 'auto' scope to ADK graph search tool Add 'auto' as an allowed scope value for ZepGraphSearchTool, letting Zep decide the best mix of edges, nodes, and episodes to return. Update scope descriptions to be more precise and add integration test coverage for the new auto scope. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix formatting in test_integration.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove ZepContextTool from auto-scope test to avoid false positive The auto-scope test (Step 11) included ZepContextTool which injects memory into every request, meaning the test could pass without the graph search tool ever being invoked. Remove it so the model must call ZepGraphSearchTool(scope="auto") to answer the question. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix mypy no-any-return in auto scope format handler Annotate the getattr result so mypy knows context is str | None. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump zep-adk to 0.2.0 for release Add auto scope, improved parameter descriptions, and None-pinning support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 25a3435 commit cd57d69

4 files changed

Lines changed: 100 additions & 7 deletions

File tree

integrations/python/zep_adk/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 0.2.0 (2026-04-07)
4+
5+
### Added
6+
7+
- `auto` scope for `ZepGraphSearchTool` -- lets Zep decide the best mix of edges, nodes, and episodes to return.
8+
- Support for pinning optional parameters to `None` to hide them from the model schema without passing them to the SDK.
9+
10+
### Changed
11+
12+
- Improved `scope` parameter descriptions to be more precise about what each scope searches.
13+
314
## 0.1.0 (2026-03-23)
415

516
### Added

integrations/python/zep_adk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "zep-adk"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
description = "Google ADK integration for Zep"
55
readme = "README.md"
66
requires-python = ">=3.10"

integrations/python/zep_adk/src/zep_adk/graph_search_tool.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@
4949
"type": "STRING",
5050
"description": (
5151
"What to search for: 'edges' for facts and relationships, "
52-
"'nodes' for entity summaries, 'episodes' for raw messages."
52+
"'nodes' for entities and their summaries, "
53+
"'episodes' for raw text data (unstructured text, messages, or JSON), "
54+
"'auto' to let Zep decide the best mix of results."
5355
),
54-
"enum": ["edges", "nodes", "episodes"],
56+
"enum": ["edges", "nodes", "episodes", "auto"],
5557
"default": "edges",
5658
},
5759
"reranker": {
@@ -110,6 +112,10 @@ class ZepGraphSearchTool(BaseTool):
110112
to the given value. Parameters not pinned are exposed in the model's tool
111113
schema with sensible defaults.
112114
115+
Pinning an optional parameter to ``None`` hides it from the model schema
116+
without passing it to the Zep SDK -- useful for suppressing irrelevant
117+
parameters (e.g. ``mmr_lambda=None`` when the reranker is not ``mmr``).
118+
113119
Args:
114120
zep_client: An initialised ``AsyncZep`` client.
115121
graph_id: Optional fixed graph ID for shared-graph search. When set,
@@ -160,6 +166,14 @@ def __init__(
160166
raise ValueError(
161167
f"Unknown pinned parameters: {unknown}. Allowed: {sorted(allowed_pinned)}"
162168
)
169+
# Pinning to None means "hide from model but don't send to SDK".
170+
# This is only valid for optional parameters.
171+
for k, v in pinned.items():
172+
if v is None and _SEARCH_PARAMS.get(k, {}).get("required"):
173+
raise ValueError(
174+
f"Cannot pin required parameter '{k}' to None. "
175+
"Only optional parameters can be hidden with None."
176+
)
163177

164178
# Store pinned search params
165179
self._pinned: dict[str, Any] = dict(pinned)
@@ -229,9 +243,12 @@ async def run_async(self, *, args: dict[str, Any], tool_context: ToolContext) ->
229243
search_kwargs["user_id"] = user_id
230244

231245
# --- Merge params: pinned > model-provided > default ----------
246+
# A param pinned to None is hidden from the model and omitted from
247+
# the SDK call (used to suppress optional params from the schema).
232248
for param_name, param_def in _SEARCH_PARAMS.items():
233249
if param_name in self._pinned:
234-
search_kwargs[param_name] = self._pinned[param_name]
250+
if self._pinned[param_name] is not None:
251+
search_kwargs[param_name] = self._pinned[param_name]
235252
elif param_name in args:
236253
search_kwargs[param_name] = args[param_name]
237254
elif "default" in param_def:
@@ -279,7 +296,13 @@ def _format_results(result: Any, scope: str) -> str:
279296
"""Format search results as readable text for the model."""
280297
parts: list[str] = []
281298

282-
if scope == "edges" and result.edges:
299+
if scope == "auto":
300+
# Auto scope returns a pre-formatted context string in result.context
301+
# rather than populating the individual edges/nodes/episodes lists.
302+
context: str | None = getattr(result, "context", None)
303+
if context and context.strip():
304+
return context.strip()
305+
elif scope == "edges" and result.edges:
283306
for edge in result.edges:
284307
if edge.fact:
285308
parts.append(f"- {edge.fact}")

integrations/python/zep_adk/tests/test_integration.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
SESSION_2_ID = f"adk-integ-s2-{_suffix}"
6161
SESSION_3_ID = f"adk-integ-s3-{_suffix}"
6262
SESSION_4_ID = f"adk-integ-s4-{_suffix}"
63+
SESSION_5_ID = f"adk-integ-s5-{_suffix}"
6364
THREAD_1_ID = f"adk-integ-t1-{_suffix}"
6465
THREAD_2_ID = f"adk-integ-t2-{_suffix}"
6566
THREAD_3_ID = f"adk-integ-t3-{_suffix}"
@@ -442,10 +443,68 @@ async def on_user_created(zep: AsyncZep, user_id: str) -> None:
442443
passed = False
443444

444445
# ==================================================================
445-
# Step 11: Tool-call message persistence — verify only final
446+
# Step 11: Session 5 — ZepGraphSearchTool with scope="auto"
447+
# ==================================================================
448+
print("\n[Step 11] Session 5: testing ZepGraphSearchTool with scope='auto'...")
449+
450+
# Build a separate agent with ONLY the graph search tool (no
451+
# ZepContextTool) so the model has no pre-injected memory and must
452+
# call the search tool to answer questions about the user.
453+
auto_agent = Agent(
454+
name="zep_integ_auto_scope_agent",
455+
model="gemini-2.5-flash",
456+
description="A test agent with auto-scope graph search.",
457+
instruction=(
458+
"You have no prior knowledge about the user. You MUST use "
459+
"the search_user_memory tool to answer any question about them."
460+
),
461+
tools=[
462+
ZepGraphSearchTool(
463+
zep_client=zep_client,
464+
name="search_user_memory",
465+
description="Search the user's knowledge graph.",
466+
scope="auto",
467+
),
468+
],
469+
)
470+
471+
auto_runner = Runner(
472+
agent=auto_agent,
473+
app_name=APP_NAME,
474+
session_service=session_service,
475+
)
476+
477+
await session_service.create_session(
478+
app_name=APP_NAME,
479+
user_id=USER_ID,
480+
session_id=SESSION_5_ID,
481+
state={"zep_user_id": USER_ID},
482+
)
483+
484+
auto_search_message = (
485+
"Use the search_user_memory tool to search for what you know "
486+
"about where I live. Tell me exactly what the search returns."
487+
)
488+
print(f" User: {auto_search_message}")
489+
response5 = await send_message(auto_runner, SESSION_5_ID, USER_ID, auto_search_message)
490+
print(f" Agent: {response5}\n")
491+
492+
response5_lower = response5.lower()
493+
auto_keywords = ["portland", "oregon"]
494+
found_auto_kw = [kw for kw in auto_keywords if kw in response5_lower]
495+
print(f" Auto-scope keywords found: {found_auto_kw}")
496+
497+
passed &= check(
498+
"Auto-scope graph search returned location facts",
499+
len(found_auto_kw) > 0,
500+
f"found={found_auto_kw}, expected_one_of={auto_keywords}",
501+
)
502+
503+
# ==================================================================
504+
# Step 12: Tool-call message persistence — verify only final
446505
# assistant message is persisted (not intermediate "thoughts")
447506
# ==================================================================
448-
print("\n[Step 11] Session 4: tool-call persistence test (get_current_weather)...")
507+
print("\n[Step 12] Session 4: tool-call persistence test (get_current_weather)...")
449508

450509
await session_service.create_session(
451510
app_name=APP_NAME,

0 commit comments

Comments
 (0)