Skip to content

Commit

Permalink
deploy: ba6f855
Browse files Browse the repository at this point in the history
  • Loading branch information
skrawcz committed Apr 9, 2024
0 parents commit 0a356bf
Show file tree
Hide file tree
Showing 167 changed files with 25,905 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .buildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 8a1100ee3fd8a3a485e4cdcbcf429e7a
tags: d77d1c0d9ca2f4c8421862c7c5a0d620
Binary file added .doctrees/concepts/actions.doctree
Binary file not shown.
Binary file added .doctrees/concepts/additional-visibility.doctree
Binary file not shown.
Binary file added .doctrees/concepts/hooks.doctree
Binary file not shown.
Binary file added .doctrees/concepts/index.doctree
Binary file not shown.
Binary file added .doctrees/concepts/planned-capabilities.doctree
Binary file not shown.
Binary file added .doctrees/concepts/state-machine.doctree
Binary file not shown.
Binary file added .doctrees/concepts/state-persistence.doctree
Binary file not shown.
Binary file added .doctrees/concepts/state.doctree
Binary file not shown.
Binary file added .doctrees/concepts/streaming-actions.doctree
Binary file not shown.
Binary file added .doctrees/concepts/tracking.doctree
Binary file not shown.
Binary file added .doctrees/concepts/transitions.doctree
Binary file not shown.
Binary file added .doctrees/contributing/architecture.doctree
Binary file not shown.
Binary file added .doctrees/contributing/contributing.doctree
Binary file not shown.
Binary file added .doctrees/contributing/index.doctree
Binary file not shown.
Binary file added .doctrees/contributing/iterating.doctree
Binary file not shown.
Binary file added .doctrees/contributing/setup.doctree
Binary file not shown.
Binary file added .doctrees/environment.pickle
Binary file not shown.
Binary file added .doctrees/examples/agents.doctree
Binary file not shown.
Binary file added .doctrees/examples/chatbot.doctree
Binary file not shown.
Binary file added .doctrees/examples/creating_tests.doctree
Binary file not shown.
Binary file added .doctrees/examples/index.doctree
Binary file not shown.
Binary file added .doctrees/examples/ml_training.doctree
Binary file not shown.
Binary file added .doctrees/examples/simple.doctree
Binary file not shown.
Binary file added .doctrees/examples/simulation.doctree
Binary file not shown.
Binary file added .doctrees/examples/web-server.doctree
Binary file not shown.
Binary file added .doctrees/getting_started/index.doctree
Binary file not shown.
Binary file added .doctrees/getting_started/install.doctree
Binary file not shown.
Binary file added .doctrees/getting_started/simple-example.doctree
Binary file not shown.
Binary file added .doctrees/getting_started/up-next.doctree
Binary file not shown.
Binary file added .doctrees/getting_started/why-burr.doctree
Binary file not shown.
Binary file added .doctrees/index.doctree
Binary file not shown.
Binary file added .doctrees/main.doctree
Binary file not shown.
Binary file added .doctrees/reference/actions.doctree
Binary file not shown.
Binary file added .doctrees/reference/application.doctree
Binary file not shown.
Binary file added .doctrees/reference/conditions.doctree
Binary file not shown.
Binary file added .doctrees/reference/index.doctree
Binary file not shown.
Binary file added .doctrees/reference/integrations/hamilton.doctree
Binary file not shown.
Binary file added .doctrees/reference/integrations/index.doctree
Binary file not shown.
Binary file not shown.
Binary file added .doctrees/reference/lifecycle.doctree
Binary file not shown.
Binary file added .doctrees/reference/persister.doctree
Binary file not shown.
Binary file added .doctrees/reference/state.doctree
Binary file not shown.
Binary file added .doctrees/reference/telemetry.doctree
Binary file not shown.
Binary file added .doctrees/reference/tracking.doctree
Binary file not shown.
Binary file added .doctrees/reference/visibility.doctree
Binary file not shown.
Empty file added .nojekyll
Empty file.
1 change: 1 addition & 0 deletions CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
burr.dagworks.io
Binary file added _images/counter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/demo_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/test-case-creation-burr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
182 changes: 182 additions & 0 deletions _sources/concepts/actions.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
=======
Actions
=======

.. _actions:


Actions do the heavy-lifting in a workflow. They should contain all complex compute. You can define actions
either through a class-based or function-based API. If actions implement ``async def run`` then will be run in an
asynchronous context (and thus require one of the async application functions).

Actions have two primary responsibilities:

1. ``run`` -- compute a result
2. ``update`` -- modify the state

