Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/actions/test-go/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ runs:
run: go test ./...

- name: Lint
uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6
# v8 installs golangci-lint v2.x, which supports go >=1.25 modules
# (the v6/golangci-lint v1.64.8 default refuses go 1.25 go.mod directives).
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with:
version: latest
working-directory: ${{ steps.paths.outputs.dir }}
43 changes: 43 additions & 0 deletions integrations/adk/go/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Changelog

## 0.1.0 (2026-06-16)

### Added

- `NewBeforeModelCallback` — an ADK `llmagent.BeforeModelCallback` that persists
each new user turn to a Zep thread and injects the returned Context Block into
the model request's system instruction (`Thread.AddMessages` with
`ReturnContext=true`). It detects tool-loop continuations (a function response
as the latest content in `req.Contents`) and skips re-persisting/re-injecting,
so a turn that calls `search_memory` is recorded exactly once. Oversize
messages are truncated to Zep's 4,096-character limit with a lengths-only
warning (content is never dropped or logged). Configurable via
`WithContextPrefix`, `WithUserMessageName`, and `WithLogger`.
- `NewAfterModelCallback` — an ADK `llmagent.AfterModelCallback` that persists the
assistant's text reply back to the same Zep thread, so the user graph captures
both halves of the conversation. Skips model errors, partial streaming chunks,
and function-call-only responses (tool-loop steps). Configurable via
`WithAssistantMessageName` and `WithAfterLogger`.
- `NewMemoryService` — an ADK `memory.Service` backed by Zep user-graph search,
attachable at the runner via `runner.Config.MemoryService`. Maps every
supported search scope into results (edges → facts, nodes → entity summaries,
episodes → message content, observations → derived memories, auto → the Context
Block) and rejects unsupported scopes loudly. Configurable via
`WithSearchScope`, `WithSearchLimit`, and `WithMemoryLogger`.
- `NewGraphSearchTool` — a `functiontool` (`search_memory`) the model can call to
search the user's Zep knowledge graph on demand, with optional standalone-graph
scoping via `WithGraphID`. Maps the same set of search scopes as the memory
service.
- `EnsureUser` / `EnsureThread` — idempotent helpers to provision the Zep user
and thread keyed on the ADK user ID and session ID.
- `NewClient` / `NewClientFromEnv` — Zep client constructors; `NewClientFromEnv`
returns `nil` when `ZEP_API_KEY` is unset so the integration degrades to a
no-op.
- `InjectSystemInstruction`, `LastUserText`, `AssistantText`,
`IsToolLoopContinuation` — exported helpers for building custom callbacks.
- Graceful error handling throughout: a `nil` client and transient Zep errors
never crash the host agent.
- Table-based unit tests (no network, via an internal client seam) and a runnable
example wiring an `llmagent` and runner.

Targets `google.golang.org/adk` v1.4.0 and `github.com/getzep/zep-go/v3` v3.23.0.
62 changes: 62 additions & 0 deletions integrations/adk/go/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Makefile for the Zep Google ADK (Go) integration

.PHONY: help tidy fmt fmt-check vet lint test test-race build example all ci clean

help:
@echo "Available commands:"
@echo " tidy - go mod tidy"
@echo " fmt - Format code with gofmt"
@echo " fmt-check - Verify code is gofmt-clean (fails if not)"
@echo " vet - Run go vet"
@echo " lint - Run golangci-lint (if installed)"
@echo " test - Run tests"
@echo " test-race - Run tests with the race detector"
@echo " build - Build all packages"
@echo " example - Run the example (needs ZEP_API_KEY + GOOGLE_API_KEY)"
@echo " all - tidy + fmt-check + vet + lint + test"
@echo " ci - vet + lint + test (no auto-formatting)"
@echo " clean - go clean"

tidy:
go mod tidy

fmt:
gofmt -w .

fmt-check:
@unformatted=$$(gofmt -l .); \
if [ -n "$$unformatted" ]; then \
echo "These files are not gofmt-clean:"; echo "$$unformatted"; exit 1; \
fi

vet:
go vet ./...

# Runs golangci-lint when available; skips with a notice otherwise.
lint:
@if command -v golangci-lint >/dev/null 2>&1; then \
golangci-lint run ./...; \
else \
echo "golangci-lint not installed; skipping (install: https://golangci-lint.run)"; \
fi

test:
go test ./...

test-race:
go test -race ./...

build:
go build ./...

example:
go run ./examples

all: tidy fmt-check vet lint test
@echo "All checks passed!"

ci: vet lint test
@echo "CI checks passed!"

clean:
go clean ./...
167 changes: 167 additions & 0 deletions integrations/adk/go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Zep Google ADK (Go) Integration

`zepadk` gives [Google Agent Development Kit (ADK) for Go](https://github.com/google/adk-go)
agents persistent, cross-session memory backed by [Zep](https://www.getzep.com),
Zep's temporal Context Graph platform for agent memory.

It plugs into ADK's native extension points — it does **not** wrap or replace the
ADK runtime:

- **Context injection** via an `llmagent.BeforeModelCallback` that persists each
new user turn to Zep and injects the user's Context Block into the model
request. It skips tool-loop continuations so a turn is recorded exactly once.
- **Assistant persistence** via an `llmagent.AfterModelCallback` that writes the
assistant's reply back to the same Zep thread, so the user graph captures both
sides of the conversation.
- **Memory service** — an ADK `memory.Service` backed by Zep user-graph search,
attached at the runner and reached by tools through `ToolContext.SearchMemory`.
- **On-demand recall** — a `functiontool` the model can call to search the user's
Zep knowledge graph.

## Installation

```bash
go get github.com/getzep/zep/integrations/adk/go@latest
```

```go
import zepadk "github.com/getzep/zep/integrations/adk/go"
```

Requirements: Go 1.25+ (`google.golang.org/adk` v1.4.0 requires Go 1.25),
`google.golang.org/adk` v1.4.0, `github.com/getzep/zep-go/v3` v3.23.0.

## Quick start

```go
zep := zepadk.NewClientFromEnv() // nil when ZEP_API_KEY is unset -> no-op

agent, _ := llmagent.New(llmagent.Config{
Name: "assistant",
Model: llm, // a model.LLM, e.g. gemini.NewModel(...)
BeforeModelCallbacks: []llmagent.BeforeModelCallback{zepadk.NewBeforeModelCallback(zep)},
AfterModelCallbacks: []llmagent.AfterModelCallback{zepadk.NewAfterModelCallback(zep)},
Tools: []tool.Tool{searchTool}, // from zepadk.NewGraphSearchTool(zep)
})

run, _ := runner.New(runner.Config{
AppName: "my_app",
Agent: agent,
SessionService: sessions,
MemoryService: zepadk.NewMemoryService(zep),
})
```

See [`examples/main.go`](examples/main.go) for a complete, runnable wiring of the
agent, runner, session service, and Zep user/thread provisioning.

## How it works

The integration contract maps ADK identifiers to Zep identifiers:

| ADK | Zep |
|-----|-----|
| session ID | thread ID |
| user ID | user ID (user graph) |

Provision the Zep user and thread out of band before the first turn with
`EnsureUser` and `EnsureThread` (both idempotent). Then, on a genuinely new user
turn, the callback returned by `NewBeforeModelCallback`:

1. Reads the user's latest message from the ADK callback context.
2. Truncates it to Zep's 4,096-character per-message limit if needed (logging a
lengths-only warning — message content is never dropped or logged).
3. Persists it to the user's Zep thread, requesting the Context Block in the same
round-trip (`Thread.AddMessages` with `ReturnContext=true`).
4. Injects the returned Context Block into `req.Config.SystemInstruction`.

During a tool loop ADK re-invokes the before-model callback after each tool
result. On those continuations the latest content in `req.Contents` is a function
response rather than new user input, so the callback returns early without
re-persisting the message or re-injecting the Context Block — a turn that calls
`search_memory` is recorded in Zep exactly once.

`NewAfterModelCallback` complements it: after the model replies, it persists the
assistant's text to the same thread (as an `assistant` message). It skips model
errors, partial streaming chunks, and function-call-only responses (tool-loop
steps), so only genuine replies are recorded.

The public surface lives in:

- [`zepadk.go`](zepadk.go) — `NewBeforeModelCallback`, `NewAfterModelCallback`,
`EnsureUser`, `EnsureThread`, and the helpers `InjectSystemInstruction`,
`LastUserText`, `AssistantText`, `IsToolLoopContinuation`.
- [`memory.go`](memory.go) — `NewMemoryService` (the ADK `memory.Service` over
Zep `Graph.Search`).
- [`tool.go`](tool.go) — `NewGraphSearchTool` (the on-demand `search_memory` tool).
- [`search.go`](search.go) — scope-aware mapping of Zep search results.
- [`client.go`](client.go) — `NewClient` / `NewClientFromEnv`.

### Search scopes

The memory service and search tool map every supported Zep search scope into
results — earlier versions read only `edges` and silently returned nothing for
other scopes:

| Scope | Result |
|-------|--------|
| `edges` (default) | facts |
| `nodes` | entity summaries (`name: summary`) |
| `episodes` | message/data content |
| `observations` | derived memories |
| `auto` | the pre-materialized Context Block |

An unsupported scope (e.g. `thread_summaries`) is rejected loudly: the service or
tool logs an error and returns no results rather than silently swallowing them.

## Configuration

Each constructor accepts functional options:

| Constructor | Options |
|-------------|---------|
| `NewBeforeModelCallback` | `WithContextPrefix`, `WithUserMessageName`, `WithLogger` |
| `NewAfterModelCallback` | `WithAssistantMessageName`, `WithAfterLogger` |
| `NewMemoryService` | `WithSearchScope`, `WithSearchLimit`, `WithMemoryLogger` |
| `NewGraphSearchTool` | `WithToolName`, `WithToolDescription`, `WithGraphID`, `WithToolSearchScope`, `WithToolSearchLimit`, `WithToolLogger` |

`WithGraphID` scopes the search tool to a standalone graph instead of the calling
user's graph (`UserID` and `GraphID` are mutually exclusive in Zep).

## Error handling

A Zep failure never crashes the host agent:

- A `nil` client (for example when `ZEP_API_KEY` is unset) makes the callback,
memory service, and tool safe no-ops.
- Transient Zep errors are logged via the configured `slog.Logger` and swallowed;
the callback proceeds to the model without injected memory, and the memory
service and tool return empty results.

## Notes

- **Ingestion is asynchronous.** A message added during a turn is not guaranteed
to be retrievable within that same turn; the returned Context Block reflects
prior turns. Design for eventual availability.
- **Reuse one client** across the lifetime of the process.
- Pass real user names (and ideally last name + email) to `EnsureUser` so Zep
resolves the user's identity in the graph.

## Development

```bash
make all # tidy + fmt-check + vet + lint + test
make test # go test ./...
```

`make lint` runs `golangci-lint` if it is installed.

## Support

- [Zep documentation](https://help.getzep.com)
- [Google ADK for Go](https://github.com/google/adk-go)
- [GitHub issues](https://github.com/getzep/zep/issues)

## License

Apache 2.0 — see the repository [LICENSE](../../../LICENSE).
98 changes: 98 additions & 0 deletions integrations/adk/go/SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Setup Guide

This guide takes you from a fresh checkout to running the example agent with
Zep memory.

## 1. Sign up for Zep and create an API key

1. Go to [https://www.getzep.com](https://www.getzep.com) and create an account.
2. Open the [Zep dashboard](https://app.getzep.com) and select (or create) a project.
3. In the project settings, go to **API Keys** and create a new key.
4. Copy the key — you will set it as `ZEP_API_KEY` below.

Zep is a paid product; see [getzep.com](https://www.getzep.com) for plan details.

## 2. Get a Google API key (for the example)

The integration is model-agnostic, but the bundled example drives the agent with
Google's Gemini models — ADK for Go's native model provider. Create a key in
[Google AI Studio](https://aistudio.google.com/apikey) and copy it for
`GOOGLE_API_KEY`.

## 3. Install

Add the module to your project:

```bash
go get github.com/getzep/zep/integrations/adk/go@latest
```

Import it as `zepadk`:

```go
import zepadk "github.com/getzep/zep/integrations/adk/go"
```

To work from the repository instead:

```bash
git clone https://github.com/getzep/zep.git
cd zep/integrations/adk/go
go mod download
```

Requirements: Go 1.25+ (`google.golang.org/adk` v1.4.0 requires Go 1.25),
`google.golang.org/adk` v1.4.0, `github.com/getzep/zep-go/v3` v3.23.0.

## 4. Configure environment variables

```bash
export ZEP_API_KEY="your-zep-api-key"
export GOOGLE_API_KEY="your-google-api-key"
```

If `ZEP_API_KEY` is unset, the integration disables itself (the Zep client is
`nil` and every Zep call becomes a no-op), so the agent still runs — useful for
confirming the wiring without a Zep account. If `GOOGLE_API_KEY` is unset, the
example prints the configured wiring and exits before calling the model.

## 5. Run the example

```bash
go run ./examples
```

The example:

1. Creates (idempotently) a Zep user and a thread keyed on the ADK session ID.
2. Builds an `llmagent` whose `BeforeModelCallback` persists each new user turn
to Zep and injects the user's Context Block into the prompt, and whose
`AfterModelCallback` persists the assistant's reply back to the same thread.
3. Registers a `search_memory` tool the model can call on demand and attaches a
Zep-backed `memory.Service` at the runner.
4. Sends two turns and prints the agent's replies.

Because Zep ingestion is asynchronous, memory recall improves across turns and
sessions rather than instantly within the first turn.

## 6. Run the tests

The tests are mock/table-based and make no network calls, so no API keys are
required:

```bash
make test # or: go test ./...
```

## Troubleshooting

- **`go get` cannot find the module** — the module path is
`github.com/getzep/zep/integrations/adk/go`; Go modules under this subpath are
tagged `integrations/adk/go/vX.Y.Z`.
- **Recall returns nothing** — Zep ingestion is asynchronous; a just-added fact
is not instantly retrievable. Recall improves on subsequent turns and across
sessions for the same user.
- **Authentication errors** — confirm `ZEP_API_KEY` is set in the same shell and
belongs to the intended project.
- **Agent runs but has no memory** — verify `ZEP_API_KEY` is exported; an unset
key makes the Zep client `nil` and all Zep calls no-ops by design.
Loading
Loading