Relationships: Edge Definitions¶
Note: The examples in this document use simplified field names and structures for teaching purposes. The actual
BillingDocumentschema atdocs/examples/templates/billing_document.pyis more comprehensive with 30+ classes, EN 16931/Peppol BIS compliance, and usesCONTAINS_LINEfor line items.
Overview¶
Relationships (edges) connect nodes in your knowledge graph. The edge() helper function marks fields as graph relationships and defines their labels. Well-designed edges create meaningful, queryable graph structures.
In this guide:
- Using the edge() function
- Edge label conventions
- Single vs list relationships
- Common edge patterns
- Relationship best practices
Using the edge() Function¶
Basic Syntax¶
field_name: TargetType = edge(
label="EDGE_LABEL",
description="Description of the relationship",
# Additional Field parameters...
)
Required vs Optional Edges¶
# Required single relationship
issued_by: Organization = edge(
label="ISSUED_BY",
description="The organization that issued this document"
)
# Optional single relationship
verified_by: Optional[Person] = edge(
label="VERIFIED_BY",
description="Person who verified this document, if applicable"
)
# Required list relationship (one-to-many)
contains_items: List[LineItem] = edge(
label="CONTAINS_LINE",
default_factory=list, # REQUIRED for lists
description="Line items contained in this document"
)
# Optional list relationship
addresses: List[Address] = edge(
label="LOCATED_AT",
default_factory=list,
description="Physical addresses for this entity"
)
Critical Rule: For list edges, you must provide default_factory=list.
Edge Label Conventions¶
Naming Standards¶
✅ DO: - Use ALL_CAPS with underscores - Use verb phrases that describe the relationship - Choose descriptive, domain-appropriate verbs - Be consistent across your template
❌ DON'T: - Use camelCase, lowercase, or mixed case - Use vague labels like "LINK" or "RELATED" - Mix naming styles
Good vs Bad Labels¶
# ✅ Good - Clear, descriptive, consistent
issued_by: Organization = edge(label="ISSUED_BY")
sent_to: Client = edge(label="SENT_TO")
contains_items: List[Item] = edge(label="CONTAINS_LINE")
located_at: Address = edge(label="LOCATED_AT")
# ❌ Bad - Inconsistent, vague
issued_by: Organization = edge(label="issuedBy") # Wrong case
sent_to: Client = edge(label="sent-to") # Wrong separator
contains_items: List[Item] = edge(label="has") # Too vague
located_at: Address = edge(label="LINK") # Not descriptive
Common Edge Labels by Category¶
Authorship & Ownership¶
# Document creation
issued_by: Organization = edge(label="ISSUED_BY")
created_by: Person = edge(label="CREATED_BY")
authored_by: Person = edge(label="AUTHORED_BY")
owned_by: Organization = edge(label="OWNED_BY")
published_by: Organization = edge(label="PUBLISHED_BY")
# Verification & approval
verified_by: Person = edge(label="VERIFIED_BY")
approved_by: Person = edge(label="APPROVED_BY")
signed_by: Person = edge(label="SIGNED_BY")
Recipients & Targets¶
# Document recipients
sent_to: Client = edge(label="SENT_TO")
addressed_to: Person = edge(label="ADDRESSED_TO")
delivered_to: Organization = edge(label="DELIVERED_TO")
billed_to: Client = edge(label="BILLED_TO")
# Beneficiaries
insured_by: Person = edge(label="INSURED_BY")
covered_by: InsurancePlan = edge(label="COVERED_BY")
Location & Physical Presence¶
# Physical locations
located_at: Address = edge(label="LOCATED_AT")
lives_at: Address = edge(label="LIVES_AT")
based_at: Address = edge(label="BASED_AT")
manufactured_at: Address = edge(label="MANUFACTURED_AT")
# Geographic relationships
operates_in: Region = edge(label="OPERATES_IN")
ships_to: Country = edge(label="SHIPS_TO")
Composition & Containment¶
# Document structure
contains_item: List[LineItem] = edge(label="CONTAINS_LINE")
has_component: List[Component] = edge(label="HAS_COMPONENT")
includes_part: List[Part] = edge(label="INCLUDES_PART")
composed_of: List[Material] = edge(label="COMPOSED_OF")
# Hierarchical relationships
has_section: List[Section] = edge(label="HAS_SECTION")
has_chapter: List[Chapter] = edge(label="HAS_CHAPTER")
Membership & Association¶
# Organizational relationships
belongs_to: Organization = edge(label="BELONGS_TO")
part_of: Group = edge(label="PART_OF")
member_of: Organization = edge(label="MEMBER_OF")
employed_by: Organization = edge(label="EMPLOYED_BY")
# Associations
affiliated_with: Organization = edge(label="AFFILIATED_WITH")
partnered_with: Organization = edge(label="PARTNERED_WITH")
Services & Offerings¶
# Insurance & coverage
has_guarantee: List[Guarantee] = edge(label="HAS_GUARANTEE")
offers_plan: List[Plan] = edge(label="OFFERS_PLAN")
provides_coverage: List[Coverage] = edge(label="PROVIDES_COVERAGE")
# Products & services
offers_product: List[Product] = edge(label="OFFERS_PRODUCT")
provides_service: List[Service] = edge(label="PROVIDES_SERVICE")
Research & Scientific¶
# Experiments & studies
has_experiment: List[Experiment] = edge(label="HAS_EXPERIMENT")
uses_material: Material = edge(label="USES_MATERIAL")
has_measurement: List[Measurement] = edge(label="HAS_MEASUREMENT")
has_result: List[Result] = edge(label="HAS_RESULT")
# Processes & methods
has_process_step: List[Step] = edge(label="HAS_PROCESS_STEP")
uses_method: Method = edge(label="USES_METHOD")
has_evaluation: Evaluation = edge(label="HAS_EVALUATION")
Single vs List Relationships¶
Single Relationships (One-to-One)¶
Use when an entity has exactly one or at most one related entity:
class BillingDocument(BaseModel):
"""BillingDocument document."""
model_config = ConfigDict(graph_id_fields=["document_no"])
document_no: str = Field(...)
# One invoice is issued by one organization
issued_by: Organization = edge(
label="ISSUED_BY",
description="The organization that issued this invoice"
)
# One invoice is sent to one client
sent_to: Client = edge(
label="SENT_TO",
description="The client receiving this invoice"
)
Graph Structure:
List Relationships (One-to-Many)¶
Use when an entity can have multiple related entities:
class BillingDocument(BaseModel):
"""BillingDocument document."""
model_config = ConfigDict(graph_id_fields=["document_no"])
document_no: str = Field(...)
# One invoice contains many line items
contains_items: List[LineItem] = edge(
label="CONTAINS_LINE",
default_factory=list, # Required!
description="Line items in this invoice"
)
Graph Structure:
BillingDocument-001 --CONTAINS_LINE--> LineItem-1
BillingDocument-001 --CONTAINS_LINE--> LineItem-2
BillingDocument-001 --CONTAINS_LINE--> LineItem-3
Optional Single Relationships¶
Use Optional[Type] for relationships that may not exist:
class Document(BaseModel):
"""Document that may or may not have a verifier."""
model_config = ConfigDict(graph_id_fields=["document_id"])
document_id: str = Field(...)
# Optional: document may not be verified
verified_by: Optional[Person] = edge(
label="VERIFIED_BY",
description="Person who verified this document, if verified"
)
Edge Patterns and Examples¶
Pattern 1: Bidirectional Relationships¶
Create meaningful relationships in both directions:
class Organization(BaseModel):
"""Organization entity."""
model_config = ConfigDict(graph_id_fields=["name"])
name: str = Field(...)
# Organization has employees
employees: List[Person] = edge(
label="EMPLOYS",
default_factory=list,
description="People employed by this organization"
)
class Person(BaseModel):
"""Person entity."""
model_config = ConfigDict(graph_id_fields=["first_name", "last_name"])
first_name: str = Field(...)
last_name: str = Field(...)
# Person works for organization
employer: Optional[Organization] = edge(
label="EMPLOYED_BY",
description="Organization employing this person"
)
Graph Structure:
Pattern 2: Shared Components¶
Multiple entities can reference the same component:
class Address(BaseModel):
"""Address component (shared)."""
model_config = ConfigDict(is_entity=False)
street: str = Field(...)
city: str = Field(...)
class Person(BaseModel):
"""Person entity."""
model_config = ConfigDict(graph_id_fields=["first_name", "last_name"])
first_name: str = Field(...)
last_name: str = Field(...)
addresses: List[Address] = edge(
label="LIVES_AT",
default_factory=list,
description="Residential addresses"
)
class Organization(BaseModel):
"""Organization entity."""
model_config = ConfigDict(graph_id_fields=["name"])
name: str = Field(...)
addresses: List[Address] = edge(
label="LOCATED_AT",
default_factory=list,
description="Business addresses"
)
Graph Structure:
Person-1 --LIVES_AT--> Address(123 Main St, Paris)
Person-2 --LIVES_AT--> Address(123 Main St, Paris) # Same address node
Organization-1 --LOCATED_AT--> Address(123 Main St, Paris) # Same address node
Pattern 3: Nested Relationships¶
Edges can point to entities that have their own edges:
class Material(BaseModel):
"""Material entity."""
model_config = ConfigDict(graph_id_fields=["name"])
name: str = Field(...)
properties: List[MaterialProperty] = edge(
label="HAS_PROPERTY",
default_factory=list,
description="Material properties"
)
class Component(BaseModel):
"""Component entity."""
model_config = ConfigDict(graph_id_fields=["component_id"])
component_id: str = Field(...)
material: Material = edge(
label="USES_MATERIAL",
description="Material used in this component"
)
class Assembly(BaseModel):
"""Assembly entity."""
model_config = ConfigDict(graph_id_fields=["assembly_id"])
assembly_id: str = Field(...)
components: List[Component] = edge(
label="HAS_COMPONENT",
default_factory=list,
description="Components in this assembly"
)
Graph Structure:
Pattern 4: Multiple Edge Types to Same Entity¶
An entity can have multiple types of relationships to the same target type:
class Document(BaseModel):
"""Document entity."""
model_config = ConfigDict(graph_id_fields=["document_id"])
document_id: str = Field(...)
# Different relationship types to Person
created_by: Person = edge(
label="CREATED_BY",
description="Person who created this document"
)
reviewed_by: Optional[Person] = edge(
label="REVIEWED_BY",
description="Person who reviewed this document"
)
approved_by: Optional[Person] = edge(
label="APPROVED_BY",
description="Person who approved this document"
)
Graph Structure:
Document --CREATED_BY--> Person-A
Document --REVIEWED_BY--> Person-B
Document --APPROVED_BY--> Person-C
Complete Example: BillingDocument Template¶
Here's a complete example showing various edge patterns:
"""BillingDocument extraction template with comprehensive edge definitions."""
from typing import Any, List, Optional
from pydantic import BaseModel, ConfigDict, Field
def edge(label: str, **kwargs: Any) -> Any:
return Field(..., json_schema_extra={"edge_label": label}, **kwargs)
# --- Components ---
class Address(BaseModel):
"""Physical address component."""
model_config = ConfigDict(is_entity=False)
street: str = Field(...)
city: str = Field(...)
postal_code: str = Field(...)
class MonetaryAmount(BaseModel):
"""Monetary value component."""
model_config = ConfigDict(is_entity=False)
value: float = Field(...)
currency: str = Field(...)
# --- Entities ---
class Organization(BaseModel):
"""Organization entity."""
model_config = ConfigDict(graph_id_fields=["name"])
name: str = Field(...)
tax_id: Optional[str] = Field(None)
# Edge to Address component
addresses: List[Address] = edge(
label="LOCATED_AT",
default_factory=list,
description="Business addresses"
)
class Client(BaseModel):
"""Client entity."""
model_config = ConfigDict(graph_id_fields=["name"])
name: str = Field(...)
email: Optional[str] = Field(None)
# Edge to Address component
addresses: List[Address] = edge(
label="LIVES_AT",
default_factory=list,
description="Client addresses"
)
class LineItem(BaseModel):
"""Line item entity."""
model_config = ConfigDict(graph_id_fields=["description", "unit_price"])
description: str = Field(...)
quantity: float = Field(...)
unit_price: float = Field(...)
# Edge to MonetaryAmount component
total: MonetaryAmount = edge(
label="HAS_TOTAL",
description="Total amount for this line item"
)
# --- Root Document ---
class BillingDocument(BaseModel):
"""BillingDocument document (root)."""
model_config = ConfigDict(graph_id_fields=["document_no"])
document_no: str = Field(...)
date: str = Field(...)
# Single edges to entities
issued_by: Organization = edge(
label="ISSUED_BY",
description="Organization that issued this invoice"
)
sent_to: Client = edge(
label="SENT_TO",
description="Client receiving this invoice"
)
# List edge to entities
contains_items: List[LineItem] = edge(
label="CONTAINS_LINE",
default_factory=list,
description="Line items in this invoice"
)
# Edge to component
total_amount: MonetaryAmount = edge(
label="HAS_TOTAL",
description="Total invoice amount"
)
Resulting Graph:
BillingDocument-001
├─ ISSUED_BY → Organization(Acme Corp)
│ └─ LOCATED_AT → Address(123 Main St, Paris)
├─ SENT_TO → Client(John Doe)
│ └─ LIVES_AT → Address(456 Oak Ave, London)
├─ CONTAINS_LINE → LineItem-1
│ └─ HAS_TOTAL → MonetaryAmount(100, EUR)
├─ CONTAINS_LINE → LineItem-2
│ └─ HAS_TOTAL → MonetaryAmount(200, EUR)
└─ HAS_TOTAL → MonetaryAmount(300, EUR)
Best Practices¶
👍 Use Descriptive Labels¶
# ✅ Good - Clear and specific
issued_by: Organization = edge(label="ISSUED_BY")
contains_items: List[Item] = edge(label="CONTAINS_LINE")
# ❌ Bad - Vague
issued_by: Organization = edge(label="HAS")
contains_items: List[Item] = edge(label="RELATED_TO")
👍 Be Consistent¶
# ✅ Good - Consistent pattern
lives_at: Address = edge(label="LIVES_AT")
works_at: Address = edge(label="WORKS_AT")
located_at: Address = edge(label="LOCATED_AT")
# ❌ Bad - Inconsistent
lives_at: Address = edge(label="LIVES_AT")
works_at: Address = edge(label="WORKS_IN")
located_at: Address = edge(label="HAS_LOCATION")
👍 Always Use default_factory for Lists¶
# ✅ Good
items: List[Item] = edge(
label="CONTAINS_LINE",
default_factory=list
)
# ❌ Bad - Missing default_factory
items: List[Item] = edge(label="CONTAINS_LINE")
👍 Provide Clear Descriptions¶
# ✅ Good - Explains the relationship
issued_by: Organization = edge(
label="ISSUED_BY",
description="The organization that created and issued this document"
)
# ❌ Bad - No description
issued_by: Organization = edge(label="ISSUED_BY")
Common Mistakes¶
❌ Missing default_factory¶
# Wrong
items: List[Item] = edge(label="CONTAINS_LINE")
# Correct
items: List[Item] = edge(
label="CONTAINS_LINE",
default_factory=list
)
❌ Inconsistent Label Format¶
# Wrong - Mixed formats
issued_by: Org = edge(label="issuedBy")
sent_to: Client = edge(label="SENT_TO")
has_items: List[Item] = edge(label="contains-item")
# Correct - Consistent ALL_CAPS_WITH_UNDERSCORES
issued_by: Org = edge(label="ISSUED_BY")
sent_to: Client = edge(label="SENT_TO")
has_items: List[Item] = edge(label="CONTAINS_LINE")
❌ Vague Labels¶
# Wrong - Too vague
org: Organization = edge(label="HAS")
items: List[Item] = edge(label="RELATED")
# Correct - Descriptive
org: Organization = edge(label="ISSUED_BY")
items: List[Item] = edge(label="CONTAINS_LINE")
Next Steps¶
Now that you understand relationships:
- Validation → - Add validators for data quality
- Advanced Patterns - Complex relationship patterns
- Best Practices - Complete template checklist