-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bf74948
Showing
114 changed files
with
15,083 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: f9b4a726caba6036f9dc56630def489d | ||
tags: 645f666f9bcd5a90fca523b33c5a78b7 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
======= | ||
Actions | ||
======= | ||
|
||
.. _actions: | ||
|
||
|
||
Actions do any 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. Compute a result from the state | ||
2. Update the state with the result | ||
|
||
We call (1) a ``Function`` and (2) a ``Reducer`` (similar to Redux). The ``run`` method is the function and the ``update`` | ||
method is the reducer. 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. | ||
|
||
In the case of a function-based action, the function returns both at the same time. | ||
|
||
------------------- | ||
Class-based actions | ||
------------------- | ||
|
||
You can define an action by implementing the `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 ``ApplicationBuilder``: | ||
|
||
.. code-block:: python | ||
from burr.core import ApplicationBuilder | ||
app = ApplicationBuilder().with_actions( | ||
custom_action=CustomAction() | ||
)... | ||
---------------------- | ||
Function-based actions | ||
---------------------- | ||
|
||
You can also define actions by decorating a function with the `@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 `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. | ||
|
||
---------------------- | ||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
===== | ||
Hooks | ||
===== | ||
|
||
.. _hooks: | ||
|
||
Burr has a system of lifecycle adapters (adapted from [Hamilton's](https://github.com/dagworks-inc/hamilton) similar concept, which allow you to run tooling before and after | ||
various places in a node's execution. For instance, you could (many of these are yet to be implemented): | ||
|
||
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 | ||
|
||
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 `ApplicationBuilder` as a 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], | ||
exception: Exception, | ||
**future_kwargs: Any, | ||
): | ||
print(f"Finishing action: {action.node.name}") | ||
To include this in the application, you would pass it in as a list of hooks: | ||
|
||
.. code-block:: python | ||
app = ( | ||
ApplicationBuilder() | ||
.with_hooks(PrintLnHook()) | ||
... | ||
.build()) | ||
Read more about the hook API in the :ref:`hooks section <hooksref>`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
==================== | ||
Concepts | ||
==================== | ||
|
||
Overview of the concepts -- read these to get a mental model for how Burr works. | ||
|
||
.. _concepts: | ||
|
||
.. toctree:: | ||
state-machine | ||
state | ||
actions | ||
transitions | ||
hooks | ||
more |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
==================== | ||
Planned capabilities | ||
==================== | ||
|
||
These are on the roadmap (and will be part of Burr in the imminent future), but have not been built yet. | ||
|
||
We build fast though, so let us know which ones you need and they'll be in there before you know it! | ||
|
||
----------- | ||
Typed State | ||
----------- | ||
|
||
We plan to add the ability to type-check state with some (or all) of the following: | ||
|
||
- Pydantic | ||
- dataclasses | ||
- TypedDict | ||
- Custom state schemas (through the ``reads``/``writes`` parameters) | ||
|
||
The idea is you would define state at the function level, parameterized by the state type, and Burr would be able to validate | ||
against that state. | ||
|
||
.. code-block:: python | ||
class InputState(TypedDict): | ||
foo: int | ||
bar: str | ||
class OutputState(TypedDict): | ||
baz: int | ||
qux: str | ||
@action(reads=["foo", "bar"], writes=["baz"]) | ||
def my_action(state: State[InputState]) -> Tuple[dict, State[OutputState]]: | ||
result = {"baz": state["foo"] + 1, "qux": state["bar"] + "!"} | ||
return result, state.update(**result) | ||
The above could also be dataclasses/pydantic models. We could also add something as simple as: | ||
|
||
.. code-block:: python | ||
@action(reads={"foo": int, "bar": str}, writes={"baz": int, "qux": str}) | ||
... | ||
----------------------------- | ||
State Management/Immutability | ||
----------------------------- | ||
|
||
We plan the ability to manage state in a few ways: | ||
1. ``commit`` -- an internal tool to commit/compile a series of changes so that we have the latest state evaluated | ||
2. ``persist`` -- a user-facing API to persist state to a database. This will be pluggable by the user, and we will have a few built-in options (e.g. a simple in-memory store, a file store, a database store, etc...) | ||
3. ``hydrate`` -- a static method to hydrate state from a database. This will be pluggable by the user, and we will have a few built-in options that mirror those in ``persist`` options. | ||
|
||
Currently state is immutable, but it utilizes an inefficient copy mechanism. This is out of expedience -- we don't anticipate this will | ||
be painful for the time being, but plan to build a more efficient functional paradigm. We will likely have: | ||
|
||
1. Each state object be a node in a linked list, with a pointer to the previous state. It carries a diff of the changes from the previous state. | ||
2. An ability to ``checkpoint`` (allowing for state garbage collection), and store state in memory/kill out the pointers. | ||
|
||
We will also consider having the ability to have a state solely backed by redis (and not memory), but we are still thinking through the API. | ||
|
||
---------------------- | ||
Compilation/Validation | ||
---------------------- | ||
|
||
We currently do not validate that the chain of actions provide a valid state, although we plan to walk the graph to ensure that no "impossible" | ||
situation is reached. E.G. if an action reads from a state that is not written to (or not initialized), we will raise an error, likely upon calling `validate`. | ||
We may be changing the behavior with defaults over time. | ||
|
||
-------------------- | ||
Exception Management | ||
-------------------- | ||
|
||
Currently, exceptions will break the control flow of an action, stopping the program early. Thus, | ||
if an exception is expected, the program will stop early. We will be adding the ability to conditionally transition based | ||
on exceptions, which will allow you to transition to an error-handling (or retry) action that does not | ||
need the full outputs of the prior action. | ||
|
||
Here is what it would look liek in the current API: | ||
|
||
.. code-block:: python | ||
@action(reads=["attempts"], writes=["output", "attempts"]) | ||
def some_flaky_action(state: State, max_retries: int=3) -> Tuple[dict, State]: | ||
result = {"output": None, "attempts": state["attempts"] + 1} | ||
try: | ||
result["output"] = call_some_api(...) | ||
excecpt APIException as e: | ||
if state["attempts"] >= max_retries: | ||
raise e | ||
return result, state.update(**result) | ||
One could imagine adding it as a condition (a few possibilities) | ||
|
||
.. code-block:: python | ||
@action(reads=[], writes=["output"]) | ||
def some_flaky_action(state: State) -> Tuple[dict, State]: | ||
result = {"output": call_some_api(...)} | ||
return result, state.update(**result) | ||
builder.with_actions( | ||
some_flaky_action=some_flaky_action | ||
).with_transitions( | ||
( | ||
"some_flaky_action", | ||
"some_flaky_action", | ||
error(APIException) # infinite retries | ||
error(APIException, max=3) # 3 visits to this edge then it gets reset if this is not chosen | ||
# That's stored in state | ||
) | ||
Will have to come up with ergonomic APIs -- the above are just some ideas. | ||
----------------- | ||
Streaming results | ||
----------------- | ||
Results should be able to stream in, but we'll want to store the final output in state. | ||
Still thinking through the UX. | ||
------------ | ||
Integrations | ||
------------ | ||
Langchain is next up (using LCEL). Please request any other integrations you'd like to see. |
Oops, something went wrong.