Skip to navigation

Customizing Graph Structure

Define the entity and edge types that Zep extracts for your domain

Zep enables the use of rich, domain-specific data structures in graphs through Entity Types and Edge Types, replacing generic graph nodes and edges with detailed models.

Zep classifies newly created nodes/edges as one of the default or custom types or leaves them unclassified. For example, a node representing a preference is classified as a Preference node, and attributes specific to that type are automatically populated. You may restrict graph queries to nodes/edges of a specific type, such as Preference. To stop Zep from extracting entities and edges outside your configured types altogether, see Strict ontology below.

The default entity and edge types are applied to user graphs (not all graphs) by default, but you may define additional custom types as needed.

Zep assigns one selected type to each node and edge. An entity type hierarchy (closed beta) adds labels from the selected entity type chain. An edge type hierarchy (closed beta) classifies an edge into a subtype of its top-level type.

Set the ontology before ingestion. Ontologies affect only data processed after they are set; existing data is not re-extracted automatically. See Prepare Data for Ingestion for identity and ordering guidance.

Why use custom ontology

Custom entity and edge types improve graph construction by focusing extraction on the most important entities and relationships for your domain. When a custom ontology is defined, Zep’s extraction process prioritizes these types, resulting in more relevant and structured data for your use case.

Even if Zep’s default types cover your basic needs, defining a custom ontology tailored to your domain often produces better results because:

  • Extraction focuses on entities and relationships that matter most to your application
  • Graph structure aligns more closely with your domain model
  • Retrieved context becomes more relevant and actionable

Default Entity and Edge Types

Definition

Zep provides default entity and edge types that are automatically applied to user graphs (not all graphs). These types help classify and structure the information extracted from conversations.

You can view the exact definition for the default ontology here.

Default Entity Types

The default entity types are:

  • User: A Zep user specified by role in chat messages. There can only be a single User entity.
  • Assistant: Represents the AI assistant in the conversation. This entity is a singleton.
  • Preference: Entities mentioned in contexts expressing user preferences, choices, opinions, or selections. This classification is prioritized over most other classifications.
  • Location: A physical or virtual place where activities occur or entities exist. Use this classification only after checking if the entity fits other more specific types.
  • Event: A time-bound activity, occurrence, or experience.
  • Object: A physical item, tool, device, or possession. Use this classification only as a last resort after checking other types.
  • Topic: A subject of conversation, interest, or knowledge domain. Use this classification only as a last resort after checking other types.
  • Organization: A company, institution, group, or formal entity.
  • Document: Information content in various forms.

Default Edge Types

The default edge types are:

  • LOCATED_AT: Represents that an entity exists or occurs at a specific location. Connects any entity to a Location.
  • OCCURRED_AT: Represents that an event happened at a specific time or location. Connects an Event to any entity.

Default entity and edge types apply to user graphs. All nodes and edges in any user graph will be classified into one of these types or none.

Adding Data

When we add data to the graph, default entity and edge types are automatically created:

from zep_cloud import AddMessage
# zep_thread_uuid is the uuid that thread.create returned, from your application database.
client.thread.add_messages(
zep_thread_uuid,
messages=[
AddMessage(
name="John Doe",
role="user",
content="I really like pop music, and I don't like metal",
)
],
)

Searching

When searching nodes in the graph, you may provide a list of types to filter the search by. The provided types are ORed together. Search results will only include nodes that satisfy one of the provided types:

from zep_cloud.types import NodeSearchFilters
# zep_user_uuid is the uuid of the user, from your application database.
search_results = client.graph.search_nodes(
zep_user_uuid,
query="the user's music preferences",
filters=NodeSearchFilters(node_labels=["Preference"]),
)
for i, node in enumerate(search_results.items or []):
preference = node.attributes
print(f"Preference {i+1}:{preference}")
Preference 1: {'category': 'Music', 'description': 'Pop Music is a genre of music characterized by its catchy melodies and widespread appeal.', 'labels': ['Entity', 'Preference']}
Preference 2: {'category': 'Music', 'description': 'Metal Music is a genre of music characterized by its heavy sound and complex compositions.', 'labels': ['Entity', 'Preference']}

Disabling Default Ontology

