README.md
   1  # DriftKit Chat Assistant Framework
   2  
   3  [![Java Version](https://img.shields.io/badge/Java-21-blue.svg)](https://adoptium.net/temurin/releases/)
   4  [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.3.1-brightgreen.svg)](https://spring.io/projects/spring-boot)
   5  [![Maven Central](https://img.shields.io/badge/Maven%20Central-1.0--SNAPSHOT-red.svg)](https://search.maven.org/)
   6  
   7  **DriftKit Chat Assistant Framework** is a powerful and flexible framework for building AI-powered conversational workflows using annotation-based step definitions. It provides a robust foundation for creating complex, multi-step chat interactions with automatic schema generation, asynchronous processing, and composable user interfaces.
   8  
   9  ## 🎯 Key Features
  10  
  11  ### Core Framework Features
  12  
  13  - **πŸ“ Annotation-based Workflow Definition** - Define complex workflows using simple Java annotations
  14  - **πŸ”„ Automatic Schema Generation** - Convert Java classes to AI function schemas automatically
  15  - **⚑ Asynchronous Step Execution** - Support for long-running operations with progress tracking
  16  - **🧩 Composable User Interfaces** - Break complex forms into step-by-step user interactions
  17  - **πŸ’Ύ Persistent Session Management** - Maintain workflow state across user interactions
  18  - **πŸ”€ Conditional Flow Control** - Dynamic workflow routing based on user input and conditions
  19  - **πŸ› οΈ Spring Boot Integration** - Seamless integration with Spring Boot applications
  20  - **πŸ“Š Execution History Tracking** - Complete audit trail of workflow execution
  21  - **πŸ”§ Extensible Architecture** - Easy to extend with custom components and integrations
  22  
  23  ### Advanced Features
  24  
  25  - **🎨 Dynamic Schema Composition** - Create complex forms by combining multiple schema classes
  26  - **πŸ”— Multi-step Workflows** - Chain multiple steps with automatic state management
  27  - **πŸ“± Multi-modal Support** - Handle text, structured data, and custom input types
  28  - **🌐 Internationalization Ready** - Built-in support for multiple languages
  29  - **πŸ” Expression Language Support** - Use SpEL for dynamic conditions and validations
  30  - **πŸ“ˆ Progress Tracking** - Real-time progress updates for long-running operations
  31  - **πŸ›‘οΈ Error Handling** - Comprehensive error handling and recovery mechanisms
  32  
  33  ## πŸ“¦ Installation
  34  
  35  ### Maven Dependency
  36  
  37  ```xml
  38  <dependency>
  39      <groupId>ai.driftkit</groupId>
  40      <artifactId>driftkit-chat-assistant-framework</artifactId>
  41      <version>1.0-SNAPSHOT</version>
  42  </dependency>
  43  ```
  44  
  45  ### Gradle Dependency
  46  
  47  ```gradle
  48  implementation 'ai.driftkit:driftkit-chat-assistant-framework:1.0-SNAPSHOT'
  49  ```
  50  
  51  ## Spring Boot Initialization
  52  
  53  To use the chat assistant framework in your Spring Boot application:
  54  
  55  ```java
  56  @SpringBootApplication
  57  @Import(FeignConfig.class) // Import Feign configuration for AI client
  58  @ComponentScan(basePackages = {"ai.driftkit.chat.framework"}) // Scan framework components
  59  public class YourApplication {
  60      public static void main(String[] args) {
  61          SpringApplication.run(YourApplication.class, args);
  62      }
  63  }
  64  ```
  65  
  66  Configuration in `application.yml`:
  67  
  68  ```yaml
  69  ai-props:
  70    host: "https://your-ai-service-host"
  71    username: "${AI_USERNAME}"
  72    password: "${AI_PASSWORD}"
  73  ```
  74  
  75  The module provides:
  76  - **Feign Configuration**: `FeignConfig` with basic authentication interceptor
  77  - **AI Client**: `AiClient` interface for external AI service communication
  78  - **Workflow Base Classes**: `AnnotatedWorkflow` for step-based conversational flows
  79  - **Annotations**: `@WorkflowStep`, `@AsyncStep` for defining workflow steps
  80  
  81  ## πŸš€ Quick Start
  82  
  83  ### 1. Implement Required Services
  84  
  85  The framework requires implementations of several key interfaces:
  86  
  87  ```java
  88  @Component
  89  public class MyWorkflowContextRepository implements WorkflowContextRepository {
  90      private final Map<String, WorkflowContext> contexts = new ConcurrentHashMap<>();
  91      
  92      @Override
  93      public Optional<WorkflowContext> findById(String id) {
  94          return Optional.ofNullable(contexts.get(id));
  95      }
  96      
  97      @Override
  98      public WorkflowContext saveOrUpdate(WorkflowContext context) {
  99          contexts.put(context.getContextId(), context);
 100          return context;
 101      }
 102      
 103      @Override
 104      public void deleteById(String id) {
 105          contexts.remove(id);
 106      }
 107  }
 108  
 109  @Component
 110  public class MyAsyncResponseTracker implements AsyncResponseTracker {
 111      @Override
 112      public String generateResponseId() {
 113          return UUID.randomUUID().toString();
 114      }
 115      
 116      @Override
 117      public void trackResponse(String responseId, ChatResponse response) {
 118          // Implementation for tracking responses
 119      }
 120      
 121      @Override
 122      public void updateResponseStatus(String responseId, ChatResponse response) {
 123          // Implementation for updating response status
 124      }
 125  }
 126  
 127  @Component
 128  public class MyChatHistoryService implements ChatHistoryService {
 129      @Override
 130      public void addRequest(ChatRequest request) {
 131          // Implementation for storing chat requests
 132      }
 133      
 134      @Override
 135      public void updateResponse(ChatResponse response) {
 136          // Implementation for updating responses
 137      }
 138  }
 139  
 140  @Component
 141  public class MyChatMessageService implements ChatMessageService {
 142      @Override
 143      public void addMessage(String chatId, String message, MessageType type) {
 144          // Implementation for adding messages
 145      }
 146  }
 147  ```
 148  
 149  ### 2. Define Schema Classes
 150  
 151  Create data classes that represent the structure of your workflow inputs:
 152  
 153  ```java
 154  @SchemaClass(
 155      id = "userRegistration",
 156      description = "User registration information",
 157      composable = true
 158  )
 159  public class UserRegistrationInput {
 160      @SchemaProperty(description = "User's full name", required = true)
 161      private String fullName;
 162      
 163      @SchemaProperty(description = "User's email address", required = true)
 164      private String email;
 165      
 166      @SchemaProperty(
 167          description = "User's age", 
 168          required = true,
 169          minValue = 18,
 170          maxValue = 120
 171      )
 172      private Integer age;
 173      
 174      @SchemaProperty(
 175          description = "User's role",
 176          values = {"USER", "ADMIN", "MODERATOR"}
 177      )
 178      private String role;
 179      
 180      // Getters and setters
 181  }
 182  
 183  @SchemaClass(id = "confirmationRequest", description = "Confirmation request")
 184  public class ConfirmationInput {
 185      @SchemaProperty(description = "Confirmation decision", required = true)
 186      private Boolean confirmed;
 187      
 188      @SchemaProperty(description = "Additional comments")
 189      private String comments;
 190      
 191      // Getters and setters
 192  }
 193  ```
 194  
 195  ### 3. Create a Workflow
 196  
 197  Extend `AnnotatedWorkflow` to define your workflow logic:
 198  
 199  ```java
 200  @Component
 201  public class UserRegistrationWorkflow extends AnnotatedWorkflow {
 202      
 203      @Override
 204      public String getWorkflowId() {
 205          return "user-registration";
 206      }
 207      
 208      @Override
 209      public boolean canHandle(String message, Map<String, String> properties) {
 210          return message != null && message.toLowerCase().contains("register");
 211      }
 212      
 213      @WorkflowStep(
 214          index = 1,
 215          inputClass = UserRegistrationInput.class,
 216          description = "Collect user registration information"
 217      )
 218      public StepEvent collectUserInfo(UserRegistrationInput input, WorkflowContext context) {
 219          // Validate input
 220          if (input.getAge() < 18) {
 221              return StepEvent.withError("User must be at least 18 years old");
 222          }
 223          
 224          // Store user data in context
 225          context.setContextValue("userData", input);
 226          
 227          // Return event with next step
 228          return StepEvent.of(input, ConfirmationInput.class);
 229      }
 230      
 231      @WorkflowStep(
 232          index = 2,
 233          inputClass = ConfirmationInput.class,
 234          description = "Confirm user registration"
 235      )
 236      public StepEvent confirmRegistration(ConfirmationInput input, WorkflowContext context) {
 237          UserRegistrationInput userData = context.getContextValue("userData", UserRegistrationInput.class);
 238          
 239          if (input.getConfirmed()) {
 240              // Process registration
 241              return StepEvent.withMessage("Registration completed successfully for " + userData.getFullName());
 242          } else {
 243              return StepEvent.withMessage("Registration cancelled");
 244          }
 245      }
 246  }
 247  ```
 248  
 249  ### 4. Configuration
 250  
 251  Configure the AI client in your `application.yml`:
 252  
 253  ```yaml
 254  ai-props:
 255    host: https://api.openai.com
 256    username: your-api-key
 257    password: your-api-secret
 258  
 259  spring:
 260    application:
 261      name: my-chat-assistant
 262  ```
 263  
 264  ### 5. Create a REST Controller
 265  
 266  ```java
 267  @RestController
 268  @RequestMapping("/api/chat")
 269  public class ChatController {
 270      
 271      @Autowired
 272      private WorkflowRegistry workflowRegistry;
 273      
 274      @PostMapping("/message")
 275      public ResponseEntity<ChatResponse> processMessage(@RequestBody ChatRequest request) {
 276          // Find appropriate workflow
 277          ChatWorkflow workflow = workflowRegistry.findWorkflow(request.getMessage(), request.getPropertiesMap());
 278          
 279          if (workflow != null) {
 280              ChatResponse response = workflow.processChat(request);
 281              return ResponseEntity.ok(response);
 282          } else {
 283              return ResponseEntity.badRequest().build();
 284          }
 285      }
 286  }
 287  ```
 288  
 289  ## 🎨 Advanced Features
 290  
 291  ### Asynchronous Step Execution
 292  
 293  For long-running operations, use async steps:
 294  
 295  ```java
 296  @WorkflowStep(
 297      index = 1,
 298      inputClass = DocumentProcessingInput.class,
 299      async = true,
 300      description = "Process document asynchronously"
 301  )
 302  public AsyncTaskEvent processDocument(DocumentProcessingInput input, WorkflowContext context) {
 303      // Return immediately with task information
 304      return AsyncTaskEvent.builder()
 305          .taskName("documentProcessing")
 306          .taskArgs(Map.of("documentId", input.getDocumentId()))
 307          .messageId("processing_document")
 308          .nextInputSchema(getSchemaFromClass(ProcessingResultInput.class))
 309          .build();
 310  }
 311  
 312  @AsyncStep(forStep = "documentProcessing")
 313  public StepEvent executeDocumentProcessing(Map<String, Object> taskArgs, WorkflowContext context) {
 314      String documentId = (String) taskArgs.get("documentId");
 315      
 316      // Simulate long-running processing
 317      try {
 318          Thread.sleep(5000); // Simulate processing time
 319          
 320          // Update progress
 321          return StepEvent.builder()
 322              .completed(true)
 323              .percentComplete(100)
 324              .properties(Map.of("result", "Document processed successfully"))
 325              .build();
 326      } catch (InterruptedException e) {
 327          return StepEvent.withError("Processing interrupted");
 328      }
 329  }
 330  ```
 331  
 332  ### Conditional Flow Control
 333  
 334  Use Spring Expression Language for dynamic workflow routing:
 335  
 336  ```java
 337  @WorkflowStep(
 338      index = 1,
 339      inputClass = AgeVerificationInput.class,
 340      condition = "#input.age >= 18",
 341      onTrue = "adultStep",
 342      onFalse = "minorStep"
 343  )
 344  public StepEvent checkAge(AgeVerificationInput input, WorkflowContext context) {
 345      return StepEvent.withProperty("age", input.getAge().toString());
 346  }
 347  
 348  @WorkflowStep(
 349      index = 2,
 350      id = "adultStep",
 351      inputClass = AdultServicesInput.class
 352  )
 353  public StepEvent handleAdultUser(AdultServicesInput input, WorkflowContext context) {
 354      return StepEvent.withMessage("Welcome to adult services");
 355  }
 356  
 357  @WorkflowStep(
 358      index = 3,
 359      id = "minorStep",
 360      inputClass = MinorServicesInput.class
 361  )
 362  public StepEvent handleMinorUser(MinorServicesInput input, WorkflowContext context) {
 363      return StepEvent.withMessage("Welcome to youth services");
 364  }
 365  ```
 366  
 367  ### Composable Schemas
 368  
 369  Break complex forms into multiple steps:
 370  
 371  ```java
 372  @SchemaClass(
 373      id = "customerInfo",
 374      description = "Complete customer information",
 375      composable = true
 376  )
 377  public class CustomerInfo {
 378      @SchemaProperty(description = "Customer's full name", required = true)
 379      private String fullName;
 380      
 381      @SchemaProperty(description = "Customer's email", required = true)
 382      private String email;
 383      
 384      @SchemaProperty(description = "Customer's phone number")
 385      private String phoneNumber;
 386      
 387      @SchemaProperty(description = "Customer's address")
 388      private String address;
 389      
 390      @SchemaProperty(description = "Customer's preferences")
 391      private String preferences;
 392      
 393      // Getters and setters
 394  }
 395  ```
 396  
 397  When `composable = true`, the framework automatically creates separate interaction steps for each field, making the user experience more conversational.
 398  
 399  ### Multi-Schema Steps
 400  
 401  Handle multiple input types in a single step:
 402  
 403  ```java
 404  @WorkflowStep(
 405      index = 1,
 406      inputClasses = {EmailInput.class, PhoneInput.class, SocialMediaInput.class},
 407      description = "Choose contact method"
 408  )
 409  public StepEvent selectContactMethod(Object input, WorkflowContext context) {
 410      if (input instanceof EmailInput) {
 411          return handleEmailContact((EmailInput) input, context);
 412      } else if (input instanceof PhoneInput) {
 413          return handlePhoneContact((PhoneInput) input, context);
 414      } else if (input instanceof SocialMediaInput) {
 415          return handleSocialMediaContact((SocialMediaInput) input, context);
 416      }
 417      
 418      return StepEvent.withError("Invalid contact method");
 419  }
 420  ```
 421  
 422  ### Progress Tracking
 423  
 424  Track progress for long-running operations:
 425  
 426  ```java
 427  @AsyncStep(forStep = "dataAnalysis")
 428  public StepEvent performDataAnalysis(Map<String, Object> taskArgs, WorkflowContext context) {
 429      String dataSetId = (String) taskArgs.get("dataSetId");
 430      
 431      // Initial progress
 432      updateProgress(0, "Starting analysis...");
 433      
 434      // Simulate analysis phases
 435      for (int i = 1; i <= 10; i++) {
 436          try {
 437              Thread.sleep(1000); // Simulate work
 438              int progress = i * 10;
 439              updateProgress(progress, "Processing phase " + i + "/10");
 440          } catch (InterruptedException e) {
 441              return StepEvent.withError("Analysis interrupted");
 442          }
 443      }
 444      
 445      return StepEvent.builder()
 446          .completed(true)
 447          .percentComplete(100)
 448          .properties(Map.of("result", "Analysis completed successfully"))
 449          .build();
 450  }
 451  
 452  private void updateProgress(int percent, String message) {
 453      // Update progress in tracking system
 454      // This would typically update a database or cache
 455  }
 456  ```
 457  
 458  ### Error Handling and Recovery
 459  
 460  Implement comprehensive error handling:
 461  
 462  ```java
 463  @WorkflowStep(
 464      index = 1,
 465      inputClass = PaymentInput.class,
 466      description = "Process payment"
 467  )
 468  public StepEvent processPayment(PaymentInput input, WorkflowContext context) {
 469      try {
 470          // Validate payment information
 471          if (!isValidPaymentInfo(input)) {
 472              return StepEvent.withError("Invalid payment information. Please check your details.");
 473          }
 474          
 475          // Process payment
 476          PaymentResult result = paymentService.processPayment(input);
 477          
 478          if (result.isSuccess()) {
 479              context.setContextValue("paymentId", result.getPaymentId());
 480              return StepEvent.of(result, ConfirmationInput.class);
 481          } else {
 482              return StepEvent.withError("Payment failed: " + result.getErrorMessage());
 483          }
 484          
 485      } catch (PaymentException e) {
 486          log.error("Payment processing failed", e);
 487          return StepEvent.withError("Payment processing temporarily unavailable. Please try again later.");
 488      } catch (Exception e) {
 489          log.error("Unexpected error during payment processing", e);
 490          return StepEvent.withError("An unexpected error occurred. Please contact support.");
 491      }
 492  }
 493  ```
 494  
 495  ## πŸ› οΈ Configuration Options
 496  
 497  ### AI Client Configuration
 498  
 499  ```yaml
 500  ai-props:
 501    host: https://api.openai.com
 502    username: ${OPENAI_API_KEY}
 503    password: ${OPENAI_API_SECRET}
 504    timeout: 30000
 505    max-retries: 3
 506  ```
 507  
 508  ### Workflow Configuration
 509  
 510  ```yaml
 511  driftkit:
 512    workflows:
 513      default-timeout: 300000  # 5 minutes
 514      max-steps: 50
 515      enable-debug: true
 516      session-timeout: 1800000  # 30 minutes
 517  ```
 518  
 519  ### Logging Configuration
 520  
 521  ```yaml
 522  logging:
 523    level:
 524      ai.driftkit.chat.framework: DEBUG
 525      ai.driftkit.chat.framework.workflow: INFO
 526      ai.driftkit.chat.framework.service: WARN
 527  ```
 528  
 529  ## πŸ“Š Monitoring and Observability
 530  
 531  ### Health Checks
 532  
 533  ```java
 534  @Component
 535  public class WorkflowHealthIndicator implements HealthIndicator {
 536      
 537      @Autowired
 538      private WorkflowRegistry workflowRegistry;
 539      
 540      @Override
 541      public Health health() {
 542          try {
 543              int workflowCount = workflowRegistry.getRegisteredWorkflows().size();
 544              
 545              return Health.up()
 546                  .withDetail("registered-workflows", workflowCount)
 547                  .withDetail("framework-version", "1.0-SNAPSHOT")
 548                  .build();
 549          } catch (Exception e) {
 550              return Health.down()
 551                  .withDetail("error", e.getMessage())
 552                  .build();
 553          }
 554      }
 555  }
 556  ```
 557  
 558  ### Metrics
 559  
 560  ```java
 561  @Component
 562  public class WorkflowMetrics {
 563      
 564      private final MeterRegistry meterRegistry;
 565      private final Counter workflowExecutions;
 566      private final Timer workflowDuration;
 567      
 568      public WorkflowMetrics(MeterRegistry meterRegistry) {
 569          this.meterRegistry = meterRegistry;
 570          this.workflowExecutions = Counter.builder("workflow.executions")
 571              .description("Number of workflow executions")
 572              .register(meterRegistry);
 573          this.workflowDuration = Timer.builder("workflow.duration")
 574              .description("Workflow execution duration")
 575              .register(meterRegistry);
 576      }
 577      
 578      public void recordExecution(String workflowId, Duration duration) {
 579          workflowExecutions.increment(Tags.of("workflow", workflowId));
 580          workflowDuration.record(duration);
 581      }
 582  }
 583  ```
 584  
 585  ## πŸ—οΈ Architecture Overview
 586  
 587  ### Core Components
 588  
 589  ```
 590  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 591  β”‚                        Framework Architecture                    β”‚
 592  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 593  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
 594  β”‚  β”‚   Annotations   β”‚  β”‚     Events      β”‚  β”‚     Models      β”‚ β”‚
 595  β”‚  β”‚                 β”‚  β”‚                 β”‚  β”‚                 β”‚ β”‚
 596  β”‚  β”‚ @WorkflowStep   β”‚  β”‚   StepEvent     β”‚  β”‚WorkflowContext  β”‚ β”‚
 597  β”‚  β”‚ @AsyncStep      β”‚  β”‚AsyncTaskEvent   β”‚  β”‚StepDefinition   β”‚ β”‚
 598  β”‚  β”‚ @SchemaClass    β”‚  β”‚                 β”‚  β”‚                 β”‚ β”‚
 599  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
 600  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 601  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
 602  β”‚  β”‚   Workflows     β”‚  β”‚    Services     β”‚  β”‚  Repositories   β”‚ β”‚
 603  β”‚  β”‚                 β”‚  β”‚                 β”‚  β”‚                 β”‚ β”‚
 604  β”‚  β”‚AnnotatedWorkflowβ”‚  β”‚ChatHistoryServiceβ”‚  β”‚WorkflowContext  β”‚ β”‚
 605  β”‚  β”‚ WorkflowRegistryβ”‚  β”‚ChatMessageServiceβ”‚  β”‚   Repository    β”‚ β”‚
 606  β”‚  β”‚                 β”‚  β”‚AsyncResponseTrackerβ”‚  β”‚              β”‚ β”‚
 607  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
 608  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 609  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
 610  β”‚  β”‚   AI Client     β”‚  β”‚    Utilities    β”‚  β”‚    Framework    β”‚ β”‚
 611  β”‚  β”‚                 β”‚  β”‚                 β”‚  β”‚                 β”‚ β”‚
 612  β”‚  β”‚    AiClient     β”‚  β”‚  SchemaUtils    β”‚  β”‚ ApplicationContextβ”‚ β”‚
 613  β”‚  β”‚ AIFunctionSchemaβ”‚  β”‚    AIUtils      β”‚  β”‚   Provider      β”‚ β”‚
 614  β”‚  β”‚                 β”‚  β”‚                 β”‚  β”‚                 β”‚ β”‚
 615  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
 616  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 617  ```
 618  
 619  ### Workflow Execution Flow
 620  
 621  ```
 622  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 623  β”‚                     Workflow Execution Flow                     β”‚
 624  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 625  β”‚                                                                 β”‚
 626  β”‚  1. Chat Request  β†’  2. Workflow Selection  β†’  3. Session Mgmt β”‚
 627  β”‚                                                                 β”‚
 628  β”‚          ↓                       ↓                      ↓      β”‚
 629  β”‚                                                                 β”‚
 630  β”‚  4. Step Discovery  β†’  5. Schema Generation  β†’  6. Validation  β”‚
 631  β”‚                                                                 β”‚
 632  β”‚          ↓                       ↓                      ↓      β”‚
 633  β”‚                                                                 β”‚
 634  β”‚  7. Step Execution  β†’  8. Event Processing  β†’  9. Response     β”‚
 635  β”‚                                                                 β”‚
 636  β”‚          ↓                       ↓                      ↓      β”‚
 637  β”‚                                                                 β”‚
 638  β”‚  10. State Update  β†’  11. History Tracking  β†’  12. Completion  β”‚
 639  β”‚                                                                 β”‚
 640  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 641  ```
 642  
 643  ## πŸ”Œ Integration Examples
 644  
 645  ### Spring Security Integration
 646  
 647  ```java
 648  @Component
 649  public class SecureWorkflow extends AnnotatedWorkflow {
 650      
 651      @Autowired
 652      private SecurityContextHolder securityContextHolder;
 653      
 654      @Override
 655      public boolean canHandle(String message, Map<String, String> properties) {
 656          // Check user permissions
 657          Authentication auth = securityContextHolder.getContext().getAuthentication();
 658          return auth != null && auth.getAuthorities().stream()
 659              .anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
 660      }
 661      
 662      @WorkflowStep(index = 1, inputClass = SecureInput.class)
 663      public StepEvent handleSecureOperation(SecureInput input, WorkflowContext context) {
 664          // Access user information
 665          String username = securityContextHolder.getContext().getAuthentication().getName();
 666          context.setContextValue("username", username);
 667          
 668          return StepEvent.withMessage("Secure operation completed for " + username);
 669      }
 670  }
 671  ```
 672  
 673  ### Database Integration
 674  
 675  ```java
 676  @Component
 677  public class DatabaseIntegratedWorkflow extends AnnotatedWorkflow {
 678      
 679      @Autowired
 680      private UserRepository userRepository;
 681      
 682      @Autowired
 683      private TransactionTemplate transactionTemplate;
 684      
 685      @WorkflowStep(index = 1, inputClass = UserLookupInput.class)
 686      public StepEvent findUser(UserLookupInput input, WorkflowContext context) {
 687          return transactionTemplate.execute(status -> {
 688              Optional<User> user = userRepository.findByEmail(input.getEmail());
 689              
 690              if (user.isPresent()) {
 691                  context.setContextValue("user", user.get());
 692                  return StepEvent.of(user.get(), UserDetailsInput.class);
 693              } else {
 694                  return StepEvent.withError("User not found");
 695              }
 696          });
 697      }
 698  }
 699  ```
 700  
 701  ### External API Integration
 702  
 703  ```java
 704  @Component
 705  public class ExternalAPIWorkflow extends AnnotatedWorkflow {
 706      
 707      @Autowired
 708      private RestTemplate restTemplate;
 709      
 710      @WorkflowStep(index = 1, inputClass = WeatherInput.class, async = true)
 711      public AsyncTaskEvent getWeather(WeatherInput input, WorkflowContext context) {
 712          return AsyncTaskEvent.builder()
 713              .taskName("fetchWeather")
 714              .taskArgs(Map.of("location", input.getLocation()))
 715              .messageId("fetching_weather")
 716              .build();
 717      }
 718      
 719      @AsyncStep(forStep = "fetchWeather")
 720      public StepEvent fetchWeatherData(Map<String, Object> taskArgs, WorkflowContext context) {
 721          String location = (String) taskArgs.get("location");
 722          
 723          try {
 724              String url = "https://api.weather.com/v1/current?location=" + location;
 725              WeatherResponse response = restTemplate.getForObject(url, WeatherResponse.class);
 726              
 727              return StepEvent.builder()
 728                  .completed(true)
 729                  .percentComplete(100)
 730                  .properties(Map.of(
 731                      "temperature", response.getTemperature().toString(),
 732                      "condition", response.getCondition()
 733                  ))
 734                  .build();
 735          } catch (Exception e) {
 736              return StepEvent.withError("Failed to fetch weather data: " + e.getMessage());
 737          }
 738      }
 739  }
 740  ```
 741  
 742  ## πŸ”§ Annotation Reference
 743  
 744  ### @WorkflowStep
 745  
 746  ```java
 747  @WorkflowStep(
 748      index = 1,                              // Step order (required)
 749      id = "customStepId",                    // Custom step ID (optional)
 750      description = "Step description",        // Human-readable description
 751      inputClass = InputClass.class,          // Single input class
 752      inputClasses = {Input1.class, Input2.class}, // Multiple input classes
 753      outputClasses = {Output1.class},        // Output classes
 754      nextClasses = {NextInput.class},        // Next step input classes
 755      nextSteps = {"stepId1", "stepId2"},     // Possible next steps
 756      requiresUserInput = true,               // Requires user input
 757      async = false,                          // Asynchronous execution
 758      condition = "#input.age >= 18",         // Condition expression
 759      onTrue = "adultStep",                   // Step if condition is true
 760      onFalse = "minorStep",                  // Step if condition is false
 761      inputSchemaId = "customSchema",         // Input schema ID
 762      outputSchemaId = "outputSchema"         // Output schema ID
 763  )
 764  ```
 765  
 766  ### @AsyncStep
 767  
 768  ```java
 769  @AsyncStep(
 770      forStep = "stepId",                     // Associated step ID (required)
 771      inputClass = InputClass.class,          // Input class
 772      inputClasses = {Input1.class},          // Multiple input classes
 773      outputClass = OutputClass.class,        // Output class
 774      nextClasses = {NextInput.class}         // Next step input classes
 775  )
 776  ```
 777  
 778  ### @SchemaClass
 779  
 780  ```java
 781  @SchemaClass(
 782      id = "schemaId",                        // Schema identifier
 783      description = "Schema description",      // Schema description
 784      composable = false                      // Composable schema flag
 785  )
 786  ```
 787  
 788  ### @SchemaProperty
 789  
 790  ```java
 791  @SchemaProperty(
 792      nameId = "propertyId",                  // Property identifier
 793      description = "Property description",    // Property description
 794      required = true,                        // Required flag
 795      defaultValue = "default",               // Default value
 796      minValue = 0,                          // Minimum value
 797      maxValue = 100,                        // Maximum value
 798      minLength = 1,                         // Minimum length
 799      maxLength = 255,                       // Maximum length
 800      values = {"option1", "option2"},       // Enum values
 801      array = false,                         // Array flag
 802      multiSelect = false,                   // Multi-select flag
 803      valueAsNameId = false,                 // Value as name ID flag
 804      type = String.class                    // Property type
 805  )
 806  ```
 807  
 808  ## πŸ› Troubleshooting
 809  
 810  ### Common Issues
 811  
 812  #### 1. Workflow Not Found
 813  
 814  **Problem**: Workflow not being selected for user input.
 815  
 816  **Solution**:
 817  ```java
 818  @Override
 819  public boolean canHandle(String message, Map<String, String> properties) {
 820      // Make sure this method returns true for expected inputs
 821      return message != null && message.toLowerCase().contains("expected keyword");
 822  }
 823  ```
 824  
 825  #### 2. Schema Generation Errors
 826  
 827  **Problem**: Schema not being generated correctly.
 828  
 829  **Solution**:
 830  ```java
 831  // Ensure proper annotations
 832  @SchemaClass(id = "uniqueId", description = "Clear description")
 833  public class MySchema {
 834      @SchemaProperty(description = "Field description", required = true)
 835      private String field;
 836      
 837      // Public constructor required
 838      public MySchema() {}
 839      
 840      // Getters and setters required
 841  }
 842  ```
 843  
 844  #### 3. Async Step Not Executing
 845  
 846  **Problem**: Async step not being called.
 847  
 848  **Solution**:
 849  ```java
 850  // Ensure task name matches
 851  @WorkflowStep(async = true)
 852  public AsyncTaskEvent startTask() {
 853      return AsyncTaskEvent.builder()
 854          .taskName("exactTaskName")  // Must match @AsyncStep forStep
 855          .build();
 856  }
 857  
 858  @AsyncStep(forStep = "exactTaskName")  // Must match taskName
 859  public StepEvent executeTask() {
 860      // Implementation
 861  }
 862  ```
 863  
 864  #### 4. Session State Issues
 865  
 866  **Problem**: Session state not persisting.
 867  
 868  **Solution**:
 869  ```java
 870  @Component
 871  public class MyWorkflowContextRepository implements WorkflowContextRepository {
 872      // Ensure proper implementation with actual persistence
 873      // Don't use in-memory maps in production
 874  }
 875  ```
 876  
 877  ### Debug Configuration
 878  
 879  ```yaml
 880  logging:
 881    level:
 882      ai.driftkit.chat.framework: DEBUG
 883      ai.driftkit.chat.framework.workflow.AnnotatedWorkflow: TRACE
 884      ai.driftkit.chat.framework.util.SchemaUtils: DEBUG
 885  ```
 886  
 887  ### Performance Optimization
 888  
 889  ```java
 890  // Cache schema generation
 891  @Configuration
 892  public class SchemaConfiguration {
 893      
 894      @Bean
 895      public CacheManager cacheManager() {
 896          return new ConcurrentMapCacheManager("schemas", "workflows");
 897      }
 898      
 899      @Cacheable("schemas")
 900      public AIFunctionSchema getSchema(Class<?> clazz) {
 901          return SchemaUtils.getSchemaFromClass(clazz);
 902      }
 903  }
 904  ```
 905  
 906  ## πŸ“ˆ Performance Considerations
 907  
 908  ### Best Practices
 909  
 910  1. **Schema Caching**: Schemas are automatically cached, but consider external caching for high-load scenarios
 911  2. **Async Processing**: Use async steps for operations taking more than 1-2 seconds
 912  3. **Database Optimization**: Implement efficient WorkflowContextRepository with proper indexing
 913  4. **Memory Management**: Clean up old sessions periodically
 914  5. **Connection Pooling**: Configure appropriate connection pools for AI client
 915  
 916  ### Monitoring
 917  
 918  ```java
 919  @Component
 920  public class WorkflowPerformanceMonitor {
 921      
 922      @EventListener
 923      public void handleStepExecution(StepExecutionEvent event) {
 924          long duration = event.getExecutionTime();
 925          if (duration > 1000) { // Log slow operations
 926              log.warn("Slow step execution: {} took {}ms", 
 927                  event.getStepId(), duration);
 928          }
 929      }
 930  }
 931  ```
 932  
 933  ## 🀝 Contributing
 934  
 935  1. Fork the repository
 936  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
 937  3. Commit your changes (`git commit -m 'Add some amazing feature'`)
 938  4. Push to the branch (`git push origin feature/amazing-feature`)
 939  5. Open a Pull Request
 940  
 941  ## πŸ“„ License
 942  
 943  This project is part of the DriftKit framework and is licensed under the terms specified in the main DriftKit project.
 944  
 945  ## πŸ†˜ Support
 946  
 947  - **Documentation**: [DriftKit Documentation](https://driftkit.ai/docs)
 948  - **Issues**: [GitHub Issues](https://github.com/driftkit/framework/issues)
 949  - **Discussions**: [GitHub Discussions](https://github.com/driftkit/framework/discussions)
 950  - **Email**: support@driftkit.ai
 951  
 952  ## πŸ”— Related Projects
 953  
 954  - [DriftKit Common](../driftkit-common) - Core utilities and shared components
 955  - [DriftKit Workflows](../driftkit-workflows) - Workflow execution engine
 956  - [DriftKit Context Engineering](../driftkit-context-engineering) - Prompt management system
 957  - [DriftKit Vector](../driftkit-vector) - Vector storage and retrieval
 958  
 959  ## 🌟 Real-World Demo Examples
 960  
 961  ### 1. E-commerce Order Assistant
 962  
 963  This example demonstrates a complete e-commerce order workflow with product search, cart management, and checkout.
 964  
 965  ```java
 966  @Component
 967  public class EcommerceOrderWorkflow extends AnnotatedWorkflow {
 968      
 969      @Autowired
 970      private ProductService productService;
 971      
 972      @Autowired
 973      private InventoryService inventoryService;
 974      
 975      @Autowired
 976      private PaymentService paymentService;
 977      
 978      @Autowired
 979      private OrderService orderService;
 980      
 981      @Override
 982      public String getWorkflowId() {
 983          return "ecommerce-order";
 984      }
 985      
 986      @Override
 987      public boolean canHandle(String message, Map<String, String> properties) {
 988          return message.toLowerCase().contains("order") || 
 989                 message.toLowerCase().contains("buy") ||
 990                 message.toLowerCase().contains("purchase");
 991      }
 992      
 993      @WorkflowStep(
 994          index = 1,
 995          inputClass = ProductSearchInput.class,
 996          description = "Search for products"
 997      )
 998      public StepEvent searchProducts(ProductSearchInput input, WorkflowContext context) {
 999          List<Product> products = productService.searchProducts(input.getQuery(), input.getCategory());
1000          
1001          if (products.isEmpty()) {
1002              return StepEvent.withMessage("No products found. Try a different search term.");
1003          }
1004          
1005          context.setContextValue("searchResults", products);
1006          
1007          // Generate product selection options
1008          ProductSelectionInput selectionInput = new ProductSelectionInput();
1009          selectionInput.setProducts(products.stream()
1010              .map(p -> new ProductOption(p.getId(), p.getName(), p.getPrice()))
1011              .collect(Collectors.toList()));
1012          
1013          return StepEvent.of(selectionInput, ProductSelectionInput.class);
1014      }
1015      
1016      @WorkflowStep(
1017          index = 2,
1018          inputClass = ProductSelectionInput.class,
1019          description = "Select products and quantities"
1020      )
1021      public StepEvent addToCart(ProductSelectionInput input, WorkflowContext context) {
1022          Cart cart = context.getContextValue("cart", Cart.class);
1023          if (cart == null) {
1024              cart = new Cart();
1025          }
1026          
1027          // Add selected products to cart
1028          for (SelectedProduct selected : input.getSelectedProducts()) {
1029              Product product = productService.getProduct(selected.getProductId());
1030              
1031              // Check inventory
1032              if (!inventoryService.isAvailable(product.getId(), selected.getQuantity())) {
1033                  return StepEvent.withError(
1034                      String.format("%s is out of stock. Only %d available.",
1035                          product.getName(),
1036                          inventoryService.getAvailableQuantity(product.getId()))
1037                  );
1038              }
1039              
1040              cart.addItem(product, selected.getQuantity());
1041          }
1042          
1043          context.setContextValue("cart", cart);
1044          
1045          // Show cart summary and ask for next action
1046          CartSummary summary = new CartSummary();
1047          summary.setItems(cart.getItems());
1048          summary.setSubtotal(cart.getSubtotal());
1049          summary.setTax(cart.getTax());
1050          summary.setTotal(cart.getTotal());
1051          
1052          return StepEvent.of(summary, CartActionInput.class);
1053      }
1054      
1055      @WorkflowStep(
1056          index = 3,
1057          inputClass = CartActionInput.class,
1058          description = "Handle cart actions",
1059          condition = "#input.action == 'CHECKOUT'",
1060          onTrue = "collectShipping",
1061          onFalse = "continueShopping"
1062      )
1063      public StepEvent handleCartAction(CartActionInput input, WorkflowContext context) {
1064          return StepEvent.withProperty("action", input.getAction());
1065      }
1066      
1067      @WorkflowStep(
1068          index = 4,
1069          id = "collectShipping",
1070          inputClass = ShippingInput.class,
1071          description = "Collect shipping information"
1072      )
1073      public StepEvent collectShippingInfo(ShippingInput input, WorkflowContext context) {
1074          // Validate address
1075          if (!addressService.validateAddress(input)) {
1076              return StepEvent.withError("Invalid address. Please check and try again.");
1077          }
1078          
1079          // Calculate shipping options
1080          Cart cart = context.getContextValue("cart", Cart.class);
1081          List<ShippingOption> options = shippingService.calculateOptions(cart, input.getAddress());
1082          
1083          context.setContextValue("shippingAddress", input);
1084          
1085          return StepEvent.of(
1086              new ShippingSelectionInput(options),
1087              ShippingSelectionInput.class
1088          );
1089      }
1090      
1091      @WorkflowStep(
1092          index = 5,
1093          inputClass = ShippingSelectionInput.class,
1094          description = "Select shipping method"
1095      )
1096      public StepEvent selectShipping(ShippingSelectionInput input, WorkflowContext context) {
1097          context.setContextValue("shippingMethod", input.getSelectedOption());
1098          
1099          // Update cart with shipping cost
1100          Cart cart = context.getContextValue("cart", Cart.class);
1101          cart.setShippingCost(input.getSelectedOption().getCost());
1102          
1103          return StepEvent.of(new PaymentInput(), PaymentInput.class);
1104      }
1105      
1106      @WorkflowStep(
1107          index = 6,
1108          inputClass = PaymentInput.class,
1109          description = "Process payment",
1110          async = true
1111      )
1112      public AsyncTaskEvent processPayment(PaymentInput input, WorkflowContext context) {
1113          Cart cart = context.getContextValue("cart", Cart.class);
1114          
1115          return AsyncTaskEvent.builder()
1116              .taskName("paymentProcessing")
1117              .taskArgs(Map.of(
1118                  "paymentMethod", input.getPaymentMethod(),
1119                  "amount", cart.getTotal(),
1120                  "cartId", cart.getId()
1121              ))
1122              .messageId("processing_payment")
1123              .nextInputSchema(getSchemaFromClass(OrderConfirmationInput.class))
1124              .build();
1125      }
1126      
1127      @AsyncStep(forStep = "paymentProcessing")
1128      public StepEvent executePayment(Map<String, Object> taskArgs, WorkflowContext context) {
1129          try {
1130              PaymentResult result = paymentService.processPayment(
1131                  (String) taskArgs.get("paymentMethod"),
1132                  (BigDecimal) taskArgs.get("amount")
1133              );
1134              
1135              if (result.isSuccess()) {
1136                  // Create order
1137                  Cart cart = context.getContextValue("cart", Cart.class);
1138                  ShippingInput shipping = context.getContextValue("shippingAddress", ShippingInput.class);
1139                  
1140                  Order order = orderService.createOrder(cart, shipping, result.getTransactionId());
1141                  context.setContextValue("orderId", order.getId());
1142                  
1143                  // Reserve inventory
1144                  inventoryService.reserveItems(order.getItems());
1145                  
1146                  return StepEvent.builder()
1147                      .completed(true)
1148                      .percentComplete(100)
1149                      .properties(Map.of(
1150                          "orderId", order.getId(),
1151                          "orderNumber", order.getOrderNumber(),
1152                          "estimatedDelivery", order.getEstimatedDelivery()
1153                      ))
1154                      .build();
1155              } else {
1156                  return StepEvent.withError("Payment failed: " + result.getErrorMessage());
1157              }
1158          } catch (Exception e) {
1159              return StepEvent.withError("Payment processing error: " + e.getMessage());
1160          }
1161      }
1162      
1163      @WorkflowStep(
1164          index = 7,
1165          id = "continueShopping",
1166          inputClass = ProductSearchInput.class,
1167          description = "Continue shopping"
1168      )
1169      public StepEvent continueShopping(ProductSearchInput input, WorkflowContext context) {
1170          // Loop back to product search
1171          return searchProducts(input, context);
1172      }
1173  }
1174  ```
1175  
1176  ### 2. Healthcare Appointment Booking
1177  
1178  This example shows a healthcare appointment booking system with doctor selection, availability checking, and confirmation.
1179  
1180  ```java
1181  @Component
1182  public class HealthcareAppointmentWorkflow extends AnnotatedWorkflow {
1183      
1184      @Autowired
1185      private DoctorService doctorService;
1186      
1187      @Autowired
1188      private AppointmentService appointmentService;
1189      
1190      @Autowired
1191      private NotificationService notificationService;
1192      
1193      @Override
1194      public String getWorkflowId() {
1195          return "healthcare-appointment";
1196      }
1197      
1198      @Override
1199      public boolean canHandle(String message, Map<String, String> properties) {
1200          return message.toLowerCase().contains("appointment") || 
1201                 message.toLowerCase().contains("doctor") ||
1202                 message.toLowerCase().contains("schedule");
1203      }
1204      
1205      @WorkflowStep(
1206          index = 1,
1207          inputClass = SymptomInput.class,
1208          description = "Describe your symptoms or reason for visit"
1209      )
1210      public StepEvent collectSymptoms(SymptomInput input, WorkflowContext context) {
1211          // Analyze symptoms to suggest appropriate specialists
1212          List<String> specialties = symptomAnalyzer.suggestSpecialties(input.getSymptoms());
1213          
1214          context.setContextValue("symptoms", input.getSymptoms());
1215          context.setContextValue("urgency", input.getUrgencyLevel());
1216          
1217          // For urgent cases, suggest immediate care
1218          if (input.getUrgencyLevel() == UrgencyLevel.EMERGENCY) {
1219              return StepEvent.withMessage(
1220                  "This seems urgent. Please visit the emergency room or call 911."
1221              );
1222          }
1223          
1224          // Find available doctors
1225          List<Doctor> doctors = doctorService.findBySpecialties(specialties);
1226          
1227          DoctorSelectionInput selection = new DoctorSelectionInput();
1228          selection.setDoctors(doctors.stream()
1229              .map(d -> new DoctorOption(
1230                  d.getId(),
1231                  d.getName(),
1232                  d.getSpecialty(),
1233                  d.getRating(),
1234                  d.getNextAvailable()
1235              ))
1236              .collect(Collectors.toList()));
1237          
1238          return StepEvent.of(selection, DoctorSelectionInput.class);
1239      }
1240      
1241      @WorkflowStep(
1242          index = 2,
1243          inputClass = DoctorSelectionInput.class,
1244          description = "Select a doctor"
1245      )
1246      public StepEvent selectDoctor(DoctorSelectionInput input, WorkflowContext context) {
1247          Doctor doctor = doctorService.getDoctor(input.getSelectedDoctorId());
1248          context.setContextValue("selectedDoctor", doctor);
1249          
1250          // Get available time slots for next 2 weeks
1251          LocalDate startDate = LocalDate.now().plusDays(1);
1252          LocalDate endDate = startDate.plusWeeks(2);
1253          
1254          List<TimeSlot> availableSlots = appointmentService.getAvailableSlots(
1255              doctor.getId(),
1256              startDate,
1257              endDate
1258          );
1259          
1260          if (availableSlots.isEmpty()) {
1261              return StepEvent.withMessage(
1262                  "No appointments available in the next 2 weeks. Would you like to see other doctors?"
1263              );
1264          }
1265          
1266          TimeSlotSelectionInput slotSelection = new TimeSlotSelectionInput();
1267          slotSelection.setTimeSlots(availableSlots);
1268          slotSelection.setDoctorName(doctor.getName());
1269          
1270          return StepEvent.of(slotSelection, TimeSlotSelectionInput.class);
1271      }
1272      
1273      @WorkflowStep(
1274          index = 3,
1275          inputClass = TimeSlotSelectionInput.class,
1276          description = "Select appointment time"
1277      )
1278      public StepEvent selectTimeSlot(TimeSlotSelectionInput input, WorkflowContext context) {
1279          TimeSlot selectedSlot = input.getSelectedSlot();
1280          Doctor doctor = context.getContextValue("selectedDoctor", Doctor.class);
1281          
1282          // Check if slot is still available (prevent race conditions)
1283          if (!appointmentService.isSlotAvailable(doctor.getId(), selectedSlot)) {
1284              return StepEvent.withError(
1285                  "Sorry, that time slot was just booked. Please select another time."
1286              );
1287          }
1288          
1289          context.setContextValue("selectedTimeSlot", selectedSlot);
1290          
1291          // Collect patient information
1292          return StepEvent.of(new PatientInfoInput(), PatientInfoInput.class);
1293      }
1294      
1295      @WorkflowStep(
1296          index = 4,
1297          inputClass = PatientInfoInput.class,
1298          description = "Provide patient information"
1299      )
1300      public StepEvent collectPatientInfo(PatientInfoInput input, WorkflowContext context) {
1301          // Validate insurance if provided
1302          if (input.hasInsurance()) {
1303              InsuranceValidation validation = insuranceService.validate(
1304                  input.getInsuranceProvider(),
1305                  input.getInsuranceNumber()
1306              );
1307              
1308              if (!validation.isValid()) {
1309                  return StepEvent.withError(
1310                      "Insurance validation failed: " + validation.getErrorMessage()
1311                  );
1312              }
1313              
1314              context.setContextValue("insuranceCoverage", validation.getCoverageInfo());
1315          }
1316          
1317          context.setContextValue("patientInfo", input);
1318          
1319          // Show appointment summary for confirmation
1320          AppointmentSummary summary = buildAppointmentSummary(context);
1321          
1322          return StepEvent.of(summary, AppointmentConfirmationInput.class);
1323      }
1324      
1325      @WorkflowStep(
1326          index = 5,
1327          inputClass = AppointmentConfirmationInput.class,
1328          description = "Confirm appointment booking",
1329          async = true
1330      )
1331      public AsyncTaskEvent confirmAppointment(AppointmentConfirmationInput input, WorkflowContext context) {
1332          if (!input.isConfirmed()) {
1333              return AsyncTaskEvent.builder()
1334                  .taskName("cancelBooking")
1335                  .messageId("booking_cancelled")
1336                  .build();
1337          }
1338          
1339          return AsyncTaskEvent.builder()
1340              .taskName("bookAppointment")
1341              .taskArgs(Map.of(
1342                  "doctorId", context.getContextValue("selectedDoctor", Doctor.class).getId(),
1343                  "timeSlot", context.getContextValue("selectedTimeSlot"),
1344                  "patientInfo", context.getContextValue("patientInfo"),
1345                  "symptoms", context.getContextValue("symptoms")
1346              ))
1347              .messageId("booking_appointment")
1348              .build();
1349      }
1350      
1351      @AsyncStep(forStep = "bookAppointment")
1352      public StepEvent executeBooking(Map<String, Object> taskArgs, WorkflowContext context) {
1353          try {
1354              // Create appointment
1355              Appointment appointment = appointmentService.createAppointment(
1356                  (String) taskArgs.get("doctorId"),
1357                  (TimeSlot) taskArgs.get("timeSlot"),
1358                  (PatientInfo) taskArgs.get("patientInfo"),
1359                  (String) taskArgs.get("symptoms")
1360              );
1361              
1362              // Send confirmation notifications
1363              notificationService.sendAppointmentConfirmation(
1364                  appointment,
1365                  NotificationChannel.EMAIL,
1366                  NotificationChannel.SMS
1367              );
1368              
1369              // Add to calendar
1370              String calendarLink = calendarService.createCalendarEvent(appointment);
1371              
1372              return StepEvent.builder()
1373                  .completed(true)
1374                  .percentComplete(100)
1375                  .properties(Map.of(
1376                      "appointmentId", appointment.getId(),
1377                      "confirmationNumber", appointment.getConfirmationNumber(),
1378                      "calendarLink", calendarLink,
1379                      "message", "Appointment booked successfully! Confirmation sent to your email and phone."
1380                  ))
1381                  .build();
1382                  
1383          } catch (Exception e) {
1384              log.error("Failed to book appointment", e);
1385              return StepEvent.withError("Failed to book appointment: " + e.getMessage());
1386          }
1387      }
1388      
1389      private AppointmentSummary buildAppointmentSummary(WorkflowContext context) {
1390          Doctor doctor = context.getContextValue("selectedDoctor", Doctor.class);
1391          TimeSlot timeSlot = context.getContextValue("selectedTimeSlot", TimeSlot.class);
1392          PatientInfo patient = context.getContextValue("patientInfo", PatientInfo.class);
1393          InsuranceCoverage coverage = context.getContextValue("insuranceCoverage", InsuranceCoverage.class);
1394          
1395          AppointmentSummary summary = new AppointmentSummary();
1396          summary.setDoctorName(doctor.getName());
1397          summary.setDoctorSpecialty(doctor.getSpecialty());
1398          summary.setDateTime(timeSlot.getDateTime());
1399          summary.setDuration(timeSlot.getDuration());
1400          summary.setLocation(doctor.getOfficeAddress());
1401          summary.setPatientName(patient.getFullName());
1402          summary.setEstimatedCost(calculateEstimatedCost(doctor, coverage));
1403          summary.setInsuranceCovered(coverage != null);
1404          
1405          return summary;
1406      }
1407  }
1408  ```
1409  
1410  ### 3. Banking Virtual Assistant
1411  
1412  This example demonstrates a banking assistant that handles account inquiries, transfers, and bill payments.
1413  
1414  ```java
1415  @Component
1416  public class BankingAssistantWorkflow extends AnnotatedWorkflow {
1417      
1418      @Autowired
1419      private AccountService accountService;
1420      
1421      @Autowired
1422      private TransactionService transactionService;
1423      
1424      @Autowired
1425      private SecurityService securityService;
1426      
1427      @Autowired
1428      private BillPayService billPayService;
1429      
1430      @Override
1431      public String getWorkflowId() {
1432          return "banking-assistant";
1433      }
1434      
1435      @Override
1436      public boolean canHandle(String message, Map<String, String> properties) {
1437          // Require authenticated user
1438          return properties.containsKey("userId") && 
1439                 (message.toLowerCase().contains("account") ||
1440                  message.toLowerCase().contains("transfer") ||
1441                  message.toLowerCase().contains("balance") ||
1442                  message.toLowerCase().contains("pay"));
1443      }
1444      
1445      @WorkflowStep(
1446          index = 1,
1447          inputClass = BankingActionInput.class,
1448          description = "What would you like to do?"
1449      )
1450      public StepEvent selectAction(BankingActionInput input, WorkflowContext context) {
1451          String userId = context.getProperty("userId");
1452          
1453          // Verify user session
1454          if (!securityService.isSessionValid(userId)) {
1455              return StepEvent.withError("Session expired. Please log in again.");
1456          }
1457          
1458          switch (input.getAction()) {
1459              case CHECK_BALANCE:
1460                  return checkBalance(userId, context);
1461              case TRANSFER_MONEY:
1462                  return StepEvent.of(new TransferInput(), TransferInput.class);
1463              case PAY_BILL:
1464                  return StepEvent.of(new BillSelectionInput(), BillSelectionInput.class);
1465              case VIEW_TRANSACTIONS:
1466                  return viewTransactions(userId, context);
1467              default:
1468                  return StepEvent.withError("Unknown action");
1469          }
1470      }
1471      
1472      private StepEvent checkBalance(String userId, WorkflowContext context) {
1473          List<Account> accounts = accountService.getUserAccounts(userId);
1474          
1475          BalanceSummary summary = new BalanceSummary();
1476          summary.setAccounts(accounts.stream()
1477              .map(a -> new AccountBalance(
1478                  a.getAccountNumber(),
1479                  a.getType(),
1480                  a.getBalance(),
1481                  a.getAvailableBalance()
1482              ))
1483              .collect(Collectors.toList()));
1484          summary.setTotalBalance(accounts.stream()
1485              .map(Account::getBalance)
1486              .reduce(BigDecimal.ZERO, BigDecimal::add));
1487          
1488          return StepEvent.withMessage(
1489              "Your account balances:\n" + formatBalances(summary)
1490          );
1491      }
1492      
1493      @WorkflowStep(
1494          index = 2,
1495          inputClass = TransferInput.class,
1496          description = "Enter transfer details"
1497      )
1498      public StepEvent setupTransfer(TransferInput input, WorkflowContext context) {
1499          String userId = context.getProperty("userId");
1500          
1501          // Validate source account
1502          Account sourceAccount = accountService.getAccount(userId, input.getFromAccountId());
1503          if (sourceAccount == null) {
1504              return StepEvent.withError("Invalid source account");
1505          }
1506          
1507          // Check balance
1508          if (sourceAccount.getAvailableBalance().compareTo(input.getAmount()) < 0) {
1509              return StepEvent.withError(
1510                  String.format("Insufficient funds. Available: $%.2f", 
1511                      sourceAccount.getAvailableBalance())
1512              );
1513          }
1514          
1515          // Validate destination
1516          TransferValidation validation = transactionService.validateTransfer(
1517              sourceAccount,
1518              input.getToAccount(),
1519              input.getAmount()
1520          );
1521          
1522          if (!validation.isValid()) {
1523              return StepEvent.withError(validation.getErrorMessage());
1524          }
1525          
1526          context.setContextValue("transferDetails", input);
1527          context.setContextValue("sourceAccount", sourceAccount);
1528          
1529          // Require 2FA for transfers
1530          return StepEvent.of(new TwoFactorInput(), TwoFactorInput.class);
1531      }
1532      
1533      @WorkflowStep(
1534          index = 3,
1535          inputClass = TwoFactorInput.class,
1536          description = "Enter verification code",
1537          async = true
1538      )
1539      public AsyncTaskEvent verifyAndTransfer(TwoFactorInput input, WorkflowContext context) {
1540          String userId = context.getProperty("userId");
1541          
1542          // Verify 2FA code
1543          if (!securityService.verify2FA(userId, input.getCode())) {
1544              return AsyncTaskEvent.builder()
1545                  .taskName("transferFailed")
1546                  .messageId("invalid_2fa")
1547                  .build();
1548          }
1549          
1550          TransferInput transfer = context.getContextValue("transferDetails", TransferInput.class);
1551          
1552          return AsyncTaskEvent.builder()
1553              .taskName("executeTransfer")
1554              .taskArgs(Map.of(
1555                  "userId", userId,
1556                  "transfer", transfer
1557              ))
1558              .messageId("processing_transfer")
1559              .build();
1560      }
1561      
1562      @AsyncStep(forStep = "executeTransfer")
1563      public StepEvent performTransfer(Map<String, Object> taskArgs, WorkflowContext context) {
1564          try {
1565              String userId = (String) taskArgs.get("userId");
1566              TransferInput transfer = (TransferInput) taskArgs.get("transfer");
1567              
1568              // Execute transfer
1569              TransactionResult result = transactionService.executeTransfer(
1570                  userId,
1571                  transfer.getFromAccountId(),
1572                  transfer.getToAccount(),
1573                  transfer.getAmount(),
1574                  transfer.getMemo()
1575              );
1576              
1577              if (result.isSuccess()) {
1578                  // Send confirmation
1579                  notificationService.sendTransferConfirmation(userId, result);
1580                  
1581                  return StepEvent.builder()
1582                      .completed(true)
1583                      .percentComplete(100)
1584                      .properties(Map.of(
1585                          "transactionId", result.getTransactionId(),
1586                          "confirmationNumber", result.getConfirmationNumber(),
1587                          "message", String.format(
1588                              "Transfer of $%.2f completed successfully. Confirmation: %s",
1589                              transfer.getAmount(),
1590                              result.getConfirmationNumber()
1591                          )
1592                      ))
1593                      .build();
1594              } else {
1595                  return StepEvent.withError("Transfer failed: " + result.getErrorMessage());
1596              }
1597              
1598          } catch (Exception e) {
1599              log.error("Transfer processing error", e);
1600              return StepEvent.withError("Transfer processing error. Please try again.");
1601          }
1602      }
1603      
1604      @WorkflowStep(
1605          index = 2,
1606          inputClass = BillSelectionInput.class,
1607          description = "Select bill to pay"
1608      )
1609      public StepEvent selectBill(BillSelectionInput input, WorkflowContext context) {
1610          String userId = context.getProperty("userId");
1611          
1612          if (input.getAction() == BillAction.VIEW_BILLS) {
1613              List<Bill> bills = billPayService.getUpcomingBills(userId);
1614              
1615              BillListDisplay display = new BillListDisplay();
1616              display.setBills(bills);
1617              display.setTotalDue(bills.stream()
1618                  .map(Bill::getAmountDue)
1619                  .reduce(BigDecimal.ZERO, BigDecimal::add));
1620              
1621              return StepEvent.of(display, BillPaymentInput.class);
1622          } else {
1623              // Add new payee
1624              return StepEvent.of(new PayeeInput(), PayeeInput.class);
1625          }
1626      }
1627  }
1628  ```
1629  
1630  ### 4. Travel Planning Assistant
1631  
1632  This example shows a comprehensive travel planning workflow with flight search, hotel booking, and itinerary creation.
1633  
1634  ```java
1635  @Component
1636  public class TravelPlanningWorkflow extends AnnotatedWorkflow {
1637      
1638      @Autowired
1639      private FlightSearchService flightService;
1640      
1641      @Autowired
1642      private HotelSearchService hotelService;
1643      
1644      @Autowired
1645      private ActivityService activityService;
1646      
1647      @Autowired
1648      private ItineraryService itineraryService;
1649      
1650      @Override
1651      public String getWorkflowId() {
1652          return "travel-planning";
1653      }
1654      
1655      @WorkflowStep(
1656          index = 1,
1657          inputClass = TravelBasicsInput.class,
1658          description = "Tell me about your travel plans"
1659      )
1660      public StepEvent collectTravelBasics(TravelBasicsInput input, WorkflowContext context) {
1661          // Validate travel dates
1662          if (input.getDepartureDate().isBefore(LocalDate.now())) {
1663              return StepEvent.withError("Departure date must be in the future");
1664          }
1665          
1666          if (input.getReturnDate().isBefore(input.getDepartureDate())) {
1667              return StepEvent.withError("Return date must be after departure date");
1668          }
1669          
1670          context.setContextValue("travelBasics", input);
1671          
1672          // Check if it's a popular destination with package deals
1673          List<TravelPackage> packages = packageService.findPackages(
1674              input.getOrigin(),
1675              input.getDestination(),
1676              input.getDepartureDate(),
1677              input.getReturnDate(),
1678              input.getTravelerCount()
1679          );
1680          
1681          if (!packages.isEmpty()) {
1682              PackageSelectionInput packageInput = new PackageSelectionInput();
1683              packageInput.setPackages(packages);
1684              packageInput.setAllowCustom(true);
1685              
1686              return StepEvent.of(packageInput, PackageSelectionInput.class);
1687          }
1688          
1689          // No packages, proceed with custom planning
1690          return StepEvent.of(new FlightPreferenceInput(), FlightPreferenceInput.class);
1691      }
1692      
1693      @WorkflowStep(
1694          index = 2,
1695          inputClass = FlightPreferenceInput.class,
1696          description = "Flight preferences",
1697          async = true
1698      )
1699      public AsyncTaskEvent searchFlights(FlightPreferenceInput input, WorkflowContext context) {
1700          TravelBasicsInput basics = context.getContextValue("travelBasics", TravelBasicsInput.class);
1701          
1702          return AsyncTaskEvent.builder()
1703              .taskName("flightSearch")
1704              .taskArgs(Map.of(
1705                  "origin", basics.getOrigin(),
1706                  "destination", basics.getDestination(),
1707                  "departureDate", basics.getDepartureDate(),
1708                  "returnDate", basics.getReturnDate(),
1709                  "travelers", basics.getTravelerCount(),
1710                  "preferences", input
1711              ))
1712              .messageId("searching_flights")
1713              .nextInputSchema(getSchemaFromClass(FlightSelectionInput.class))
1714              .build();
1715      }
1716      
1717      @AsyncStep(forStep = "flightSearch")
1718      public StepEvent performFlightSearch(Map<String, Object> taskArgs, WorkflowContext context) {
1719          try {
1720              // Search flights with progress updates
1721              updateProgress(0, "Searching flights...");
1722              
1723              FlightSearchCriteria criteria = buildSearchCriteria(taskArgs);
1724              List<FlightOption> flights = flightService.searchFlights(criteria);
1725              
1726              updateProgress(50, "Found " + flights.size() + " flights");
1727              
1728              // Sort by price and duration
1729              flights.sort(Comparator
1730                  .comparing(FlightOption::getTotalPrice)
1731                  .thenComparing(FlightOption::getTotalDuration));
1732              
1733              updateProgress(75, "Analyzing best options...");
1734              
1735              // Get top 5 options
1736              List<FlightOption> topFlights = flights.stream()
1737                  .limit(5)
1738                  .collect(Collectors.toList());
1739              
1740              context.setContextValue("flightOptions", topFlights);
1741              
1742              FlightSelectionInput selection = new FlightSelectionInput();
1743              selection.setFlights(topFlights);
1744              
1745              return StepEvent.builder()
1746                  .completed(true)
1747                  .percentComplete(100)
1748                  .data(selection)
1749                  .build();
1750                  
1751          } catch (Exception e) {
1752              return StepEvent.withError("Flight search failed: " + e.getMessage());
1753          }
1754      }
1755      
1756      @WorkflowStep(
1757          index = 3,
1758          inputClass = FlightSelectionInput.class,
1759          description = "Select your flights"
1760      )
1761      public StepEvent selectFlights(FlightSelectionInput input, WorkflowContext context) {
1762          FlightOption selected = input.getSelectedFlight();
1763          context.setContextValue("selectedFlight", selected);
1764          
1765          // Calculate hotel search parameters based on flight times
1766          LocalDateTime arrivalTime = selected.getOutboundArrival();
1767          LocalDateTime departureTime = selected.getReturnDeparture();
1768          
1769          HotelSearchInput hotelSearch = new HotelSearchInput();
1770          hotelSearch.setCheckIn(arrivalTime.toLocalDate());
1771          hotelSearch.setCheckOut(departureTime.toLocalDate());
1772          hotelSearch.setLocation(selected.getDestinationCity());
1773          
1774          return StepEvent.of(hotelSearch, HotelPreferenceInput.class);
1775      }
1776      
1777      @WorkflowStep(
1778          index = 4,
1779          inputClass = HotelPreferenceInput.class,
1780          description = "Hotel preferences"
1781      )
1782      public StepEvent searchHotels(HotelPreferenceInput input, WorkflowContext context) {
1783          HotelSearchInput searchParams = context.getContextValue("hotelSearch", HotelSearchInput.class);
1784          
1785          List<Hotel> hotels = hotelService.searchHotels(
1786              searchParams.getLocation(),
1787              searchParams.getCheckIn(),
1788              searchParams.getCheckOut(),
1789              input
1790          );
1791          
1792          // Filter by preferences
1793          hotels = hotels.stream()
1794              .filter(h -> h.getStarRating() >= input.getMinStarRating())
1795              .filter(h -> h.getPricePerNight().compareTo(input.getMaxPricePerNight()) <= 0)
1796              .sorted(Comparator.comparing(Hotel::getGuestRating).reversed())
1797              .limit(5)
1798              .collect(Collectors.toList());
1799          
1800          HotelSelectionInput selection = new HotelSelectionInput();
1801          selection.setHotels(hotels);
1802          
1803          return StepEvent.of(selection, HotelSelectionInput.class);
1804      }
1805      
1806      @WorkflowStep(
1807          index = 5,
1808          inputClass = ActivityPreferenceInput.class,
1809          description = "What activities interest you?"
1810      )
1811      public StepEvent suggestActivities(ActivityPreferenceInput input, WorkflowContext context) {
1812          TravelBasicsInput basics = context.getContextValue("travelBasics", TravelBasicsInput.class);
1813          
1814          List<Activity> activities = activityService.findActivities(
1815              basics.getDestination(),
1816              input.getInterests(),
1817              input.getActivityLevel(),
1818              input.getBudgetPerDay()
1819          );
1820          
1821          // Group by day
1822          Map<LocalDate, List<Activity>> dailyActivities = groupActivitiesByDay(
1823              activities,
1824              basics.getDepartureDate(),
1825              basics.getReturnDate()
1826          );
1827          
1828          ItineraryDraft draft = new ItineraryDraft();
1829          draft.setDailyActivities(dailyActivities);
1830          draft.setFlight(context.getContextValue("selectedFlight", FlightOption.class));
1831          draft.setHotel(context.getContextValue("selectedHotel", Hotel.class));
1832          
1833          return StepEvent.of(draft, ItineraryConfirmationInput.class);
1834      }
1835      
1836      @WorkflowStep(
1837          index = 6,
1838          inputClass = ItineraryConfirmationInput.class,
1839          description = "Review and confirm your itinerary"
1840      )
1841      public StepEvent confirmItinerary(ItineraryConfirmationInput input, WorkflowContext context) {
1842          if (!input.isConfirmed()) {
1843              return StepEvent.withMessage("No problem! Let me know if you'd like to plan another trip.");
1844          }
1845          
1846          // Create final itinerary
1847          Itinerary itinerary = itineraryService.createItinerary(context);
1848          
1849          // Generate booking links
1850          BookingLinks links = bookingService.generateBookingLinks(itinerary);
1851          
1852          // Create calendar events
1853          String calendarFile = calendarService.exportItinerary(itinerary);
1854          
1855          return StepEvent.builder()
1856              .completed(true)
1857              .properties(Map.of(
1858                  "itineraryId", itinerary.getId(),
1859                  "totalCost", itinerary.getTotalCost(),
1860                  "bookingLinks", links,
1861                  "calendarFile", calendarFile,
1862                  "message", "Your travel itinerary is ready! Check your email for booking links and calendar invites."
1863              ))
1864              .build();
1865      }
1866  }
1867  ```
1868  
1869  ### 5. HR Onboarding Assistant
1870  
1871  This example demonstrates an employee onboarding workflow with document collection, training scheduling, and equipment setup.
1872  
1873  ```java
1874  @Component  
1875  public class HROnboardingWorkflow extends AnnotatedWorkflow {
1876      
1877      @Autowired
1878      private EmployeeService employeeService;
1879      
1880      @Autowired
1881      private DocumentService documentService;
1882      
1883      @Autowired
1884      private TrainingService trainingService;
1885      
1886      @Autowired
1887      private ITService itService;
1888      
1889      @Autowired
1890      private FacilitiesService facilitiesService;
1891      
1892      @Override
1893      public String getWorkflowId() {
1894          return "hr-onboarding";
1895      }
1896      
1897      @WorkflowStep(
1898          index = 1,
1899          inputClass = NewEmployeeInput.class,
1900          description = "Welcome! Let's get your information"
1901      )
1902      public StepEvent collectEmployeeInfo(NewEmployeeInput input, WorkflowContext context) {
1903          // Validate employee ID
1904          Employee employee = employeeService.findByEmail(input.getEmail());
1905          if (employee == null) {
1906              return StepEvent.withError("Employee record not found. Please contact HR.");
1907          }
1908          
1909          context.setContextValue("employee", employee);
1910          context.setContextValue("startDate", input.getStartDate());
1911          
1912          // Get required documents for role
1913          List<DocumentRequirement> requiredDocs = documentService.getRequiredDocuments(
1914              employee.getRole(),
1915              employee.getDepartment(),
1916              employee.getLocation()
1917          );
1918          
1919          DocumentCollectionInput docInput = new DocumentCollectionInput();
1920          docInput.setRequiredDocuments(requiredDocs);
1921          docInput.setEmployeeName(employee.getFullName());
1922          
1923          return StepEvent.of(docInput, DocumentCollectionInput.class);
1924      }
1925      
1926      @WorkflowStep(
1927          index = 2,
1928          inputClass = DocumentCollectionInput.class,
1929          description = "Upload required documents",
1930          async = true
1931      )
1932      public AsyncTaskEvent processDocuments(DocumentCollectionInput input, WorkflowContext context) {
1933          return AsyncTaskEvent.builder()
1934              .taskName("documentProcessing")
1935              .taskArgs(Map.of(
1936                  "documents", input.getUploadedDocuments(),
1937                  "employeeId", context.getContextValue("employee", Employee.class).getId()
1938              ))
1939              .messageId("processing_documents")
1940              .nextInputSchema(getSchemaFromClass(EmergencyContactInput.class))
1941              .build();
1942      }
1943      
1944      @AsyncStep(forStep = "documentProcessing")
1945      public StepEvent verifyDocuments(Map<String, Object> taskArgs, WorkflowContext context) {
1946          List<UploadedDocument> documents = (List<UploadedDocument>) taskArgs.get("documents");
1947          String employeeId = (String) taskArgs.get("employeeId");
1948          
1949          updateProgress(0, "Verifying documents...");
1950          
1951          List<DocumentVerificationResult> results = new ArrayList<>();
1952          int processed = 0;
1953          
1954          for (UploadedDocument doc : documents) {
1955              DocumentVerificationResult result = documentService.verifyDocument(doc);
1956              results.add(result);
1957              
1958              processed++;
1959              int progress = (processed * 100) / documents.size();
1960              updateProgress(progress, "Verified " + doc.getType());
1961              
1962              if (!result.isValid()) {
1963                  return StepEvent.withError(
1964                      "Document verification failed for " + doc.getType() + ": " + result.getErrorMessage()
1965                  );
1966              }
1967          }
1968          
1969          // Store verified documents
1970          documentService.storeEmployeeDocuments(employeeId, documents);
1971          context.setContextValue("documentsVerified", true);
1972          
1973          return StepEvent.builder()
1974              .completed(true)
1975              .percentComplete(100)
1976              .build();
1977      }
1978      
1979      @WorkflowStep(
1980          index = 3,
1981          inputClass = EmergencyContactInput.class,
1982          description = "Emergency contact information"
1983      )
1984      public StepEvent collectEmergencyContacts(EmergencyContactInput input, WorkflowContext context) {
1985          Employee employee = context.getContextValue("employee", Employee.class);
1986          
1987          // Save emergency contacts
1988          employeeService.updateEmergencyContacts(employee.getId(), input.getContacts());
1989          
1990          // Determine required training based on role
1991          List<TrainingModule> requiredTraining = trainingService.getRequiredTraining(
1992              employee.getRole(),
1993              employee.getDepartment(),
1994              employee.getLevel()
1995          );
1996          
1997          TrainingScheduleInput trainingInput = new TrainingScheduleInput();
1998          trainingInput.setModules(requiredTraining);
1999          trainingInput.setStartDate(context.getContextValue("startDate", LocalDate.class));
2000          trainingInput.setPreferredTimes(employee.getPreferredTrainingTimes());
2001          
2002          return StepEvent.of(trainingInput, TrainingScheduleInput.class);
2003      }
2004      
2005      @WorkflowStep(
2006          index = 4,
2007          inputClass = TrainingScheduleInput.class,
2008          description = "Schedule your training"
2009      )
2010      public StepEvent scheduleTraining(TrainingScheduleInput input, WorkflowContext context) {
2011          Employee employee = context.getContextValue("employee", Employee.class);
2012          
2013          // Create training schedule
2014          TrainingSchedule schedule = trainingService.createSchedule(
2015              employee.getId(),
2016              input.getSelectedModules(),
2017              input.getSchedulePreference()
2018          );
2019          
2020          context.setContextValue("trainingSchedule", schedule);
2021          
2022          // Prepare equipment request
2023          EquipmentRequestInput equipmentInput = new EquipmentRequestInput();
2024          equipmentInput.setRole(employee.getRole());
2025          equipmentInput.setDepartment(employee.getDepartment());
2026          equipmentInput.setStandardPackages(itService.getStandardPackages(employee.getRole()));
2027          
2028          return StepEvent.of(equipmentInput, EquipmentRequestInput.class);
2029      }
2030      
2031      @WorkflowStep(
2032          index = 5,
2033          inputClass = EquipmentRequestInput.class,
2034          description = "Select equipment and workspace"
2035      )
2036      public StepEvent setupWorkspace(EquipmentRequestInput input, WorkflowContext context) {
2037          Employee employee = context.getContextValue("employee", Employee.class);
2038          LocalDate startDate = context.getContextValue("startDate", LocalDate.class);
2039          
2040          // Create IT ticket for equipment
2041          ITTicket equipmentTicket = itService.createEquipmentRequest(
2042              employee,
2043              input.getSelectedPackage(),
2044              input.getAdditionalItems(),
2045              startDate
2046          );
2047          
2048          // Assign workspace
2049          WorkspaceAssignment workspace = facilitiesService.assignWorkspace(
2050              employee,
2051              input.getWorkspacePreference()
2052          );
2053          
2054          context.setContextValue("equipmentTicket", equipmentTicket);
2055          context.setContextValue("workspace", workspace);
2056          
2057          // Create onboarding summary
2058          OnboardingSummary summary = buildOnboardingSummary(context);
2059          
2060          return StepEvent.of(summary, OnboardingConfirmationInput.class);
2061      }
2062      
2063      @WorkflowStep(
2064          index = 6,
2065          inputClass = OnboardingConfirmationInput.class,
2066          description = "Review onboarding checklist",
2067          async = true
2068      )
2069      public AsyncTaskEvent finalizeOnboarding(OnboardingConfirmationInput input, WorkflowContext context) {
2070          if (!input.isConfirmed()) {
2071              return AsyncTaskEvent.builder()
2072                  .taskName("onboardingIncomplete")
2073                  .messageId("onboarding_incomplete")
2074                  .build();
2075          }
2076          
2077          return AsyncTaskEvent.builder()
2078              .taskName("completeOnboarding")
2079              .taskArgs(Map.of("context", context.getAllValues()))
2080              .messageId("completing_onboarding")
2081              .build();
2082      }
2083      
2084      @AsyncStep(forStep = "completeOnboarding")
2085      public StepEvent completeOnboardingProcess(Map<String, Object> taskArgs, WorkflowContext context) {
2086          try {
2087              Employee employee = context.getContextValue("employee", Employee.class);
2088              
2089              // Send notifications to relevant parties
2090              notificationService.notifyManager(employee.getManagerId(), employee);
2091              notificationService.notifyIT(context.getContextValue("equipmentTicket", ITTicket.class));
2092              notificationService.notifyFacilities(context.getContextValue("workspace", WorkspaceAssignment.class));
2093              notificationService.notifyTraining(context.getContextValue("trainingSchedule", TrainingSchedule.class));
2094              
2095              // Create first day agenda
2096              FirstDayAgenda agenda = onboardingService.createFirstDayAgenda(employee, context);
2097              
2098              // Send welcome package
2099              emailService.sendWelcomePackage(employee, agenda);
2100              
2101              return StepEvent.builder()
2102                  .completed(true)
2103                  .percentComplete(100)
2104                  .properties(Map.of(
2105                      "employeeId", employee.getId(),
2106                      "message", "Onboarding completed successfully! Welcome package sent to " + employee.getEmail(),
2107                      "firstDayAgenda", agenda
2108                  ))
2109                  .build();
2110                  
2111          } catch (Exception e) {
2112              log.error("Failed to complete onboarding", e);
2113              return StepEvent.withError("Failed to complete onboarding: " + e.getMessage());
2114          }
2115      }
2116      
2117      private OnboardingSummary buildOnboardingSummary(WorkflowContext context) {
2118          OnboardingSummary summary = new OnboardingSummary();
2119          
2120          Employee employee = context.getContextValue("employee", Employee.class);
2121          summary.setEmployeeName(employee.getFullName());
2122          summary.setStartDate(context.getContextValue("startDate", LocalDate.class));
2123          summary.setDocumentsVerified(context.getContextValue("documentsVerified", Boolean.class));
2124          summary.setTrainingSchedule(context.getContextValue("trainingSchedule", TrainingSchedule.class));
2125          summary.setEquipmentTicket(context.getContextValue("equipmentTicket", ITTicket.class));
2126          summary.setWorkspace(context.getContextValue("workspace", WorkspaceAssignment.class));
2127          
2128          // Generate checklist
2129          summary.setChecklist(onboardingService.generateChecklist(employee));
2130          
2131          return summary;
2132      }
2133  }
2134  ```
2135  
2136  ---
2137  
2138  **Built with ❀️ by the DriftKit team**