Python: Define Workflow and Executor APIs (#272)

* Workflow init commit

* Add samples and clean up

* ExecutionContext -> WorkflowContext

* Address comments 1

* Fix mypy

* flatting folder structure, and rename contexts

* Remove add_loop

* Add map reduce sample, remove Activation conditions

* Add AgentExecutor and allow multiple handlers per executor

* Minor improvement

* Add RequestInfoExecutor

* Add unit tests part 1

* Address comments 2

* Pre-commit update

* Add run method and more unit tests

* Add xml docs

* run_stream -> run_streaming

* message_handler -> handler

---------

Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
This commit is contained in:
Tao Chen
2025-08-06 16:26:15 -07:00
committed by GitHub
Unverified
parent 0d4d7abde1
commit c8694a8c76
34 changed files with 5036 additions and 431 deletions
@@ -0,0 +1,199 @@
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
@@ -0,0 +1,65 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.workflow import Executor, WorkflowBuilder, WorkflowCompletedEvent, WorkflowContext, handler
"""
The following sample demonstrates a basic workflow with two executors
that process a string in sequence. The first executor converts the
input string to uppercase, and the second executor reverses the string.
"""
class UpperCaseExecutor(Executor):
"""An executor that converts text to uppercase."""
@handler(output_types=[str])
async def to_upper_case(self, text: str, ctx: WorkflowContext) -> None:
"""Execute the task by converting the input string to uppercase."""
result = text.upper()
# Send the result to the next executor in the workflow.
await ctx.send_message(result)
class ReverseTextExecutor(Executor):
"""An executor that reverses text."""
@handler
async def reverse_text(self, text: str, ctx: WorkflowContext) -> None:
"""Execute the task by reversing the input string."""
result = text[::-1]
# Send the result with a workflow completion event.
await ctx.add_event(WorkflowCompletedEvent(result))
async def main():
"""Main function to run the workflow."""
# Step 1: Create the executors.
upper_case_executor = UpperCaseExecutor(id="upper_case_executor")
reverse_text_executor = ReverseTextExecutor(id="reverse_text_executor")
# Step 2: Build the workflow with the defined edges.
workflow = (
WorkflowBuilder()
.add_edge(upper_case_executor, reverse_text_executor)
.set_start_executor(upper_case_executor)
.build()
)
# Step 3: Run the workflow with an initial message.
completion_event = None
async for event in workflow.run_streaming("hello world"):
print(f"Event: {event}")
if isinstance(event, WorkflowCompletedEvent):
# The WorkflowCompletedEvent contains the final result.
completion_event = event
if completion_event:
print(f"Workflow completed with result: {completion_event.data}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,58 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.workflow import Executor, WorkflowBuilder, WorkflowCompletedEvent, WorkflowContext, handler
"""
The following sample demonstrates a basic workflow with two executors
that process a string in sequence. The first executor converts the
input string to uppercase, and the second executor reverses the string.
"""
class UpperCaseExecutor(Executor):
"""An executor that converts text to uppercase."""
@handler(output_types=[str])
async def to_upper_case(self, text: str, ctx: WorkflowContext) -> None:
"""Execute the task by converting the input string to uppercase."""
result = text.upper()
# Send the result to the next executor in the workflow.
await ctx.send_message(result)
class ReverseTextExecutor(Executor):
"""An executor that reverses text."""
@handler
async def reverse_text(self, text: str, ctx: WorkflowContext) -> None:
"""Execute the task by reversing the input string."""
result = text[::-1]
# Send the result with a workflow completion event.
await ctx.add_event(WorkflowCompletedEvent(result))
async def main():
"""Main function to run the workflow."""
# Step 1: Create the executors.
upper_case_executor = UpperCaseExecutor(id="upper_case_executor")
reverse_text_executor = ReverseTextExecutor(id="reverse_text_executor")
# Step 2: Build the workflow with the defined edges.
workflow = (
WorkflowBuilder()
.add_edge(upper_case_executor, reverse_text_executor)
.set_start_executor(upper_case_executor)
.build()
)
# Step 3: Run the workflow with an initial message.
events = await workflow.run("hello world")
print(events.get_completed_event())
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,119 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from dataclasses import dataclass
from agent_framework.workflow import (
Executor,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
"""
The following sample demonstrates a basic workflow with two executors
that detect spam messages and respond accordingly. The first executor
checks if the input string is spam, and depending on the result, the
workflow takes different paths.
"""
@dataclass
class SpamDetectorResponse:
"""A data class to hold the email message content."""
email: str
is_spam: bool = False
class SpamDetector(Executor):
"""An executor that determines if a message is spam."""
def __init__(self, spam_keywords: list[str], id: str | None = None):
"""Initialize the executor with spam keywords."""
super().__init__(id=id)
self._spam_keywords = spam_keywords
@handler(output_types=[SpamDetectorResponse])
async def handle_email(self, email: str, ctx: WorkflowContext) -> None:
"""Determine if the input string is spam."""
result = any(keyword in email.lower() for keyword in self._spam_keywords)
await ctx.send_message(SpamDetectorResponse(email=email, is_spam=result))
class SendResponse(Executor):
"""An executor that responds to a message based on spam detection."""
@handler
async def handle_detector_response(
self,
spam_detector_response: SpamDetectorResponse,
ctx: WorkflowContext,
) -> None:
"""Respond with a message based on whether the input is spam."""
if spam_detector_response.is_spam:
raise RuntimeError("Input is spam, cannot respond.")
# Simulate processing delay
print(f"Responding to message: {spam_detector_response.email}")
await asyncio.sleep(1)
await ctx.add_event(WorkflowCompletedEvent("Message processed successfully."))
class RemoveSpam(Executor):
"""An executor that removes spam messages."""
@handler
async def handle_detector_response(
self,
spam_detector_response: SpamDetectorResponse,
ctx: WorkflowContext,
) -> None:
"""Remove the spam message."""
if spam_detector_response.is_spam is False:
raise RuntimeError("Input is not spam, cannot remove.")
# Simulate processing delay
print(f"Removing spam message: {spam_detector_response.email}")
await asyncio.sleep(1)
await ctx.add_event(WorkflowCompletedEvent("Spam message removed."))
async def main():
"""Main function to run the workflow."""
# Keyword based spam detection
spam_keywords = ["spam", "advertisement", "offer"]
# Step 1: Create the executors.
spam_detector = SpamDetector(spam_keywords, id="spam_detector")
send_response = SendResponse(id="send_response")
remove_spam = RemoveSpam(id="remove_spam")
# Step 2: Build the workflow with the defined edges with conditions.
workflow = (
WorkflowBuilder()
.set_start_executor(spam_detector)
.add_edge(
spam_detector,
send_response,
condition=lambda x: x.is_spam is False,
)
.add_edge(
spam_detector,
remove_spam,
condition=lambda x: x.is_spam is True,
)
.build()
)
# Step 3: Run the workflow with an input message.
async for event in workflow.run_streaming("This is a spam."):
print(f"Event: {event}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,120 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from enum import Enum
from agent_framework.workflow import (
Executor,
ExecutorCompletedEvent,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
"""
The following sample demonstrates a basic workflow with two executors
where one executor guesses a number and the other executor judges the
guess iteratively.
"""
class NumberSignal(Enum):
"""Enum to represent number signals for the workflow."""
# The target number is above the guess.
ABOVE = "above"
# The target number is below the guess.
BELOW = "below"
# The guess matches the target number.
MATCHED = "matched"
# Initial signal to start the guessing process.
INIT = "init"
class GuessNumberExecutor(Executor):
"""An executor that guesses a number."""
def __init__(self, bound: tuple[int, int], id: str | None = None):
"""Initialize the executor with a target number."""
super().__init__(id=id)
self._lower = bound[0]
self._upper = bound[1]
@handler(output_types=[int])
async def guess_number(self, feedback: NumberSignal, ctx: WorkflowContext) -> None:
"""Execute the task by guessing a number."""
if feedback == NumberSignal.INIT:
self._guess = (self._lower + self._upper) // 2
await ctx.send_message(self._guess)
elif feedback == NumberSignal.MATCHED:
# The previous guess was correct.
await ctx.add_event(WorkflowCompletedEvent(f"Guessed the number: {self._guess}"))
elif feedback == NumberSignal.ABOVE:
# The previous guess was too low.
# Update the lower bound to the previous guess.
# Generate a new number that is between the new bounds.
self._lower = self._guess + 1
self._guess = (self._lower + self._upper) // 2
await ctx.send_message(self._guess)
else:
# The previous guess was too high.
# Update the upper bound to the previous guess.
# Generate a new number that is between the new bounds.
self._upper = self._guess - 1
self._guess = (self._lower + self._upper) // 2
await ctx.send_message(self._guess)
class JudgeExecutor(Executor):
"""An executor that judges the guessed number."""
def __init__(self, target: int, id: str | None = None):
"""Initialize the executor with a target number."""
super().__init__(id=id)
self._target = target
@handler(output_types=[NumberSignal])
async def judge(self, number: int, ctx: WorkflowContext) -> None:
"""Judge the guessed number."""
if number == self._target:
result = NumberSignal.MATCHED
elif number < self._target:
result = NumberSignal.ABOVE
else:
result = NumberSignal.BELOW
await ctx.send_message(result)
async def main():
"""Main function to run the workflow."""
# Step 1: Create the executors.
guess_number_executor = GuessNumberExecutor((1, 100))
judge_executor = JudgeExecutor(30)
# Step 2: Build the workflow with the defined edges.
# This time we are creating a loop in the workflow.
workflow = (
WorkflowBuilder()
.add_edge(guess_number_executor, judge_executor)
.add_edge(judge_executor, guess_number_executor)
.set_start_executor(guess_number_executor)
.build()
)
# Step 3: Run the workflow and print the events.
iterations = 0
async for event in workflow.run_streaming(NumberSignal.INIT):
if isinstance(event, ExecutorCompletedEvent) and event.executor_id == guess_number_executor.id:
iterations += 1
print(f"Event: {event}")
# This is essentially a binary search, so the number of iterations should be logarithmic.
# The maximum number of iterations is [log2(range size)]. For a range of 1 to 100, this is log2(100) which is 7.
# Subtract because the last round is the MATCHED event.
print(f"Guessed {iterations - 1} times.")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,147 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import ChatClientAgent, ChatMessage, ChatRole
from agent_framework.azure import AzureChatClient
from agent_framework.workflow import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunEvent,
Executor,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
"""
The following sample demonstrates a basic workflow that simulates
a round-robin group chat.
"""
class RoundRobinGroupChatManager(Executor):
"""An executor that manages a round-robin group chat."""
def __init__(self, members: list[str], max_round: int, id: str | None = None):
"""Initialize the executor with a unique identifier."""
super().__init__(id)
self._members = members
self._max_round = max_round
self._current_round = 0
@handler(output_types=[AgentExecutorRequest])
async def start(self, task: str, ctx: WorkflowContext) -> None:
"""Execute the task by sending messages to the next executor in the round-robin sequence."""
initial_message = ChatMessage(ChatRole.USER, text=task)
# Send the initial message to the members
await asyncio.gather(*[
ctx.send_message(
AgentExecutorRequest(messages=[initial_message], should_respond=False),
target_id=member_id,
)
for member_id in self._members
])
# Invoke the first member to start the round-robin chat
await ctx.send_message(
AgentExecutorRequest(messages=[], should_respond=True),
target_id=self._get_next_member(),
)
@handler(output_types=[AgentExecutorRequest])
async def handle_agent_response(self, response: AgentExecutorResponse, ctx: WorkflowContext) -> None:
"""Execute the task by sending messages to the next executor in the round-robin sequence."""
# Send the response to the other members
await asyncio.gather(*[
ctx.send_message(
AgentExecutorRequest(messages=response.agent_run_response.messages, should_respond=False),
target_id=member_id,
)
for member_id in self._members
if member_id != response.executor_id
])
# Check for termination condition
if self._should_terminate():
await ctx.add_event(WorkflowCompletedEvent(data=response))
return
# Request the next member to respond
selection = self._get_next_member()
await ctx.send_message(AgentExecutorRequest(messages=[], should_respond=True), target_id=selection)
def _should_terminate(self) -> bool:
"""Determine if the group chat should terminate based on the current round."""
return self._current_round >= self._max_round
def _get_next_member(self) -> str:
"""Get the next member in the round-robin sequence."""
next_member = self._members[self._current_round % len(self._members)]
self._current_round += 1
return next_member
async def main():
"""Main function to run the group chat workflow."""
# Step 1: Create the executors.
chat_client = AzureChatClient()
writer = AgentExecutor(
ChatClientAgent(
chat_client,
instructions=(
"You are an excellent content writer. You create new content and edit contents based on the feedback."
),
),
id="writer",
)
reviewer = AgentExecutor(
ChatClientAgent(
chat_client,
instructions=(
"You are an excellent content reviewer. You review the content and provide feedback to the writer."
),
),
id="reviewer",
)
group_chat_manager = RoundRobinGroupChatManager(
members=[writer.id, reviewer.id],
# max_rounds is odd, so that the writer gets the last round
max_round=5,
id="group_chat_manager",
)
# Step 2: Build the workflow with the defined edges.
workflow = (
WorkflowBuilder()
.set_start_executor(group_chat_manager)
.add_edge(group_chat_manager, writer)
.add_edge(group_chat_manager, reviewer)
.add_edge(writer, group_chat_manager)
.add_edge(reviewer, group_chat_manager)
.build()
)
# Step 3: Run the workflow with an initial message.
completion_event = None
async for event in workflow.run_streaming(
"Create a slogan for a new electric SUV that is affordable and fun to drive."
):
if isinstance(event, AgentRunEvent):
print(f"{event}")
if isinstance(event, WorkflowCompletedEvent):
completion_event = event
if completion_event:
print(f"Completion Event: {completion_event}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,215 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import ChatClientAgent, ChatMessage, ChatRole
from agent_framework.azure import AzureChatClient
from agent_framework.workflow import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
Executor,
RequestInfoEvent,
RequestInfoExecutor,
RequestInfoMessage,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
"""
The following sample demonstrates a basic workflow that simulates
a round-robin group chat with a Human-in-the-Loop (HIL) executor.
"""
class CriticGroupChatManager(Executor):
"""An executor that manages a round-robin group chat."""
def __init__(self, members: list[str], id: str | None = None):
"""Initialize the executor with a unique identifier."""
super().__init__(id)
self._members = members
self._current_round = 0
self._chat_history: list[ChatMessage] = []
@handler(output_types=[AgentExecutorRequest])
async def start(self, task: str, ctx: WorkflowContext) -> None:
"""Handler that starts the group chat with an initial task."""
initial_message = ChatMessage(ChatRole.USER, text=task)
# Send the initial message to the members
await self._broadcast_message([initial_message], ctx)
# Invoke the first member to start the round-robin chat
await ctx.send_message(
AgentExecutorRequest(messages=[], should_respond=True),
target_id=self._get_next_member(),
)
# Update the cache with the initial message
self._chat_history.append(initial_message)
@handler(output_types=[AgentExecutorRequest, RequestInfoMessage])
async def handle_agent_response(self, response: AgentExecutorResponse, ctx: WorkflowContext) -> None:
"""Handler that processes the response from the agent."""
# Update the chat history with the response
self._chat_history.extend(response.agent_run_response.messages)
# Send the response to the other members
await self._broadcast_message(response.agent_run_response.messages, ctx, exclude_id=response.executor_id)
# Check if we need to request additional information
if self._should_request_info():
await ctx.send_message(RequestInfoMessage())
return
# Check for termination condition
if self._should_terminate():
await ctx.add_event(WorkflowCompletedEvent(data=response))
return
# Request the next member to respond
selection = self._get_next_member()
await ctx.send_message(AgentExecutorRequest(messages=[], should_respond=True), target_id=selection)
@handler(output_types=[AgentExecutorRequest])
async def handle_request_response(self, response: list[ChatMessage], ctx: WorkflowContext) -> None:
"""Handler that processes the response from the RequestInfoExecutor."""
# Update the chat history with the response
self._chat_history.extend(response)
# Send the response to the other members
await self._broadcast_message(response, ctx)
# Check for termination condition
if self._should_terminate():
await ctx.add_event(WorkflowCompletedEvent(data=response))
return
# Request the next member to respond
selection = self._get_next_member()
await ctx.send_message(AgentExecutorRequest(messages=[], should_respond=True), target_id=selection)
async def _broadcast_message(
self,
messages: list[ChatMessage],
ctx: WorkflowContext,
exclude_id: str | None = None,
) -> None:
"""Broadcast messages to all members."""
await asyncio.gather(*[
ctx.send_message(
AgentExecutorRequest(messages=messages, should_respond=False),
target_id=member_id,
)
for member_id in self._members
if member_id != exclude_id
])
def _should_terminate(self) -> bool:
"""Determine if the group chat should terminate based on the last message."""
if len(self._chat_history) == 0:
return False
last_message = self._chat_history[-1]
return bool(last_message.role == ChatRole.USER and "approve" in last_message.text.lower())
def _should_request_info(self) -> bool:
"""Determine if the group chat should request HIL based on the last message."""
if len(self._chat_history) == 0:
return True
last_message = self._chat_history[-1]
return last_message.role == ChatRole.ASSISTANT
def _get_next_member(self) -> str:
"""Get the next member in the round-robin sequence."""
next_member = self._members[self._current_round % len(self._members)]
self._current_round += 1
return next_member
async def main():
"""Main function to run the group chat workflow."""
# Step 1: Create the executors.
writer = AgentExecutor(
ChatClientAgent(
AzureChatClient(),
instructions=(
"You are an excellent content writer. You create new content and edit contents based on the feedback."
),
name="Writer",
id="Writer",
),
)
reviewer = AgentExecutor(
ChatClientAgent(
AzureChatClient(),
instructions=(
"You are an excellent content reviewer. You review the content and provide feedback to the writer. "
"You do not address user requests. Only provide feedback to the writer."
),
name="Reviewer",
id="Reviewer",
),
)
group_chat_manager = CriticGroupChatManager(members=[writer.id, reviewer.id], id="GroupChatManager")
request_info_executor = RequestInfoExecutor()
# Step 2: Build the workflow with the defined edges.
workflow = (
WorkflowBuilder()
.set_start_executor(group_chat_manager)
.add_edge(group_chat_manager, request_info_executor)
.add_edge(request_info_executor, group_chat_manager)
.add_edge(group_chat_manager, writer)
.add_edge(group_chat_manager, reviewer)
.add_edge(writer, group_chat_manager)
.add_edge(reviewer, group_chat_manager)
.build()
)
# Step 3: Run the workflow with an initial message.
# Here we are capturing the RequestInfoEvent event and allowing the user to provide input.
# Once the user provides input, we will provide it back to the workflow to continue the execution.
completion_event: WorkflowCompletedEvent | None = None
request_info_event: RequestInfoEvent | None = None
user_input = ""
while True:
# Depending on whether we have a RequestInfoEvent event, we either
# run the workflow normally or send the message to the HIL executor.
if not request_info_event:
response_stream = workflow.run_streaming(
"Create a slogan for a new electric SUV that is affordable and fun to drive."
)
else:
response_stream = workflow.send_responses_streaming({
request_info_event.request_id: [ChatMessage(ChatRole.USER, text=user_input)]
})
request_info_event = None
async for event in response_stream:
print(event)
if isinstance(event, WorkflowCompletedEvent):
completion_event = event
elif isinstance(event, RequestInfoEvent):
request_info_event = event
# Prompt for user input if we are waiting for human intervention
if request_info_event:
user_input = input("Human feedback required. Please provide your input (type 'approve' to end): ")
elif completion_event:
break
print(f"Completion Event: {completion_event}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,312 @@
# Copyright (c) Microsoft. All rights reserved.
import ast
import asyncio
import os
import sys
from collections import defaultdict
from dataclasses import dataclass
import aiofiles
from agent_framework.workflow import (
Executor,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
if sys.version_info >= (3, 12):
pass # pragma: no cover
else:
pass # pragma: no cover
"""
The following sample demonstrates a basic map reduce workflow that
processes a large text file by splitting it into smaller chunks,
mapping each word to a count, shuffling the results, and reducing them
to a final count per word.
Intermediate results are stored in a temporary directory, and the
final results are written to a file in the same directory.
"""
# Define the temporary directory for storing intermediate results
DIR = os.path.dirname(__file__)
TEMP_DIR = os.path.join(DIR, "tmp")
# Ensure the temporary directory exists
os.makedirs(TEMP_DIR, exist_ok=True)
# Define a key for the shared state to store the data to be processed
SHARED_STATE_DATA_KEY = "data_to_be_processed"
class SplitCompleted:
"""A class to signal the completion of the Split executor."""
...
class Split(Executor):
"""An executor that splits data into smaller chunks based on the number of nodes available."""
def __init__(self, map_executor_ids: list[str], id: str | None = None):
"""Initialize the executor with the number of nodes."""
super().__init__(id)
self._map_executor_ids = map_executor_ids
@handler(output_types=[SplitCompleted])
async def split(self, data: str, ctx: WorkflowContext) -> None:
"""Execute the task by splitting the data into chunks.
Args:
data: A string containing the text to be processed.
ctx: The execution context containing the shared state and other information.
"""
# Process data into a list of words and remove empty lines/words.
word_list = self._preprocess(data)
# Store the data to be processed state for later use.
await ctx.set_shared_state(SHARED_STATE_DATA_KEY, word_list)
# Split the word_list into chunks that are represented by the start and end indices.
# The start and end indices tuples will be stored in the shared state.
map_executor_count = len(self._map_executor_ids)
chunk_size = len(word_list) // map_executor_count # Assuming map_executor_count is not 0.
async def _process_chunk(i: int) -> None:
"""Process each chunk and send a message to the executor."""
start_index = i * chunk_size
end_index = start_index + chunk_size if i < map_executor_count - 1 else len(word_list)
# The start and end indices are stored in the shared state for the MapExecutor.
# This allows the MapExecutor to know which part of the data it should process.
await ctx.set_shared_state(self._map_executor_ids[i], (start_index, end_index))
await ctx.send_message(SplitCompleted(), self._map_executor_ids[i])
tasks = [asyncio.create_task(_process_chunk(i)) for i in range(map_executor_count)]
await asyncio.gather(*tasks)
def _preprocess(self, data: str) -> list[str]:
"""Preprocess the input data and return a list of words.
Args:
data: The input data to be processed.
Returns:
A list of words extracted from the input data.
"""
line_list = [line.strip() for line in data.splitlines() if line.strip()]
return [word for line in line_list for word in line.split() if word]
@dataclass
class MapCompleted:
"""A data class to hold the completed state of the MapExecutor."""
file_path: str
class Map(Executor):
"""An executor that applies a function to each item in the data and save the result to a file."""
@handler(output_types=[MapCompleted])
async def map(self, _: SplitCompleted, ctx: WorkflowContext) -> None:
"""Execute the task by applying a function to each item and same result to a file.
Args:
data: An instance of SplitCompleted signaling the map step can be started.
ctx: The execution context containing the shared state and other information.
"""
# Retrieve the data to be processed from the shared state.# Define a key for the shared state to store the data to be processed
data_to_be_processed: list[str] = await ctx.get_shared_state(SHARED_STATE_DATA_KEY)
chunk_start, chunk_end = await ctx.get_shared_state(self.id)
results = [(item, 1) for item in data_to_be_processed[chunk_start:chunk_end]]
file_path = os.path.join(TEMP_DIR, f"map_results_{self.id}.txt")
async with aiofiles.open(file_path, "w") as f:
await f.writelines([f"{item}: {count}\n" for item, count in results])
await ctx.send_message(MapCompleted(file_path))
@dataclass
class ShuffleCompleted:
"""A data class to hold the completed state of the ShuffleExecutor."""
file_path: str
reducer_id: str
class Shuffle(Executor):
"""An executor that redistributes results from the map step to the reduce step."""
def __init__(self, reducer_ids: list[str], id: str | None = None):
"""Initialize the executor with the number of nodes."""
super().__init__(id)
self._reducer_ids = reducer_ids
@handler(output_types=[ShuffleCompleted])
async def shuffle(self, data: list[MapCompleted], ctx: WorkflowContext) -> None:
"""Execute the task by aggregating the results.
Args:
data: A list of MapCompleted instances containing the file paths of the map results.
ctx: The execution context containing the shared state and other information.
"""
chunks = await self._preprocess(data)
async def _process_chunk(chunk: list[tuple[str, list[int]]], index: int) -> None:
"""Process each chunk and save it to a file."""
file_path = os.path.join(TEMP_DIR, f"shuffle_results_{index}.txt")
async with aiofiles.open(file_path, "w") as f:
await f.writelines([f"{key}: {value}\n" for key, value in chunk])
await ctx.send_message(ShuffleCompleted(file_path, self._reducer_ids[index]))
tasks = [asyncio.create_task(_process_chunk(chunk, i)) for i, chunk in enumerate(chunks)]
await asyncio.gather(*tasks)
async def _preprocess(self, data: list[MapCompleted]) -> list[list[tuple[str, list[int]]]]:
"""Preprocess the input data and return a list of data to be processed by the reduce executors.
Args:
data: A list of MapCompleted instances containing the file paths of the map results.
Returns:
A list of lists, where each inner list contains tuples of (key, value) pairs to be processed
by the reduce executors.
"""
map_results: list[tuple[str, int]] = []
for result in data:
async with aiofiles.open(result.file_path, "r") as f:
map_results.extend([
(line.strip().split(": ")[0], int(line.strip().split(": ")[1])) for line in await f.readlines()
])
# Group values by the first element
intermediate_results: defaultdict[str, list[int]] = defaultdict(list[int])
for item in map_results:
key = item[0]
value = item[1]
intermediate_results[key].append(value)
# Convert defaultdict to a list
aggregated_results = [(key, values) for key, values in intermediate_results.items()]
# Sort by the first element
aggregated_results.sort(key=lambda x: x[0])
# Split the intermediate results into chunks for the reduce executors
reduce_executor_count = len(self._reducer_ids)
chunk_size = len(aggregated_results) // reduce_executor_count
remaining = len(aggregated_results) % reduce_executor_count
chunks = [
aggregated_results[i : i + chunk_size] for i in range(0, len(aggregated_results) - remaining, chunk_size)
]
# Append the remaining items to the last chunk
if remaining > 0:
chunks[-1].extend(aggregated_results[-remaining:])
return chunks
@dataclass
class ReduceCompleted:
"""A data class to hold the completed state of the ReduceExecutor."""
file_path: str
class Reduce(Executor):
"""An executor that reduces the results from the ShuffleExecutor."""
@handler(output_types=[ReduceCompleted])
async def _execute(self, data: ShuffleCompleted, ctx: WorkflowContext) -> None:
"""Execute the task by reducing the results.
Args:
data: An instance of ShuffleCompleted containing the file path of the shuffle results.
ctx: The execution context containing the shared state and other information.
"""
if data.reducer_id != self.id:
# If the reducer ID does not match, skip processing.
return
# Read the intermediate results from the file
async with aiofiles.open(data.file_path, "r") as f:
lines = await f.readlines()
# Aggregate the results
reduced_results: dict[str, int] = defaultdict(int)
for line in lines:
key, value = line.split(": ")
reduced_results[key] = sum(ast.literal_eval(value))
# Write the reduced results to a file
file_path = os.path.join(TEMP_DIR, f"reduced_results_{self.id}.txt")
async with aiofiles.open(file_path, "w") as f:
await f.writelines([f"{key}: {value}\n" for key, value in reduced_results.items()])
await ctx.send_message(ReduceCompleted(file_path))
class CompletionExecutor(Executor):
"""An executor that completes the workflow by aggregating the results from the ReduceExecutors."""
@handler
async def complete(self, data: list[ReduceCompleted], ctx: WorkflowContext) -> None:
"""Execute the task by aggregating the results.
Args:
data: A list of ReduceCompleted instances containing the file paths of the reduced results.
ctx: The execution context containing the shared state and other information.
"""
await ctx.add_event(WorkflowCompletedEvent(data=[result.file_path for result in data]))
async def main():
"""Main function to run the workflow."""
# Step 1: Create the executors.
map_operations = [Map(id=f"map_executor_{i}") for i in range(3)]
split_operation = Split(
[map_operation.id for map_operation in map_operations],
id="split_data_executor",
)
reduce_operations = [Reduce(id=f"reduce_executor_{i}") for i in range(4)]
shuffle_operation = Shuffle(
[reduce_operation.id for reduce_operation in reduce_operations],
id="shuffle_executor",
)
completion_executor = CompletionExecutor(id="completion_executor")
# Step 2: Build the workflow.
workflow = (
WorkflowBuilder()
.set_start_executor(split_operation)
.add_fan_out_edges(split_operation, map_operations)
.add_fan_in_edges(map_operations, shuffle_operation)
.add_fan_out_edges(shuffle_operation, reduce_operations)
.add_fan_in_edges(reduce_operations, completion_executor)
.build()
)
# Step 3: Open the text file and read its content.
async with aiofiles.open(os.path.join(DIR, "resources", "long_text.txt"), "r") as f:
raw_text = await f.read()
# Step 4: Run the workflow with the raw text as input.
completion_event = None
async for event in workflow.run_streaming(raw_text):
print(f"Event: {event}")
if isinstance(event, WorkflowCompletedEvent):
completion_event = event
if completion_event:
print(f"Completion Event: {completion_event}")
if __name__ == "__main__":
asyncio.run(main())