|
1 | 1 | """Test Flow creation and execution basic functionality."""
|
2 | 2 |
|
3 | 3 | import asyncio
|
| 4 | +from datetime import datetime |
4 | 5 |
|
5 | 6 | import pytest
|
6 | 7 | from pydantic import BaseModel
|
7 | 8 |
|
8 | 9 | from crewai.flow.flow import Flow, and_, listen, or_, router, start
|
| 10 | +from crewai.flow.flow_events import ( |
| 11 | + FlowFinishedEvent, |
| 12 | + FlowStartedEvent, |
| 13 | + MethodExecutionFinishedEvent, |
| 14 | + MethodExecutionStartedEvent, |
| 15 | +) |
9 | 16 |
|
10 | 17 |
|
11 | 18 | def test_simple_sequential_flow():
|
@@ -398,3 +405,220 @@ def log_final_step(self):
|
398 | 405 |
|
399 | 406 | # final_step should run after router_and
|
400 | 407 | assert execution_order.index("log_final_step") > execution_order.index("router_and")
|
| 408 | + |
| 409 | + |
| 410 | +def test_unstructured_flow_event_emission(): |
| 411 | + """Test that the correct events are emitted during unstructured flow |
| 412 | + execution with all fields validated.""" |
| 413 | + |
| 414 | + class PoemFlow(Flow): |
| 415 | + @start() |
| 416 | + def prepare_flower(self): |
| 417 | + self.state["flower"] = "roses" |
| 418 | + return "foo" |
| 419 | + |
| 420 | + @start() |
| 421 | + def prepare_color(self): |
| 422 | + self.state["color"] = "red" |
| 423 | + return "bar" |
| 424 | + |
| 425 | + @listen(prepare_color) |
| 426 | + def write_first_sentence(self): |
| 427 | + return f"{self.state["flower"]} are {self.state["color"]}" |
| 428 | + |
| 429 | + @listen(write_first_sentence) |
| 430 | + def finish_poem(self, first_sentence): |
| 431 | + separator = self.state.get("separator", "\n") |
| 432 | + return separator.join([first_sentence, "violets are blue"]) |
| 433 | + |
| 434 | + @listen(finish_poem) |
| 435 | + def save_poem_to_database(self): |
| 436 | + # A method without args/kwargs to ensure events are sent correctly |
| 437 | + pass |
| 438 | + |
| 439 | + event_log = [] |
| 440 | + |
| 441 | + def handle_event(_, event): |
| 442 | + event_log.append(event) |
| 443 | + |
| 444 | + flow = PoemFlow() |
| 445 | + flow.event_emitter.connect(handle_event) |
| 446 | + flow.kickoff(inputs={"separator": ", "}) |
| 447 | + |
| 448 | + assert isinstance(event_log[0], FlowStartedEvent) |
| 449 | + assert event_log[0].flow_name == "PoemFlow" |
| 450 | + assert event_log[0].inputs == {"separator": ", "} |
| 451 | + assert isinstance(event_log[0].timestamp, datetime) |
| 452 | + |
| 453 | + # Asserting for concurrent start method executions in a for loop as you |
| 454 | + # can't guarantee ordering in asynchronous executions |
| 455 | + for i in range(1, 5): |
| 456 | + event = event_log[i] |
| 457 | + assert isinstance(event.state, dict) |
| 458 | + assert isinstance(event.state["id"], str) |
| 459 | + |
| 460 | + if event.method_name == "prepare_flower": |
| 461 | + if isinstance(event, MethodExecutionStartedEvent): |
| 462 | + assert event.params == {} |
| 463 | + assert event.state.get("separator") == ", " |
| 464 | + elif isinstance(event, MethodExecutionFinishedEvent): |
| 465 | + assert event.result == "foo" |
| 466 | + assert event.state.get("flower") == "roses" |
| 467 | + assert event.state.get("separator") == ", " |
| 468 | + else: |
| 469 | + assert False, "Unexpected event type for prepare_flower" |
| 470 | + elif event.method_name == "prepare_color": |
| 471 | + if isinstance(event, MethodExecutionStartedEvent): |
| 472 | + assert event.params == {} |
| 473 | + assert event.state.get("separator") == ", " |
| 474 | + elif isinstance(event, MethodExecutionFinishedEvent): |
| 475 | + assert event.result == "bar" |
| 476 | + assert event.state.get("color") == "red" |
| 477 | + assert event.state.get("separator") == ", " |
| 478 | + else: |
| 479 | + assert False, "Unexpected event type for prepare_color" |
| 480 | + else: |
| 481 | + assert False, f"Unexpected method {event.method_name} in prepare events" |
| 482 | + |
| 483 | + assert isinstance(event_log[5], MethodExecutionStartedEvent) |
| 484 | + assert event_log[5].method_name == "write_first_sentence" |
| 485 | + assert event_log[5].params == {} |
| 486 | + assert isinstance(event_log[5].state, dict) |
| 487 | + assert event_log[5].state.get("flower") == "roses" |
| 488 | + assert event_log[5].state.get("color") == "red" |
| 489 | + assert event_log[5].state.get("separator") == ", " |
| 490 | + |
| 491 | + assert isinstance(event_log[6], MethodExecutionFinishedEvent) |
| 492 | + assert event_log[6].method_name == "write_first_sentence" |
| 493 | + assert event_log[6].result == "roses are red" |
| 494 | + |
| 495 | + assert isinstance(event_log[7], MethodExecutionStartedEvent) |
| 496 | + assert event_log[7].method_name == "finish_poem" |
| 497 | + assert event_log[7].params == {"_0": "roses are red"} |
| 498 | + assert isinstance(event_log[7].state, dict) |
| 499 | + assert event_log[7].state.get("flower") == "roses" |
| 500 | + assert event_log[7].state.get("color") == "red" |
| 501 | + |
| 502 | + assert isinstance(event_log[8], MethodExecutionFinishedEvent) |
| 503 | + assert event_log[8].method_name == "finish_poem" |
| 504 | + assert event_log[8].result == "roses are red, violets are blue" |
| 505 | + |
| 506 | + assert isinstance(event_log[9], MethodExecutionStartedEvent) |
| 507 | + assert event_log[9].method_name == "save_poem_to_database" |
| 508 | + assert event_log[9].params == {} |
| 509 | + assert isinstance(event_log[9].state, dict) |
| 510 | + assert event_log[9].state.get("flower") == "roses" |
| 511 | + assert event_log[9].state.get("color") == "red" |
| 512 | + |
| 513 | + assert isinstance(event_log[10], MethodExecutionFinishedEvent) |
| 514 | + assert event_log[10].method_name == "save_poem_to_database" |
| 515 | + assert event_log[10].result is None |
| 516 | + |
| 517 | + assert isinstance(event_log[11], FlowFinishedEvent) |
| 518 | + assert event_log[11].flow_name == "PoemFlow" |
| 519 | + assert event_log[11].result is None |
| 520 | + assert isinstance(event_log[11].timestamp, datetime) |
| 521 | + |
| 522 | + |
| 523 | +def test_structured_flow_event_emission(): |
| 524 | + """Test that the correct events are emitted during structured flow |
| 525 | + execution with all fields validated.""" |
| 526 | + |
| 527 | + class OnboardingState(BaseModel): |
| 528 | + name: str = "" |
| 529 | + sent: bool = False |
| 530 | + |
| 531 | + class OnboardingFlow(Flow[OnboardingState]): |
| 532 | + @start() |
| 533 | + def user_signs_up(self): |
| 534 | + self.state.sent = False |
| 535 | + |
| 536 | + @listen(user_signs_up) |
| 537 | + def send_welcome_message(self): |
| 538 | + self.state.sent = True |
| 539 | + return f"Welcome, {self.state.name}!" |
| 540 | + |
| 541 | + event_log = [] |
| 542 | + |
| 543 | + def handle_event(_, event): |
| 544 | + event_log.append(event) |
| 545 | + |
| 546 | + flow = OnboardingFlow() |
| 547 | + flow.event_emitter.connect(handle_event) |
| 548 | + flow.kickoff(inputs={"name": "Anakin"}) |
| 549 | + |
| 550 | + assert isinstance(event_log[0], FlowStartedEvent) |
| 551 | + assert event_log[0].flow_name == "OnboardingFlow" |
| 552 | + assert event_log[0].inputs == {"name": "Anakin"} |
| 553 | + assert isinstance(event_log[0].timestamp, datetime) |
| 554 | + |
| 555 | + assert isinstance(event_log[1], MethodExecutionStartedEvent) |
| 556 | + assert event_log[1].method_name == "user_signs_up" |
| 557 | + |
| 558 | + assert isinstance(event_log[2], MethodExecutionFinishedEvent) |
| 559 | + assert event_log[2].method_name == "user_signs_up" |
| 560 | + |
| 561 | + assert isinstance(event_log[3], MethodExecutionStartedEvent) |
| 562 | + assert event_log[3].method_name == "send_welcome_message" |
| 563 | + assert event_log[3].params == {} |
| 564 | + assert isinstance(event_log[3].state, dict) |
| 565 | + assert event_log[3].state.get("sent") == False |
| 566 | + |
| 567 | + assert isinstance(event_log[4], MethodExecutionFinishedEvent) |
| 568 | + assert event_log[4].method_name == "send_welcome_message" |
| 569 | + assert isinstance(event_log[4].state, dict) |
| 570 | + assert event_log[4].state.get("sent") == True |
| 571 | + assert event_log[4].result == "Welcome, Anakin!" |
| 572 | + |
| 573 | + assert isinstance(event_log[5], FlowFinishedEvent) |
| 574 | + assert event_log[5].flow_name == "OnboardingFlow" |
| 575 | + assert event_log[5].result == "Welcome, Anakin!" |
| 576 | + assert isinstance(event_log[5].timestamp, datetime) |
| 577 | + |
| 578 | + |
| 579 | +def test_stateless_flow_event_emission(): |
| 580 | + """Test that the correct events are emitted stateless during flow execution |
| 581 | + with all fields validated.""" |
| 582 | + |
| 583 | + class StatelessFlow(Flow): |
| 584 | + @start() |
| 585 | + def init(self): |
| 586 | + pass |
| 587 | + |
| 588 | + @listen(init) |
| 589 | + def process(self): |
| 590 | + return "Deeds will not be less valiant because they are unpraised." |
| 591 | + |
| 592 | + event_log = [] |
| 593 | + |
| 594 | + def handle_event(_, event): |
| 595 | + event_log.append(event) |
| 596 | + |
| 597 | + flow = StatelessFlow() |
| 598 | + flow.event_emitter.connect(handle_event) |
| 599 | + flow.kickoff() |
| 600 | + |
| 601 | + assert isinstance(event_log[0], FlowStartedEvent) |
| 602 | + assert event_log[0].flow_name == "StatelessFlow" |
| 603 | + assert event_log[0].inputs is None |
| 604 | + assert isinstance(event_log[0].timestamp, datetime) |
| 605 | + |
| 606 | + assert isinstance(event_log[1], MethodExecutionStartedEvent) |
| 607 | + assert event_log[1].method_name == "init" |
| 608 | + |
| 609 | + assert isinstance(event_log[2], MethodExecutionFinishedEvent) |
| 610 | + assert event_log[2].method_name == "init" |
| 611 | + |
| 612 | + assert isinstance(event_log[3], MethodExecutionStartedEvent) |
| 613 | + assert event_log[3].method_name == "process" |
| 614 | + |
| 615 | + assert isinstance(event_log[4], MethodExecutionFinishedEvent) |
| 616 | + assert event_log[4].method_name == "process" |
| 617 | + |
| 618 | + assert isinstance(event_log[5], FlowFinishedEvent) |
| 619 | + assert event_log[5].flow_name == "StatelessFlow" |
| 620 | + assert ( |
| 621 | + event_log[5].result |
| 622 | + == "Deeds will not be less valiant because they are unpraised." |
| 623 | + ) |
| 624 | + assert isinstance(event_log[5].timestamp, datetime) |
0 commit comments