/ driftkit-workflows-examples / README.md
README.md
1 # DriftKit Workflows Examples Module 2 3 ## Overview 4 5 The `driftkit-workflows-examples` module provides comprehensive reference implementations demonstrating the capabilities of the new DriftKit workflow engine. It includes production-ready examples for chat systems, RAG (Retrieval-Augmented Generation), reasoning workflows, prompt engineering, and intelligent routing - all built on the `driftkit-workflow-engine-core` orchestration engine. 6 7 ## Spring Boot Initialization 8 9 To use the workflows examples module in your Spring Boot application: 10 11 ```java 12 @SpringBootApplication 13 @ComponentScan(basePackages = {"ai.driftkit.workflows.examples"}) // Scan example workflows 14 @EnableMongoRepositories(basePackages = "ai.driftkit.workflows.repository") // For persistence 15 public class YourApplication { 16 public static void main(String[] args) { 17 SpringApplication.run(YourApplication.class, args); 18 } 19 } 20 ``` 21 22 The module provides: 23 - **Example workflows**: Ready-to-use workflow implementations in `ai.driftkit.workflows.examples.workflows` 24 - **Service wrappers**: Spring services that wrap workflow execution 25 - **Configuration**: Workflow instances configured via `EtlConfig` 26 - **Dependencies**: Requires other DriftKit modules to be configured 27 28 Example workflow registration: 29 30 ```java 31 @Configuration 32 public class WorkflowConfig { 33 34 @Bean 35 public ReasoningWorkflow reasoningWorkflow(EtlConfig config, 36 PromptService promptService, 37 ModelRequestService modelRequestService) throws IOException { 38 return new ReasoningWorkflow(config, promptService, modelRequestService); 39 } 40 41 @Bean 42 public ChatWorkflow chatWorkflow(ModelClient modelClient, 43 ModelRequestService modelRequestService, 44 PromptService promptService) { 45 return new ChatWorkflow(modelClient, modelRequestService, promptService); 46 } 47 } 48 ``` 49 50 ## Architecture 51 52 ### Module Structure 53 54 ``` 55 driftkit-workflows-examples/ 56 ├── driftkit-workflows-examples-core/ # Core workflow implementations 57 │ ├── domain/ # Domain objects 58 │ └── workflows/ # Example workflow classes 59 │ ├── ChatWorkflow.java # Conversational AI with memory 60 │ ├── RouterWorkflow.java # Intelligent message routing 61 │ ├── RAGModifyWorkflow.java # Document ingestion for RAG 62 │ ├── RAGSearchWorkflow.java # Vector similarity search 63 │ ├── ReasoningWorkflow.java # Multi-step reasoning 64 │ ├── EnhancedReasoningWorkflow.java # Advanced reasoning with validation 65 │ ├── PromptEngineerWorkflow.java # Automated prompt engineering 66 │ └── ModelWorkflow.java # Base class for AI workflows 67 ├── driftkit-workflows-examples-spring-boot-starter/ # Spring Boot integration 68 │ └── service/ # Spring service wrappers 69 └── pom.xml # Parent module configuration 70 ``` 71 72 ### Key Dependencies 73 74 The module integrates extensively with DriftKit components: 75 76 - **driftkit-workflow-engine-core** - Core workflow orchestration engine with fluent builder API 77 - **driftkit-workflow-engine-agents** - Multi-agent patterns and LLM agents 78 - **driftkit-workflow-test-framework** - Testing utilities and mock builders 79 - **DriftKit Clients** - AI model client abstractions 80 - **DriftKit Vector** - Vector storage and similarity search 81 - **DriftKit Embedding** - Text embedding generation 82 - **DriftKit Context Engineering** - Prompt management 83 - **DriftKit Common** - Shared utilities and domain objects 84 85 ## Base Architecture 86 87 ### ModelWorkflow Base Class 88 89 All AI-powered workflows in these examples extend the `ModelWorkflow` base class (example implementation), which provides common patterns and utilities for AI interactions. Note that actual workflows should use the `driftkit-workflow-engine-core` fluent API: 90 91 ```java 92 public abstract class ModelWorkflow<I extends StartEvent, O> extends ExecutableWorkflow<I, O> { 93 94 protected final ModelClient modelClient; 95 protected final ModelRequestService modelRequestService; 96 protected final PromptService promptService; 97 98 // Core text-to-text operations 99 protected ModelTextResponse sendTextToText(ModelRequestParams params, WorkflowContext context) throws Exception { 100 if (StringUtils.isNotBlank(params.getPromptId())) { 101 return executePromptById(params, context); 102 } else { 103 return executeCustomPrompt(params, context); 104 } 105 } 106 107 // Text-to-image operations 108 protected ModelImageResponse sendTextToImage(ModelRequestParams params, WorkflowContext context) throws Exception { 109 ModelImageRequest request = ModelImageRequest.builder() 110 .prompt(params.getPromptText()) 111 .quality(ModelImageRequest.Quality.standard) 112 .n(1) 113 .build(); 114 115 return modelClient.textToImage(request); 116 } 117 118 // Image-to-text operations 119 protected ModelTextResponse sendImageToText(ModelRequestParams params, WorkflowContext context) throws Exception { 120 List<ModelImageResponse.ModelContentMessage> messages = buildMultiModalMessages(params); 121 122 ModelTextRequest request = ModelTextRequest.builder() 123 .messages(messages) 124 .temperature(params.getTemperature()) 125 .maxTokens(params.getMaxTokens()) 126 .build(); 127 128 return modelClient.imageToText(request); 129 } 130 131 // Helper for building multi-modal messages 132 private List<ModelImageResponse.ModelContentMessage> buildMultiModalMessages(ModelRequestParams params) { 133 List<ModelContentElement> content = new ArrayList<>(); 134 135 // Add text content 136 if (StringUtils.isNotBlank(params.getPromptText())) { 137 content.add(ModelContentElement.builder() 138 .type(ModelTextRequest.MessageType.text) 139 .text(params.getPromptText()) 140 .build()); 141 } 142 143 // Add image content 144 if (CollectionUtils.isNotEmpty(params.getImageData())) { 145 for (String imageData : params.getImageData()) { 146 content.add(ModelContentElement.builder() 147 .type(ModelTextRequest.MessageType.image) 148 .image(ModelContentElement.ImageData.builder() 149 .image(imageData.getBytes()) 150 .mimeType("image/jpeg") 151 .build()) 152 .build()); 153 } 154 } 155 156 return List.of( 157 ModelImageResponse.ModelContentMessage.builder() 158 .role(Role.user) 159 .content(content) 160 .build() 161 ); 162 } 163 } 164 ``` 165 166 ### ModelRequestParams Builder 167 168 Fluent builder for configuring AI model requests: 169 170 ```java 171 @Data 172 @Builder 173 @NoArgsConstructor 174 @AllArgsConstructor 175 public class ModelRequestParams { 176 private String promptId; 177 private String promptText; 178 private Map<String, Object> variables = new HashMap<>(); 179 private Double temperature; 180 private String model; 181 private List<ModelImageResponse.ModelContentMessage> contextMessages; 182 private List<String> imageData; 183 private Integer maxTokens; 184 185 public static ModelRequestParams create() { 186 return new ModelRequestParams(); 187 } 188 189 public ModelRequestParams withVariable(String key, Object value) { 190 if (this.variables == null) { 191 this.variables = new HashMap<>(); 192 } 193 this.variables.put(key, value); 194 return this; 195 } 196 197 // Fluent setters for all properties 198 public ModelRequestParams promptId(String promptId) { 199 this.promptId = promptId; 200 return this; 201 } 202 203 public ModelRequestParams promptText(String promptText) { 204 this.promptText = promptText; 205 return this; 206 } 207 208 public ModelRequestParams temperature(Double temperature) { 209 this.temperature = temperature; 210 return this; 211 } 212 } 213 ``` 214 215 ## Workflow Examples 216 217 ### 1. ChatWorkflow - Conversational AI with Memory 218 219 Advanced conversational AI system with routing, memory management, and multi-modal capabilities: 220 221 ```java 222 @Component 223 public class ChatWorkflow extends ModelWorkflow<ChatWorkflow.ChatStartEvent, ChatWorkflow.ChatResult> { 224 225 private final LLMMemoryProvider memoryProvider; 226 private final WorkflowRegistry workflowRegistry; 227 228 @Step(name = "start") 229 public ExternalEvent<RouterWorkflow.RouterStartEvent> start(ChatStartEvent startEvent, WorkflowContext workflowContext) throws Exception { 230 MessageTask task = startEvent.getTask(); 231 232 // Check for image generation request 233 if (task.getMessage().startsWith("image:")) { 234 return handleImageGeneration(task, workflowContext); 235 } 236 237 // Route through RouterWorkflow for intent classification 238 RouterWorkflow.RouterStartEvent routerEvent = new RouterWorkflow.RouterStartEvent(); 239 routerEvent.setMessage(task.getMessage()); 240 routerEvent.setChatId(task.getChatId()); 241 routerEvent.setRoutes(extractCustomRoutes(task)); 242 243 return new ExternalEvent<>( 244 "router-workflow", 245 routerEvent, 246 "processResponse" 247 ); 248 } 249 250 @Step(name = "processResponse") 251 public WorkflowEvent processResponse(DataEvent<RouterWorkflow.RouterResult> routerResult, WorkflowContext workflowContext) throws Exception { 252 RouterWorkflow.RouterResult result = routerResult.getData(); 253 ChatStartEvent startEvent = workflowContext.get("startEvent", ChatStartEvent.class); 254 MessageTask task = startEvent.getTask(); 255 256 // Handle different routing outcomes 257 if (result.getRoutes().stream().anyMatch(r -> r.getValue() == RouterWorkflow.RouterDefaultOutputTypes.RAG)) { 258 return handleRAGResponse(result, task, workflowContext); 259 } else if (result.getRoutes().stream().anyMatch(r -> r.getValue() == RouterWorkflow.RouterDefaultOutputTypes.REASONING)) { 260 return handleReasoningResponse(result, task, workflowContext); 261 } else { 262 return handleChatResponse(result, task, workflowContext); 263 } 264 } 265 266 private WorkflowEvent handleRAGResponse(RouterWorkflow.RouterResult result, MessageTask task, WorkflowContext workflowContext) throws Exception { 267 // Build context from retrieved documents 268 StringBuilder context = new StringBuilder(); 269 for (DocumentsResult docs : result.getRelatedDocs()) { 270 for (Document doc : docs.documents()) { 271 context.append(doc.getPageContent()).append("\n\n"); 272 } 273 } 274 275 // Create prompt with RAG context 276 Map<String, Object> variables = new HashMap<>(); 277 variables.put("user_message", task.getMessage()); 278 variables.put("context", context.toString()); 279 variables.put("chat_history", getRecentChatHistory(task.getChatId())); 280 281 ModelRequestParams params = ModelRequestParams.create() 282 .promptId("rag-response") 283 .withVariable("user_message", task.getMessage()) 284 .withVariable("context", context.toString()) 285 .temperature(0.7); 286 287 ModelTextResponse response = sendTextToText(params, workflowContext); 288 289 // Create result 290 ChatResult chatResult = new ChatResult(); 291 chatResult.setRoute(result); 292 chatResult.setResponse(response.getChoices().get(0).getMessage().getContent()); 293 294 return StopEvent.of(chatResult); 295 } 296 297 private WorkflowEvent handleImageGeneration(MessageTask task, WorkflowContext workflowContext) throws Exception { 298 String prompt = task.getMessage().substring(6); // Remove "image:" prefix 299 300 ModelRequestParams params = ModelRequestParams.create() 301 .promptText(prompt) 302 .temperature(0.8); 303 304 ModelImageResponse response = sendTextToImage(params, workflowContext); 305 306 // Save image and return reference 307 String imageId = saveGeneratedImage(response.getBytes().get(0).getImage()); 308 309 ChatResult result = new ChatResult(); 310 result.setImageId(imageId); 311 result.setResponse("Image generated successfully"); 312 313 return StopEvent.of(result); 314 } 315 316 // Data classes 317 @Data 318 @AllArgsConstructor 319 @NoArgsConstructor 320 public static class ChatStartEvent extends StartEvent { 321 private MessageTask task; 322 private int memoryLength = 10; 323 } 324 325 @Data 326 @AllArgsConstructor 327 @NoArgsConstructor 328 public static class ChatResult { 329 private RouterWorkflow.RouterResult route; 330 private String response; 331 private String imageId; 332 } 333 } 334 ``` 335 336 ### 2. RouterWorkflow - Intelligent Message Routing 337 338 Sophisticated intent classification and routing system: 339 340 ```java 341 @Component 342 public class RouterWorkflow extends ModelWorkflow<RouterWorkflow.RouterStartEvent, RouterWorkflow.RouterResult> { 343 344 private final VectorStore vectorStore; 345 private final EmbeddingModel embeddingModel; 346 347 @Step(name = "start") 348 public DataEvent<String> start(RouterStartEvent startEvent, WorkflowContext workflowContext) throws Exception { 349 String message = startEvent.getMessage(); 350 List<Route> customRoutes = startEvent.getRoutes(); 351 352 // Build routing prompt with available options 353 Map<String, Object> variables = new HashMap<>(); 354 variables.put("user_message", message); 355 variables.put("custom_routes", formatCustomRoutes(customRoutes)); 356 variables.put("default_input_types", Arrays.toString(RouterDefaultInputTypes.values())); 357 variables.put("default_output_types", Arrays.toString(RouterDefaultOutputTypes.values())); 358 359 ModelRequestParams params = ModelRequestParams.create() 360 .promptId("router-classification") 361 .variables(variables) 362 .temperature(0.1); // Low temperature for consistent classification 363 364 ModelTextResponse response = sendTextToText(params, workflowContext); 365 String routingDecision = response.getChoices().get(0).getMessage().getContent(); 366 367 return DataEvent.of(routingDecision, "processRouting"); 368 } 369 370 @Step(name = "processRouting") 371 public DataEvent<RouterResult> processRouting(DataEvent<String> routingData, WorkflowContext workflowContext) throws Exception { 372 String routingJson = routingData.getData(); 373 RouterStartEvent startEvent = workflowContext.get("startEvent", RouterStartEvent.class); 374 375 // Parse routing decision 376 ObjectMapper mapper = new ObjectMapper(); 377 JsonNode routingNode = mapper.readTree(routingJson); 378 379 RouterResult result = new RouterResult(); 380 381 // Extract input type classifications 382 JsonNode inputTypes = routingNode.get("input_types"); 383 if (inputTypes != null && inputTypes.isArray()) { 384 Set<RouterDecision<RouterDefaultInputTypes>> inputDecisions = new HashSet<>(); 385 for (JsonNode inputType : inputTypes) { 386 RouterDecision<RouterDefaultInputTypes> decision = new RouterDecision<>(); 387 decision.setValue(RouterDefaultInputTypes.valueOf(inputType.get("type").asText())); 388 decision.setConfidence(inputType.get("confidence").asDouble()); 389 inputDecisions.add(decision); 390 } 391 result.setInputTypes(inputDecisions); 392 } 393 394 // Extract routing decisions 395 JsonNode routes = routingNode.get("routes"); 396 if (routes != null && routes.isArray()) { 397 Set<RouterDecision<RouterDefaultOutputTypes>> routeDecisions = new HashSet<>(); 398 for (JsonNode route : routes) { 399 RouterDecision<RouterDefaultOutputTypes> decision = new RouterDecision<>(); 400 decision.setValue(RouterDefaultOutputTypes.valueOf(route.get("type").asText())); 401 decision.setConfidence(route.get("confidence").asDouble()); 402 routeDecisions.add(decision); 403 } 404 result.setRoutes(routeDecisions); 405 } 406 407 // If RAG routing is detected, perform document retrieval 408 boolean needsRAG = result.getRoutes().stream() 409 .anyMatch(r -> r.getValue() == RouterDefaultOutputTypes.RAG); 410 411 if (needsRAG) { 412 return DataEvent.of(result, "performRAGSearch"); 413 } else { 414 return DataEvent.of(result, "finalizeResult"); 415 } 416 } 417 418 @Step(name = "performRAGSearch") 419 public DataEvent<RouterResult> performRAGSearch(DataEvent<RouterResult> routerData, WorkflowContext workflowContext) throws Exception { 420 RouterResult result = routerData.getData(); 421 RouterStartEvent startEvent = workflowContext.get("startEvent", RouterStartEvent.class); 422 423 // Generate embedding for the user message 424 TextSegment segment = TextSegment.from(startEvent.getMessage()); 425 Response<Embedding> embeddingResponse = embeddingModel.embed(segment); 426 427 // Search for relevant documents across multiple indexes 428 List<DocumentsResult> allResults = new ArrayList<>(); 429 430 // Search default indexes 431 for (String index : getDefaultIndexes()) { 432 try { 433 DocumentsResult docs = vectorStore.findRelevant( 434 index, 435 embeddingResponse.content().vector(), 436 5 437 ); 438 if (!docs.isEmpty()) { 439 allResults.add(docs); 440 } 441 } catch (Exception e) { 442 log.warn("Failed to search index: {}", index, e); 443 } 444 } 445 446 result.setRelatedDocs(allResults); 447 448 return DataEvent.of(result, "finalizeResult"); 449 } 450 451 @Step(name = "finalizeResult") 452 public StopEvent<RouterResult> finalizeResult(DataEvent<RouterResult> routerData, WorkflowContext workflowContext) { 453 RouterResult result = routerData.getData(); 454 455 // Apply confidence thresholds and quality checks 456 result.setRoutes(filterByConfidence(result.getRoutes(), 0.6)); 457 result.setInputTypes(filterByConfidence(result.getInputTypes(), 0.7)); 458 459 return StopEvent.of(result); 460 } 461 462 // Enums and data classes 463 public enum RouterDefaultInputTypes { 464 GREETING, INFORMATION_REQUEST, CLARIFICATION, CHAT, 465 IMAGE_GENERATION, FEEDBACK, ESCALATION, SALES_SUPPORT, 466 PRODUCT_ISSUE, CUSTOM, UNKNOWN 467 } 468 469 public enum RouterDefaultOutputTypes { 470 RAG, SUPPORT_REQUEST, REDO_WITH_SMARTER_MODEL, 471 REASONING, SALES_REQUEST, CHAT 472 } 473 474 @Data 475 @AllArgsConstructor 476 @NoArgsConstructor 477 public static class RouterStartEvent extends StartEvent { 478 private String message; 479 private String chatId; 480 private List<Route> routes = new ArrayList<>(); 481 } 482 483 @Data 484 @AllArgsConstructor 485 @NoArgsConstructor 486 public static class RouterResult { 487 private Set<RouterDecision<RouterDefaultInputTypes>> inputTypes = new HashSet<>(); 488 private Set<RouterDecision<RouterDefaultOutputTypes>> routes = new HashSet<>(); 489 private Set<RouterDecision<String>> customRoutes = new HashSet<>(); 490 private Set<RouterDecision<String>> indexes = new HashSet<>(); 491 private List<DocumentsResult> relatedDocs = new ArrayList<>(); 492 } 493 494 @Data 495 @AllArgsConstructor 496 @NoArgsConstructor 497 public static class RouterDecision<T> { 498 private T value; 499 private double confidence; 500 private String reasoning; 501 } 502 } 503 ``` 504 505 ### 3. RAGModifyWorkflow - Document Ingestion 506 507 Comprehensive document processing and vector storage workflow: 508 509 ```java 510 @Component 511 public class RAGModifyWorkflow extends ModelWorkflow<RAGModifyWorkflow.DocumentsEvent, RAGModifyWorkflow.DocumentSaveResult> { 512 513 private final UnifiedParser unifiedParser; 514 private final VectorStore vectorStore; 515 private final EmbeddingModel embeddingModel; 516 private final DocumentSplitter documentSplitter; 517 private final ThreadPoolExecutor executor; 518 519 public RAGModifyWorkflow() { 520 this.documentSplitter = DocumentSplitter.builder() 521 .maxChunkSize(512) 522 .overlapSize(50) 523 .tokenizer(new SimpleTokenizer()) 524 .build(); 525 526 this.executor = new ThreadPoolExecutor( 527 4, 8, 60L, TimeUnit.SECONDS, 528 new LinkedBlockingQueue<>(100) 529 ); 530 } 531 532 @Step 533 public DataEvent<ParsedContent> parseInput(DocumentsEvent startEvent, WorkflowContext workflowContext) throws Exception { 534 ParserInput input = createParserInput(startEvent); 535 ParsedContent parsed = unifiedParser.parse(input); 536 537 // Enhance metadata 538 Map<String, Object> metadata = new HashMap<>(parsed.getMetadata()); 539 metadata.put("index_id", startEvent.getIndexId()); 540 metadata.put("source_type", startEvent.getSourceType()); 541 metadata.put("processing_time", System.currentTimeMillis()); 542 543 ParsedContent enriched = ParsedContent.builder() 544 .content(parsed.getContent()) 545 .contentType(parsed.getContentType()) 546 .metadata(metadata) 547 .build(); 548 549 return DataEvent.of(enriched); 550 } 551 552 @Step 553 public StopEvent<DocumentSaveResult> ingestDocument(DataEvent<ParsedContent> documentEvent, WorkflowContext workflowContext) throws Exception { 554 ParsedContent parsed = documentEvent.getData(); 555 DocumentsEvent startEvent = workflowContext.get("startEvent", DocumentsEvent.class); 556 557 // Split content into chunks 558 List<String> chunks = documentSplitter.split(parsed.getContent()); 559 560 // Process chunks concurrently 561 List<CompletableFuture<Document>> futures = new ArrayList<>(); 562 563 for (int i = 0; i < chunks.size(); i++) { 564 final int chunkIndex = i; 565 final String chunk = chunks.get(i); 566 567 CompletableFuture<Document> future = CompletableFuture.supplyAsync(() -> { 568 try { 569 // Generate embedding 570 TextSegment segment = TextSegment.from(chunk); 571 Response<Embedding> embeddingResponse = embeddingModel.embed(segment); 572 573 // Create document 574 Document document = new Document(); 575 document.setId(UUID.randomUUID().toString()); 576 document.setPageContent(chunk); 577 document.setVector(embeddingResponse.content().vector()); 578 579 // Add chunk metadata 580 Map<String, Object> chunkMetadata = new HashMap<>(parsed.getMetadata()); 581 chunkMetadata.put("chunk_index", chunkIndex); 582 chunkMetadata.put("total_chunks", chunks.size()); 583 chunkMetadata.put("chunk_size", chunk.length()); 584 585 document.setMetadata(chunkMetadata); 586 587 return document; 588 } catch (Exception e) { 589 throw new RuntimeException("Failed to process chunk " + chunkIndex, e); 590 } 591 }, executor); 592 593 futures.add(future); 594 } 595 596 // Wait for all chunks to be processed 597 List<Document> documents = futures.stream() 598 .map(CompletableFuture::join) 599 .collect(Collectors.toList()); 600 601 // Store documents in vector database 602 List<String> documentIds = vectorStore.addDocuments(startEvent.getIndexId(), documents); 603 604 // Create result 605 DocumentSaveResult result = DocumentSaveResult.builder() 606 .documentIds(documentIds) 607 .totalDocuments(documents.size()) 608 .indexId(startEvent.getIndexId()) 609 .contentType(parsed.getContentType()) 610 .originalSize(parsed.getContent().length()) 611 .totalChunks(chunks.size()) 612 .build(); 613 614 return StopEvent.of(result); 615 } 616 617 private ParserInput createParserInput(DocumentsEvent event) { 618 switch (event.getSourceType()) { 619 case "youtube": 620 return YoutubeIdParserInput.builder() 621 .youtubeId(event.getYoutubeId()) 622 .languages(event.getLanguages()) 623 .build(); 624 625 case "file": 626 return ByteArrayParserInput.builder() 627 .data(event.getFileData()) 628 .contentType(ContentType.fromMimeType(event.getMimeType())) 629 .metadata(event.getMetadata()) 630 .build(); 631 632 case "text": 633 default: 634 return StringParserInput.builder() 635 .content(event.getTextContent()) 636 .metadata(event.getMetadata()) 637 .build(); 638 } 639 } 640 641 // Data classes 642 @Data 643 @Builder 644 @NoArgsConstructor 645 @AllArgsConstructor 646 public static class DocumentsEvent extends StartEvent { 647 private String indexId; 648 private String sourceType; // "youtube", "file", "text" 649 650 // YouTube specific 651 private String youtubeId; 652 private List<String> languages; 653 654 // File specific 655 private byte[] fileData; 656 private String mimeType; 657 658 // Text specific 659 private String textContent; 660 661 // Common metadata 662 private Map<String, Object> metadata = new HashMap<>(); 663 } 664 665 @Data 666 @Builder 667 @NoArgsConstructor 668 @AllArgsConstructor 669 public static class DocumentSaveResult { 670 private List<String> documentIds; 671 private int totalDocuments; 672 private String indexId; 673 private ContentType contentType; 674 private int originalSize; 675 private int totalChunks; 676 private long processingTimeMs; 677 } 678 } 679 ``` 680 681 ### 4. EnhancedReasoningWorkflow - Advanced Reasoning with Quality Control 682 683 Sophisticated reasoning system with planning, validation, and quality control: 684 685 ```java 686 @Component 687 public class EnhancedReasoningWorkflow extends ModelWorkflow<StartEvent, EnhancedReasoningWorkflow.EnhancedReasoningResult> { 688 689 private static final double SATISFACTION_THRESHOLD = 0.8; 690 private static final int MAX_RETRY_ATTEMPTS = 3; 691 private static final double MIN_RESULT_LENGTH_RATIO = 0.5; 692 693 private final ChecklistService checklistService; 694 private final WorkflowRegistry workflowRegistry; 695 696 @Step(name = "start") 697 public DataEvent<EnhancedReasoningPlan> start(StartEvent startEvent, WorkflowContext workflowContext) throws Exception { 698 String task = startEvent.getData(); 699 700 // Generate reasoning plan with checklist 701 Map<String, Object> variables = new HashMap<>(); 702 variables.put("task", task); 703 704 ModelRequestParams params = ModelRequestParams.create() 705 .promptId("enhanced-reasoning-plan") 706 .variables(variables) 707 .temperature(0.3); 708 709 ModelTextResponse response = sendTextToText(params, workflowContext); 710 String planJson = response.getChoices().get(0).getMessage().getContent(); 711 712 // Parse plan 713 ObjectMapper mapper = new ObjectMapper(); 714 JsonNode planNode = mapper.readTree(planJson); 715 716 EnhancedReasoningPlan plan = new EnhancedReasoningPlan(); 717 plan.setPlan(planNode.get("plan").asText()); 718 plan.setPlanConfidence(planNode.get("confidence").asDouble()); 719 plan.setTask(task); 720 plan.setAttemptCount(1); 721 722 // Extract and save checklist items 723 JsonNode checklistNode = planNode.get("checklist"); 724 if (checklistNode != null && checklistNode.isArray()) { 725 String checklistId = UUID.randomUUID().toString(); 726 plan.setChecklistId(checklistId); 727 728 for (JsonNode item : checklistNode) { 729 checklistService.addChecklistItem(checklistId, item.asText()); 730 } 731 } 732 733 return DataEvent.of(plan, "executeStep"); 734 } 735 736 @Step(name = "executeStep", invocationLimit = 5) 737 public DataEvent<EnhancedReasoningExecution> executeStep(DataEvent<EnhancedReasoningPlan> planEvent, WorkflowContext workflowContext) throws Exception { 738 EnhancedReasoningPlan plan = planEvent.getData(); 739 740 // Build execution context 741 Map<String, Object> variables = new HashMap<>(); 742 variables.put("task", plan.getTask()); 743 variables.put("plan", plan.getPlan()); 744 745 // Add checklist items if available 746 if (plan.getChecklistId() != null) { 747 List<ChecklistItemEntity> checklistItems = checklistService.getChecklistItems(plan.getChecklistId()); 748 List<String> checklist = checklistItems.stream() 749 .map(ChecklistItemEntity::getContent) 750 .collect(Collectors.toList()); 751 variables.put("checklist", checklist); 752 } 753 754 ModelRequestParams params = ModelRequestParams.create() 755 .promptId("enhanced-reasoning-execution") 756 .variables(variables) 757 .temperature(0.7); 758 759 ModelTextResponse response = sendTextToText(params, workflowContext); 760 String executionJson = response.getChoices().get(0).getMessage().getContent(); 761 762 // Parse execution result 763 ObjectMapper mapper = new ObjectMapper(); 764 JsonNode executionNode = mapper.readTree(executionJson); 765 766 EnhancedReasoningExecution execution = new EnhancedReasoningExecution(); 767 execution.setResult(executionNode.get("result").asText()); 768 execution.setResultConfidence(executionNode.get("confidence").asDouble()); 769 execution.setChecklist(plan.getChecklistId()); 770 execution.setPlan(plan); 771 772 return DataEvent.of(execution, "validateStep"); 773 } 774 775 @Step(name = "validateStep", invocationLimit = 3) 776 public WorkflowEvent validateStep(DataEvent<EnhancedReasoningExecution> executionEvent, WorkflowContext workflowContext) throws Exception { 777 EnhancedReasoningExecution execution = executionEvent.getData(); 778 779 // Validate result quality 780 Map<String, Object> variables = new HashMap<>(); 781 variables.put("task", execution.getPlan().getTask()); 782 variables.put("plan", execution.getPlan().getPlan()); 783 variables.put("result", execution.getResult()); 784 785 ModelRequestParams params = ModelRequestParams.create() 786 .promptId("enhanced-reasoning-validation") 787 .variables(variables) 788 .temperature(0.2); 789 790 ModelTextResponse response = sendTextToText(params, workflowContext); 791 String validationJson = response.getChoices().get(0).getMessage().getContent(); 792 793 // Parse validation result 794 ObjectMapper mapper = new ObjectMapper(); 795 JsonNode validationNode = mapper.readTree(validationJson); 796 797 EnhancedReasoningValidation validation = new EnhancedReasoningValidation(); 798 validation.setValidation(validationNode.get("validation").asText()); 799 validation.setSatisfied(validationNode.get("satisfied").asBoolean()); 800 validation.setSatisfactionScore(validationNode.get("satisfaction_score").asDouble()); 801 validation.setExecution(execution); 802 803 // Check quality thresholds 804 boolean passesQualityCheck = validation.getSatisfactionScore() >= SATISFACTION_THRESHOLD && 805 validation.isSatisfied() && 806 execution.getResult().length() >= (execution.getPlan().getTask().length() * MIN_RESULT_LENGTH_RATIO); 807 808 if (passesQualityCheck) { 809 // Success - create final result 810 EnhancedReasoningResult finalResult = EnhancedReasoningResult.builder() 811 .plan(execution.getPlan().getPlan()) 812 .planValidation("Plan executed successfully") 813 .planConfidence(execution.getPlan().getPlanConfidence()) 814 .result(execution.getResult()) 815 .resultValidation(validation.getValidation()) 816 .resultConfidence(execution.getResultConfidence()) 817 .isSatisfied(true) 818 .attemptHistory(List.of("Attempt " + execution.getPlan().getAttemptCount() + ": Success")) 819 .build(); 820 821 return StopEvent.of(finalResult); 822 } else if (execution.getPlan().getAttemptCount() < MAX_RETRY_ATTEMPTS) { 823 // Retry with improvements 824 return DataEvent.of(validation, "retryStep"); 825 } else { 826 // Fallback to original ReasoningWorkflow 827 return DataEvent.of(validation, "fallbackStep"); 828 } 829 } 830 831 @Step(name = "retryStep") 832 public WorkflowEvent retryStep(DataEvent<EnhancedReasoningValidation> validationEvent, WorkflowContext workflowContext) throws Exception { 833 EnhancedReasoningValidation validation = validationEvent.getData(); 834 EnhancedReasoningPlan originalPlan = validation.getExecution().getPlan(); 835 836 // Improve plan based on validation feedback 837 Map<String, Object> variables = new HashMap<>(); 838 variables.put("original_task", originalPlan.getTask()); 839 variables.put("original_plan", originalPlan.getPlan()); 840 variables.put("previous_result", validation.getExecution().getResult()); 841 variables.put("validation_feedback", validation.getValidation()); 842 843 ModelRequestParams params = ModelRequestParams.create() 844 .promptId("enhanced-reasoning-improvement") 845 .variables(variables) 846 .temperature(0.4); 847 848 ModelTextResponse response = sendTextToText(params, workflowContext); 849 String improvedPlanJson = response.getChoices().get(0).getMessage().getContent(); 850 851 // Parse improved plan 852 ObjectMapper mapper = new ObjectMapper(); 853 JsonNode planNode = mapper.readTree(improvedPlanJson); 854 855 EnhancedReasoningPlan improvedPlan = new EnhancedReasoningPlan(); 856 improvedPlan.setPlan(planNode.get("improved_plan").asText()); 857 improvedPlan.setPlanConfidence(planNode.get("confidence").asDouble()); 858 improvedPlan.setTask(originalPlan.getTask()); 859 improvedPlan.setAttemptCount(originalPlan.getAttemptCount() + 1); 860 improvedPlan.setChecklistId(originalPlan.getChecklistId()); 861 862 return DataEvent.of(improvedPlan, "executeStep"); 863 } 864 865 @Step(name = "fallbackStep") 866 public WorkflowEvent fallbackStep(DataEvent<EnhancedReasoningValidation> validationEvent, WorkflowContext workflowContext) throws Exception { 867 EnhancedReasoningValidation validation = validationEvent.getData(); 868 String task = validation.getExecution().getPlan().getTask(); 869 870 // Fallback to original ReasoningWorkflow 871 StartEvent fallbackEvent = new StartEvent() { 872 @Override 873 public String getData() { 874 return task; 875 } 876 }; 877 878 ExternalEvent<StartEvent> externalEvent = new ExternalEvent<>( 879 "reasoning-workflow", 880 fallbackEvent, 881 "finalizeFallback" 882 ); 883 884 return externalEvent; 885 } 886 887 @FinalStep(name = "finalizeFallback") 888 public StopEvent<EnhancedReasoningResult> finalizeFallback(DataEvent<String> fallbackResult, WorkflowContext workflowContext) { 889 String result = fallbackResult.getData(); 890 891 EnhancedReasoningResult finalResult = EnhancedReasoningResult.builder() 892 .plan("Fallback to standard reasoning") 893 .planValidation("Enhanced reasoning failed, used fallback") 894 .planConfidence(0.6) 895 .result(result) 896 .resultValidation("Fallback result") 897 .resultConfidence(0.7) 898 .isSatisfied(false) 899 .fallbackWorkflowId("reasoning-workflow") 900 .attemptHistory(List.of("Enhanced reasoning failed, used fallback workflow")) 901 .build(); 902 903 return StopEvent.of(finalResult); 904 } 905 906 // Data classes 907 @Data 908 @Builder 909 @NoArgsConstructor 910 @AllArgsConstructor 911 public static class EnhancedReasoningPlan { 912 private String plan; 913 private String task; 914 private double planConfidence; 915 private String checklistId; 916 private int attemptCount; 917 } 918 919 @Data 920 @Builder 921 @NoArgsConstructor 922 @AllArgsConstructor 923 public static class EnhancedReasoningExecution { 924 private String result; 925 private double resultConfidence; 926 private String checklist; 927 private EnhancedReasoningPlan plan; 928 } 929 930 @Data 931 @Builder 932 @NoArgsConstructor 933 @AllArgsConstructor 934 public static class EnhancedReasoningValidation { 935 private String validation; 936 private boolean satisfied; 937 private double satisfactionScore; 938 private EnhancedReasoningExecution execution; 939 } 940 941 @Data 942 @Builder 943 @NoArgsConstructor 944 @AllArgsConstructor 945 public static class EnhancedReasoningResult { 946 private String plan; 947 private String planValidation; 948 private Double planConfidence; 949 private String result; 950 private String resultValidation; 951 private Double resultConfidence; 952 private Boolean isSatisfied; 953 private String fallbackWorkflowId; 954 private List<String> attemptHistory; 955 private Map<String, Object> metadata; 956 } 957 } 958 ``` 959 960 ### 5. PromptEngineerWorkflow - Automated Prompt Engineering 961 962 Intelligent prompt creation, editing, and testing system: 963 964 ```java 965 @Component 966 public class PromptEngineerWorkflow extends ModelWorkflow<StartEvent, PromptEngineerWorkflow.PromptEngineerResult> { 967 968 private final WorkflowRegistry workflowRegistry; 969 private final VariableExtractor variableExtractor; 970 971 @Step(name = "start") 972 public DataEvent<PromptParameters> start(StartEvent startEvent, WorkflowContext workflowContext) throws Exception { 973 String userRequest = startEvent.getData(); 974 975 // Extract parameters from user request 976 Map<String, Object> variables = new HashMap<>(); 977 variables.put("user_request", userRequest); 978 979 ModelRequestParams params = ModelRequestParams.create() 980 .promptId("prompt-parameter-extraction") 981 .variables(variables) 982 .temperature(0.2); 983 984 ModelTextResponse response = sendTextToText(params, workflowContext); 985 String parametersJson = response.getChoices().get(0).getMessage().getContent(); 986 987 // Parse parameters 988 ObjectMapper mapper = new ObjectMapper(); 989 JsonNode parametersNode = mapper.readTree(parametersJson); 990 991 PromptParameters promptParams = new PromptParameters(); 992 promptParams.setTask(parametersNode.get("task").asText()); 993 promptParams.setPromptId(parametersNode.path("prompt_id").asText(null)); 994 promptParams.setWorkflowId(parametersNode.path("workflow_id").asText(null)); 995 promptParams.setLanguage(Language.valueOf( 996 parametersNode.path("language").asText("GENERAL").toUpperCase() 997 )); 998 999 // Parse additional parameters 1000 JsonNode paramsNode = parametersNode.get("parameters"); 1001 if (paramsNode != null) { 1002 Map<String, Object> additionalParams = mapper.convertValue(paramsNode, Map.class); 1003 promptParams.setParameters(additionalParams); 1004 } 1005 1006 return DataEvent.of(promptParams, "engineerPrompt"); 1007 } 1008 1009 @Step(name = "engineerPrompt") 1010 public DataEvent<String> engineerPrompt(DataEvent<PromptParameters> parametersEvent, WorkflowContext workflowContext) throws Exception { 1011 PromptParameters promptParams = parametersEvent.getData(); 1012 1013 String promptAction = promptParams.getPromptId() != null ? "edit_prompt" : "create_prompt"; 1014 1015 // Build context for prompt engineering 1016 Map<String, Object> variables = new HashMap<>(); 1017 variables.put("task", promptParams.getTask()); 1018 variables.put("action", promptAction); 1019 variables.put("language", promptParams.getLanguage().toString()); 1020 1021 if (promptParams.getPromptId() != null) { 1022 // Get existing prompt for editing 1023 Optional<Prompt> existingPrompt = promptService.getPromptById(promptParams.getPromptId()); 1024 if (existingPrompt.isPresent()) { 1025 variables.put("existing_prompt", existingPrompt.get().getMessage()); 1026 variables.put("existing_system_message", existingPrompt.get().getSystemMessage()); 1027 } 1028 } 1029 1030 if (promptParams.getParameters() != null) { 1031 variables.putAll(promptParams.getParameters()); 1032 } 1033 1034 // Use EnhancedReasoningWorkflow for sophisticated prompt engineering 1035 StartEvent reasoningEvent = new StartEvent() { 1036 @Override 1037 public String getData() { 1038 return String.format( 1039 "Engineer a high-quality prompt for the following task: %s. " + 1040 "Action: %s. Language: %s. Consider best practices for prompt engineering.", 1041 promptParams.getTask(), promptAction, promptParams.getLanguage() 1042 ); 1043 } 1044 }; 1045 1046 ExternalEvent<StartEvent> externalEvent = new ExternalEvent<>( 1047 "enhanced-reasoning-workflow", 1048 reasoningEvent, 1049 "processEngineeredPrompt" 1050 ); 1051 1052 return DataEvent.of(externalEvent, "processEngineeredPrompt"); 1053 } 1054 1055 @Step(name = "processEngineeredPrompt") 1056 public DataEvent<String> processEngineeredPrompt(DataEvent<EnhancedReasoningWorkflow.EnhancedReasoningResult> reasoningResult, WorkflowContext workflowContext) throws Exception { 1057 EnhancedReasoningWorkflow.EnhancedReasoningResult result = reasoningResult.getData(); 1058 String engineeredPrompt = result.getResult(); 1059 1060 // Extract variables from the engineered prompt 1061 Set<String> variables = variableExtractor.extractVariables(engineeredPrompt); 1062 1063 // Parse the engineered prompt if it's structured 1064 String finalPrompt = engineeredPrompt; 1065 String systemMessage = ""; 1066 1067 try { 1068 ObjectMapper mapper = new ObjectMapper(); 1069 JsonNode promptNode = mapper.readTree(engineeredPrompt); 1070 1071 if (promptNode.has("prompt")) { 1072 finalPrompt = promptNode.get("prompt").asText(); 1073 } 1074 if (promptNode.has("system_message")) { 1075 systemMessage = promptNode.get("system_message").asText(); 1076 } 1077 } catch (Exception e) { 1078 // Not JSON, use as-is 1079 } 1080 1081 // Create or update prompt 1082 PromptParameters promptParams = workflowContext.get("promptParameters", PromptParameters.class); 1083 1084 Prompt prompt = new Prompt(); 1085 if (promptParams.getPromptId() != null) { 1086 // Update existing prompt 1087 Optional<Prompt> existing = promptService.getPromptById(promptParams.getPromptId()); 1088 if (existing.isPresent()) { 1089 prompt = existing.get(); 1090 } 1091 } else { 1092 // Create new prompt 1093 prompt.setId(UUID.randomUUID().toString()); 1094 prompt.setMethod("generated-" + UUID.randomUUID().toString().substring(0, 8)); 1095 prompt.setCreatedTime(System.currentTimeMillis()); 1096 prompt.setState(Prompt.State.MODERATION); 1097 } 1098 1099 prompt.setMessage(finalPrompt); 1100 prompt.setSystemMessage(systemMessage); 1101 prompt.setLanguage(promptParams.getLanguage()); 1102 prompt.setUpdatedTime(System.currentTimeMillis()); 1103 1104 // Save prompt 1105 Prompt savedPrompt = promptService.savePrompt(prompt); 1106 1107 return DataEvent.of(savedPrompt.getId(), "testPrompt"); 1108 } 1109 1110 @Step(name = "testPrompt") 1111 public StopEvent<PromptEngineerResult> testPrompt(DataEvent<String> promptIdEvent, WorkflowContext workflowContext) throws Exception { 1112 String promptId = promptIdEvent.getData(); 1113 PromptParameters promptParams = workflowContext.get("promptParameters", PromptParameters.class); 1114 1115 // Generate test variables based on the prompt 1116 Optional<Prompt> promptOpt = promptService.getPromptById(promptId); 1117 if (promptOpt.isEmpty()) { 1118 throw new IllegalStateException("Prompt not found: " + promptId); 1119 } 1120 1121 Prompt prompt = promptOpt.get(); 1122 Set<String> variables = variableExtractor.extractVariables(prompt.getMessage()); 1123 1124 // Auto-generate test values 1125 Map<String, Object> testVariables = new HashMap<>(); 1126 for (String variable : variables) { 1127 testVariables.put(variable, generateTestValue(variable)); 1128 } 1129 1130 Object testResult = null; 1131 1132 // Test the prompt 1133 if (promptParams.getWorkflowId() != null && workflowRegistry.hasWorkflow(promptParams.getWorkflowId())) { 1134 // Test with specific workflow 1135 testResult = testWithWorkflow(promptParams.getWorkflowId(), promptId, testVariables); 1136 } else { 1137 // Test with direct prompt execution 1138 testResult = testWithDirectExecution(promptId, testVariables); 1139 } 1140 1141 // Create result 1142 PromptEngineerResult result = new PromptEngineerResult(); 1143 result.setPrompt(prompt.getMessage()); 1144 result.setTestResult(testResult); 1145 1146 return StopEvent.of(result); 1147 } 1148 1149 private Object testWithWorkflow(String workflowId, String promptId, Map<String, Object> variables) throws Exception { 1150 // Create appropriate start event for the workflow 1151 StartEvent startEvent = createWorkflowStartEvent(workflowId, promptId, variables); 1152 1153 // Execute workflow 1154 StopEvent<?> result = workflowRegistry.executeWorkflow(workflowId, startEvent, new WorkflowContext()); 1155 1156 return result.getResult(); 1157 } 1158 1159 private Object testWithDirectExecution(String promptId, Map<String, Object> variables) throws Exception { 1160 ModelRequestParams params = ModelRequestParams.create() 1161 .promptId(promptId) 1162 .variables(variables) 1163 .temperature(0.7); 1164 1165 ModelTextResponse response = sendTextToText(params, new WorkflowContext()); 1166 return response.getChoices().get(0).getMessage().getContent(); 1167 } 1168 1169 private String generateTestValue(String variableName) { 1170 // Simple heuristic-based test value generation 1171 String lowerName = variableName.toLowerCase(); 1172 1173 if (lowerName.contains("name")) { 1174 return "John Doe"; 1175 } else if (lowerName.contains("email")) { 1176 return "john.doe@example.com"; 1177 } else if (lowerName.contains("date")) { 1178 return "2024-01-15"; 1179 } else if (lowerName.contains("age")) { 1180 return "25"; 1181 } else if (lowerName.contains("message") || lowerName.contains("text")) { 1182 return "Sample message for testing"; 1183 } else if (lowerName.contains("number") || lowerName.contains("count")) { 1184 return "42"; 1185 } else { 1186 return "test_value_" + variableName; 1187 } 1188 } 1189 1190 // Data classes 1191 @Data 1192 @AllArgsConstructor 1193 @NoArgsConstructor 1194 public static class PromptParameters { 1195 private String promptId; 1196 private String task; 1197 private String workflowId; 1198 private Language language; 1199 private Map<String, Object> parameters; 1200 } 1201 1202 @Data 1203 @AllArgsConstructor 1204 @NoArgsConstructor 1205 public static class PromptEngineerResult { 1206 private String prompt; 1207 private Object testResult; 1208 } 1209 } 1210 ``` 1211 1212 ## Spring Boot Integration 1213 1214 ### Service Layer Pattern 1215 1216 All workflow examples are wrapped in Spring services for dependency injection and configuration: 1217 1218 ```java 1219 @Service 1220 public class ChatWorkflowService extends ChatWorkflow { 1221 1222 public ChatWorkflowService( 1223 EtlConfig config, 1224 PromptService promptService, 1225 ModelRequestService modelRequestService, 1226 LLMMemoryProvider memoryProvider, 1227 WorkflowRegistry workflowRegistry) throws Exception { 1228 1229 super( 1230 ModelClientFactory.fromConfig(config.getVault().get(0)), 1231 promptService, 1232 modelRequestService, 1233 memoryProvider, 1234 workflowRegistry 1235 ); 1236 } 1237 } 1238 ``` 1239 1240 ### Configuration Integration 1241 1242 Workflows automatically integrate with DriftKit configuration: 1243 1244 ```yaml 1245 driftkit: 1246 vault: 1247 - name: "primary" 1248 type: "openai" 1249 apiKey: "${OPENAI_API_KEY}" 1250 model: "gpt-4" 1251 temperature: 0.7 1252 - name: "claude" 1253 type: "claude" 1254 apiKey: "${CLAUDE_API_KEY}" 1255 model: "claude-sonnet-4-20250514" 1256 temperature: 0.7 1257 1258 vectorStores: 1259 - name: "primary" 1260 type: "pinecone" 1261 apiKey: "${PINECONE_API_KEY}" 1262 1263 embeddingServices: 1264 - name: "primary" 1265 type: "openai" 1266 apiKey: "${OPENAI_API_KEY}" 1267 model: "text-embedding-ada-002" 1268 ``` 1269 1270 ## Usage Patterns 1271 1272 ### Basic Workflow Execution 1273 1274 ```java 1275 @Service 1276 public class WorkflowOrchestrationService { 1277 1278 private final WorkflowRegistry workflowRegistry; 1279 1280 public String processUserMessage(String message, String chatId) throws Exception { 1281 ChatWorkflow.ChatStartEvent startEvent = new ChatWorkflow.ChatStartEvent(); 1282 startEvent.setTask(MessageTask.builder() 1283 .message(message) 1284 .chatId(chatId) 1285 .build()); 1286 1287 StopEvent<ChatWorkflow.ChatResult> result = workflowRegistry.executeWorkflow( 1288 "chat-workflow", 1289 startEvent, 1290 new WorkflowContext() 1291 ); 1292 1293 return result.getResult().getResponse(); 1294 } 1295 } 1296 ``` 1297 1298 ### Document Ingestion Pipeline 1299 1300 ```java 1301 @Service 1302 public class DocumentIngestionService { 1303 1304 private final WorkflowRegistry workflowRegistry; 1305 1306 public DocumentSaveResult ingestDocument(MultipartFile file, String indexId) throws Exception { 1307 RAGModifyWorkflow.DocumentsEvent startEvent = RAGModifyWorkflow.DocumentsEvent.builder() 1308 .indexId(indexId) 1309 .sourceType("file") 1310 .fileData(file.getBytes()) 1311 .mimeType(file.getContentType()) 1312 .metadata(Map.of( 1313 "filename", file.getOriginalFilename(), 1314 "size", file.getSize() 1315 )) 1316 .build(); 1317 1318 StopEvent<RAGModifyWorkflow.DocumentSaveResult> result = workflowRegistry.executeWorkflow( 1319 "rag-modify-workflow", 1320 startEvent, 1321 new WorkflowContext() 1322 ); 1323 1324 return result.getResult(); 1325 } 1326 } 1327 ``` 1328 1329 ### Intelligent Routing 1330 1331 ```java 1332 @Service 1333 public class MessageRoutingService { 1334 1335 private final WorkflowRegistry workflowRegistry; 1336 1337 public RouterResult routeMessage(String message, String chatId) throws Exception { 1338 RouterWorkflow.RouterStartEvent startEvent = new RouterWorkflow.RouterStartEvent(); 1339 startEvent.setMessage(message); 1340 startEvent.setChatId(chatId); 1341 1342 StopEvent<RouterWorkflow.RouterResult> result = workflowRegistry.executeWorkflow( 1343 "router-workflow", 1344 startEvent, 1345 new WorkflowContext() 1346 ); 1347 1348 return result.getResult(); 1349 } 1350 } 1351 ``` 1352 1353 ## Testing and Quality Assurance 1354 1355 ### Workflow Testing Framework 1356 1357 ```java 1358 @SpringBootTest 1359 class WorkflowIntegrationTest { 1360 1361 @Autowired 1362 private WorkflowRegistry workflowRegistry; 1363 1364 @Test 1365 void shouldExecuteChatWorkflow() throws Exception { 1366 ChatWorkflow.ChatStartEvent startEvent = new ChatWorkflow.ChatStartEvent(); 1367 startEvent.setTask(MessageTask.builder() 1368 .message("Hello, how can you help me?") 1369 .chatId("test-chat") 1370 .build()); 1371 1372 StopEvent<ChatWorkflow.ChatResult> result = workflowRegistry.executeWorkflow( 1373 "chat-workflow", 1374 startEvent, 1375 new WorkflowContext() 1376 ); 1377 1378 assertThat(result.getResult()).isNotNull(); 1379 assertThat(result.getResult().getResponse()).isNotBlank(); 1380 } 1381 1382 @Test 1383 void shouldIngestDocument() throws Exception { 1384 RAGModifyWorkflow.DocumentsEvent startEvent = RAGModifyWorkflow.DocumentsEvent.builder() 1385 .indexId("test-index") 1386 .sourceType("text") 1387 .textContent("This is a test document for ingestion.") 1388 .build(); 1389 1390 StopEvent<RAGModifyWorkflow.DocumentSaveResult> result = workflowRegistry.executeWorkflow( 1391 "rag-modify-workflow", 1392 startEvent, 1393 new WorkflowContext() 1394 ); 1395 1396 assertThat(result.getResult().getTotalDocuments()).isGreaterThan(0); 1397 assertThat(result.getResult().getDocumentIds()).isNotEmpty(); 1398 } 1399 } 1400 ``` 1401 1402 ## Performance Considerations 1403 1404 ### Concurrent Processing 1405 1406 The RAGModifyWorkflow demonstrates concurrent processing for document chunking and embedding generation: 1407 1408 ```java 1409 // Process chunks concurrently using ThreadPoolExecutor 1410 List<CompletableFuture<Document>> futures = chunks.stream() 1411 .map(chunk -> CompletableFuture.supplyAsync(() -> { 1412 // Process chunk asynchronously 1413 return processChunk(chunk); 1414 }, executor)) 1415 .collect(Collectors.toList()); 1416 1417 // Wait for all chunks to complete 1418 List<Document> documents = futures.stream() 1419 .map(CompletableFuture::join) 1420 .collect(Collectors.toList()); 1421 ``` 1422 1423 ### Memory Management 1424 1425 Workflows implement proper resource cleanup and memory management: 1426 1427 ```java 1428 @PreDestroy 1429 public void cleanup() { 1430 if (executor != null && !executor.isShutdown()) { 1431 executor.shutdown(); 1432 try { 1433 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { 1434 executor.shutdownNow(); 1435 } 1436 } catch (InterruptedException e) { 1437 executor.shutdownNow(); 1438 Thread.currentThread().interrupt(); 1439 } 1440 } 1441 } 1442 ``` 1443 1444 ## Extension Points 1445 1446 ### Custom Workflow Development 1447 1448 Developers should use the `driftkit-workflow-engine-core` fluent API to create custom workflows: 1449 1450 ```java 1451 @Component 1452 public class CustomWorkflowBuilder { 1453 1454 public Workflow<CustomInput, CustomOutput> buildWorkflow() { 1455 return WorkflowBuilder.<CustomInput, CustomOutput>create("custom-workflow") 1456 .withDescription("Custom business workflow") 1457 .step("process", ProcessStep.class) 1458 .asyncStep("analyze", AnalysisStep.class, 1459 AsyncConfig.builder() 1460 .timeout(Duration.ofMinutes(5)) 1461 .build()) 1462 .branch("decision", DecisionStep.class, 1463 branchBuilder -> branchBuilder 1464 .branch("option1", Option1Step.class) 1465 .branch("option2", Option2Step.class) 1466 .otherwise(DefaultStep.class)) 1467 .step("finalize", FinalStep.class) 1468 .build(); 1469 } 1470 } 1471 ``` 1472 1473 ### Integration with External Services 1474 1475 Workflows can easily integrate with external APIs and services: 1476 1477 ```java 1478 @Step(name = "callExternalService") 1479 public DataEvent<String> callExternalService(DataEvent<String> input, WorkflowContext context) throws Exception { 1480 ExternalServiceRequest request = new ExternalServiceRequest(input.getData()); 1481 ExternalServiceResponse response = externalServiceClient.call(request); 1482 1483 return DataEvent.of(response.getData(), "processResponse"); 1484 } 1485 ``` 1486 1487 This comprehensive documentation demonstrates the power and flexibility of the DriftKit workflows framework through practical, production-ready examples. Each workflow showcases different aspects of AI application development, from conversational interfaces to document processing and intelligent reasoning systems.