/
planned-capabilities.rst
129 lines (93 loc) · 4.87 KB
/
planned-capabilities.rst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
====================
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.