| .. |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| |
| |
| ==================== |
| Applications |
| ==================== |
| |
| .. _applications: |
| |
| .. note:: |
| |
| Applications tie everything in Burr together. They specify which actions should be included, |
| how they connect (including conditional edges), and how to run. |
| |
| Applications form the core representation of the state machine. You build them with the ``ApplicationBuilder``. |
| Here is the minimum that is required: |
| |
| 1. A ``**kwargs`` of actions passed to ``with_actions(...)`` |
| 2. Any relevant transitions (with conditions) |
| 3. An entry point -- this is the first action to execute |
| |
| This is shown in the example from :ref:`getting started <simpleexample>` |
| |
| .. code-block:: python |
| |
| from burr.core import ApplicationBuilder, default, expr |
| app = ( |
| ApplicationBuilder() |
| .with_actions(human_input, ai_response) |
| .with_transitions( |
| ("human_input", "ai_response"), |
| ("ai_response", "human_input") |
| ).with_state(chat_history=[]) |
| .with_entrypoint("human_input") |
| .build() |
| ) |
| |
| ------- |
| Running |
| ------- |
| |
| There are four APIs for executing an application. |
| |
| ``step``/``astep`` |
| ------------------ |
| |
| Returns the tuple of the action, the result of that action, and the new state. Call this if you want to run the application step-by-step. |
| |
| .. code-block:: python |
| |
| action, result, state = application.step() |
| |
| If you're in an async context, you can run `astep` instead: |
| |
| .. code-block:: python |
| |
| action, result, state = await application.astep() |
| |
| |
| Step can also take in ``inputs`` as a dictionary, which will be passed to the action's run function as keyword arguments. |
| This is specifically meant for a "human in the loop" scenario, where the action needs to ask for input from a user. In this case, |
| the control flow is meant to be interrupted to allow for the user to provide input. See :ref:`inputs <inputref>` for more information. |
| |
| .. code-block:: python |
| |
| action, result, state = application.step(inputs={"prompt": input()}) |
| |
| ``iterate``/``aiterate`` |
| ------------------------ |
| |
| Iterate just runs ``step`` in a row, functioning as a generator: |
| |
| .. code-block:: python |
| |
| for action, result, state in application.iterate(halt_after=["final_action_1", "final_action_2"]): |
| print(action.name, result) |
| |
| You can also run ``aiterate`` in an async context: |
| |
| .. code-block:: python |
| |
| async for action, result, state in application.aiterate(): |
| print(action.name, result) |
| |
| |
| In the synchronous context this also has a return value of a tuple of: |
| |
| |
| #. the action that was specified in ``halt_after`` or ``halt_before``. In the `after` case the action will have already run. In the ``before`` case the action will not have run. |
| |
| #. The result of that action, in the ``halt_after`` case, else None in the ``halt_before`` case. |
| |
| #. The state of the application at the time of halting. |
| |
| You can access this by looking at the ``value`` variable of the ``StopIteration`` exception that is thrown |
| at the end of the loop, as is standard for python generators. |
| See the function implementation of ``run`` to show how this is done. |
| |
| In the async context, this does not return anything (asynchronous generators are not allowed a return value). |
| |
| If you want it to (attempt to) run forever, you can pass empty lists to ``halt_after`` and ``halt_before``. |
| |
| .. note:: |
| You can add inputs to ``iterate``/``aiterate`` by passing in a dictionary of inputs through the ``inputs`` parameter. |
| This will only apply to the first action. Actions that are not the first but require inputs are considered undefined behavior. |
| |
| .. warning:: |
| The state machine has the capability of halting even if it does not reach one of the specified conditions -- if there are |
| no more transitions to take. This, however, is not a supported feature (and will log a warning). This is considered undefined behavior |
| -- use at your own risk, and consider adding a halting condition, or a default transition to a terminal. |
| |
| ``run``/``arun`` |
| ---------------- |
| |
| Run just calls out to ``iterate`` and returns the final state. |
| |
| The ``halt_after`` and ``halt_before`` keyword arguments specify when to break out of running the state machine |
| and return control back. ``halt_after`` will stop after the specified action(s) has run, and ``halt_before`` will stop before the specified action(s) has run. |
| If multiple are specified, it will stop after the first one encountered, and the return values will be for that action. |
| |
| .. code-block:: python |
| |
| action, result, state = application.run( |
| halt_after=["final_action_1", "final_action_2"] |
| ) |
| |
| |
| In the async context, you can run ``arun``: |
| |
| .. code-block:: python |
| |
| action, result, state = await application.arun( |
| halt_after=["final_action_1", "final_action_2"] |
| ) |
| |
| .. note:: |
| You can add inputs to ``run``/``arun`` in the same way as you can with ``iterate`` -- it will only apply to the first action. |
| |
| ``run`` and ``arun`` largely have the same behavior as ``iterate`` and ``aiterate``. |
| |
| ``stream_result``/``astream_result`` |
| ------------------------------------ |
| |
| These allow you to stream responses back. Note they work best with streaming actions. |
| Read about them in the :ref:`streaming <streaming>` section. |
| |
| --------- |
| Graph API |
| --------- |
| |
| The application exposes a graph API, allowing you to specify it as a graph separate from application concerns. |
| While you likely won't use this in your first few applications, it will become useful when you want to run it |
| in a web-server (create a graph once, application many times), import the graph from another context, or run it in multiple contexts. |
| |
| |
| .. code-block:: python |
| |
| from burr.core import ApplicationBuilder, default, expr |
| graph = ( |
| GraphBuilder() |
| .with_actions(human_input, ai_response) |
| .with_transitions( |
| ("human_input", "ai_response"), |
| ("ai_response", "human_input") |
| ).build() |
| ) |
| app = ( |
| ApplicationBuilder() |
| .with_graph(graph) |
| with_state(chat_history=[]) |
| .with_entrypoint("human_input") |
| .build() |
| ) |
| |
| You can read more about it in the :py:class:`GraphBuilder docs <burr.core.graph.GraphBuilder>`. |
| |
| ---------- |
| Inspection |
| ---------- |
| |
| You can ask various questions of the state machine using publicly-supported APIs: |
| |
| - ``application.graph`` will give you a static representation of the state machine with enough information to visualize |
| - ``application.state`` will give you the current state of the state machine. Note that if you modify it the results will not show up -- state is immutable! Modify the state through actions. |
| |
| See the :ref:`application docs <applicationref>` |