Skip to content

Core Concepts

alexheifetz edited this page Sep 22, 2025 · 1 revision

Core Concepts

Understanding Embabel's fundamental concepts is key to building sophisticated agents. This guide uses analogies and practical examples to explain how the framework thinks about intelligent automation.

The Restaurant Analogy 🍽️

Think of Embabel like a master chef's kitchen where different cooks (agents) can accomplish complex meals (goals) by combining their skills (actions) based on available ingredients (domain model).

Traditional Programming = Fast Food Chain

  • Fixed menu (predetermined workflows)
  • Rigid recipes (hardcoded sequences)
  • Limited flexibility (can't adapt to new requests)

Embabel = Master Chef Kitchen

  • Dynamic planning (chef creates custom recipes)
  • Skill combination (combines techniques creatively)
  • Adaptive execution (adjusts based on available ingredients)

Core Framework Components

1. Agents 🤖

What they are: The orchestrating components that coordinate actions to achieve goals.

Restaurant analogy: The head chef who oversees the entire cooking process.

@Agent(description = "Travel planning specialist")
public class TravelPlannerAgent {
    // Actions and goals defined here
}

Key characteristics:

  • Spring-managed beans - Full dependency injection support
  • Stateless by design - Safe for concurrent execution
  • Domain-focused - Each agent handles a specific business area

2. Goals 🎯

What they are: Desired outcomes that agents work toward achieving.

Restaurant analogy: The specific dish you want to prepare (e.g., "prepare a romantic dinner for two vegetarians").

@AchievesGoal(
    description = "Create comprehensive travel itinerary with activities and accommodations",
    export = @Export(remote = true, name = "travelPlanning")
)
@Action
public TravelItinerary createItinerary(
    Destination destination,
    TravelPreferences preferences,
    OperationContext context
) {
    // Implementation that satisfies the goal
    return context.ai().withDefaultLlm()
        .createObject("Create itinerary for " + destination.getName(), TravelItinerary.class);
}

Goal characteristics:

  • Declarative - What you want, not how to get it
  • Measurable - Clear success criteria
  • Composable - Can be broken into sub-goals

3. Actions ⚡

What they are: Individual steps that agents can take to progress toward goals.

Restaurant analogy: Specific cooking techniques like "chop vegetables," "heat pan," or "season to taste."

@Action
public DestinationInfo searchDestinationInfo(
    String destination,
    OperationContext context
) {
    return context.ai().withDefaultLlm()
        .createObject(String.format("""
            Research comprehensive information about %s including:
            - Best time to visit
            - Top attractions
            - Local customs and tips
        """, destination), DestinationInfo.class);
}

@Action(toolGroups = {ToolGroup.WEB})
public AccommodationOptions findAccommodations(
    Destination destination,
    AccommodationPreferences preferences,
    OperationContext context
) {
    // Uses web tools to search for hotels/accommodations
    return context.ai().withDefaultLlm()
        .createObject("Find accommodations in " + destination.getName(), 
                     AccommodationOptions.class);
}

Action types:

  • LLM-powered - Leverage language models for reasoning
  • Code-based - Pure business logic without AI
  • Tool-enabled - Use external tools (web search, APIs, etc.)
  • Hybrid - Combine multiple approaches

4. Conditions ✅

What they are: Prerequisites and post-conditions that govern action execution.

Restaurant analogy: Requirements like "vegetables must be fresh," "pan must be heated," or "sauce should be thick enough."

@Condition
public boolean hasValidApiKey() {
    String apiKey = System.getenv("BOOKING_API_KEY");
    return apiKey != null && !apiKey.isEmpty();
}

@Condition  
public boolean destinationIsAccessible(Destination destination) {
    // Check travel advisories, visa requirements, etc.
    return destination.getTravelAdvisoryLevel().ordinal() <= TravelAdvisoryLevel.EXERCISE_CAUTION.ordinal();
}

Most conditions are inferred from data flow:

// The framework automatically infers that findAccommodations 
// requires a Destination object to be available
@Action
public AccommodationOptions findAccommodations(Destination destination) {
    // Implementation
    return new AccommodationOptions();
}

5. Domain Model 🏗️

What it is: Rich business objects that represent your problem space and can have behavior.

Restaurant analogy: Your ingredients, cooking tools, and recipe knowledge - everything the chef works with.

@JsonClassDescription("Travel destination with comprehensive details")
public record Destination(
    String name,
    String country,
    Coordinates coordinates,
    ClimateInfo climate,
    AccessibilityInfo accessibility
) {
    // Domain behavior - exposed to LLMs via @Tool
    @Tool("Calculate travel distance between destinations")
    public Distance distanceTo(Destination other) {
        return calculateDistance(this.coordinates, other.coordinates);
    }
}

public record TravelPreferences(
    Budget budget,
    Duration duration,
    List<Interest> interests,
    AccommodationType accommodationType,
    TravelStyle travelStyle
) {}

Domain model benefits:

  • Type safety - Compile-time checking and refactoring support
  • Rich behavior - Objects can have methods exposed to LLMs
  • Clear contracts - Well-defined interfaces between actions
  • Jackson annotations - Help LLMs understand object structure

6. GOAP Planning 🧠

What it is: Goal Oriented Action Planning - the AI algorithm that figures out how to achieve goals.

Restaurant analogy: The master chef's ability to look at available ingredients and techniques, then create a novel recipe to achieve the desired dish.

graph TD
    A[Current State] --> B[GOAP Planner]
    B --> C[Evaluate Available Actions]
    B --> D[Check Conditions]
    B --> E[Calculate Action Costs]
    B --> F[Generate Plan]
    F --> G[Execute Next Action]
    G --> H[Reassess State]
    H --> B
    H --> I[Goal Achieved!]
Loading

GOAP characteristics:

  • Non-deterministic - Can find multiple paths to the same goal
  • Dynamic - Replans after each action based on new state
  • Cost-aware - Considers efficiency and resource usage
  • Composable - Can combine actions from different agents

Agent Execution Modes

Focused Mode 🎯

What: User code requests specific functionality from a known agent.

When to use: Code-driven flows, specific business processes, API endpoints.

Restaurant analogy: Ordering a specific dish from a known chef.

@Service
public class TravelService {
    private final TravelPlannerAgent travelAgent;
    
    public TravelService(TravelPlannerAgent travelAgent) {
        this.travelAgent = travelAgent;
    }
    
    public TravelItinerary planTrip(TripRequest request) {
        return agentPlatform.run(travelAgent.getClass(), request);
    }
}

Closed Mode 🔒

What: Platform dynamically selects the best agent for the user's intent.

When to use: User-driven requests where the appropriate agent should be chosen automatically.

Restaurant analogy: Telling the maître d' what kind of cuisine you want, and they choose the right chef.

// Platform analyzes intent and selects appropriate agent
Object result = agentPlatform.execute("I need help planning a business trip to Tokyo");

Open Mode 🌐

What: Platform uses ALL available goals and actions to achieve user intent, potentially combining multiple agents.

When to use: Complex, multi-faceted requests that might require combining capabilities.

Restaurant analogy: Describing your perfect meal and having the entire kitchen staff collaborate to create something novel.

// Platform combines travel planning + research + booking + documentation
Object result = agentPlatform.executeOpen("""
    Research sustainable tourism options in Costa Rica, 
    plan a 10-day eco-friendly itinerary, 
    book accommodations, 
    and create a detailed travel guide
""");

The Power of GOAP Planning

Traditional Approach (Finite State Machine)

// Rigid, predetermined sequence
public TravelItinerary planTrip(TripRequest request) {
    Destination destination = validateDestination(request.getDestination());
    List<Flight> flights = searchFlights(destination, request.getDates());
    List<Hotel> hotels = searchHotels(destination, request.getPreferences());
    List<Activity> activities = findActivities(destination, request.getInterests());
    return combineIntoItinerary(flights, hotels, activities);
}

Embabel Approach (GOAP Planning)

// Dynamic planning based on available actions and current state
@Agent
public class TravelPlannerAgent {
    
    @Action 
    public Destination validateDestination(String destination) { return null; }
    
    @Action 
    public FlightOptions searchFlights(Destination dest, DateRange dates) { return null; }
    
    @Action 
    public HotelOptions searchHotels(Destination dest, Preferences prefs) { return null; }
    
    @Action 
    public ActivityOptions findActivities(Destination dest, List<Interest> interests) { return null; }
    
    @Action 
    public VisaInfo checkVisaRequirements(Destination dest) { return null; }
    
    @Action 
    public TransportOptions findLocalTransportation(Destination dest) { return null; }
    
    @Action 
    public BudgetSummary createBudgetBreakdown(TripRequest request) { return null; }
    
    @AchievesGoal("Create comprehensive travel itinerary")
    @Action 
    public TravelItinerary finalizeItinerary(/* various inputs */) { return null; }
}

The GOAP planner might discover novel sequences like:

  1. Validate destination → Check visa requirements → Search flights
  2. Or: Search flights → Validate destination (if flights aren't available, reconsider)
  3. Or: Check budget → Search accommodation → Find flights within remaining budget

Data Flow and Conditions

Automatic Condition Inference

The framework automatically infers most conditions from your method signatures:

@Action
public HotelReservation bookHotel(
    Destination destination,        // Requires: destination must be available
    HotelPreferences preferences,   // Requires: preferences must be defined  
    PaymentMethod paymentMethod     // Requires: valid payment method
) {
    // Implementation
    return new HotelReservation();
}

Explicit Conditions

For complex business rules:

@Condition("User has sufficient budget for luxury accommodation")
public boolean hasLuxuryBudget(Budget budget, Destination destination) {
    return budget.getAmount() >= destination.getAverageLuxuryHotelCost() * 1.2;
}

@Action
@ConditionalOnCondition("hasLuxuryBudget")
public LuxuryHotelReservation bookLuxuryHotel(Destination dest, Budget budget) {
    return new LuxuryHotelReservation();
}

Integration with Spring Framework

Dependency Injection

@Agent
public class TravelPlannerAgent {
    private final FlightService flightService;
    private final AccommodationService accommodationService;
    private final String defaultCurrency;
    
    public TravelPlannerAgent(
        FlightService flightService,           // Injected
        AccommodationService accommodationService, // Injected
        @Value("${travel.default-currency}") 
        String defaultCurrency                   // Configuration
    ) {
        this.flightService = flightService;
        this.accommodationService = accommodationService;
        this.defaultCurrency = defaultCurrency;
    }
    
    // Actions can use injected services
}

Configuration and Profiles

@Configuration
@Profile("production")
public class ProductionTravelConfig {
    
    @Bean
    public FlightService realFlightService() {
        return new AmadeusFlightService();
    }
    
    @Bean  
    public PaymentProcessor realPaymentProcessor() {
        return new StripePaymentProcessor();
    }
}

@Configuration
@Profile("test")  
public class TestTravelConfig {
    
    @Bean
    public FlightService mockFlightService() {
        return new MockFlightService();
    }
}

Testing Strategy

Unit Testing Actions

@Test
fun `should find appropriate accommodations for budget travelers`() {
    val context = FakeOperationContext()
    context.expectResponse(AccommodationOptions(
        hotels = listOf(BudgetHotel("Hostel Tokyo", 50.dollars))
    ))
    
    val destination = Destination("Tokyo", "Japan")
    val preferences = AccommodationPreferences(
        budget = Budget(100.dollars),
        type = AccommodationType.BUDGET
    )
    
    val result = agent.findAccommodations(destination, preferences, context)
    
    assertThat(result.hotels).hasSize(1)
    assertThat(result.hotels.first().pricePerNight).isLessThan(75.dollars)
}

Integration Testing Flows

@SpringBootTest
class TravelPlannerAgentIntegrationTest {
    
    @Autowired
    private lateinit var agentPlatform: AgentPlatform
    
    @Test
    fun `should create complete travel itinerary`() {
        val request = TripRequest(
            destination = "Kyoto",
            dates = DateRange(LocalDate.now(), LocalDate.now().plusDays(3)),
            preferences = TravelPreferences(/* ... */)
        )
        
        val result = agentPlatform.run(TravelPlannerAgent::class, request)
        
        assertThat(result).isInstanceOf<TravelItinerary>()
        assertThat(result.accommodations).isNotEmpty()
        assertThat(result.activities).hasSize(3) // One per day
    }
}

Next Steps

Now that you understand the core concepts, explore:

  1. Development Guide - Writing your first agent
  2. Advanced Agent Patterns - Complex workflows and error handling
  3. Testing Guide - Comprehensive testing strategies
  4. Spring Integration - Deep dive into Spring Framework integration

The power of Embabel lies in its ability to combine simple actions into complex behaviors through intelligent planning, while maintaining the type safety and testability you expect from enterprise Java/Kotlin applications.

Clone this wiki locally