Introducing Zep Agent Skills! Read the announcement. →
Zep

Your agent's context is only as precise as your entity types

In a flat ontology, entity types must not overlap, so each kind of record gets either a broad type or a specific type. Type hierarchies let a parent type contain its subtypes, so each entity or fact gets a broad type and the most specific subtype that fits. They are now generally available.

Preston Rasmussen · 7 min read · Updated

An agent is only as precise as the context that it retrieves. A procurement agent that answers "Which purchase orders are open with Acme?" must retrieve purchase orders, and only purchase orders. The same agent that answers "What contracts do we have with Acme?" must retrieve every kind of contract. The first question needs a specific type, and the second question needs a broad type. A flat ontology gives the agent only one of the two.

A custom entity type tells Zep which kinds of things to extract from your data, such as a product, a patient, or a contract. Until now, the custom types in a Zep ontology were flat. Each node got the label Entity and at most one custom type label. With type hierarchies, you can now arrange custom entity types and edge types as types and subtypes, and Zep classifies each entity or fact into the most specific type that fits.

Flat types force a choice

A flat ontology gives each entity one custom type, so the types must be disjoint: each item belongs to one type only. The custom ontology guide says to make the types mutually exclusive. When two type descriptions overlap, no single type is correct, and Zep classifies the same kind of item inconsistently.

Disjoint types force one level of detail for each kind of record. A procurement team cannot define Contract together with MSA, OrderForm and PurchaseOrder, because each order form is also a contract. The team must select the broad type or the specific types.

A complex business has many kinds of records across legal, sales and support, and the entity type limit makes this choice harder. Zep limits the number of types for the same reason that types must not overlap: a long, flat list of similar types is difficult to classify. A team that reaches the limit usually selects the broad type.

A broad type makes retrieval less precise. Every MSA, order form and purchase order gets the same Contract label, so a search for purchase orders ranks them against every other contract in the graph. Other contracts crowd the relevant purchase orders out of the top K results, and the agent gets less of the context that it asked for. A broad type also makes classification harder. The Contract description must cover every kind of contract, and an LLM classifier can still decide that an order form is not a contract.

Specific types lose the broad level. In a flat ontology, MSA, OrderForm and PurchaseOrder are three unrelated types. No label groups them as contracts, so the graph does not record that an order form is a contract.

A type hierarchy gives the ontology both levels. In a type hierarchy, PurchaseOrder is a kind of Contract, and an item of a subtype is also an item of each parent type. A parent type contains its subtypes by design, so only sibling types must be disjoint. Every PurchaseOrder node also gets the Contract label. At each level, the classifier selects among a few sibling types with distinct descriptions. Ontology engineers use the same model for classes and subclasses, and so do object-oriented languages such as Java and C#.

Define a type hierarchy

Every type in the hierarchy is an ordinary custom entity type, with its own name, description, and properties. The new optional field entity_type_hierarchy holds only the structure. Its value is a dictionary of dictionaries. Each key names an entity type in the same request, and {} marks a leaf type.

PYTHON
from zep_cloud.client import Zepfrom zep_cloud.types import EntityProperty, EntityType client = Zep(api_key="YOUR_API_KEY") details = [EntityProperty(name="details", type="text", description="Details about the contract.")] entity_types = [    EntityType(name="Contract", description="An agreement or an order between the company and a customer or a vendor, such as an MSA, an order form, or a purchase order. Do not use for a quote or a proposal.", properties=details),    EntityType(name="MSA", description="A master services agreement that sets the general terms for later orders, such as a three-year MSA with a reseller. Do not use for an order under an MSA.", properties=details),    EntityType(name="OrderForm", description="A form that a customer signs to buy specific products, such as an order form for 500 seats. Do not use for a purchase order that a buyer issues.", properties=details),    EntityType(name="PurchaseOrder", description="An order that a buyer issues to a seller for goods or services, such as PO 4471 for 40 laptops. Do not use for an order form that a seller sends.", properties=details),] client.project.set_ontology(    entity_types=entity_types,    entity_type_hierarchy={        "Contract": {"MSA": {}, "OrderForm": {}, "PurchaseOrder": {}},    },)

Each type description gives a membership rule, an example, and a similar item that does not belong. Each subtype must pass the "is a kind of" test for its parent type: an order form is a kind of contract. The parent type Contract contains its subtypes by design. Its subtypes are siblings, so they must not overlap, and they must be at the same level of detail. The Contract description names its subtypes, because Zep must first recognize an order form as a contract.

A type can have up to 10 direct subtypes, and a hierarchy can have up to 4 subtype levels below a top-level type. Subtypes do not count against the entity type limit of your plan. One ontology can have up to 100 entity types in total. The entity type hierarchy guide has the TypeScript and Go versions and the full validation rules.

How Zep selects a subtype

Zep first classifies each extracted entity into a top-level entity type, such as Contract. Then it classifies the entity into the subtypes of that type, one level at a time. Classification stops when the entity does not fit any subtype at the next level, or when the type has no more subtype levels.