Set disable_default_ontology when you create or update a user to omit Zep’s default entity and edge types except User. Zep still extracts the User type on that user’s graph so the speaker maps to the user node.

When disable_default_ontology is set to true:

  • Zep still uses the User entity type on a user graph so the speaker maps to the node that user.create creates
  • Other default entity and edge types (Assistant, Preference, Location, and the rest) are not applied
  • Classification uses the custom entity and edge types you define
  • Other nodes stay unclassified when they do not match a custom type

Use this when the graph must use domain-specific types instead of Zep’s default types.

from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# Create a user with default ontology disabled
user = client.user.create(
first_name="Jane",
last_name="Smith",
disable_default_ontology=True,
)
# Store user.uuid_ in your application database.
# Or update an existing user to disable default ontology
client.user.update(
zep_user_uuid,
disable_default_ontology=True,
)

Custom Entity and Edge Types

Definition

In addition to the default entity and edge types, you may specify your own custom entity and custom edge types. You need to provide a description of the type and a description for each of the fields. The syntax for this is different for each language.

The number of custom entity types and custom edge types you may define on one ontology depends on your plan. The pricing page lists those limits.

The entity-type limit and the edge-type limit apply independently. The same limits apply whether you set the ontology for a specific graph or project-wide. The entity-type limit does not include Zep’s default types.

Each model may have up to 10 fields.

When creating custom entity or edge types, you may not use the following attribute names (including in Go struct tags), as they conflict with default node attributes: uuid, name, graph_id, name_embedding, summary, and created_at.

Add custom attributes when a Context Block needs structured field values. The custom type example shows this method.

Use node summaries and facts when the agent does not need structured fields.

from zep_cloud.ontology import EntityModel, EntityText, EdgeModel, EntityBoolean
from pydantic import Field
class Restaurant(EntityModel):
"""
A business that serves prepared meals to customers, such as Green Leaf Cafe, Chipotle, or a local pizza place. Do not use for a cuisine such as Mexican food, a dish such as a burrito, or a grocery store.
"""
cuisine_type: EntityText = Field(description="The cuisine type of the restaurant, for example: American, Mexican, Indian, etc.", default=None)
dietary_accommodation: EntityText = Field(description="The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc.", default=None)
class Audiobook(EntityModel):
"""
A specific audiobook title that a person can listen to, such as 'The 7 Habits of Highly Effective People' or 'Dune'. Do not use for a printed book that the user reads, a podcast, or an author.
"""
genre: EntityText = Field(description="The genre of the audiobook, for example: self-help, fiction, nonfiction, etc.", default=None)
class RestaurantVisit(EdgeModel):
"""
The user goes to, visits, or eats at a restaurant. Examples: 'Let's go to Green Leaf Cafe', 'I had lunch at Chipotle yesterday'. Do not use when the user only asks about a restaurant or gets a recommendation and does not go there.
"""
restaurant_name: EntityText = Field(description="The name of the restaurant the user visited", default=None)
class AudiobookListen(EdgeModel):
"""
The user listens to or plays an audiobook. Examples: 'Play the 7 habits of highly effective people', 'I finished Dune on audiobook last night'. Do not use when the user only mentions, buys, or asks about an audiobook.
"""
audiobook_title: EntityText = Field(description="The title of the audiobook the user listened to or played", default=None)
class DietaryPreference(EdgeModel):
"""
The user follows a diet or has a food preference, a food restriction, or a food allergy. Examples: 'I'm vegetarian', 'I'm allergic to peanuts', 'I don't eat pork'. Do not use for a one-time meal choice such as 'I want pasta tonight', or for a preference about a restaurant.
"""
preference_type: EntityText = Field(description="Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc.", default=None)
allergy: EntityBoolean = Field(description="Whether this dietary preference represents a user allergy: True or false", default=None)

The name of each type is the key that you give to the ontology builder in the next step. In TypeScript, an edge definition contains its source and target pairs. In Python and Go, you give the pairs to the builder.

Setting Entity and Edge Types

You can set these custom entity and edge types as the graph ontology for your current Zep project. The ontology can be applied either project-wide to all users and graphs, or targeted to specific graphs only. First, build the ontology from the declarations with the ontology builder of the SDK: build_ontology (Python), buildOntology (TypeScript), or zep.BuildOntology (Go). Each set call replaces the full ontology at its scope.

