Skip to content

Add zep-pydantic-ai integration#523

Open
danielchalef wants to merge 3 commits into
mainfrom
feat/zep-pydantic-ai
Open

Add zep-pydantic-ai integration#523
danielchalef wants to merge 3 commits into
mainfrom
feat/zep-pydantic-ai

Conversation

@danielchalef

Copy link
Copy Markdown
Member

Adds the zep-pydantic-ai package (integrations/pydantic-ai/python) — Zep memory for Pydantic AI.

Hook (verified against installed pydantic-ai 1.107.0)

Built on the current API capabilities=[ProcessHistory(zep_history_processor)] (not the deprecated history_processors= kwarg). The async processor persists the latest user turn via thread.add_messages(return_context=True) and prepends Zep's Context Block; it dedupes on the latest user content to avoid re-persisting on the once-per-model-request re-invocation in tool-calling runs. ZepDeps (via deps_type) carries the client/user/thread; create_zep_search_tool exposes graph.search; persist_run writes result.new_messages().

Deps

pydantic-ai>=1.107,<2, zep-cloud>=3.23.0. Python ≥3.11.

Ships

README, SETUP.md (Zep signup), example, 57 mock tests, Makefile, CHANGELOG.

Validation

ruff + ruff format + mypy + pytest (57 passed); live example smoke test passed. Approach in integrations/SPIKE_FINDINGS.md.

🤖 Generated with Claude Code

@danielchalef danielchalef force-pushed the feat/zep-pydantic-ai branch from 4195502 to 8044f5d Compare June 17, 2026 19:22
danielchalef and others added 2 commits June 18, 2026 11:01
Zep memory for Pydantic AI (Python) via the current capabilities API.

- capabilities=[ProcessHistory(zep_history_processor)] (async) persists the
  latest user turn and prepends Zep's Context Block; guards the once-per-model-
  request re-invocation by deduping on the latest user content.
- ZepDeps (deps_type) for per-run client/user/thread; create_zep_search_tool
  over graph.search; persist_run helper for result.new_messages().
- Depends on pydantic-ai>=1.107,<2 and zep-cloud>=3.23.0. Python >=3.11.
- README, SETUP, example, 57 mock tests, Makefile, CHANGELOG. CI filter added.

Verified: ruff + ruff format + mypy + pytest (57 passed); live example smoke
test passed. See integrations/SPIKE_FINDINGS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in zep-pydantic-ai

- Set MAX_MESSAGE_CHARS to 4000 (Zep rejects messages >4096) and truncate the
  user turn on the history-processor hot path before add_messages; warn with
  lengths only (no content/PII) via a shared truncate_message_content helper.
- Re-scope turn dedupe to the RunContext.run_id (falling back to user/thread)
  so identical consecutive runs each persist; replace the unbounded module-global
  cache with a bounded, lock-guarded LRU.
- graph.search: clamp limit to <=50 and omit reranker when scope="auto"
  (Zep rejects node_distance/episode_mentions there).
- Add 'observations' and 'thread_summaries' to the Scope literal (zep-cloud 3.23).
- Add regression tests for each fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danielchalef danielchalef force-pushed the feat/zep-pydantic-ai branch from b175f1d to bb12c33 Compare June 18, 2026 18:01
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code review

1 issue found.


Bug: observations and thread_summaries scopes silently return "No results found."

File: integrations/pydantic-ai/python/src/zep_pydantic_ai/search.py, lines 158–181

The Scope type and create_zep_search_tool docstring both advertise "observations" and "thread_summaries" as valid scopes, and the search call is issued with those scopes. However, _format_results only has rendering branches for auto, edges, nodes, and episodes — for the two missing scopes parts stays empty and the function always returns "No results found." regardless of actual API results.

def _format_results(results: GraphSearchResults, scope: Scope) -> str:
"""Render Zep search results as readable text for the model."""
if scope == "auto":
context = getattr(results, "context", None)
if context and str(context).strip():
return str(context).strip()
return "No results found."
parts: list[str] = []
if scope == "edges" and results.edges:
parts = [f"- {edge.fact}" for edge in results.edges if edge.fact]
elif scope == "nodes" and results.nodes:
for node in results.nodes:
node_name = getattr(node, "name", None) or "Entity"
summary = getattr(node, "summary", None)
if summary:
parts.append(f"- {node_name}: {summary}")
else:
parts.append(f"- {node_name}")
elif scope == "episodes" and results.episodes:
parts = [f"- {ep.content}" for ep in results.episodes if ep.content]
return "\n".join(parts) if parts else "No results found."

Fix: Either add rendering branches for the missing scopes, or remove "observations" and "thread_summaries" from the Scope literal if they are not yet intended to be user-selectable.

_format_results advertised the "observations" and "thread_summaries"
scopes (in the Scope literal and docstring) but had no rendering branch
for them, so those scopes always returned "No results found." regardless
of API results.

Add rendering branches using the correct zep_cloud types:
- observations -> DerivedNode.name (+ optional summary)
- thread_summaries -> GraphitiSagaNode.summary (falling back to name)

Add unit tests asserting both scopes render their items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant