> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://help.getzep.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://help.getzep.com/_mcp/server.

# Build Tools for an Agent

> Select tools from the agent task, pin the parameters that the application controls, write tool descriptions, and return compact results.

An agent retrieves context from Zep through tools. The tool set decides which retrievals the agent can make, and the tool descriptions decide when the agent makes them. This page shows the tools of the [reference agent](https://github.com/getzep/zep/tree/main/examples/python/agent-with-zep). Read [Build an Agent with Zep](/build-an-agent-with-zep) first for the agent loop.

## Choose tools for the task

Select tools from what the agent must do. The ontology tells you what the graph contains. The task tells you which retrievals the agent repeats.

1. Start with the functional tools. They cover search, complete lists, graph traversal, and full details.
2. Run the agent on your gold questions, and read the tool calls.
3. Add a domain-specific tool when the agent often makes the same sequence of generic calls.
4. Add a domain-specific tool when a business term maps to a fixed retrieval recipe.

The reference agent has four functional tools and two domain-specific tools:

| Tool               | Type            | Zep call                                                   | Result                              |
| ------------------ | --------------- | ---------------------------------------------------------- | ----------------------------------- |
| `search_context`   | Functional      | `graph.search`                                             | Ranked sample                       |
| `list_nodes`       | Functional      | `graph.node.get_by_graph_id`                               | Complete, or truncated at the limit |
| `get_neighborhood` | Functional      | `graph.node.get_neighbors`                                 | Complete, or truncated at the limit |
| `get_details`      | Functional      | `graph.node.get`, `graph.episode.get`                      | One item                            |
| `get_employees`    | Domain-specific | Node list and neighbors, with pinned labels and edge types | Complete                            |
| `search_products`  | Domain-specific | Node list and neighbors, with pinned labels and edge types | Complete                            |

In the reference dataset, one question asks which members of Reliability Engineering work on the Aster 410. With functional tools, the agent needs a team lookup, a product lookup, two neighbor calls, and an intersection. `get_employees` does the same retrieval in one call.

## Pin and expose parameters

The application pins the parameters that control identity, security, cost, and result quality. The model controls only the arguments that the task changes.

| Parameter                                        | Owner               | Reason                                                                                   |
| ------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------------------- |
| `graph_id` or `user_id`                          | Application         | The application decides which graph the user can read. The model never selects the graph |
| Security-sensitive filters                       | Application         | Authorization does not come from the model                                               |
| Reranker                                         | Application         | One reranker gives results that you can compare across runs                              |
| Limits and result size                           | Application         | Limits control token use and latency                                                     |
| Query                                            | Model               | The query depends on the plan step                                                       |
| Scope (`edges`, `nodes`, `episodes`)             | Model               | The plan step decides the result type                                                    |
| Entity type and edge type filters                | Model, from an enum | An enum from the ontology prevents labels that do not exist                              |
| Metadata filters, such as report type or product | Model               | The plan step can restrict the search to one source                                      |
| Handles of known nodes                           | Model               | The model selects the nodes for BFS or traversal                                         |

The pinned and exposed parameters for each tool in the reference agent:

| Tool               | Application pins                                                                                                   | Model controls                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `search_context`   | `graph_id`, `reranker="cross_encoder"`, maximum limit of 20                                                        | `query`, `scope`, `node_labels`, `edge_types`, `report_type`, `product`, `near` (handles), `limit` |
| `list_nodes`       | `graph_id`, maximum limit of 49                                                                                    | `label`, `order_by`, `limit`                                                                       |
| `get_neighborhood` | Maximum limit of 30                                                                                                | `handle`, `edge_types`, `direction`, `limit`                                                       |
| `get_details`      | Maximum episode length of 3,000 characters                                                                         | `handle`                                                                                           |
| `get_employees`    | `graph_id`, the `Employee`, `Team`, and `Product` labels, the `MEMBER_OF`, `WORKS_ON`, and `REPORTS_TO` edge types | `team`, `product`                                                                                  |
| `search_products`  | `graph_id`, the `Product` and `RegulatoryFiling` labels, the `FILED_FOR` edge type                                 | `query`, `category`                                                                                |

When a request sets a limit, the list API returns at most 50 nodes in a page. The tool requests one extra node to find out if the list is complete.

## Functional tools

The functional tools map to the Zep graph API. The examples use [Pydantic AI](/pydantic-ai-memory). The same structure works in other agent frameworks. The examples are shorter than the reference agent code. Helper functions such as `format_list` format the results, as the [result shape](#result-shape) section describes.

### `search_context`

`search_context` searches one scope with the `cross_encoder` reranker. The model can add entity type, edge type, and episode metadata filters. In the edge and node scopes, the model can add results from the neighborhood of known nodes. These breadth-first search (BFS) results are merged with the other search results. They do not restrict the search.

```python
@toolset.tool
async def search_context(
    ctx: RunContext[AgentDeps],
    query: str,
    scope: Literal["edges", "nodes", "episodes"] = "edges",
    node_labels: list[EntityLabel] | None = None,
    edge_types: list[EdgeTypeName] | None = None,
    report_type: str | None = None,
    product: str | None = None,
    near: list[str] | None = None,
    limit: int = 10,
) -> str:
    """Search the graph for the items most relevant to a query. Returns a ranked sample, not a complete set.

    Use scope="edges" for facts and relationships, scope="nodes" for entities, and scope="episodes" for the source reports. Use report_type and product to restrict the search to reports with that metadata. Use near with one or more handles, and scope="edges" or scope="nodes", to add results from the graph neighborhood of known nodes. The results can also include matches from outside the neighborhood."""
    meta_filters = [
        EpisodeMetadataFilter(comparison_operator="=", property_name=name, property_value=value)
        for name, value in (("report_type", report_type), ("product", product))
        if value
    ]
    results = await ctx.deps.zep.graph.search(
        graph_id=ctx.deps.graph_id,          # pinned
        query=query,
        scope=scope,
        limit=min(limit, SEARCH_LIMIT_MAX),  # pinned maximum
        reranker="cross_encoder",            # pinned
        bfs_origin_node_uuids=[ctx.deps.registry.resolve(h) for h in near] if near else None,
        search_filters=SearchFilters(
            node_labels=node_labels,
            edge_types=edge_types,
            episode_metadata_filters=MetadataFilterGroup(type="and", filters=meta_filters)
            if meta_filters else None,
        ),
    )
    return format_ranked_sample(ctx.deps, results)
```

### `list_nodes`

`list_nodes` returns every node of one entity type, up to a limit. Use a list tool when the question needs a complete set. A search returns only the most relevant items.

```python
@toolset.tool
async def list_nodes(
    ctx: RunContext[AgentDeps],
    label: EntityLabel,
    order_by: Literal["degree"] | None = None,
    limit: int = LIST_LIMIT_MAX,
) -> str:
    """List the nodes that have one entity type. Returns every match up to the limit, and says if the list is complete.

    Use this when the question needs a complete set, such as all open quality issues or all products. Set order_by="degree" to list the most connected nodes first."""
    page_size = min(limit, LIST_LIMIT_MAX)
    nodes = await ctx.deps.zep.graph.node.get_by_graph_id(
        ctx.deps.graph_id,
        filters=SearchFilters(node_labels=[label]),
        order_by=order_by,
        limit=page_size + 1,  # one extra node shows if the list is complete
    )
    return format_list(ctx.deps, nodes, page_size)
```

### `get_neighborhood`

`get_neighborhood` follows the edges of a known node. The `direction` argument selects the edge direction: `out` for edges from the node, `in` for edges to the node, or `both`.

```python
@toolset.tool
async def get_neighborhood(
    ctx: RunContext[AgentDeps],
    handle: str,
    edge_types: list[EdgeTypeName] | None = None,
    direction: Literal["out", "in", "both"] = "both",
    limit: int = 30,
) -> str:
    """Get the edges and neighbor nodes of one node. Returns every match up to the limit, and says if the list is complete.

    Use this to follow relationships from a node you already have, such as the components of a product, the supplier of a component, or the manager of an employee. Use edge_types to keep only some relationships. Use direction="out" for edges that start at the node, such as the manager of an employee (REPORTS_TO). Use direction="in" for edges that end at the node, such as the members of a team (MEMBER_OF)."""
    page_size = min(limit, NEIGHBOR_LIMIT_MAX)
    neighbors = await ctx.deps.zep.graph.node.get_neighbors(
        ctx.deps.registry.resolve(handle, prefix="n"),
        filters=SearchFilters(edge_types=edge_types) if edge_types else None,
        direction=direction,
        limit=page_size + 1,
    )
    return format_list(ctx.deps, neighbors, page_size)
```

### `get_details`

`get_details` returns the full attributes of a node or the full text of an episode. Search results are short. The agent calls `get_details` when a result is relevant but does not contain enough text to answer from.

```python
@toolset.tool
async def get_details(ctx: RunContext[AgentDeps], handle: str) -> str:
    """Get the full attributes of a node (n handle) or the full text and metadata of an episode (p handle).

    Use this when a search result is relevant but too short to answer from, for example to read a full report."""
    if handle.startswith("p"):
        episode = await ctx.deps.zep.graph.episode.get(ctx.deps.registry.resolve(handle, prefix="p"))
        return format_episode(ctx.deps, episode, max_chars=EPISODE_TEXT_MAX_CHARS)
    node = await ctx.deps.zep.graph.node.get(ctx.deps.registry.resolve(handle, prefix="n"))
    return format_node_details(ctx.deps, node)
```

## Domain-specific tools

A domain-specific tool maps a business task to a fixed retrieval recipe. The application pins the labels, the edge types, and the call sequence. The model gives arguments in domain terms.

### `get_employees`

`get_employees` finds the employees on a team, on a product, or on both. The tool returns the title, the team, and the manager of each employee.

```python
@toolset.tool
async def get_employees(
    ctx: RunContext[AgentDeps],
    team: str | None = None,
    product: str | None = None,
) -> str:
    """Find employees by team, by the product they work on, or both. Returns every match, with each person's title, team, and manager.

    Use this for questions about who works on something or who is on a team. Names must match the graph, such as "Reliability Engineering" or "Aster 410"."""
    if team is None and product is None:
        return "pass team, product, or both."
    groups = []
    if team:
        team_node = await node_by_name(ctx.deps, "Team", team)
        groups.append(await members(ctx.deps, team_node, "MEMBER_OF"))   # edges end at the team
    if product:
        product_node = await node_by_name(ctx.deps, "Product", product)
        groups.append(await members(ctx.deps, product_node, "WORKS_ON"))  # edges end at the product
    employees = intersect(groups)
    return await format_employees(ctx.deps, employees)  # one REPORTS_TO lookup (direction="out") for each employee
```

### `search_products`

`search_products` returns the products with their category, lifecycle status, and each regulatory filing. The tool description includes the domain rule for clearance, so the agent reads each filing status.

```python
@toolset.tool
async def search_products(
    ctx: RunContext[AgentDeps],
    query: str | None = None,
    category: str | None = None,
) -> str:
    """Get products with their category, lifecycle status, and every regulatory filing (region, status, and key dates). Returns every product that matches, and says if the product list is complete.

    Use this for questions about which products are cleared, launched, or in a category. Read each filing status: only a cleared 510(k) or a valid CE certificate clears a product in a region."""
    products = await ctx.deps.zep.graph.node.get_by_graph_id(
        ctx.deps.graph_id, filters=SearchFilters(node_labels=["Product"]), limit=LIST_LIMIT_MAX + 1
    )
    truncated = len(products) > LIST_LIMIT_MAX
    products = filter_products(products[:LIST_LIMIT_MAX], query=query, category=category)
    return await format_products_with_filings(ctx.deps, products, truncated)  # FILED_FOR neighbors for each product
```

The full code is in `agent_with_zep/tools.py` in the reference agent.

## Write tool descriptions

The model selects a tool from its name, its description, and its parameter types. A tool description contains:

* What the tool returns.
* When to use the tool, with an example from the domain.
* If the result is a ranked sample or a complete set.
* The rules for each argument that the model can get wrong, such as a filter value or a handle prefix.

Compare two descriptions for the same tool:

| Description                                                                                                                                                | Problem                                                                           |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `Search the knowledge graph.`                                                                                                                              | The model does not know the scopes, the filters, or that the result is incomplete |
| `Search the graph for the items most relevant to a query. Returns a ranked sample, not a complete set. Use scope="edges" for facts and relationships, ...` | None. The model knows when to use a list tool instead                             |

Use enum types from the ontology for entity type and edge type arguments. The model then cannot send a label that does not exist in the graph.

## Result shape

Return compact text. Each line is one item, with only the fields that the agent needs.

```text
complete:
- n4 QI-1041 [QualityIssue] issue_id=QI-1041; severity=high; status=open Aster 410 flow-rate under-delivery ...
- n9 QI-1068 [QualityIssue] issue_id=QI-1068; severity=high; status=open Lyric 350 alarm forwarding dropout ...
```

The reference agent applies these rules to each result:

* **Handles.** Each node, edge, and episode gets a short handle (`n1`, `e1`, `p1`) in place of its UUID. The model passes handles to the tools that take a node or an episode. The application resolves each handle to its UUID.
* **Deduplication.** The same UUID gets the same handle in every result of a run. An item that the agent already has is marked `seen`.
* **Completeness.** The first line says `ranked sample`, `complete`, or `truncated at N`. A list tool requests one item more than the limit to find out if more items match.
* **Size.** Each result is limited to 4,000 characters.
* **Budget.** A tool that is called after the budget is spent returns a notice. A repeated call with the same arguments also returns a notice.
* **Errors.** A tool returns invalid arguments and Zep API errors to the model as text. The model can then change the arguments or use a different tool. An error does not stop the run. A tool that rejects its arguments does not use the budget.

## Use the tools with an agent framework

The reference agent registers the tools in one Pydantic AI `FunctionToolset`. The agent gets the toolset, the system prompt, and a function that hides the retrieval tools until the model submits a plan.

The [framework integration pages](/memory-for-agent-frameworks) show how to connect Zep to each framework. Use the same tool contracts in each framework. Pin the graph and the limits in the application, and expose only the task arguments to the model.