Setting Types Project Wide

Use project.set_ontology to set the ontology for the entire project. All users and graphs within the project will use this ontology, unless a graph has its own ontology. Note that for custom edge types, you can require the source and destination nodes to be a certain type, or allow them to be any type:

from zep_cloud.ontology import build_ontology
from zep_cloud.types import EdgeSourceTarget
entity_types, edge_types = build_ontology(
entities={
"Restaurant": Restaurant,
"Audiobook": Audiobook,
},
edges={
"RESTAURANT_VISIT": (
RestaurantVisit,
[EdgeSourceTarget(source="User", target="Restaurant")],
),
"AUDIOBOOK_LISTEN": (
AudiobookListen,
[EdgeSourceTarget(source="User", target="Audiobook")],
),
"DIETARY_PREFERENCE": (
DietaryPreference,
[EdgeSourceTarget(source="User")],
),
},
)
client.project.set_ontology(
entity_types=entity_types,
edge_types=edge_types,
)

Setting Types For Specific Graphs

Use graph.set_ontology to set the ontology for one graph. Give the UUID of the graph. For a user graph, use the uuid that user.create returns. For another graph, use the uuid that graph.create returns. The ontology will only apply to that graph, while other graphs in the project will continue using the previously set ontology (whether that was due to a project-wide setting of ontology or due to a graph-specific setting of ontology). Zep uses the entity types and the edge types of a graph ontology separately. If the graph ontology has entity types and no edge types, the graph uses the project edge types. If the graph ontology has edge types and no entity types, the graph uses the project entity types. To replace the project types of both kinds, give entity types and edge types in the graph.set_ontology call. When the graph has types of its own, graph.get_ontology returns only those types. When the graph has no types of its own, graph.get_ontology returns the project ontology, and inherited is true. To apply the same ontology to more than one graph, send the call once for each graph:

# zep_graph_uuids are graph UUIDs from your application database,
# for example the uuid of each user.
for graph_uuid in zep_graph_uuids:
client.graph.set_ontology(
graph_uuid,
entity_types=entity_types,
edge_types=edge_types,
)

Adding Data

Now, when you add data to the graph, new nodes and edges are classified into exactly one of the overall set of entity or edge types respectively, or no type:

from zep_cloud import AddMessage
messages_thread1 = [
AddMessage(content="Take me to a lunch place", role="user", name="John Doe"),
AddMessage(content="How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", role="assistant", name="Assistant"),
AddMessage(content="Do any of those have vegetarian options? I'm vegetarian", role="user", name="John Doe"),
AddMessage(content="Yes, Green Leaf Cafe has vegetarian options", role="assistant", name="Assistant"),
AddMessage(content="Let's go to Green Leaf Cafe", role="user", name="John Doe"),
AddMessage(content="Navigating to Green Leaf Cafe", role="assistant", name="Assistant"),
]
messages_thread2 = [
AddMessage(content="Play the 7 habits of highly effective people", role="user", name="John Doe"),
AddMessage(content="Playing the 7 habits of highly effective people", role="assistant", name="Assistant"),
]
user = client.user.create(first_name="John", last_name="Doe", email="john.doe@example.com")
# Store user.uuid_ next to your own user ID in your application database.
thread1 = client.thread.create(user_uuid=user.uuid_)
thread2 = client.thread.create(user_uuid=user.uuid_)
# Store thread1.uuid_ and thread2.uuid_ next to your own conversation IDs.
client.thread.add_messages(thread1.uuid_, messages=messages_thread1, ignore_roles=["assistant"])
client.thread.add_messages(thread2.uuid_, messages=messages_thread2, ignore_roles=["assistant"])

Retrieving Custom Types

Once you’ve created custom entity and edge types in your graph, you’ll typically want to retrieve information filtered by these specific types. There are two primary approaches:

  1. Context Templates (Recommended): Use context templates to create reusable context blocks that automatically filter by type. This is ideal when you want consistent formatting with automatic relevance detection.
  2. Graph Search: Use direct graph search for more granular control over queries and when you need dynamic, query-based retrieval.

Using Context Templates

Context templates provide a convenient way to create context blocks that filter by your custom entity and edge types. You define a template once and Zep automatically retrieves and formats the relevant information.

