|
| 1 | +# Zep Google ADK (Go) Integration |
| 2 | + |
| 3 | +`zepadk` gives [Google Agent Development Kit (ADK) for Go](https://github.com/google/adk-go) |
| 4 | +agents persistent, cross-session memory backed by [Zep](https://www.getzep.com), |
| 5 | +Zep's temporal Context Graph platform for agent memory. |
| 6 | + |
| 7 | +It plugs into ADK's native extension points — it does **not** wrap or replace the |
| 8 | +ADK runtime: |
| 9 | + |
| 10 | +- **Context injection** via an `llmagent.BeforeModelCallback` that persists each |
| 11 | + new user turn to Zep and injects the user's Context Block into the model |
| 12 | + request. It skips tool-loop continuations so a turn is recorded exactly once. |
| 13 | +- **Assistant persistence** via an `llmagent.AfterModelCallback` that writes the |
| 14 | + assistant's reply back to the same Zep thread, so the user graph captures both |
| 15 | + sides of the conversation. |
| 16 | +- **Memory service** — an ADK `memory.Service` backed by Zep user-graph search, |
| 17 | + attached at the runner and reached by tools through `ToolContext.SearchMemory`. |
| 18 | +- **On-demand recall** — a `functiontool` the model can call to search the user's |
| 19 | + Zep knowledge graph. |
| 20 | + |
| 21 | +## Installation |
| 22 | + |
| 23 | +```bash |
| 24 | +go get github.com/getzep/zep/integrations/adk/go@latest |
| 25 | +``` |
| 26 | + |
| 27 | +```go |
| 28 | +import zepadk "github.com/getzep/zep/integrations/adk/go" |
| 29 | +``` |
| 30 | + |
| 31 | +Requirements: Go 1.25+ (`google.golang.org/adk` v1.4.0 requires Go 1.25), |
| 32 | +`google.golang.org/adk` v1.4.0, `github.com/getzep/zep-go/v3` v3.23.0. |
| 33 | + |
| 34 | +## Quick start |
| 35 | + |
| 36 | +```go |
| 37 | +zep := zepadk.NewClientFromEnv() // nil when ZEP_API_KEY is unset -> no-op |
| 38 | + |
| 39 | +agent, _ := llmagent.New(llmagent.Config{ |
| 40 | + Name: "assistant", |
| 41 | + Model: llm, // a model.LLM, e.g. gemini.NewModel(...) |
| 42 | + BeforeModelCallbacks: []llmagent.BeforeModelCallback{zepadk.NewBeforeModelCallback(zep)}, |
| 43 | + AfterModelCallbacks: []llmagent.AfterModelCallback{zepadk.NewAfterModelCallback(zep)}, |
| 44 | + Tools: []tool.Tool{searchTool}, // from zepadk.NewGraphSearchTool(zep) |
| 45 | +}) |
| 46 | + |
| 47 | +run, _ := runner.New(runner.Config{ |
| 48 | + AppName: "my_app", |
| 49 | + Agent: agent, |
| 50 | + SessionService: sessions, |
| 51 | + MemoryService: zepadk.NewMemoryService(zep), |
| 52 | +}) |
| 53 | +``` |
| 54 | + |
| 55 | +See [`examples/main.go`](examples/main.go) for a complete, runnable wiring of the |
| 56 | +agent, runner, session service, and Zep user/thread provisioning. |
| 57 | + |
| 58 | +## How it works |
| 59 | + |
| 60 | +The integration contract maps ADK identifiers to Zep identifiers: |
| 61 | + |
| 62 | +| ADK | Zep | |
| 63 | +|-----|-----| |
| 64 | +| session ID | thread ID | |
| 65 | +| user ID | user ID (user graph) | |
| 66 | + |
| 67 | +Provision the Zep user and thread out of band before the first turn with |
| 68 | +`EnsureUser` and `EnsureThread` (both idempotent). Then, on a genuinely new user |
| 69 | +turn, the callback returned by `NewBeforeModelCallback`: |
| 70 | + |
| 71 | +1. Reads the user's latest message from the ADK callback context. |
| 72 | +2. Truncates it to Zep's 4,096-character per-message limit if needed (logging a |
| 73 | + lengths-only warning — message content is never dropped or logged). |
| 74 | +3. Persists it to the user's Zep thread, requesting the Context Block in the same |
| 75 | + round-trip (`Thread.AddMessages` with `ReturnContext=true`). |
| 76 | +4. Injects the returned Context Block into `req.Config.SystemInstruction`. |
| 77 | + |
| 78 | +During a tool loop ADK re-invokes the before-model callback after each tool |
| 79 | +result. On those continuations the latest content in `req.Contents` is a function |
| 80 | +response rather than new user input, so the callback returns early without |
| 81 | +re-persisting the message or re-injecting the Context Block — a turn that calls |
| 82 | +`search_memory` is recorded in Zep exactly once. |
| 83 | + |
| 84 | +`NewAfterModelCallback` complements it: after the model replies, it persists the |
| 85 | +assistant's text to the same thread (as an `assistant` message). It skips model |
| 86 | +errors, partial streaming chunks, and function-call-only responses (tool-loop |
| 87 | +steps), so only genuine replies are recorded. |
| 88 | + |
| 89 | +The public surface lives in: |
| 90 | + |
| 91 | +- [`zepadk.go`](zepadk.go) — `NewBeforeModelCallback`, `NewAfterModelCallback`, |
| 92 | + `EnsureUser`, `EnsureThread`, and the helpers `InjectSystemInstruction`, |
| 93 | + `LastUserText`, `AssistantText`, `IsToolLoopContinuation`. |
| 94 | +- [`memory.go`](memory.go) — `NewMemoryService` (the ADK `memory.Service` over |
| 95 | + Zep `Graph.Search`). |
| 96 | +- [`tool.go`](tool.go) — `NewGraphSearchTool` (the on-demand `search_memory` tool). |
| 97 | +- [`search.go`](search.go) — scope-aware mapping of Zep search results. |
| 98 | +- [`client.go`](client.go) — `NewClient` / `NewClientFromEnv`. |
| 99 | + |
| 100 | +### Search scopes |
| 101 | + |
| 102 | +The memory service and search tool map every supported Zep search scope into |
| 103 | +results — earlier versions read only `edges` and silently returned nothing for |
| 104 | +other scopes: |
| 105 | + |
| 106 | +| Scope | Result | |
| 107 | +|-------|--------| |
| 108 | +| `edges` (default) | facts | |
| 109 | +| `nodes` | entity summaries (`name: summary`) | |
| 110 | +| `episodes` | message/data content | |
| 111 | +| `observations` | derived memories | |
| 112 | +| `auto` | the pre-materialized Context Block | |
| 113 | + |
| 114 | +An unsupported scope (e.g. `thread_summaries`) is rejected loudly: the service or |
| 115 | +tool logs an error and returns no results rather than silently swallowing them. |
| 116 | + |
| 117 | +## Configuration |
| 118 | + |
| 119 | +Each constructor accepts functional options: |
| 120 | + |
| 121 | +| Constructor | Options | |
| 122 | +|-------------|---------| |
| 123 | +| `NewBeforeModelCallback` | `WithContextPrefix`, `WithUserMessageName`, `WithLogger` | |
| 124 | +| `NewAfterModelCallback` | `WithAssistantMessageName`, `WithAfterLogger` | |
| 125 | +| `NewMemoryService` | `WithSearchScope`, `WithSearchLimit`, `WithMemoryLogger` | |
| 126 | +| `NewGraphSearchTool` | `WithToolName`, `WithToolDescription`, `WithGraphID`, `WithToolSearchScope`, `WithToolSearchLimit`, `WithToolLogger` | |
| 127 | + |
| 128 | +`WithGraphID` scopes the search tool to a standalone graph instead of the calling |
| 129 | +user's graph (`UserID` and `GraphID` are mutually exclusive in Zep). |
| 130 | + |
| 131 | +## Error handling |
| 132 | + |
| 133 | +A Zep failure never crashes the host agent: |
| 134 | + |
| 135 | +- A `nil` client (for example when `ZEP_API_KEY` is unset) makes the callback, |
| 136 | + memory service, and tool safe no-ops. |
| 137 | +- Transient Zep errors are logged via the configured `slog.Logger` and swallowed; |
| 138 | + the callback proceeds to the model without injected memory, and the memory |
| 139 | + service and tool return empty results. |
| 140 | + |
| 141 | +## Notes |
| 142 | + |
| 143 | +- **Ingestion is asynchronous.** A message added during a turn is not guaranteed |
| 144 | + to be retrievable within that same turn; the returned Context Block reflects |
| 145 | + prior turns. Design for eventual availability. |
| 146 | +- **Reuse one client** across the lifetime of the process. |
| 147 | +- Pass real user names (and ideally last name + email) to `EnsureUser` so Zep |
| 148 | + resolves the user's identity in the graph. |
| 149 | + |
| 150 | +## Development |
| 151 | + |
| 152 | +```bash |
| 153 | +make all # tidy + fmt-check + vet + lint + test |
| 154 | +make test # go test ./... |
| 155 | +``` |
| 156 | + |
| 157 | +`make lint` runs `golangci-lint` if it is installed. |
| 158 | + |
| 159 | +## Support |
| 160 | + |
| 161 | +- [Zep documentation](https://help.getzep.com) |
| 162 | +- [Google ADK for Go](https://github.com/google/adk-go) |
| 163 | +- [GitHub issues](https://github.com/getzep/zep/issues) |
| 164 | + |
| 165 | +## License |
| 166 | + |
| 167 | +Apache 2.0 — see the repository [LICENSE](../../../LICENSE). |
0 commit comments