enact 0.4.0.dev0__tar.gz → 0.4.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- enact-0.4.0.dev1/PKG-INFO +254 -0
- enact-0.4.0.dev1/README.md +243 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/pyproject.toml +1 -1
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/__init__.py +8 -3
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/contexts.py +5 -6
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/digests.py +41 -40
- enact-0.4.0.dev1/src/enact/gradio/__init__.py +17 -0
- enact-0.4.0.dev0/src/enact/guis.py → enact-0.4.0.dev1/src/enact/gradio/gradio.py +30 -14
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/interfaces.py +13 -8
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/invocations.py +199 -21
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/pretty_print.py +1 -1
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/references.py +22 -11
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/resource_registry.py +2 -1
- enact-0.4.0.dev1/src/enact.egg-info/PKG-INFO +254 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact.egg-info/SOURCES.txt +2 -1
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_invocations.py +205 -106
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_references.py +12 -11
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_resource_types.py +2 -2
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_resources.py +1 -1
- enact-0.4.0.dev0/PKG-INFO +0 -152
- enact-0.4.0.dev0/README.md +0 -141
- enact-0.4.0.dev0/src/enact.egg-info/PKG-INFO +0 -152
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/LICENSE +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/setup.cfg +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/resource_types.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/resources.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact/serialization.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact.egg-info/dependency_links.txt +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact.egg-info/requires.txt +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/src/enact.egg-info/top_level.txt +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_contexts.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_digest.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_resource_registry.py +0 -0
- {enact-0.4.0.dev0 → enact-0.4.0.dev1}/tests/test_serialize.py +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: enact
|
|
3
|
+
Version: 0.4.0.dev1
|
|
4
|
+
Summary: A framework for generative software.
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
# enact - A Framework for Generative Software.
|
|
13
|
+
|
|
14
|
+
Enact is a python framework for building generative software, specifically
|
|
15
|
+
software that integrates with machine learning models or APIs that generate
|
|
16
|
+
distributions of outputs.
|
|
17
|
+
|
|
18
|
+
The advent of generative AI is driving changes in the way software is built.
|
|
19
|
+
The unique challenges of implementing, maintaining and improving generative
|
|
20
|
+
systems indicate a need to rethink the software stack from a first-principles
|
|
21
|
+
perspective. See [why-enact](#why-enact) for a more in-depth discussion.
|
|
22
|
+
|
|
23
|
+
The design philosophy of enact is to provide an easy-to-use python framework
|
|
24
|
+
that addresses the needs of emerging AI-based systems in a fundamental manner.
|
|
25
|
+
Enact is designed as a core framework that provides low-level primitives
|
|
26
|
+
required by generative software systems.
|
|
27
|
+
|
|
28
|
+
To this end, enact provides support for the following features:
|
|
29
|
+
* The ability to commit data, generative components and executions to
|
|
30
|
+
persistent storage in a versioned manner.
|
|
31
|
+
* Journaled executions of generative python components.
|
|
32
|
+
* The ability to rewind and replay past executions.
|
|
33
|
+
* Easy interchangeability of human and AI-driven subsystems.
|
|
34
|
+
* Support for all of the above features in higher-order generative flows, i.e.,
|
|
35
|
+
generative programs that generate and execute other generative flows.
|
|
36
|
+
* A simple hash-based storage model that simplifies distributed and
|
|
37
|
+
asynchronous generative flows.
|
|
38
|
+
|
|
39
|
+
## Installation and overview
|
|
40
|
+
|
|
41
|
+
Enact is available as a [pypi package](https://pypi.org/project/enact/) and can
|
|
42
|
+
be installed via:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install enact
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Enact defines generative components as python classes with annotated input and
|
|
49
|
+
output types:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import enact
|
|
53
|
+
|
|
54
|
+
import dataclasses
|
|
55
|
+
import random
|
|
56
|
+
|
|
57
|
+
@enact.typed_invokable(
|
|
58
|
+
input_type=enact.NoneResource,
|
|
59
|
+
output_type=enact.Int)
|
|
60
|
+
@dataclasses.dataclass
|
|
61
|
+
class RollDie(enact.Invokable):
|
|
62
|
+
"""An enact invokable that rolls a die."""
|
|
63
|
+
sides: int
|
|
64
|
+
|
|
65
|
+
def call(self):
|
|
66
|
+
return enact.Int(random.randint(1, self.sides))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@enact.typed_invokable(
|
|
70
|
+
input_type=enact.Int,
|
|
71
|
+
output_type=enact.Int)
|
|
72
|
+
@dataclasses.dataclass
|
|
73
|
+
class RollDice(enact.Invokable):
|
|
74
|
+
"""An enact invokable that rolls a specified number of dice."""
|
|
75
|
+
roll: enact.Invokable
|
|
76
|
+
def call(self, num_dice: enact.Int):
|
|
77
|
+
return enact.Int(sum(self.roll() for _ in range(num_dice)))
|
|
78
|
+
|
|
79
|
+
roll_dice = RollDice(RollDie(6))
|
|
80
|
+
print(roll_dice(enact.Int(3))) # Print sum of 3 rolls.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Executions can be journaled and committed to persistent storage. A journaled
|
|
84
|
+
execution supports rewinding and replaying.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
with enact.FileStore('/tmp/my_store') as store:
|
|
88
|
+
num_rolls = enact.commit(enact.Int(3)) # commit input to store.
|
|
89
|
+
invocation = roll_dice.invoke(num_rolls) # create journaled execution.
|
|
90
|
+
|
|
91
|
+
print(invocation.get_output()) # Print sum of 3 rolls
|
|
92
|
+
for i in range(3):
|
|
93
|
+
print(invocation.get_child(i).get_output()) # Print each die roll.
|
|
94
|
+
|
|
95
|
+
invocation = invocation.rewind() # Rewind by one dice roll.
|
|
96
|
+
invocation = invocation.replay() # Replay dice roll 1 & 2, resample roll 3.
|
|
97
|
+
print(invocation.get_output())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Human input can be flexibly swapped in for generative components:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
@enact.typed_invokable(enact.NoneResource, enact.Int)
|
|
104
|
+
class HumanRollsDie(enact.Invokable):
|
|
105
|
+
def call(self):
|
|
106
|
+
return enact.request_input(
|
|
107
|
+
enact.Int, context='Please roll a six-sided die')
|
|
108
|
+
|
|
109
|
+
with store:
|
|
110
|
+
roll_dice = RollDice(HumanRollsDie())
|
|
111
|
+
inv_gen = enact.InvocationGenerator(roll_dice, num_rolls)
|
|
112
|
+
for input_request in inv_gen:
|
|
113
|
+
inv_gen.set_input(enact.Int(6)) # Provide a roll of 6.
|
|
114
|
+
|
|
115
|
+
print(inv_gen.invocation.get_output()) # Prints '18'.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Documentation
|
|
119
|
+
|
|
120
|
+
Full documentation is work in progress. A quickstart tutorial, explanation of
|
|
121
|
+
enact concepts and examples can be found in the
|
|
122
|
+
[examples directory](https://github.com/agentic-ai/enact/tree/main/examples).
|
|
123
|
+
|
|
124
|
+
## Why enact?
|
|
125
|
+
|
|
126
|
+
The rise of generative AI models is transforming the software development
|
|
127
|
+
process.
|
|
128
|
+
|
|
129
|
+
Traditional software relies primarily on functional buildings blocks in which
|
|
130
|
+
inputs and system state directly determine outputs. In contrast, modern software
|
|
131
|
+
increasingly utilizes generative AI elements, in which each input is associated
|
|
132
|
+
with a range of possible outputs.
|
|
133
|
+
|
|
134
|
+
This seemingly small change in emphasis - from functions to conditional
|
|
135
|
+
distributions - implies a shift across multiple dimensions of the engineering
|
|
136
|
+
process, summarized in the table below.
|
|
137
|
+
|
|
138
|
+
| | Traditional | Generative |
|
|
139
|
+
| :----: | :-----------: | :----------: |
|
|
140
|
+
| Building block | Deterministic functions | Conditional distributions |
|
|
141
|
+
| Engineering | Add features, debug errors | Improve output distributions |
|
|
142
|
+
| Subsystems | Interchangeable | Unique |
|
|
143
|
+
| Code sharing | Frameworks | Components |
|
|
144
|
+
| Executions | Logged for metrics/debugging | Training data |
|
|
145
|
+
| Interactivity | At system boundaries | Within system components |
|
|
146
|
+
| Code vs data | Distinct | Overlapping |
|
|
147
|
+
|
|
148
|
+
### Building generative software means fitting distributions
|
|
149
|
+
|
|
150
|
+
In traditional software, a large part of engineering effort revolves around
|
|
151
|
+
implementing features and debugging errors. In contrast, a generative software
|
|
152
|
+
system may be feature-complete and bug-free, but still not suitable for
|
|
153
|
+
deployment. Consider the following examples:
|
|
154
|
+
|
|
155
|
+
* A search chatbot that is sometimes rude and unhelpful
|
|
156
|
+
* An autonomous agent that recursively sets itself goals and accomplishes tasks,
|
|
157
|
+
but has a tendency to drift off into behavioral loops.
|
|
158
|
+
* An AI avatar generator that produces accurate but unattractive portrait
|
|
159
|
+
images.
|
|
160
|
+
|
|
161
|
+
System correctness is no longer merely a question of specific inputs leading
|
|
162
|
+
to outputs that are either correct or incorrect, but of the _distribution of
|
|
163
|
+
outputs_ satisfying some implicitly or explicitly defined quality target.
|
|
164
|
+
|
|
165
|
+
In cases where generativity is localized, e.g., when the software is a thin
|
|
166
|
+
wrapper around a large language model (LLM) or image generator, machine-learning
|
|
167
|
+
tools and techniques can directly be used to improve the system, but in cases
|
|
168
|
+
where multiple generative components work together to produce an output the
|
|
169
|
+
system must be fitted to the target distribution as a whole.
|
|
170
|
+
|
|
171
|
+
### Generative software requires recursively swapping subsystems
|
|
172
|
+
|
|
173
|
+
In traditional software engineering, the choice between two API-identical
|
|
174
|
+
implementations primarily revolves around practicalities such as performance or
|
|
175
|
+
maintainability. However, in generative software, individual components produce
|
|
176
|
+
distributions that may be more or less well-fitted to the system's overall goal,
|
|
177
|
+
for example:
|
|
178
|
+
|
|
179
|
+
* A foundation text-to-image model may perform well on a wide range of
|
|
180
|
+
prompts, whereas an API-identical fine-tuned version may be less general but
|
|
181
|
+
produce better results for images of a particular style.
|
|
182
|
+
* An instruction-tuned LLM and an LLM trained as a chatbot both autoregressively
|
|
183
|
+
extend token sequences, but one may be better suited towards data processing
|
|
184
|
+
applications, while another will make a better math tutor.
|
|
185
|
+
* Different components may have vastly different execution costs.
|
|
186
|
+
|
|
187
|
+
Generative system outputs are distributions that optimized towards some - often
|
|
188
|
+
implicitly specified - target. Data selection, training parameters, model
|
|
189
|
+
composition, sampling of feedback and self-improvement flows produce systems
|
|
190
|
+
whose conditional output distributions represent a unique, opinionated take on
|
|
191
|
+
the problem they were trained to solve. Therefore the development of generative
|
|
192
|
+
systems motivates ongoing reevaluation, tuning and replacement of subsystems,
|
|
193
|
+
more so than in traditional engineering applications.
|
|
194
|
+
|
|
195
|
+
### Generative software should be shareable
|
|
196
|
+
|
|
197
|
+
There are large numbers of generative AI components that are mutually
|
|
198
|
+
API-compatible (e.g., text-to-image generation, LLMs) but cannot be directly
|
|
199
|
+
ranked in terms of quality since they represent a unique take on the problem
|
|
200
|
+
domain. The combination of API compatibility and variation lends itself to a
|
|
201
|
+
more evolutionary collaborative style than traditional software, where
|
|
202
|
+
shared effort tends to centralize into fewer, lower-level frameworks.
|
|
203
|
+
|
|
204
|
+
This new collaborative approach is already evidenced by widespread sharing of
|
|
205
|
+
prompt templates and the existence of public databases of fine-tuned models.
|
|
206
|
+
|
|
207
|
+
### Generative software reflects on its executions.
|
|
208
|
+
|
|
209
|
+
In conventional software, executions are typically tracked at a low level
|
|
210
|
+
of resolution, since their primary use is to track metrics and debug errors.
|
|
211
|
+
A generative system represents an implicitly specified distribution, and
|
|
212
|
+
its execution history provides valuable information that may be used for
|
|
213
|
+
analysis or training.
|
|
214
|
+
|
|
215
|
+
* System outputs can be corrected, scored or critiqued by humans or AI
|
|
216
|
+
models to produce data for fine-tuning.
|
|
217
|
+
* Data from one generative model can be distilled into another.
|
|
218
|
+
* Complex orchestrations of different ML models can be replaced by end-to-end
|
|
219
|
+
trained models once sufficient data is available.
|
|
220
|
+
* Failed executions are potential input data for generative systems, e.g., in
|
|
221
|
+
the case of program synthesis and 'self-healing programs'.
|
|
222
|
+
|
|
223
|
+
### Humans are in the loop
|
|
224
|
+
|
|
225
|
+
Unlike traditional systems, where user interactions happen at system boundaries,
|
|
226
|
+
AI and humans are often equal participants in generative computations. An
|
|
227
|
+
example is Reinforcement Learning from Human Feedback (RLHF), where humans
|
|
228
|
+
provide feedback on AI output, only to be replaced by an AI model that mimics
|
|
229
|
+
their feedback behavior during the training process. Critical generative flows
|
|
230
|
+
(e.g., the drafting of legal documents) may require a human verification step.
|
|
231
|
+
|
|
232
|
+
The choice between sampling human input and sampling a generative model is
|
|
233
|
+
one of cost, quality and timing constraints, and may be subject to change during
|
|
234
|
+
the development of a system. Therefore, ideally, a generative system will be able
|
|
235
|
+
to sample output from a human in the same way that it would call into an API or
|
|
236
|
+
subsystem.
|
|
237
|
+
|
|
238
|
+
### Data is code, code is data
|
|
239
|
+
|
|
240
|
+
Traditional software systems tend to allow a clear distinction between code
|
|
241
|
+
(written by developers) and data (generated by the user and the system). In
|
|
242
|
+
generative systems this distinction breaks down: Approaches such as AutoGPT or
|
|
243
|
+
Voyager use generative AI to generate programs (specified in code or plain
|
|
244
|
+
text), which in turn may be interpreted by generative AI systems; prompts for
|
|
245
|
+
chat-based generative AI could equally be considered code or data.
|
|
246
|
+
|
|
247
|
+
## Development and Contributing
|
|
248
|
+
|
|
249
|
+
Enact is currently in alpha release. The framework is open source and Apache
|
|
250
|
+
licensed. We are actively looking for contributors that are excited about the
|
|
251
|
+
vision.
|
|
252
|
+
|
|
253
|
+
You can download the source code, report issues and create pull requests at
|
|
254
|
+
https://github.com/agentic-ai/enact.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# enact - A Framework for Generative Software.
|
|
2
|
+
|
|
3
|
+
Enact is a python framework for building generative software, specifically
|
|
4
|
+
software that integrates with machine learning models or APIs that generate
|
|
5
|
+
distributions of outputs.
|
|
6
|
+
|
|
7
|
+
The advent of generative AI is driving changes in the way software is built.
|
|
8
|
+
The unique challenges of implementing, maintaining and improving generative
|
|
9
|
+
systems indicate a need to rethink the software stack from a first-principles
|
|
10
|
+
perspective. See [why-enact](#why-enact) for a more in-depth discussion.
|
|
11
|
+
|
|
12
|
+
The design philosophy of enact is to provide an easy-to-use python framework
|
|
13
|
+
that addresses the needs of emerging AI-based systems in a fundamental manner.
|
|
14
|
+
Enact is designed as a core framework that provides low-level primitives
|
|
15
|
+
required by generative software systems.
|
|
16
|
+
|
|
17
|
+
To this end, enact provides support for the following features:
|
|
18
|
+
* The ability to commit data, generative components and executions to
|
|
19
|
+
persistent storage in a versioned manner.
|
|
20
|
+
* Journaled executions of generative python components.
|
|
21
|
+
* The ability to rewind and replay past executions.
|
|
22
|
+
* Easy interchangeability of human and AI-driven subsystems.
|
|
23
|
+
* Support for all of the above features in higher-order generative flows, i.e.,
|
|
24
|
+
generative programs that generate and execute other generative flows.
|
|
25
|
+
* A simple hash-based storage model that simplifies distributed and
|
|
26
|
+
asynchronous generative flows.
|
|
27
|
+
|
|
28
|
+
## Installation and overview
|
|
29
|
+
|
|
30
|
+
Enact is available as a [pypi package](https://pypi.org/project/enact/) and can
|
|
31
|
+
be installed via:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install enact
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Enact defines generative components as python classes with annotated input and
|
|
38
|
+
output types:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import enact
|
|
42
|
+
|
|
43
|
+
import dataclasses
|
|
44
|
+
import random
|
|
45
|
+
|
|
46
|
+
@enact.typed_invokable(
|
|
47
|
+
input_type=enact.NoneResource,
|
|
48
|
+
output_type=enact.Int)
|
|
49
|
+
@dataclasses.dataclass
|
|
50
|
+
class RollDie(enact.Invokable):
|
|
51
|
+
"""An enact invokable that rolls a die."""
|
|
52
|
+
sides: int
|
|
53
|
+
|
|
54
|
+
def call(self):
|
|
55
|
+
return enact.Int(random.randint(1, self.sides))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@enact.typed_invokable(
|
|
59
|
+
input_type=enact.Int,
|
|
60
|
+
output_type=enact.Int)
|
|
61
|
+
@dataclasses.dataclass
|
|
62
|
+
class RollDice(enact.Invokable):
|
|
63
|
+
"""An enact invokable that rolls a specified number of dice."""
|
|
64
|
+
roll: enact.Invokable
|
|
65
|
+
def call(self, num_dice: enact.Int):
|
|
66
|
+
return enact.Int(sum(self.roll() for _ in range(num_dice)))
|
|
67
|
+
|
|
68
|
+
roll_dice = RollDice(RollDie(6))
|
|
69
|
+
print(roll_dice(enact.Int(3))) # Print sum of 3 rolls.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Executions can be journaled and committed to persistent storage. A journaled
|
|
73
|
+
execution supports rewinding and replaying.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
with enact.FileStore('/tmp/my_store') as store:
|
|
77
|
+
num_rolls = enact.commit(enact.Int(3)) # commit input to store.
|
|
78
|
+
invocation = roll_dice.invoke(num_rolls) # create journaled execution.
|
|
79
|
+
|
|
80
|
+
print(invocation.get_output()) # Print sum of 3 rolls
|
|
81
|
+
for i in range(3):
|
|
82
|
+
print(invocation.get_child(i).get_output()) # Print each die roll.
|
|
83
|
+
|
|
84
|
+
invocation = invocation.rewind() # Rewind by one dice roll.
|
|
85
|
+
invocation = invocation.replay() # Replay dice roll 1 & 2, resample roll 3.
|
|
86
|
+
print(invocation.get_output())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Human input can be flexibly swapped in for generative components:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
@enact.typed_invokable(enact.NoneResource, enact.Int)
|
|
93
|
+
class HumanRollsDie(enact.Invokable):
|
|
94
|
+
def call(self):
|
|
95
|
+
return enact.request_input(
|
|
96
|
+
enact.Int, context='Please roll a six-sided die')
|
|
97
|
+
|
|
98
|
+
with store:
|
|
99
|
+
roll_dice = RollDice(HumanRollsDie())
|
|
100
|
+
inv_gen = enact.InvocationGenerator(roll_dice, num_rolls)
|
|
101
|
+
for input_request in inv_gen:
|
|
102
|
+
inv_gen.set_input(enact.Int(6)) # Provide a roll of 6.
|
|
103
|
+
|
|
104
|
+
print(inv_gen.invocation.get_output()) # Prints '18'.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
Full documentation is work in progress. A quickstart tutorial, explanation of
|
|
110
|
+
enact concepts and examples can be found in the
|
|
111
|
+
[examples directory](https://github.com/agentic-ai/enact/tree/main/examples).
|
|
112
|
+
|
|
113
|
+
## Why enact?
|
|
114
|
+
|
|
115
|
+
The rise of generative AI models is transforming the software development
|
|
116
|
+
process.
|
|
117
|
+
|
|
118
|
+
Traditional software relies primarily on functional buildings blocks in which
|
|
119
|
+
inputs and system state directly determine outputs. In contrast, modern software
|
|
120
|
+
increasingly utilizes generative AI elements, in which each input is associated
|
|
121
|
+
with a range of possible outputs.
|
|
122
|
+
|
|
123
|
+
This seemingly small change in emphasis - from functions to conditional
|
|
124
|
+
distributions - implies a shift across multiple dimensions of the engineering
|
|
125
|
+
process, summarized in the table below.
|
|
126
|
+
|
|
127
|
+
| | Traditional | Generative |
|
|
128
|
+
| :----: | :-----------: | :----------: |
|
|
129
|
+
| Building block | Deterministic functions | Conditional distributions |
|
|
130
|
+
| Engineering | Add features, debug errors | Improve output distributions |
|
|
131
|
+
| Subsystems | Interchangeable | Unique |
|
|
132
|
+
| Code sharing | Frameworks | Components |
|
|
133
|
+
| Executions | Logged for metrics/debugging | Training data |
|
|
134
|
+
| Interactivity | At system boundaries | Within system components |
|
|
135
|
+
| Code vs data | Distinct | Overlapping |
|
|
136
|
+
|
|
137
|
+
### Building generative software means fitting distributions
|
|
138
|
+
|
|
139
|
+
In traditional software, a large part of engineering effort revolves around
|
|
140
|
+
implementing features and debugging errors. In contrast, a generative software
|
|
141
|
+
system may be feature-complete and bug-free, but still not suitable for
|
|
142
|
+
deployment. Consider the following examples:
|
|
143
|
+
|
|
144
|
+
* A search chatbot that is sometimes rude and unhelpful
|
|
145
|
+
* An autonomous agent that recursively sets itself goals and accomplishes tasks,
|
|
146
|
+
but has a tendency to drift off into behavioral loops.
|
|
147
|
+
* An AI avatar generator that produces accurate but unattractive portrait
|
|
148
|
+
images.
|
|
149
|
+
|
|
150
|
+
System correctness is no longer merely a question of specific inputs leading
|
|
151
|
+
to outputs that are either correct or incorrect, but of the _distribution of
|
|
152
|
+
outputs_ satisfying some implicitly or explicitly defined quality target.
|
|
153
|
+
|
|
154
|
+
In cases where generativity is localized, e.g., when the software is a thin
|
|
155
|
+
wrapper around a large language model (LLM) or image generator, machine-learning
|
|
156
|
+
tools and techniques can directly be used to improve the system, but in cases
|
|
157
|
+
where multiple generative components work together to produce an output the
|
|
158
|
+
system must be fitted to the target distribution as a whole.
|
|
159
|
+
|
|
160
|
+
### Generative software requires recursively swapping subsystems
|
|
161
|
+
|
|
162
|
+
In traditional software engineering, the choice between two API-identical
|
|
163
|
+
implementations primarily revolves around practicalities such as performance or
|
|
164
|
+
maintainability. However, in generative software, individual components produce
|
|
165
|
+
distributions that may be more or less well-fitted to the system's overall goal,
|
|
166
|
+
for example:
|
|
167
|
+
|
|
168
|
+
* A foundation text-to-image model may perform well on a wide range of
|
|
169
|
+
prompts, whereas an API-identical fine-tuned version may be less general but
|
|
170
|
+
produce better results for images of a particular style.
|
|
171
|
+
* An instruction-tuned LLM and an LLM trained as a chatbot both autoregressively
|
|
172
|
+
extend token sequences, but one may be better suited towards data processing
|
|
173
|
+
applications, while another will make a better math tutor.
|
|
174
|
+
* Different components may have vastly different execution costs.
|
|
175
|
+
|
|
176
|
+
Generative system outputs are distributions that optimized towards some - often
|
|
177
|
+
implicitly specified - target. Data selection, training parameters, model
|
|
178
|
+
composition, sampling of feedback and self-improvement flows produce systems
|
|
179
|
+
whose conditional output distributions represent a unique, opinionated take on
|
|
180
|
+
the problem they were trained to solve. Therefore the development of generative
|
|
181
|
+
systems motivates ongoing reevaluation, tuning and replacement of subsystems,
|
|
182
|
+
more so than in traditional engineering applications.
|
|
183
|
+
|
|
184
|
+
### Generative software should be shareable
|
|
185
|
+
|
|
186
|
+
There are large numbers of generative AI components that are mutually
|
|
187
|
+
API-compatible (e.g., text-to-image generation, LLMs) but cannot be directly
|
|
188
|
+
ranked in terms of quality since they represent a unique take on the problem
|
|
189
|
+
domain. The combination of API compatibility and variation lends itself to a
|
|
190
|
+
more evolutionary collaborative style than traditional software, where
|
|
191
|
+
shared effort tends to centralize into fewer, lower-level frameworks.
|
|
192
|
+
|
|
193
|
+
This new collaborative approach is already evidenced by widespread sharing of
|
|
194
|
+
prompt templates and the existence of public databases of fine-tuned models.
|
|
195
|
+
|
|
196
|
+
### Generative software reflects on its executions.
|
|
197
|
+
|
|
198
|
+
In conventional software, executions are typically tracked at a low level
|
|
199
|
+
of resolution, since their primary use is to track metrics and debug errors.
|
|
200
|
+
A generative system represents an implicitly specified distribution, and
|
|
201
|
+
its execution history provides valuable information that may be used for
|
|
202
|
+
analysis or training.
|
|
203
|
+
|
|
204
|
+
* System outputs can be corrected, scored or critiqued by humans or AI
|
|
205
|
+
models to produce data for fine-tuning.
|
|
206
|
+
* Data from one generative model can be distilled into another.
|
|
207
|
+
* Complex orchestrations of different ML models can be replaced by end-to-end
|
|
208
|
+
trained models once sufficient data is available.
|
|
209
|
+
* Failed executions are potential input data for generative systems, e.g., in
|
|
210
|
+
the case of program synthesis and 'self-healing programs'.
|
|
211
|
+
|
|
212
|
+
### Humans are in the loop
|
|
213
|
+
|
|
214
|
+
Unlike traditional systems, where user interactions happen at system boundaries,
|
|
215
|
+
AI and humans are often equal participants in generative computations. An
|
|
216
|
+
example is Reinforcement Learning from Human Feedback (RLHF), where humans
|
|
217
|
+
provide feedback on AI output, only to be replaced by an AI model that mimics
|
|
218
|
+
their feedback behavior during the training process. Critical generative flows
|
|
219
|
+
(e.g., the drafting of legal documents) may require a human verification step.
|
|
220
|
+
|
|
221
|
+
The choice between sampling human input and sampling a generative model is
|
|
222
|
+
one of cost, quality and timing constraints, and may be subject to change during
|
|
223
|
+
the development of a system. Therefore, ideally, a generative system will be able
|
|
224
|
+
to sample output from a human in the same way that it would call into an API or
|
|
225
|
+
subsystem.
|
|
226
|
+
|
|
227
|
+
### Data is code, code is data
|
|
228
|
+
|
|
229
|
+
Traditional software systems tend to allow a clear distinction between code
|
|
230
|
+
(written by developers) and data (generated by the user and the system). In
|
|
231
|
+
generative systems this distinction breaks down: Approaches such as AutoGPT or
|
|
232
|
+
Voyager use generative AI to generate programs (specified in code or plain
|
|
233
|
+
text), which in turn may be interpreted by generative AI systems; prompts for
|
|
234
|
+
chat-based generative AI could equally be considered code or data.
|
|
235
|
+
|
|
236
|
+
## Development and Contributing
|
|
237
|
+
|
|
238
|
+
Enact is currently in alpha release. The framework is open source and Apache
|
|
239
|
+
licensed. We are actively looking for contributors that are excited about the
|
|
240
|
+
vision.
|
|
241
|
+
|
|
242
|
+
You can download the source code, report issues and create pull requests at
|
|
243
|
+
https://github.com/agentic-ai/enact.
|
|
@@ -26,6 +26,7 @@ from enact.invocations import InputChanged
|
|
|
26
26
|
from enact.invocations import InputRequest
|
|
27
27
|
from enact.invocations import InputRequestOutsideInvocation
|
|
28
28
|
from enact.invocations import RequestedTypeUndetermined
|
|
29
|
+
from enact.invocations import InvocationGenerator
|
|
29
30
|
from enact.invocations import Invokable
|
|
30
31
|
from enact.invocations import InvokableBase
|
|
31
32
|
from enact.invocations import InvokableBase
|
|
@@ -34,8 +35,8 @@ from enact.invocations import ReplayContext
|
|
|
34
35
|
from enact.invocations import ReplayError
|
|
35
36
|
from enact.invocations import Request
|
|
36
37
|
from enact.invocations import Response
|
|
37
|
-
from enact.invocations import request_input
|
|
38
38
|
from enact.invocations import RequestInput
|
|
39
|
+
from enact.invocations import request_input
|
|
39
40
|
from enact.invocations import typed_invokable
|
|
40
41
|
from enact.invocations import WrappedException
|
|
41
42
|
|
|
@@ -51,6 +52,8 @@ from enact.references import InMemoryBackend
|
|
|
51
52
|
from enact.references import Ref
|
|
52
53
|
from enact.references import RefError
|
|
53
54
|
from enact.references import Store
|
|
55
|
+
from enact.references import InMemoryStore
|
|
56
|
+
from enact.references import FileStore
|
|
54
57
|
|
|
55
58
|
from enact.resources import Resource
|
|
56
59
|
|
|
@@ -64,7 +67,9 @@ from enact.resource_types import Str
|
|
|
64
67
|
from enact.resource_types import Bytes
|
|
65
68
|
from enact.resource_types import NPArray
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
from enact.
|
|
70
|
+
from enact.serialization import Serializer
|
|
71
|
+
from enact.serialization import JsonSerializer
|
|
72
|
+
from enact.serialization import SerializationError
|
|
73
|
+
from enact.serialization import DeserializationError
|
|
69
74
|
|
|
70
75
|
from enact import contexts
|
|
@@ -25,12 +25,10 @@ _context_vars: Dict[Type['Context'],
|
|
|
25
25
|
|
|
26
26
|
class ContextError(Exception):
|
|
27
27
|
"""Error raised when there is a problem with the context."""
|
|
28
|
-
pass
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class NoActiveContext(ContextError):
|
|
32
31
|
"""Raised when there is no active context."""
|
|
33
|
-
pass
|
|
34
32
|
|
|
35
33
|
C = TypeVar('C', bound='Context')
|
|
36
34
|
|
|
@@ -47,10 +45,10 @@ class Context:
|
|
|
47
45
|
"""Returns the context var for this type."""
|
|
48
46
|
try:
|
|
49
47
|
return cast(contextvars.ContextVar[Optional[C]], _context_vars[cls])
|
|
50
|
-
except KeyError:
|
|
48
|
+
except KeyError as key_error:
|
|
51
49
|
raise ContextError(
|
|
52
50
|
f'Context {cls} not registered. A context class must be registered '
|
|
53
|
-
f'with the "@register" decorator.')
|
|
51
|
+
f'with the "@register" decorator.') from key_error
|
|
54
52
|
|
|
55
53
|
@classmethod
|
|
56
54
|
@contextlib.contextmanager
|
|
@@ -69,11 +67,12 @@ class Context:
|
|
|
69
67
|
context_var = cls._get_context_var()
|
|
70
68
|
try:
|
|
71
69
|
current_context = context_var.get()
|
|
72
|
-
except LookupError:
|
|
70
|
+
except LookupError as lookup_error:
|
|
73
71
|
raise ContextError(
|
|
74
72
|
f'Context {cls} not initialized. If running inside a thread, make sure '
|
|
75
73
|
f'to annotate the thread function with either the '
|
|
76
|
-
f'"@with_current_contexts" or "@with_new_contexts" decorator.'
|
|
74
|
+
f'"@with_current_contexts" or "@with_new_contexts" decorator.'
|
|
75
|
+
) from lookup_error
|
|
77
76
|
return current_context
|
|
78
77
|
|
|
79
78
|
@classmethod
|