Context templates support the types parameter on %{entities} and %{edges} variables to filter by your custom types:

# Create a template that filters by custom types
template = client.context.create_template(
name="restaurant-audiobook-context",
template="""# USER PREFERENCES
%{edges types=[DIETARY_PREFERENCE] limit=5}
# RESTAURANT VISITS
%{edges types=[RESTAURANT_VISIT] limit=10}
# RESTAURANTS
%{entities types=[Restaurant] limit=5}
# AUDIOBOOKS
%{entities types=[Audiobook] limit=5}
# AUDIOBOOK LISTENING HISTORY
%{edges types=[AUDIOBOOK_LISTEN] limit=10}"""
)
# Store template.uuid_ to use the template later.
# Use the template to get context
results = client.thread.get_context(
thread1.uuid_,
template_uuid=template.uuid_,
)
print(results.context)

The resulting context block will automatically include only the specified types, formatted according to your template:

# USER PREFERENCES
- (2024-11-20 10:30:00 - present) [DIETARY_PREFERENCE] <User states 'I'm vegetarian' indicating a dietary preference> { preference_type: vegetarian, allergy: false }
# RESTAURANT VISITS
- (2024-11-20 10:35:00 - present) [RESTAURANT_VISIT] <User John Doe is going to Green Leaf Cafe> { restaurant_name: Green Leaf Cafe }
# RESTAURANTS
- { name: Green Leaf Cafe, types: [Entity,Restaurant], summary: Green Leaf Cafe is a restaurant that offers vegetarian options, attributes: { dietary_accommodation: vegetarian } }
# AUDIOBOOKS
- { name: 7 habits of highly effective people, types: [Entity,Audiobook], summary: '7 habits of highly effective people' is an audiobook }
# AUDIOBOOK LISTENING HISTORY
- (2024-11-20 10:40:00 - present) [AUDIOBOOK_LISTEN] <John Doe requested to play the audiobook '7 habits of highly effective people'> { audiobook_title: 7 habits of highly effective people }

Learn more about context templates in the Context Templates documentation.

For more granular control or dynamic queries, you can use graph search to filter by entity or edge types. Graph search is ideal when you need to specify custom search queries, want direct access to individual nodes or edges, or are building dynamic filtering based on runtime conditions.

Search for entities (nodes) by filtering on custom entity types using the node_labels parameter:

from zep_cloud.types import NodeSearchFilters
search_results_restaurants = client.graph.search_nodes(
user.uuid_,
query="Take me to a restaurant",
filters=NodeSearchFilters(node_labels=["Restaurant"]),
page_size=1,
)
node = (search_results_restaurants.items or [])[0]
print(f"Node name: {node.name}")
print(f"Node labels: {node.labels}")
print(f"Cuisine type: {node.attributes.get('cuisine_type')}")
print(f"Dietary accommodation: {node.attributes.get('dietary_accommodation')}")
Node name: Green Leaf Cafe
Node labels: Entity,Restaurant
Cuisine type: undefined
Dietary accommodation: vegetarian

You can provide multiple types in search filters, and the types will be ORed together. For example, searching with edge_types: ["DIETARY_PREFERENCE", "RESTAURANT_VISIT"] will return edges matching either type.

Behavior of custom types

Zep applies custom entity and edge types with these rules:

  • Each set_ontology call replaces the custom entity and edge types at its scope. The custom types are always the types of the last set_ontology call.
  • The full set of entity and edge types for a project contains the custom types and the default types.
  • A custom type with the same name as a default type replaces that default type.
  • A change to the custom types does not change existing nodes or edges. Existing nodes and edges keep their classification and attributes. Only new data that has new information can change them.
  • Each custom entity type and each custom edge type must have at least one custom property.

Ontology design best practices

