Skip to content

Commit 7b02bfd

Browse files
authored
[docs] Add details about not overriding lists + AI example (#137)
1 parent e9426eb commit 7b02bfd

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed

README.md

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Optify
44

5-
Simplifies **configuration driven development**: getting the right configuration options for a process or request using pre-loaded configurations from files (JSON, YAML, etc.) to manage options for feature flags, experiments, or flights.
5+
Powers **configuration driven development**: getting the right configuration options for a process or request using pre-loaded configurations from files (JSON, YAML, etc.) to manage options for feature flags, experiments, or flights.
66
Configurations for different experiments or feature flags are mergeable to support multiple experiments or feature flags for the same request.
77

88
[![Crates.io](https://img.shields.io/crates/v/optify?logo=Rust)](https://crates.io/crates/optify)
@@ -15,11 +15,14 @@ Configurations for different experiments or feature flags are mergeable to suppo
1515
> The configuration should declare **what** to do, but **not how** to do it.
1616
1717
This project helps improve the scalability and maintainability of code.
18-
We should determine the right configuration for a request or process when it starts by passing the enabled features to an `OptionsProvider`.
18+
Determine the right configuration for a request or process when it starts by passing the enabled features to an `OptionsProvider`.
1919
The returned options would be used throughout the request or process to change business logic.
2020
Supporting deep configurations with many types of properties instead of simple enabled/disabled feature flags is important to help avoid conditional statements (`if` statements) and thus improve the scalability of our code and make it easier to maintain our code as explained in [this article][cond-article].
2121

22-
Instead of working with feature flags:
22+
See [AI Example](./docs/AI_Example.md) for an example of using Optify to manage the configuration for using an LLM with customizing the system instructions and enabled tools.
23+
24+
## Motivation
25+
Instead of working with feature flags like this:
2326
```Python
2427
if Settings.is_feature_A_enabled:
2528
handle_A(params)
@@ -45,7 +48,7 @@ Instead we can convert each enabled flag to a string and then build the merged c
4548

4649
See [tests](./tests/) for examples and tests for different variations of this paradigm for managing options.
4750

48-
Core Features:
51+
## Core Features
4952
* **Each *feature flag* can be represented by a JSON or YAML file** which contains options to override default configuration values when processing feature names or experiment names in a request.
5053
* Each file is a granular **partial** representation of the overall configuration.
5154
Features are intended to be combined to build the final configuration.
@@ -82,10 +85,35 @@ Configurations in the cloud are fine for temporary experiments, but make the dai
8285
The main point is to keep the configurations private and internal to your codebase while feature flags names are part of your external API.
8386

8487
# Merging Configuration Files
85-
When merging configurations for features, objects are merged with the last feature taking precedence.
86-
Key values, including lists are overwritten.
88+
Objects are merged with the last feature taking precedence.
89+
**All values, including arrays/lists, but not objects/dictionaries, are overwritten.**
90+
I.e., all types override previous values except for objects/dictionaries which are merged recursively.
91+
92+
**Array items are not overwritten** because it would make it impossible or confusing to insert items or remove items.
93+
To replicate an array that needs configurable items, use an object with keys to sort and treat `null` values as removed items.
94+
For example, consider this configuration built after merging others:
95+
```JSON
96+
{
97+
"items": {
98+
"item_01": {"k": "value 1"},
99+
"item_03": null,
100+
"item_05": {"k": "value 5"}
101+
}
102+
}
103+
```
104+
105+
It represents an array: `[{"k": "value 1"}, {"k": "value 5"}]`
106+
107+
The following Python code can be used to filter, sort, and convert it to the dictionary to a list:
108+
```Python
109+
items = sorted(
110+
((k,v) for k, v in config["items"].items() if v is not None),
111+
key=lambda kv: kv[0])
112+
items = [v for (_k,v) in items]
113+
```
114+
87115

88-
As explained below, the .NET version works a little differently that the versions in this repository which are backed by the Rust implementation.
116+
As explained below, the .NET version works a little differently than the versions in this repository which are backed by the Rust implementation.
89117

90118
## Example
91119
Suppose you have a class that you want to use to configure your logic at runtime:
@@ -156,6 +184,7 @@ options:
156184
You'll load the `configurations` folder using an `OptionsProviderBuilder` and then get an `OptionsProvider` from that builder.
157185
Some languages also have an `OptionsWatcherBuilder` which can be used to watch for changes in the configuration files and automatically reload changes into the `OptionsProvider`.
158186
The exact class names and methods may vary slightly depending on the language you are using.
187+
`OptionsProvider` and `OptionsWatcher` also have static `build` methods.
159188
See below for links to implementations in different languages.
160189

161190
The result of using features: `["A", "B"]` will be:
@@ -361,10 +390,12 @@ For example,
361390
See the [here](./tests/test_suites/inheritance/configs/.optify/schema.json) for an example.
362391

363392
# Inheritance
364-
Feature files can list ordered dependencies to declare other files to eagerly import.
393+
Feature files can list ordered dependencies to declare other files to eagerly import before the options in that files are applied.
394+
I.e., the configuration for the feature file is built by first applying the imported features in order and then applying the options in the feature file itself.
395+
See [merging configuration files](#merging-configuration-files) for details on how options are merged.
365396

366397
This allows grouping related configurations while keeping most files small, focused, and like granular building blocks.
367-
This also helps keep lists of enabled features smaller at runtime for typical feature that are used together.
398+
This also helps keep lists of enabled features smaller at runtime for typical features that are used together.
368399

369400
Imports are resolved at build time, when `OptionsProviderBuilder::build` is called so that getting to right configuration from an `OptionsProvider` is as fast as possible, but sacrificing some extra memory overhead to store redundant options because the options will also be stored in each parent.
370401

@@ -416,7 +447,7 @@ The resulting options for `feature_C` will be as if we included the features in
416447
```JSON
417448
{
418449
"myConfig":{
419-
// The values from feature_B as feature_B is listed after feature_A so it overrides it.
450+
// The array has the values from feature_B as feature_B is listed after feature_A.
420451
"myArray": [
421452
"feature B item 1"
422453
],

docs/AI_Example.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# AI Example
2+
3+
This is an example of how an application to use an LLM can be configured to easily customize the system instructions and enabled tools using Optify.
4+
5+
See [Configurable Strings](./ConfigurableStrings.md) for more details on how configurable strings work.
6+
7+
Map a tool to `null` to disable it so that other configurations that override the base can disable tools.
8+
9+
Other properties such as the tool input schema can also be customized adjacent to the tool's description.
10+
11+
```JSON
12+
{
13+
"options": {
14+
"model": {
15+
"name": "GPT-5",
16+
"parameters": {
17+
"max_tokens": 1500,
18+
"temperature": 0.7
19+
}
20+
},
21+
"system_instructions": {
22+
"$type": "Optify.ConfigurableString",
23+
"base": {
24+
"file": "templates/system_instructions.liquid"
25+
},
26+
"arguments": {
27+
"product_name": "MyApp",
28+
"personality": {
29+
"file": "templates/personality.txt"
30+
}
31+
}
32+
},
33+
"tools": {
34+
"code_executor": {
35+
"description": {
36+
"$type": "Optify.ConfigurableString",
37+
"base": "Excutes code snippets in a secure environment."
38+
}
39+
},
40+
// Map to `null` to disable a tool.
41+
"math": null,
42+
"web_search": {
43+
"description": {
44+
"$type": "Optify.ConfigurableString",
45+
"base": {
46+
"liquid": "Search the web using {{ provider }}."
47+
},
48+
"arguments": {
49+
"provider": "Bing Search"
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
56+
```

0 commit comments

Comments
 (0)