You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/concepts/concurrency.md
+130-7Lines changed: 130 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,11 @@
1
-
# Parallelization
1
+
# Concurrency
2
2
## And the Orchestration of Guard Executions
3
3
4
4
This document is a description of the current implementation of the Guardrails' validation loop. It attempts to explain the current patterns used with some notes on why those patterns were accepted at the time of implementation and potential future optimizations. It is _not_ meant to be prescriptive as there can, and will, be improvements made in future versions.
5
5
6
6
In general you will find that our approach to performance is two fold:
7
7
1. Complete computationally cheaper, static checks first and exit early to avoid spending time and resources on more expensive checks that are unlikely to pass when the former fail.
8
-
2.Parallelize processing where possible.
8
+
2.Run processes concurrently where possible.
9
9
10
10
## Background: The Validation Loop
11
11
When a Guard is executed, that is called via `guard()`, `guard.parse()`, `guard.validate()`, etc., it goes through an internal process that has the following steps:
@@ -60,7 +60,7 @@ Besides handling asynchronous calls to the LLM, using an `AsyncGuard` also ensur
60
60
* An asyncio event loop is available.
61
61
* The asyncio event loop is not taken/already running.
62
62
63
-
## Validation Orchestration and Parallelization
63
+
## Validation Orchestration and Concurrency
64
64
65
65
### Structured Data Validation
66
66
We perform validation with a "deep-first" approach. This has no meaning for unstructured text output since there is only one value, but for structured output it means that the objects are validated from the inside out.
@@ -79,7 +79,7 @@ Take the below structure as an example:
79
79
}
80
80
```
81
81
82
-
As of versions v0.4.x and v0.5.x of Guardrails, the above object would validated as follows:
82
+
As of versions v0.4.x and v0.5.x of Guardrails, the above object would be validated as follows:
83
83
84
84
1. foo.baz
85
85
2. foo.bez
@@ -88,11 +88,134 @@ As of versions v0.4.x and v0.5.x of Guardrails, the above object would validated
88
88
5. bar.buz
89
89
6. bar
90
90
91
-
> NOTE: The approach currently used, and outlined above, was predicated on the assumption that if child properties fail validation, it is unlikely that the parent property would pass. With the current atomic state of validation, it can be argued that this assumption is false. That is, the types of validations applied to parent properties typically take the form of checking the appropriate format of the container like a length check on a list. These types of checks are generally independent of any requirements the child properties have. This opens up the possibility of running all six paths listed above in parallel at once instead of performing them in steps based on key path.
91
+
> NOTE: The approach currently used, and outlined above, was predicated on the assumption that if child properties fail validation, it is unlikely that the parent property would pass. With the current atomic state of validation, it can be argued that this assumption is false. That is, the types of validations applied to parent properties typically take the form of checking the appropriate format of the container like a length check on a list. These types of checks are generally independent of any requirements the child properties have. This opens up the possibility of running all six paths listed above concurrently instead of performing them in steps based on key path.
92
92
93
93
When synchronous validation occurs as defined in [Benefits of AsyncGuard](#benefits-of-async-guard), the validators for each property would be run in the order they are defined on the schema. That also means that any on fail actions are applied in that same order.
94
94
95
-
When asynchronous validation occurs, there are multiple levels of parallelization possible. First, running validation on the child properties (e.g. `foo.baz` and `foo.bez`) will happen in parallel via the asyncio event loop. Second, within the validation for each property, if the validators have `run_in_separate_process` set to `True`, they are run in parallel via multiprocessing. This multiprocessing is capped to the process count specified by the `GUARDRAILS_PROCESS_COUNT` environment variable which defaults to 10. Note that some environments, like AWS Lambda, may not support multiprocessing in which case you would need to set this environment variable to 1.
95
+
When asynchronous validation occurs, there are multiple levels of concurrency possible. First, running validation on the child properties (e.g. `foo.baz` and `foo.bez`) will happen concurrently via the asyncio event loop. Second, the validators on any given property are also run concurrently via the event loop. For validators that only define a synchronous `validate` method, calls to this method are run in the event loops default executor. Note that some environments, like AWS Lambda, may not support multiprocessing in which case you would need to either set the executor to a thread processor instead or limit validation to running synchronously by setting `GUARDRAILS_PROCESS_COUNT=1` or `GUARDRAILS_RUN_SYNC=true`.
96
96
97
97
### Unstructured Data Validation
98
-
When validating unstructured data, i.e. text, the LLM output is treated the same as if it were a property on an object. This means that the validators applied to is have the ability to run in parallel utilizing multiprocessing when `run_in_separate_process` is set to `True` on the validators.
98
+
When validating unstructured data, i.e. text, the LLM output is treated the same as if it were a property on an object. This means that the validators applied to is have the ability to run concurrently utilizing the event loop.
99
+
100
+
### Handling Failures During Async Concurrency
101
+
The Guardrails validation loop is opinionated about how it handles failures when running validators concurrently so that it spends the least amount of time processing an output that would result in a failure. It's behavior comes down to when and what it returns based on the [corrective action](/how_to_guides/custom_validators#on-fail) specified on a validator. Corrective actions are processed concurrently since they are specific to a given validator on a given property. This means that interruptive corrective actions, namely `EXCEPTION`, will be the first corrective action enforced because the exception is raised as soon as the failure is evaluated. The remaining actions are handled in the following order after all futures are collected from the validation of a specific property:
102
+
1. `FILTER` and `REFRAIN`
103
+
2. `REASK`
104
+
3. `FIX`
105
+
106
+
\*_NOTE:_ `NOOP` Does not require any special handling because it does not alter the value.
107
+
108
+
\*_NOTE:_ `FIX_REASK` Will fall into either the `REASK` or `FIX` bucket based on if the fixed value passes the second round of validation.
109
+
110
+
This means that if any validator with `on_fail=OnFailAction.EXCEPTION` returns a `FailResult`, then Guardrails will raise a `ValidationError` interrupting the process.
111
+
112
+
If any validator on a specific property which has `on_fail=OnFailAction.FILTER` or `on_fail=OnFailAction.REFRAIN` returns a `FailResult`, whichever of these is the first to finish will the returned early as the value for that property,
113
+
114
+
If any validator on a specific property which has `on_fail=OnFailAction.REASK` returns a `FailResult`, all reasks for that property will be merged and a `FieldReAsk` will be returned early as the value for that property.
115
+
116
+
If any validator on a specific property which has `on_fail=OnFailAction.FIX` returns a `FailResult`, all fix values for that property will be merged and the result of that merge will be returned as the value for that property.
117
+
118
+
Custom on_fail handlers will fall into one of the above actions based on what it returns; i.e. if it returns an updated value it's considered a `FIX`, if it returns an instance of `Filter` then `FILTER`, etc..
119
+
120
+
Let's look at an example. We'll keep the validation logic simple and write out some assertions to demonstrate the evaluation order discussed above.
121
+
122
+
```py
123
+
import asyncio
124
+
from random import randint
125
+
from typing import Optional
126
+
from guardrails import AsyncGuard, ValidationOutcome
Copy file name to clipboardExpand all lines: docs/how_to_guides/custom_validators.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,6 +75,7 @@ Validators ship with several out of the box `on_fail` policies. The `OnFailActio
75
75
|`OnFailAction.NOOP`| Do nothing. The failure will still be recorded in the logs, but no corrective action will be taken. |
76
76
|`OnFailAction.EXCEPTION`| Raise an exception when validation fails. |
77
77
|`OnFailAction.FIX_REASK`| First, fix the generated output deterministically, and then rerun validation with the deterministically fixed output. If validation fails, then perform reasking. |
78
+
|`OnFailAction.CUSTOM`| This action is set internally when the validator is passed a custom function to handle failures. The function is called with the value that failed validation and the FailResult returned from the Validator. i.e. the custom on fail handler must implement the method signature `def on_fail(value: Any, fail_result: FailResult) -> Any`|
78
79
79
80
In the code below, a `fix_value` will be supplied in the `FailResult`. This value will represent a programmatic fix that can be applied to the output if `on_fail='fix'` is passed during validator initialization.
0 commit comments