Zep classifies each extracted entity and edge into one type, or leaves it unclassified when no type matches. Zep selects the type from the description of the type, not from its name. For an edge type, Zep also uses the source and target entity types. The quality of the graph depends on these descriptions.

  • Avoid category overlap. Make the types mutually exclusive, so that each entity or fact has one correct type. When two descriptions overlap, no single type is correct, and Zep classifies the same kind of item inconsistently. Two types can have similar names when their descriptions make them distinct. This rule applies to types at the same level. In a type hierarchy, a parent type contains its subtypes by design. For example, a Dog node also gets the Mammal and Animal labels.
  • State a priority when two types must overlap. For example, if you need both Hobby and Hiking, write in the description of Hiking that it takes priority over Hobby.
  • Start with a small number of generic types. Use few types, few fields, and short definitions. Add types and fields one at a time, and examine the graph after each change. Type and field descriptions are prompts, and they need iteration.
  • Model entity types as nouns and edge types as relationships. An entity type names a kind of thing, such as a person, a restaurant, or a product. An edge type names a relationship or an event between two entities, such as “visits”, “works for”, or “is allergic to”. If an entity type describes a relationship, Zep often stores the information as an edge, not as a node of that type. If an edge type describes a thing, Zep often stores the information as a node, not as an edge of that type.
  • Write clear and unambiguous definitions. State the membership rule of the type: what an item must be to belong to the type. Do not use vague words such as “relevant” or “important” as the rule.
  • Give positive and negative examples in each definition. The extraction model uses examples in the description to find the boundary of the type. Give one or more examples that belong to the type. Give one or more similar items that do not belong to the type, and name the type that they belong to instead, if one exists. A field description also benefits from examples of valid values.

The examples on this page use this pattern. Each type description gives a membership rule, positive examples, and negative examples.

Entity type hierarchy

Entity type hierarchies are in closed beta. Zep enables the feature only for select accounts. To ask for access, contact Zep sales. Without the feature, use custom entity types without a hierarchy, as the earlier sections of this page show.

An entity type hierarchy groups custom entity types into types and subtypes. The optional entity_type_hierarchy field on project.set_ontology and graph.set_ontology holds a dictionary of dictionaries. Each key must name an entity type in the same request. Use {} for a leaf type.

This example defines Animal with the subtypes Mammal and Bird. Mammal has the leaf types Ape and Dog.

from zep_cloud.types import EntityProperty, EntityType
properties = [
EntityProperty(
name="details",
type="text",
description="Additional details about the entity.",
),
]
entity_types = [
EntityType(name="Animal", description="An animal, such as a dog, a parrot, or a salmon. Do not use for a plant, a person, or a toy animal.", properties=properties),
EntityType(name="Mammal", description="A mammal, such as a dog, a whale, or a bat. Do not use for a bird, a fish, or a reptile.", properties=properties),
EntityType(name="Ape", description="An ape, such as a chimpanzee, a gorilla, or an orangutan. Do not use for a monkey such as a baboon.", properties=properties),
EntityType(name="Dog", description="A dog, such as a pet Labrador or a police German shepherd. Do not use for a wolf or a fox.", properties=properties),
EntityType(name="Bird", description="A bird, such as a parrot, an eagle, or a penguin. Do not use for a bat or a flying insect.", properties=properties),
]
hierarchy = {
"Animal": {
"Mammal": {"Ape": {}, "Dog": {}},
"Bird": {},
},
}
client.project.set_ontology(
entity_types=entity_types,
entity_type_hierarchy=hierarchy,
)

Every hierarchy name must be unique. Do not use Entity or a default Zep entity type. A type can have up to 10 direct subtypes. A hierarchy can have up to 4 subtype levels below a top-level type. Subtypes do not count against the entity type limit.

Each write replaces the ontology and its hierarchy. A request without entity_type_hierarchy removes the hierarchy. project.get_ontology and graph.get_ontology return the hierarchy when it is set.

A graph ontology replaces the project ontology for that graph. Zep does not merge the project and graph hierarchies.

An ingested node gets every label in its type chain. A label filter for Mammal matches nodes labeled Mammal, Ape, or Dog. A subtype does not inherit properties or identity properties from its parent. A node uses the properties and identity properties of the last type in its chain only. To match subtype nodes by an identity value, set identity_properties on each subtype. If the last type in the chain has no identity properties, ingestion does not use the identity properties of a parent type. It matches the node by name, as for any type that has no identity properties. Ontology changes apply to episodes that Zep processes after the change. Existing nodes keep their labels.

Zep enables entity type hierarchies for each account. If the feature is not enabled for your account, the API rejects a non-empty entity_type_hierarchy with HTTP 400.

