Skip to content

Commit a6f34ef

Browse files
authored
Merge pull request #18 from Neverbolt/unify
Adds documentation for use cases and configurable
2 parents e9f87a9 + 81538c8 commit a6f34ef

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ wintermute.py: error: the following arguments are required: {linux_privesc,windo
132132
$ python wintermute.py linux_privesc --enable_explanation true --enable_update_state true
133133
~~~
134134

135+
## Contribution
136+
137+
If you want to contribute additional use-cases, please take a look at [docs/use_case.md](docs/use_case.md) for a start on how to extend this codebase.
138+
135139
# Disclaimers
136140

137141
Please note and accept all of them.

‎docs/configurable.md‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Configurable
2+
3+
Marking a class as `@configurable` allows for the class to be configured via command line arguments or environment variables.
4+
5+
This is done by analyzing the parameters to the class' `__init__` method and its `__dataclass_fields__` attribute if it is a `@dataclass`.
6+
As having a `@configurable` also be a `@dataclass` makes it easier to extend it, it is usually recommended to define a configurable as a `@dataclass`.
7+
Furthermore, using a dataclass allows more natural use of the `parameter()` definition.
8+
9+
All [use-cases](use_case.md) are automatically configurable.
10+
11+
## Parameter Definition
12+
13+
Parameters can either be defined using type hints and default values, or by using the `parameter()` method.
14+
15+
```python
16+
from dataclasses import dataclass
17+
from utils.configurable import configurable, parameter
18+
19+
20+
@configurable("inner-example", "Inner Example Configurable for documentation")
21+
@dataclass
22+
class InnerConfigurableExample:
23+
text_value: str
24+
25+
26+
@configurable("example", "Example Configurable for documentation")
27+
@dataclass
28+
class ConfigurableExample:
29+
inner_configurable: InnerConfigurableExample
30+
text_value: str
31+
number_value_with_description: int = parameter(desc="This is a number value", default=42)
32+
number_value_without_description: int = 43
33+
```
34+
35+
As can be seen, the `parameter` method allows additionally setting a description for the parameter, while returning a `dataclasses.Field` to allow interoperability with existing tools.
36+
37+
The type of a configurable parameter may only be a primitive type (`int`, `str`, `bool`) or another configurable.
38+
39+
## Usage
40+
41+
When a class is marked as `@configurable`, it can be configured via command line arguments or environment variables.
42+
The name of the parameter is automatically built from the field name (in the case of the example to be `text_value`, `number_with_description` and `number_value_without_description`).
43+
44+
If a configurable has other configurable fields as parameters, they can be recursively configured, the name of the parameter is built from the field name and the field name of the inner configurable (here `inner_configurable.text_value`).
45+
46+
These parameters are looked up in the following order:
47+
48+
1. Command line arguments
49+
2. Environment variables (with `.` being replaced with `_`)
50+
3. .env file
51+
4. Default values
52+
53+
When you have a simple use case as follows:
54+
55+
```python
56+
from dataclasses import dataclass
57+
from usecases import use_case, UseCase
58+
59+
@use_case("example", "Example Use Case")
60+
@dataclass
61+
class ExampleUseCase(UseCase):
62+
conf: ConfigurableExample
63+
64+
def run(self):
65+
print(self.conf)
66+
```
67+
68+
You can configure the `ConfigurableExample` class as follows:
69+
70+
```bash
71+
echo "conf.text_value = 'Hello World'" > .env
72+
export CONF_NUMBER_VALUE_WITH_DESCRIPTION=120
73+
export CONF_INNER_CONFIGURABLE_TEXT_VALUE="Inner Hello World"
74+
75+
python3 wintermute.py example --conf.inner_configurable.text_value "Inner Hello World Overwrite"
76+
```
77+
78+
This results in
79+
80+
```
81+
ConfigurableExample(
82+
inner_configurable=InnerConfigurableExample(text_value='Inner Hello World Overwrite'),
83+
text_value='Hello World',
84+
number_value_with_description=120,
85+
number_value_without_description=43
86+
)
87+
```
88+

‎docs/use_case.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use Cases
2+
3+
Wintermute consists of different use-cases (classes extending `UseCase`, being annotated with `@use_case` and being imported somewhere from the main `wintermute.py` file), which can be run individually.
4+
5+
The `@use_case` annotation takes a name and description as arguments, which are then used for the sub-commands in the command line interface.
6+
7+
When building a use-case, the `run` method must be implemented, which is called after calling the (optional) `init` method (note that this is not the `__init__` method).
8+
The `run` method should contain the main logic of the use-case, though it is recommended to split the logic into smaller methods that are called from `run` for better readability (see the code for [`RoundBasedUseCase`](#round-based-use-case) for an example).
9+
10+
A use-case is automatically a `configurable`, which means, that all parameters of its `__init__` function (or fields for dataclasses) can be set via command line / environment parameters. For more information read the [configurable](configurable.md) documentation.
11+
It is recommended to define a use case to be a `@dataclass`, so that all parameters are directly visible, and the use-case can be easily extended.
12+
13+
## General Use Cases
14+
15+
Usually a use case follows the pattern, that it has a connections to the log database, a LLM and a system with which it is interacting.
16+
17+
The LLM should be defined as closely as necessary for the use case, as prompt templates are dependent on the LLM in use.
18+
If you don't yet want to specify eg. `GPT4Turbo`, you can use `llm: OpenAIConnection`, and dynamically specify the LLM to be used in the parameters `llm.model` and `llm.context_size`.
19+
20+
In addition to that, arbitrary parameters and flags can be defined, with which to control the use-case. For consistency reasons please take a look if similar parameters are used in other use cases, and try to have them act accordingly.
21+
22+
When interacting with a LLM, the prompt and output should always be logged `add_log_query`, `add_log_analyze_response`, `add_log_update_state` or alike, to record all interactions.
23+
24+
## Round Based Use Case
25+
26+
The `RoundBasedUseCase` is an abstract base class for use-cases that are based on rounds where the LLM is called with a certain input and the result is evaluated using different capabilities.
27+
28+
An implementation needs to implement the `perform_round` method, which is called for each round. It can also optionally implement the `setup` and `teardown` methods, which are called before and after the rounds, respectively.

0 commit comments

Comments
 (0)