For example, "the PO that Acme sent for 40 laptops" becomes Contract, then PurchaseOrder. A mention of "the new Acme contract" with no other detail stops at Contract, because no subtype fits. At each step, Zep compares only a few sibling types, so each choice stays simple.

Filter broadly or precisely

A classified node gets the label of every type in its chain. A PurchaseOrder node has the custom labels Contract and PurchaseOrder. A node never has a subtype label without the labels of all its parent types.

A label filter matches a node when any label of the node matches. A filter on PurchaseOrder returns only purchase orders, so other contracts do not crowd them out:

PYTHON
purchase_orders = client.graph.node.list(    "YOUR_GRAPH_UUID",    filters={"node_labels": ["PurchaseOrder"]},)

A filter on Contract returns Contract nodes and the nodes of all its subtypes.

MSA, OrderForm and PurchaseOrder sit inside Contract. A filter on Contract returns four contract nodes and no invoice.
A PO classified as PurchaseOrder is also a Contract, so a filter on Contract returns it.

A subtype does not inherit properties or identity properties from its parent type. A node uses the properties of the last type in its chain only. Declare every property that a subtype needs on the subtype itself. To match subtype nodes by an identity value, such as a purchase order number, set identity_properties on each subtype.

Edge type hierarchies

Facts get the same model. The optional field edge_type_hierarchy has the same shape as entity_type_hierarchy. For example, the edge type EMPLOYED_BY can have the subtypes WORKS_FULL_TIME_FOR and CONTRACTS_FOR. Extraction selects a top-level edge type, and then the subtype walk selects the most specific edge type that fits. The edge type limit of your plan counts only top-level edge types.

An edge has one type name, which is the name of the last type in its chain. Search expands an edge type filter through the hierarchy. A filter on EMPLOYED_BY returns EMPLOYED_BY, WORKS_FULL_TIME_FOR, and CONTRACTS_FOR edges. A filter on CONTRACTS_FOR returns only contract work. The exclude_edge_types filter expands in the same way.

An edge subtype does not inherit properties or source_targets from its parent type. Set source_targets on each subtype, such as Person to Company on WORKS_FULL_TIME_FOR and on CONTRACTS_FOR.

BehaviorEntity type hierarchyEdge type hierarchy
Ontology fieldentity_type_hierarchyedge_type_hierarchy
Saved typeEvery label in the chainLast type in the chain
Filter on a parent typeMatches all subtypesMatches all subtypes
Counts against the plan limitTop-level types onlyTop-level types only
Property inheritanceNoneNone, including source_targets

Entity and edge type hierarchies classify the same way and store the result differently. Source: the Zep custom ontology guide.

Existing data keeps its types

A change to a hierarchy applies to episodes that Zep processes after the change. Existing nodes and edges keep their labels and names, and Zep does not reclassify them. Each ontology write replaces the full ontology. A request without entity_type_hierarchy removes the stored hierarchy. A graph ontology replaces the project ontology for that graph, and Zep does not merge the two hierarchies.

Availability

Entity and edge type hierarchies are generally available on managed Zep and on BYOC deployments.

To learn how types fit with the other kinds of context in a graph, read Zep context types. For facts that relate three or more entities, read about hyperedges.

FAQ

What is a type hierarchy in a knowledge graph?

A type hierarchy arranges entity types or relationship types as types and subtypes. Each item of a subtype is also an item of its parent types. In Zep, PurchaseOrder can be a subtype of Contract. Zep classifies each entity into the most specific subtype that fits, and a filter on a parent type also returns the items of its subtypes.

Does a subtype inherit the properties of its parent type?

No. Each type in a Zep hierarchy declares its own properties and identity properties. A node uses the properties of the last type in its type chain only. A PurchaseOrder node does not get a property that only Contract declares. Declare on each subtype every property that the subtype needs, and set identity_properties on each subtype when you match nodes by an identity value.

How many LLM calls does subtype classification add?

Subtype classification makes one LLM call for each level that Zep walks, for each entity or edge. A hierarchy has at most 4 subtype levels below a top-level type, so one entity makes at most four calls. Calls for different entities run in parallel. An entity whose top-level type has no subtypes makes no calls, and an ontology without a hierarchy adds no calls.

Does Zep reclassify existing nodes when I change a hierarchy?

No. A hierarchy change applies to episodes that Zep processes after the change. Existing nodes keep their labels, and existing edges keep their names. When a new extraction merges with an existing node, and the existing chain is a prefix of the new chain, the node gets the longer chain. For example, an existing Contract node can become a PurchaseOrder node.

Written byPreston RasmussenMember of Technical Staff, Zep

Start building with Zep

Zep unifies business data, documents, and conversations into shared, governed context so agents can complete tasks correctly.

Sign up
Albert Bierstadt, Sunset in the Yosemite Valley

More from the blog