Edge type hierarchy

Edge type hierarchies are in closed beta. Zep enables this feature only for select accounts. To ask for access, contact Zep sales. Without the feature, use custom edge types without a hierarchy, as the earlier sections show.

An edge type hierarchy groups custom edge types into types and subtypes. The optional edge_type_hierarchy field on project.set_ontology and graph.set_ontology holds a dictionary of dictionaries. Each key must name an edge type in the same request. Use {} for a leaf type.

This example defines EMPLOYED_BY with the subtypes WORKS_FULL_TIME_FOR and CONTRACTS_FOR.

from zep_cloud.types import EdgeSourceTarget, EdgeType, EntityProperty, EntityType
name_property = [
EntityProperty(name="full_name", type="text", description="The full name."),
]
entity_types = [
EntityType(name="Person", description="A human individual, such as an employee, a contractor, or a founder. Do not use for a company, a team, or a job title.", properties=name_property),
EntityType(name="Company", description="A business or an organization that employs people, such as Acme Corp or a local bakery. Do not use for a team or a department inside a company, or for a product.", properties=name_property),
]
start_date_property = EntityProperty(
name="start_date",
type="text",
description="The date when the work started.",
)
source_targets = [EdgeSourceTarget(source="Person", target="Company")]
edge_types = [
EdgeType(
name="EMPLOYED_BY",
description="A person works for a company in any role. Examples: 'Ana works at Acme', 'Raj joined Globex as an engineer'. Do not use when the person is only a customer, an investor, or an applicant of the company.",
properties=[start_date_property],
source_targets=source_targets,
),
EdgeType(
name="WORKS_FULL_TIME_FOR",
description="A person is a full-time employee of a company. Example: 'Ana is a full-time engineer at Acme'. Do not use for contract, part-time, or freelance work. Use CONTRACTS_FOR for contract work.",
properties=[
EntityProperty(
name="title",
type="text",
description="The job title.",
),
start_date_property,
],
source_targets=source_targets,
),
EdgeType(
name="CONTRACTS_FOR",
description="A person does contract or freelance work for a company. Example: 'Raj is a contractor for Globex'. Do not use for a full-time employee. Use WORKS_FULL_TIME_FOR for a full-time employee.",
properties=[start_date_property],
source_targets=source_targets,
),
]
edge_type_hierarchy = {
"EMPLOYED_BY": {
"WORKS_FULL_TIME_FOR": {},
"CONTRACTS_FOR": {},
},
}
client.project.set_ontology(
entity_types=entity_types,
edge_types=edge_types,
edge_type_hierarchy=edge_type_hierarchy,
)

An edge type hierarchy has these limits:

  • Every name in the hierarchy must be unique.
  • Do not include RELATES_TO, IS_DUPLICATE_OF, or a default Zep edge type such as LOCATED_AT or OCCURRED_AT.
  • A type can have up to 10 direct subtypes.
  • A hierarchy can have up to 4 subtype levels below a top-level type.
  • The plan limit counts only top-level edge types. Subtypes do not count against the plan limit.
  • One ontology can contain up to 100 edge types, including subtypes.

Each edge type has its own properties and source_targets. A subtype does not inherit these fields from its parent type. Set the properties and source_targets on each subtype.

Zep first extracts a top-level edge type. It then selects a subtype at each level. Classification stops at a leaf type or when no subtype fits.

A filter on a type in edge_types matches that type and all of its subtypes. The exclude_edge_types filter works the same way. A filter on a leaf type matches only that type. If Zep turns off the feature for your account, these filters match only the exact type names, also when the ontology still has a hierarchy.

Each write replaces the ontology and its hierarchy. A request without edge_type_hierarchy removes the edge type hierarchy. project.get_ontology and graph.get_ontology return the hierarchy when it is set.

A graph ontology replaces the project ontology for that graph. Zep does not merge the project and graph hierarchies.

Ontology changes apply to episodes that Zep processes after the change. Existing edges keep their names.

Zep enables edge type hierarchies for each account. If the feature is not enabled for your account, the API rejects a non-empty edge_type_hierarchy with HTTP 400.

Protected properties

Ingestion can merge new information into an existing node. By default, ingestion can replace the stored value of a property with a new extracted value. To keep a stored value, set protected: true on the entity type property. For example, protect a legal name that you import from a system of record.

