RAG (Retrieval-Augmented Generation) framework for the Embabel Agent platform providing content ingestion, chunking, hierarchical navigation, and semantic search capabilities
This guide covers working with named entities including creation, relationships, search, and type conversion.
Named entities represent structured objects like people, projects, organizations, or any domain-specific concepts with:
import com.embabel.agent.rag.service.NamedEntityDataRepository
import com.embabel.agent.rag.model.*
val entityRepo: NamedEntityDataRepository = // implementation
// Create a simple entity
val person = SimpleNamedEntityData(
id = "person-123",
name = "Alice Smith",
description = "Senior software engineer specializing in distributed systems",
properties = mapOf(
"role" to "engineer",
"team" to "platform",
"yearsExperience" to 8,
"skills" to listOf("Kotlin", "Java", "Python", "Go"),
"email" to "alice@example.com",
"location" to "San Francisco"
)
)
// Save entity
val saved = entityRepo.save(person)
println("Saved entity: ${saved.name}")// Find by ID
val found = entityRepo.findById("person-123")
if (found != null) {
println("Name: ${found.name}")
println("Description: ${found.description}")
println("Properties: ${found.properties}")
}
// Find all entities with a label
val allPeople = entityRepo.findByLabel("Person")
println("Found ${allPeople.size} people")// Update properties
val updated = saved.copy(
properties = saved.properties + mapOf(
"level" to "senior",
"certifications" to listOf("AWS", "Kubernetes")
)
)
entityRepo.update(updated)
println("Updated entity with new properties")// Delete by ID
val deleted = entityRepo.delete("person-123")
if (deleted) {
println("Entity deleted successfully")
} else {
println("Entity not found")
}Save multiple entities efficiently.
// Create multiple entities
val entities = listOf(
SimpleNamedEntityData(
id = "person-1",
name = "Alice",
description = "Software Engineer",
properties = mapOf(
"team" to "platform",
"role" to "engineer"
)
),
SimpleNamedEntityData(
id = "person-2",
name = "Bob",
description = "Product Designer",
properties = mapOf(
"team" to "product",
"role" to "designer"
)
),
SimpleNamedEntityData(
id = "person-3",
name = "Carol",
description = "Engineering Manager",
properties = mapOf(
"team" to "platform",
"role" to "manager"
)
)
)
// Batch save
val savedEntities = entityRepo.saveAll(entities)
println("Saved ${savedEntities.size} entities")import com.embabel.agent.rag.service.*
import com.embabel.agent.rag.model.*
// Create entities
val alice = SimpleNamedEntityData(
id = "person-1",
name = "Alice",
description = "Senior Engineer"
)
val bob = SimpleNamedEntityData(
id = "person-2",
name = "Bob",
description = "Engineer"
)
val project = SimpleNamedEntityData(
id = "project-1",
name = "Platform Rewrite",
description = "Major platform refactoring initiative"
)
// Save entities
entityRepo.saveAll(listOf(alice, bob, project))
// Create relationships
entityRepo.createRelationship(
a = RetrievableIdentifier("person-1", "Person"),
b = RetrievableIdentifier("person-2", "Person"),
relationship = RelationshipData(
name = "WORKS_WITH",
properties = mapOf(
"since" to "2024-01-01",
"collaboration" to "high"
)
)
)
entityRepo.createRelationship(
a = RetrievableIdentifier("person-1", "Person"),
b = RetrievableIdentifier("project-1", "Project"),
relationship = RelationshipData(
name = "CONTRIBUTES_TO",
properties = mapOf(
"role" to "lead",
"startDate" to "2024-01-15"
)
)
)
entityRepo.createRelationship(
a = RetrievableIdentifier("person-2", "Person"),
b = RetrievableIdentifier("project-1", "Project"),
relationship = RelationshipData(
name = "CONTRIBUTES_TO",
properties = mapOf(
"role" to "contributor",
"startDate" to "2024-02-01"
)
)
)// Find outgoing relationships
val colleagues = entityRepo.findRelated(
source = RetrievableIdentifier("person-1", "Person"),
relationshipName = "WORKS_WITH",
direction = RelationshipDirection.OUTGOING
)
println("Alice works with:")
colleagues.forEach { colleague ->
println(" - ${colleague.name}")
}
// Find projects
val projects = entityRepo.findRelated(
source = RetrievableIdentifier("person-1", "Person"),
relationshipName = "CONTRIBUTES_TO",
direction = RelationshipDirection.OUTGOING
)
println("Alice contributes to:")
projects.forEach { project ->
println(" - ${project.name}")
}
// Find incoming relationships (who contributes to a project)
val contributors = entityRepo.findRelated(
source = RetrievableIdentifier("project-1", "Project"),
relationshipName = "CONTRIBUTES_TO",
direction = RelationshipDirection.INCOMING
)
println("Project contributors:")
contributors.forEach { contributor ->
println(" - ${contributor.name}")
}// Find manager (expecting single result)
val manager = entityRepo.findRelatedSingle(
source = RetrievableIdentifier("person-1", "Person"),
relationshipName = "REPORTS_TO",
direction = RelationshipDirection.OUTGOING
)
if (manager != null) {
println("Reports to: ${manager.name}")
} else {
println("No manager found")
}Define strongly-typed interfaces for entities.
// Define entity interface
interface Employee : NamedEntity {
val role: String
val team: String
val yearsExperience: Int
val skills: List<String>
}
// Convert to typed instance
val entityData = entityRepo.findById("person-123")!!
val employee = entityData.toTypedInstance<Employee>(
objectMapper = entityRepo.objectMapper,
type = Employee::class.java
)
if (employee != null) {
println("Employee: ${employee.name}")
println("Role: ${employee.role}")
println("Team: ${employee.team}")
println("Experience: ${employee.yearsExperience} years")
println("Skills: ${employee.skills.joinToString()}")
}
// Find all employees
val allEmployees = entityRepo.findAll(Employee::class.java)
allEmployees.forEach { emp ->
println("${emp.name} - ${emp.role} (${emp.team})")
}Use annotations to define relationship navigation methods.
import com.embabel.agent.rag.model.*
// Define entity with relationship methods
interface Person : NamedEntity {
@Relationship(name = "WORKS_WITH", direction = RelationshipDirection.OUTGOING)
fun getColleagues(): List<NamedEntity>
@Relationship(name = "CONTRIBUTES_TO", direction = RelationshipDirection.OUTGOING)
fun getProjects(): List<NamedEntity>
@Relationship(name = "REPORTS_TO", direction = RelationshipDirection.OUTGOING)
fun getManager(): NamedEntity?
@Relationship(name = "MANAGES", direction = RelationshipDirection.OUTGOING)
fun getDirectReports(): List<NamedEntity>
}
// Load entity as dynamic proxy
val entityData = entityRepo.findById("person-1")!!
val person = entityData.toInstance<Person>(
navigator = entityRepo,
Person::class.java
)
// Navigate relationships via methods
val colleagues = person.getColleagues()
println("Colleagues: ${colleagues.map { it.name }}")
val projects = person.getProjects()
println("Projects: ${projects.map { it.name }}")
val manager = person.getManager()
println("Manager: ${manager?.name ?: "None"}")
val reports = person.getDirectReports()
println("Direct reports: ${reports.map { it.name }}")interface Project : NamedEntity {
// Use custom relationship name
@Relationship(name = "HAS_CONTRIBUTOR", direction = RelationshipDirection.INCOMING)
fun getTeamMembers(): List<NamedEntity>
@Relationship(name = "DEPENDS_ON", direction = RelationshipDirection.OUTGOING)
fun getDependencies(): List<NamedEntity>
@Relationship(name = "BLOCKS", direction = RelationshipDirection.OUTGOING)
fun getBlockedProjects(): List<NamedEntity>
}import com.embabel.agent.rag.filter.*
// Basic vector search
val results = entityRepo.vectorSearch(
request = TextSimilaritySearchRequest(
query = "experienced platform engineer",
topK = 10,
similarityThreshold = 0.7
)
)
results.forEach { result ->
println("Score: ${"%.3f".format(result.score)}")
println("Name: ${result.content.name}")
println("Description: ${result.content.description}")
println()
}
// Vector search with filters
val filteredResults = entityRepo.vectorSearch(
request = TextSimilaritySearchRequest(
query = "software engineer",
topK = 10
),
metadataFilter = PropertyFilter.eq("team", "platform")
.and(PropertyFilter.gte("yearsExperience", 5)),
entityFilter = EntityFilter.hasAnyLabel("Person", "Employee")
)// Check Lucene syntax support
println("Syntax notes: ${entityRepo.luceneSyntaxNotes}")
// Text search with Lucene query
val textResults = entityRepo.textSearch(
request = TextSimilaritySearchRequest(
query = "engineer AND (platform OR infrastructure)",
topK = 20
)
)
// Text search with filters
val filteredTextResults = entityRepo.textSearch(
request = TextSimilaritySearchRequest(
query = "manager",
topK = 10
),
metadataFilter = PropertyFilter.contains("team", "platform")
)// Find entities by label
val allPeople = entityRepo.findByLabel("Person")
// Find with property filter
val platformEngineers = entityRepo.find(
label = "Person",
filter = PropertyFilter.eq("team", "platform")
.and(PropertyFilter.eq("role", "engineer"))
)
// Find with multiple labels
val teamMembers = entityRepo.find(
labels = EntityFilter.hasAnyLabel("Person", "Contractor"),
filter = PropertyFilter.eq("team", "platform")
)Link entities to domain type definitions.
// Assuming you have a DomainType defined
val personType: DomainType = // from data dictionary
// Find entities by domain type
val people = entityRepo.findByDomainType<NamedEntity>(personType)
println("Found ${people.size} entities of type ${personType.name}")
// Get entity data
val entityData = entityRepo.findEntityDataByDomainType(personType)
entityData.forEach { data ->
println("${data.name}: ${data.properties}")
}
// Create entity with linked domain type
val linkedEntity = SimpleNamedEntityData(
id = "person-456",
name = "David",
description = "Data Scientist",
properties = mapOf("specialty" to "machine learning"),
linkedDomainType = personType
)
entityRepo.save(linkedEntity)Check support for different entity types.
// Check if type is supported
if (entityRepo.supportsType("Person")) {
val person = entityRepo.findById<NamedEntity>("person-123", "Person")
println("Found person: ${person?.name}")
}
// Check native type support
if (entityRepo.isNativeType(CustomEntity::class.java)) {
val entity = entityRepo.findNativeById("id-123", CustomEntity::class.java)
println("Found native entity: ${entity?.name}")
}
// Find all native entities of a type
val nativeEntities = entityRepo.findNativeAll(CustomEntity::class.java)
nativeEntities?.forEach { entity ->
println("Native entity: ${entity.name}")
}// Create org structure
val ceo = SimpleNamedEntityData(
id = "person-ceo",
name = "CEO",
description = "Chief Executive Officer",
properties = mapOf("level" to 1)
)
val vp = SimpleNamedEntityData(
id = "person-vp",
name = "VP Engineering",
description = "Vice President of Engineering",
properties = mapOf("level" to 2)
)
val manager = SimpleNamedEntityData(
id = "person-manager",
name = "Engineering Manager",
description = "Platform Team Manager",
properties = mapOf("level" to 3)
)
val engineer = SimpleNamedEntityData(
id = "person-engineer",
name = "Senior Engineer",
description = "Platform Engineer",
properties = mapOf("level" to 4)
)
entityRepo.saveAll(listOf(ceo, vp, manager, engineer))
// Create hierarchy
entityRepo.createRelationship(
RetrievableIdentifier("person-vp", "Person"),
RetrievableIdentifier("person-ceo", "Person"),
RelationshipData("REPORTS_TO")
)
entityRepo.createRelationship(
RetrievableIdentifier("person-manager", "Person"),
RetrievableIdentifier("person-vp", "Person"),
RelationshipData("REPORTS_TO")
)
entityRepo.createRelationship(
RetrievableIdentifier("person-engineer", "Person"),
RetrievableIdentifier("person-manager", "Person"),
RelationshipData("REPORTS_TO")
)
// Navigate upward
fun findManagerChain(entityRepo: NamedEntityDataRepository, personId: String): List<String> {
val chain = mutableListOf<String>()
var current: NamedEntityData? = entityRepo.findById(personId)
while (current != null) {
chain.add(current.name)
current = entityRepo.findRelatedSingle(
RetrievableIdentifier(current.id, "Person"),
"REPORTS_TO",
RelationshipDirection.OUTGOING
)
}
return chain
}
val chain = findManagerChain(entityRepo, "person-engineer")
println("Management chain: ${chain.joinToString(" → ")}")// Create projects with dependencies
val projectA = SimpleNamedEntityData(
id = "project-a",
name = "Project A",
description = "Core platform"
)
val projectB = SimpleNamedEntityData(
id = "project-b",
name = "Project B",
description = "API service",
properties = mapOf("dependsOn" to listOf("project-a"))
)
val projectC = SimpleNamedEntityData(
id = "project-c",
name = "Project C",
description = "UI application",
properties = mapOf("dependsOn" to listOf("project-b"))
)
entityRepo.saveAll(listOf(projectA, projectB, projectC))
// Create dependency relationships
entityRepo.createRelationship(
RetrievableIdentifier("project-b", "Project"),
RetrievableIdentifier("project-a", "Project"),
RelationshipData("DEPENDS_ON")
)
entityRepo.createRelationship(
RetrievableIdentifier("project-c", "Project"),
RetrievableIdentifier("project-b", "Project"),
RelationshipData("DEPENDS_ON")
)
// Find all dependencies recursively
fun findAllDependencies(
entityRepo: NamedEntityDataRepository,
projectId: String,
visited: MutableSet<String> = mutableSetOf()
): Set<String> {
if (projectId in visited) return emptySet()
visited.add(projectId)
val dependencies = entityRepo.findRelated(
RetrievableIdentifier(projectId, "Project"),
"DEPENDS_ON",
RelationshipDirection.OUTGOING
)
val allDeps = mutableSetOf<String>()
dependencies.forEach { dep ->
allDeps.add(dep.id)
allDeps.addAll(findAllDependencies(entityRepo, dep.id, visited))
}
return allDeps
}
val allDeps = findAllDependencies(entityRepo, "project-c")
println("All dependencies for Project C: $allDeps")// Create skill entities
val kotlin = SimpleNamedEntityData(
id = "skill-kotlin",
name = "Kotlin",
description = "Modern JVM language",
properties = mapOf("category" to "programming")
)
val springBoot = SimpleNamedEntityData(
id = "skill-spring",
name = "Spring Boot",
description = "Java application framework",
properties = mapOf("category" to "framework")
)
entityRepo.saveAll(listOf(kotlin, springBoot))
// Link people to skills
entityRepo.createRelationship(
RetrievableIdentifier("person-1", "Person"),
RetrievableIdentifier("skill-kotlin", "Skill"),
RelationshipData(
name = "HAS_SKILL",
properties = mapOf("level" to "expert", "years" to 5)
)
)
// Find people by skill
val kotlinExperts = entityRepo.findRelated(
RetrievableIdentifier("skill-kotlin", "Skill"),
"HAS_SKILL",
RelationshipDirection.INCOMING
)
println("Kotlin experts: ${kotlinExperts.map { it.name }}")// Handle circular dependencies
fun findRelatedSafe(
entityRepo: NamedEntityDataRepository,
entityId: String,
relationshipName: String,
visited: MutableSet<String> = mutableSetOf()
): List<NamedEntityData> {
if (entityId in visited) {
return emptyList()
}
visited.add(entityId)
return entityRepo.findRelated(
RetrievableIdentifier(entityId, "Entity"),
relationshipName,
RelationshipDirection.BOTH
)
}// Safely handle missing entities
val entity = entityRepo.findById("unknown-id")
if (entity == null) {
println("Entity not found")
// Handle appropriately
} else {
println("Found: ${entity.name}")
}// Validate properties before saving
fun validateAndSave(
entityRepo: NamedEntityDataRepository,
entity: SimpleNamedEntityData
): SimpleNamedEntityData? {
// Check required properties
if (!entity.properties.containsKey("team")) {
println("Error: 'team' property is required")
return null
}
// Validate property types
val yearsExp = entity.properties["yearsExperience"]
if (yearsExp != null && yearsExp !is Number) {
println("Error: 'yearsExperience' must be a number")
return null
}
return entityRepo.save(entity)
}