The ``run`` method should return a dictionary of the result and the ``update`` method should return
the updated state. They declare their dependencies so the framework knows *which* state variables they read and write. This allows the
framework to optimize the execution of the workflow. We call (1) a ``Function`` and (2) a ``Reducer`` (similar to `Redux <https://redux.js.org/>`_, if you're familiar with frontend UI technology).

.. _inputref:

--------------
Runtime Inputs
--------------

Actions can declare inputs that are not part of the state. This is for the case that you want to pause workflow execution for human input.

For instance, say you have a chatbot. The first step will declare the ``input`` parameter ``prompt`` -- it will take that, process it, and put
it in the state. The subsequent steps will read the result of that from state.

There are two APIs for defining actions: class-based and function-based. They are largely equivalent, but differ in use.

- use the function-based API when you want to write something quick and terse that reads from a fixed set of state variables
- use the class-based API when you want to leverage inheritance or parameterize the action in more powerful ways

-------------------
Class-Based Actions
-------------------

You can define an action by implementing the :py:class:`Action <burr.core.action.Action>` class:

.. code-block:: python
from burr.core import Action, State
class CustomAction(Action):
@property
def reads(self) -> list[str]:
return ["var_from_state"]
def run(self, state: State) -> dict:
return {"var_to_update": state["var_from_state"] + 1}
@property
def writes(self) -> list[str]:
return ["var_to_update"]
def update(self, result: dict, state: State) -> State:
return state.update(**result)
You then pass the action to the :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>`:

.. code-block:: python
from burr.core import ApplicationBuilder
app = ApplicationBuilder().with_actions(
custom_action=CustomAction()
)...
Note that if the action has inputs, you have to define the optional ``inputs`` property:

.. code-block:: python
from burr.core import Action, State
class CustomAction(Action):
@property
def reads(self) -> list[str]:
return ["var_from_state"]
def run(self, state: State, increment_by: int) -> dict:
return {"var_to_update": state["var_from_state"] + increment_by}
@property
def writes(self) -> list[str]:
return ["var_to_update"]
def update(self, result: dict, state: State) -> State:
return state.update(**result)
@property
def inputs(self) -> list[str]:
return ["increment_by"]
----------------------
Function-based actions
----------------------

You can also define actions by decorating a function with the :py:func:`@action <burr.core.action.action>` decorator:

.. code-block:: python
from burr.core import action, State
@action(reads=["var_from_state"], writes=["var_to_update"])
def custom_action(state: State) -> Tuple[dict, State]:
result = {"var_to_update": state["var_from_state"] + 1}
return result, state.update(**result)
app = ApplicationBuilder().with_actions(
custom_action=custom_action
)...
Function-based actions can take in parameters which are akin to passing in constructor parameters. This is done through the :py:meth:`bind <burr.core.action.bind>` method:

.. code-block:: python
@action(reads=["var_from_state"], writes=["var_to_update"])
def custom_action(state: State, increment_by: int) -> Tuple[dict, State]:
result = {"var_to_update": state["var_from_state"] + increment_by}
return result, state.update(**result)
app = ApplicationBuilder().with_actions(
custom_action=custom_action.bind(increment_by=2)
)...
This is the same as ``functools.partial``, but it is more explicit and easier to read. If an action has parameters that are not
bound, they will be referred to as inputs. For example:


.. code-block:: python
@action(reads=["var_from_state"], writes=["var_to_update"])
def custom_action(state: State, increment_by: int) -> Tuple[dict, State]:
result = {"var_to_update": state["var_from_state"] + increment_by}
return result, state.update(**result)
app = ApplicationBuilder().with_actions(
custom_action=custom_action
)...
Will require the inputs to be passed in at runtime.

Note that these combine the ``update`` and ``run`` methods into a single function, and they're both executed at the same time.

-----------
``Inputs``
-----------

If you simply want a node to take in inputs and pass them to the state, you can use the `Input` action:

.. code-block:: python
app = ApplicationBuilder().with_actions(
get_input=Input("var_from_state")
)...
This will look for the `var_from_state` in the inputs and pass it to the state. Note this is just syntactic sugar
for declaring inputs through one of the other APIs and adding it to state -- if you want to do anything more complex
with the input, you should use other APIs.

-----------
``Results``
-----------

If you just want to fill a result from the state, you can use the `Result` action:

.. code-block:: python
app = ApplicationBuilder().with_actions(
get_result=Result("var_from_state")
)...
This simply grabs the value from the state and returns it as the result. It is purely a placeholder
for an action that should just use the result, although you do not need it.

Refer to :ref:`actions <actions>` for documentation.
93 changes: 93 additions & 0 deletions _sources/concepts/additional-visibility.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
=====================
Additional Visibility
=====================

Burr comes with the ability to see inside your actions. This is a very pluggable framework
that comes with the default tracking client.


-------
Tracing
-------

Burr comes with a tracing capability to see recursive spans inside an action. This is similar to
the `OpenTelemetry <https://opentelemetry.io/>`_ sdk, although it is simplified significantly.

To add the tracing capability, the action first has to declare a ``__tracer`` input. This is a
an object that instantiates spans and tracks state.

Then, using the `__tracer` as a callable, you can instantiate spans and track state.

For the function-based API, this would look as follows:

.. code-block:: python
from burr.visibility import TracingFactory
from burr.core import action
@action(reads=['input_var'], writes=['output_var'])
def my_action(state: State, __tracer: TracingFactory) -> Tuple[dict, State]:
with __tracer('process_data'):
initial_data = _process_data(state['input_var'])
with __tracer('validate_data'):
_validate(initial_data)
with __tracer('transform_data', dependencies=['process_data']):
transformed_data = _transform(initial_data)
return {'output_var': transformed_data}, state.update({'output_var': transformed_data})
This would create the following traces:

#. ``process_data``
#. ``validate_data`` as a child of ``process_data``
#. ``transform_data`` as a causal dependent of ``process_data``

Dependencies are used to express [dag](-style structures of spans within actions. This is useful for gaining visibility into the internal structure
of an action, but is likely best used with integrations with micro-orchestration systems for implementating actions, such as Hamilton or Lanchain.
This maps to the `span link <https://opentelemetry.io/docs/concepts/signals/traces/#span-links>`_ concept in OpenTelemetry.

Note that, on the surface, this doesn't actually *do* anything. It has to be paired with the appropriate hooks.
These just function as callbacks (on enter/exit). The :py:class:`LocalTrackingClient <burr.tracking.LocalTrackingClient>`, used by the
:ref:`tracking <tracking>` feature forms one of these hooks, but we will be adding more, including:

1. An OpenTelemetry client
2. A DataDog client

.. note::

The class-based API can leverage this by declaring ``inputs`` as ``__tracer`` and then using the ``__tracer`` inside the ``run`` method.

------------
Observations
------------

(This is a work in progress, and is not complete)

You can make observations on the state by calling out to the `log_artifact` method on the `__tracer` context manager.
For instance:

.. code-block:: python
from burr.visibility import TracingFactory, ArtifactLogger
from burr.core import action
@action(reads=['input_var'], writes=['output_var'])
def my_action(
state: State,
__tracer: TracingFactory,
__logger: ArtifactLogger
) -> Tuple[dict, State]:
with __tracer('process_data'):
initial_data = _process_data(state['input_var'])
with __tracer('validate_data'):
validation_results = _validate(initial_data)
t.log_artifact(validation_results=validation_results)
with __tracer('transform_data', dependencies=['process_data'])
transformed_data = _transform(initial_data)
__logger.log_artifact(transformed_data_size=len(transformed_data))
return {'output_var': transformed_data}, state.update({'output_var': transformed_data})
The output can be any "json-dumpable" object (or pydantic model). This will be stored along with the span and can be used for debugging or analysis.

You can read more in the :ref:`reference documentation <visibility>`.
65 changes: 65 additions & 0 deletions _sources/concepts/hooks.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
=====
Hooks
=====

.. _hooks:

Burr has a system of lifecycle adapters (adapted from the similar `Hamilton <https://github.com/dagworks-inc/hamilton>`_ concept), which allow you to run tooling before and after
various places in a node's execution. For instance, you could:

1. Log every step as a trace in datadog
2. Add a time-delay to your steps to allow for rendering
3. Add a print statement to every step to see what happened (E.G. implement the printline in cowsay above)
4. Synchronize state/updates to an external database
5. Put results on a queue to feed to some monitoring system

Note some of the above are yet to be implemented.

To implement hooks, you subclass any number of the :ref:`available lifecycle hooks <hooksref>`.
These have synchronous and asynchronous versions, and your hook can subclass as many as you want
(as long as it doesn't do both the synchronous and asynchronous versions of the same hook).

To use them, you pass them into the :py:class:`ApplicationBuilder <burr.core.application.ApplicationBuilder>` as a ``*args`` list of hooks. For instance,
a hook that prints out the nodes name during execution looks like this.
We implement the pre/post run step hooks.

.. code-block:: python
class PrintLnHook(PostRunStepHook, PreRunStepHook):
def pre_run_step(self, *, state: "State", action: "Action", **future_kwargs: Any):
print(f"Starting action: {action.node.name}")
def post_run_step(
self,
*,
state: "State",
action: "Action",
result: Optional[dict],
sequence_id: int,
exception: Exception,
**future_kwargs: Any,
):
print(f"Finishing action: {action.node.name}")
To include this in the application, you pass it into the :py:meth:`with_hooks <burr.core.application.ApplicationBuilder.with_hooks>` method.

.. code-block:: python
app = (
ApplicationBuilder()
.with_hooks(PrintLnHook())
...
.build())
.. note::

There are synchronous and asynchronous hooks. Synchronous hooks will be called with both synchronous and asynchronous run methods
(all of ``step``, ``astep``, ``iterate``, ``aiterate``, ``run``, and ``arun``), whereas synchronous hooks will only be called with
the asynchronous methods (``astep``, ``aiterate``, ``arun``).

.. warning::
Hook order is currently undefined -- they happen to be called now in the order in which they are defined. We will likely
alter them to be called in the order they are defined (for `pre...`) hooks and in reverse order for `post...` hooks,
but this is not yet implemented.

Read more about the hook API in the :ref:`hooks section <hooksref>`.
21 changes: 21 additions & 0 deletions _sources/concepts/index.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
====================
Concepts
====================

Overview of the concepts -- read these to get a mental model for how Burr works.

.. _concepts:

.. toctree::
:maxdepth: 2

state-machine
state
actions
transitions
hooks
tracking
state-persistence
streaming-actions
additional-visibility
planned-capabilities
Loading

0 comments on commit 0a356bf

Please sign in to comment.