When ingestion merges new information into an existing node, it applies these rules to each protected property:

  • If the stored value is not empty, ingestion keeps the stored value.
  • If the stored value is empty, ingestion fills it with the extracted value.

Ingestion always keeps the stored values of identity properties. You do not need to set protected on them. Ingestion does not use a protected property to match nodes. It uses only the properties in identity_properties.

In this example, account_id identifies a Customer node, and legal_name is protected. Ingestion can replace the value of support_tier.

from zep_cloud.types import EntityProperty, EntityType
customer = EntityType(
name="Customer",
description="A company or a person that has an account in the billing system, such as Acme Corp with account A-1042. Do not use for a vendor, a partner, or a prospect that has no account.",
properties=[
EntityProperty(
name="account_id",
type="text",
description="The account ID in the billing system.",
),
EntityProperty(
name="legal_name",
type="text",
description="The registered legal name of the customer.",
protected=True,
),
EntityProperty(
name="support_tier",
type="text",
description="The current support tier, for example: standard or premium.",
),
],
identity_properties=["account_id"],
)
client.project.set_ontology(entity_types=[customer])

The typed ontology helpers do not set protected. These helpers are EntityModel subclasses in Python, entityFields in TypeScript, and struct tags in Go. To set protected, use EntityType and EntityProperty values as in the example above.

Edge type properties do not support protected. A project.set_ontology or graph.set_ontology request with protected: true on an edge type property fails with HTTP 400.

In an entity type hierarchy (closed beta), a node uses the properties of the last type in its chain only. Set protected on the property of each type that needs it.

A change to protected applies to episodes that Zep processes after the change. Ingestion does not change existing nodes because of the change.

Strict Ontology

By default, Zep’s extraction favors recall over strict adherence to your schema: entities and edges that don’t clearly match one of your configured types are still added to the graph, classified generically rather than dropped. Set strict_ontology: true when you need the graph to conform strictly to your declared types — for example, when downstream code or context templates assume every node and edge belongs to a type you defined.

What Strict Ontology Does

When strict_ontology is true on an add-data request:

  • Entities are limited to your configured entity types — the custom entity types you’ve defined, plus Zep’s default entity types unless you’ve also disabled the default ontology. When the default ontology is disabled, a user graph still includes the User type. The generic Entity type is excluded from the candidates, so an entity that doesn’t match a declared type is dropped instead of added as an untyped node.
  • Edges are limited to your configured edge types, when you have any configured. An edge type the extraction proposes that isn’t in your configured set is discarded instead of saved under a derived relation name.

Setting Strict Ontology

The strict_ontology field defaults to false. Set it to true on graph.episode.add, on thread.add_messages, or on batch.create:

from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# zep_graph_uuid is a graph UUID from your application database,
# for example the uuid of a user.
new_episode = client.graph.episode.add(
zep_graph_uuid,
type="text",
data="The user is an avid fan of Eric Clapton",
strict_ontology=True,
)

The same field is available on thread.add_messages:

from zep_cloud import AddMessage
message = AddMessage(
name="John Doe",
role="user",
content="I really like pop music, and I don't like metal",
)
# zep_thread_uuid is the uuid that thread.create returned, from your application database.
client.thread.add_messages(
zep_thread_uuid,
messages=[message],
strict_ontology=True,
)

The same field is available on batch.create. Processing applies it to every graph episode and every thread message in that batch:

batch = client.batch.create(
strict_ontology=True,
metadata={"description": "Customer support backfill"},
)

Strict ontology has two edge cases worth checking before you rely on it:

  • No edge types configured: edge enforcement only takes effect if you have edge types configured at all — custom or default. If you have none, strict ontology has no effect on edges, and edge extraction stays open-vocabulary.
  • No entity types available: if strict_ontology is true and you have no custom entity types and the default ontology is disabled, a user graph still has the User type. A group graph in that state has no entity types left to match against. Extraction then returns zero entities rather than an error, so this misconfiguration fails silently.

Strict ontology restricts extraction to your project’s configured ontology, which includes Zep’s default entity and edge types unless you’ve separately disabled the default ontology — turning on strict_ontology doesn’t disable the default ontology by itself.